【问题标题】:Finding the memory address of a Java local variable on the Dalvik Stack from native code从本机代码中查找 Dalvik Stack 上的 Java 局部变量的内存地址
【发布时间】:2016-01-04 17:35:08
【问题描述】:

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

Q) 在不使用工具或更改/修改 Dalvik VM 源代码的情况下,如何从本地代码中找到 Dalvik Stack 上 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@classes.dex 的内存区域中找到它不是 Dalvik 堆栈。

但是,通过运行上面的代码并查看这两篇论文:paper1(附录B,只有寻蛋代码,我不需要更改任何Dalvik代码,因此跳过代码更改部分)和paper2,我们可以注意到以下内容(下面还有关于fadden的评论):

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

(2) 根据论文 1,answer 和运行附加的最新代码作为证据,我们不是使用 memchr 函数搜索 0x14,但我们要确保我们位于存储单元的开头将 int 存储在 ARM CPU 中。

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

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

(5) 该项目只针对 Dalvik,所以 ART 不是问题

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

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

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

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

正如您在论文 1 中看到的,作者在 B.2 节第 4 行中没有解释他们使用 procs/maps 中的哪个内存区域来为 start 和 end 变量分配它们的值。

【问题讨论】:

    标签: android java-native-interface stack native dalvik


    【解决方案1】:

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

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

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

    access() 系统调用采用文件名,而不是内存地址,并报告文件权限。我认为没有“此地址是否有效”的调用,但由于您只是扫描了地图条目,因此您不需要它。

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

    总结:代码被严重破坏,方法不健全。你想解决什么问题?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-28
      • 2014-09-06
      • 1970-01-01
      相关资源
      最近更新 更多