Search code examples
androidjava-native-interfacestacknativedalvik

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


I am using JNI with Android Studio 1.5.1 targeting Android API 18 and my question is:

Q) Without using a tool or changing/modifying the Dalvik VM source code, how can I find the memory address of a Java local variable on the Dalvik Stack from native code?

For example, I try to use the following code (adapted from the Internet) to find the memory address of a Java local variable magicNumber = 0x23420023 but I am getting segmentation fault errors.

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)))){;
            }
        }
    }
}

This is an update:

The previous code that I posted was not the latest one, here is the latest one:

The Java Code:

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 );
   }
}

The native Code:

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, "[email protected]")))==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));
       }
   }
}

This code is working and it can find the magic number but it finds it in the memory region mapped to /data/dalvik-cache/data@[email protected]@classes.dex which is not the Dalvik stack.

However, by running the above code and by looking at these two papers : 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, we can notice the following (also to comments on fadden's remarks below):

(1) It seems that the int value magicNumber is stored in one Dalvik register. Also it seems that it is stored in the Dalvik stack and it is not on the native code stack because the int variable magicNumber is declared and assigned a value in the Java code section.

(2) According to the paper1, this answer and as evidence by running the attached latest code, we are not searching for 0x14 using the memchr function but we want to make sure we are at the beginning of a memory cell that store the int in ARM CPUs.

(3) I do not need to call the findMagicNumber function again. I just need to find the memory address of the magic number in the Dalvik stack

(4) I do not need to find nearby variables to the MagicNumber, so this is not a problem in my case.

(5) The project is targeting only Dalvik, so ART is not a problem

(6) I agree, using mprotect() is not a good idea and was not necessary in my case.

(7) If you refer to paper2, you can see that access() can be used for something that it is not designed for, checking if a virtual memory address is valid or not. I’m not using access() for any file related operations, although it was written for that purpose

(8) I do not need to change the variable. I just need the address of the variable magicNumber on the Dalvik stack programmatically without using any tool or changing the Dalvik source code

I want to know which of the memory regions of /proc/pid/task/tid/maps Dalvik uses to store its stack.

As you can see in paper1, the authors in section B.2, line #4, did not explain which memory region from procs/maps they used to assign the start and end variables their values.


Solution

  • It looks like you're trying to open /proc/[pid]/task/[tid]/maps, walk through the map, and manually scan through every address range for a magic number. Setting aside the fact that you're opening the task directory rather than the maps magic file in that directory, this approach has a few problems:

    1. You're not guaranteed that the value is unique. If it appears somewhere else in memory -- perhaps because the value was stored in two different Dalvik registers -- you'll be in the wrong place. If the JIT compiler has compiled this stretch of code, you don't know if the "active" value will be on the managed stack or in a spilled register on the native stack.
    2. You're searching for 0x14, which isn't part of your magic number.
    3. You're scanning for the local variable on the thread that created it. Even if you find it and can change it, once the findMagicNumber method returns, the stack-allocated variable will disappear. It's not guaranteed to be in the same place if the method is called again.
    4. If you were hoping to find related variables nearby, you will again have trouble if the JIT compiler rearranges things.
    5. ART compiles almost everything ahead of time, so this is even less likely to be useful there.

    I'm also not sure why you're calling mprotect(), rather than simply skipping the segments that aren't readable. You're forcing the permissions to read-only, disabling write and execute permission, which will cause you to crash when execute permission is disabled for the chunk of code that's executing, or when a thread tries to execute and touches its stack.

    The access() system call takes a filename, not a memory address, and reports file permissions. I don't think there's an "is this address valid" call, but since you just sscanf'ed the map entry you shouldn't need one.

    The only reliable way to find and change the value of a local variable is to use the JDWP debug interface. The compiler and debugger support work together to make reliable read-write access to local variables possible.

    To sum up: the code is seriously broken, the approach is unsound. What problem are you trying to solve?