从本机代码查找 Dalvik 堆栈上 Java 局部变量的内存地址

Finding the memory address of a Java local variable on the Dalvik Stack from native code

我将 JNI 与 Android Studio 1.5.1 一起使用,目标是 Android API 18,我的问题是:

Q) 在不使用工具或changing/modifying Dalvik VM 源代码的情况下,如何从本机代码中找到 Dalvik 堆栈上 Java 局部变量的内存地址?

例如,我尝试使用以下代码(改编自互联网)来查找 Java 局部变量 magicNumber = 0x23420023 的内存地址,但出现分段错误。

public class MainActivity extends AppCompatActivity {
static {
    System.loadLibrary("MyLibrary");
}

public native boolean findMagicNumber(int pid, int tid);
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    int magicNumber = 0x23420023 ;
    int pid = android.os.Process.myPid();
    int tid = android.os.Process.myTid();
    findMagicNumber(pid, tid);
}

}

#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdlib.h>
#include "com_example_magicnumber2_MainActivity.h"
#include <unistd.h>
#include <memory.h>
#define ENOENT           2      /* No such file or directory */
#define ENOMEM          12      /* Out of memory */
#define EACCES          13      /* Permission denied */
#define EFAULT          14      /* Bad address */
#define EINVAL          22      /* Invalid argument */

jboolean validAddress(char* address)
{
    if ((access(address, F_OK)==-1) && !(errno == ENOENT) && !(errno == ENAMETOOLONG))
        return JNI_FALSE;
    else if ((access(address, F_OK)==-1) && (errno == ENOMEM) ||
             (access(address, F_OK)==-1) && (errno == EACCES) ||
             (access(address, F_OK)==-1) && (errno == EFAULT) ||
             (access(address, F_OK)==-1) && (errno == EINVAL))
        return JNI_FALSE;

    else if (address == NULL)
        return JNI_FALSE;
    else
        return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL Java_com_example_magicnumber2_MainActivity_findMagicNumber(JNIEnv *env, jobject obj, jint pid, jint tid) {

    long long startaddr, endaddr, size, offset, inode;
    char permissions[8], device[8], filename[200], line[250];
    char *start, *end, *candidate;
    int result, i = 0;
    char filepath[100];
    sprintf(filepath,"/proc/%d/task/%d", pid, tid);

    FILE* file = fopen(filepath, "r");
    jboolean found = JNI_FALSE;
    while (fgets(line, sizeof(line), file) && !found) {
        sscanf(line,"%llx-%llx %s %llx %s %llx", &startaddr, &endaddr, permissions, &offset, device, &inode);
        start = startaddr;
        end = endaddr;
        mprotect( (void*)start , (end-start), PROT_READ);
        candidate = memchr( start, 0x14, (end-start));
        while( validAddress(candidate) && !found){
            if ((validAddress(candidate[2]) && (candidate[2]== 0x23)) &&
                (validAddress(candidate[3]) && (candidate[3] == 0x00)) &&
                (validAddress(candidate[4]) && (candidate[4] == 0x42)) &&
                (validAddress(candidate[5]) && (candidate[5] == 0x23))){
                __android_log_print(ANDROID_LOG_DEBUG,"***","Location=%p WE FOUND IT!", candidate);
                found = JNI_TRUE;
                break;
                return JNI_TRUE;
            }
            else if ((validAddress(candidate)) &&
                     validAddress(candidate=memchr(candidate+1, 0x14, (end-candidate)))){;
            }
        }
    }
}

这是一个更新:

我之前发布的代码不是最新的,这里是最新的:

Java代码:

public class MainActivity extends AppCompatActivity {
   static {
       System.loadLibrary("MyLibrary");
   }

   public native boolean findMagicNumber(int pid, int tid);
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       int magicNumber = 0x23420023 ;
       int pid = android.os.Process.myPid();
       int tid = android.os.Process.myTid();
       findMagicNumber(pid, tid);
       System.out.println("magicNumber = " + magicNumber );
   }
}

本机代码:

JNIEXPORT jboolean JNICALL Java_com_example_magicnumber2_MainActivity_findMagicNumber(JNIEnv *env, jobject obj, jint pid, jint tid) {

   long long startaddr, endaddr, size, offset, inode;
   char permissions[8], device[8], filename[200], line[250];
   char *start, *end, *candidate;
   int result, i = 0;
   char filepath[100];
   sprintf(filepath,"/proc/%d/task/%d/maps", pid, tid);
   FILE* file = fopen(filepath, "r");
   jboolean found = JNI_FALSE;

   while (fgets(line, sizeof(line), file) && !found) {
       sscanf(line,"%llx-%llx %s %llx %s %llx %s", &startaddr, &endaddr, permissions, &offset, device, &inode, filename);

       if (((strstr(filename, "apk@classes.dex")))==NULL) {
       continue;
       }
       __android_log_print(ANDROID_LOG_DEBUG, "*****************", "%llx-%llx %s %llx %s %llx %s",
                           startaddr, endaddr, permissions, offset, device, inode, filename);
       start = startaddr;
       end = endaddr;
       candidate = memchr( start, 0x14, (end-start));
       while( candidate !=0 && !found){
           if ((candidate[2]== 0x23) &&
               (candidate[3] == 0x00) &&
               (candidate[4] == 0x42) &&
               (candidate[5] == 0x23)){
               __android_log_print(ANDROID_LOG_DEBUG,"********************************************************************","WE FOUND IT at %p!!!", candidate);
               found = JNI_TRUE;
               break;
           }
           else
               candidate=memchr(candidate+1, 0x14, (end-candidate));
       }
   }
}

此代码有效,它可以找到幻数,但它是在映射到 /data/dalvik-cache/data@app@com.example.magicnumber2-1.apk@[= 的内存区域中找到的65=] 这不是 Dalvik 堆栈。

但是,通过运行上面的代码和查看这两篇论文:paper1 (appendix B, only the egg-hunting code, I do not need to change any Dalvik code, so skip the code changing part) and paper2,我们可以注意到以下内容(也对下面fadden的评论进行评论):

(1) int 值magicNumber 似乎存储在一个Dalvik 寄存器中。此外,它似乎存储在 Dalvik 堆栈中,而不是在本机代码堆栈中,因为 int 变量 magicNumber 在 Java 代码部分中声明并分配了一个值。

(2) 根据论文 1,这个 answer 和 运行 所附最新代码的证据,我们没有使用 memchr 函数搜索 0x14,但我们想确保我们位于 ARM CPU 中存储 int 的存储单元的开头。

(3) 我不需要再次调用 findMagicNumber 函数。我只需要在Dalvik栈中找到幻数的内存地址

(4) 我不需要找到 MagicNumber 附近的变量,所以这对我来说不是问题。

(5) 该项目仅针对 Dalvik,因此 ART 不是问题

(6) 我同意,使用 mprotect() 不是一个好主意,在我的情况下也没有必要。

(7) 如果你参考 paper2,你可以看到 access() 可以用于它不是为它设计的东西,检查虚拟内存地址是否有效。我没有将 access() 用于任何与文件相关的操作,尽管它是为此目的而编写的

(8) 我不需要更改变量。我只需要以编程方式获取 Dalvik 堆栈上变量 magicNumber 的地址,而无需使用任何工具或更改 Dalvik 源代码

我想知道 /proc/pid/task/tid/maps Dalvik 使用哪个内存区域来存储其堆栈。

正如您在 paper1 中看到的那样,B.2 节第 4 行的作者没有解释他们使用 procs/maps 中的哪个内存区域为开始和结束变量赋值。

您似乎在尝试打开 /proc/[pid]/task/[tid]/maps、遍历地图并手动扫描每个地址范围以寻找幻数。抛开您打开的是 task 目录而不是该目录中的 maps 魔术文件这一事实,这种方法存在一些问题:

  1. 您不能保证该值是唯一的。如果它出现在内存中的其他地方——可能是因为该值存储在两个不同的 Dalvik 寄存器中——你就来错地方了。如果 JIT 编译器编译了这段代码,您不知道 "active" 值是在托管堆栈上还是在本机堆栈的溢出寄存器中。
  2. 您正在搜索 0x14,这不是您的幻数的一部分。
  3. 您正在扫描创建它的线程上的局部变量。即使找到了,能改了,一旦findMagicNumber方法returns,stack-allocated变量就会消失。如果再次调用该方法,则不能保证在同一个地方。
  4. 如果您希望在附近找到相关变量,如果 JIT 编译器重新排列,您将再次遇到麻烦。
  5. ART 几乎可以提前编译所有内容,因此它在那里用处更小。

我也不确定您为什么调用 mprotect(),而不是简单地跳过不可读的片段。您将权限强制设置为 read-only,禁用写入和执行权限,这将导致您在为正在执行的代码块禁用执行权限时或线程尝试执行并触及其堆栈时崩溃。

access() 系统调用采用文件名而不是内存地址,并报告文件权限。我认为没有 "is this address valid" 调用,但由于您只是 sscanf'ed 地图条目,因此您不需要一个。

查找和更改局部变量值的唯一可靠方法是使用 JDWP 调试界面。编译器和调试器支持协同工作,使对局部变量的可靠 read-write 访问成为可能。

综上所述:代码严重破损,方法不健全。你想解决什么问题?