@oro-oro
2015-08-19T10:38:58.000000Z
字数 6172
阅读 2902
IDAForAndroid
第一个分析的so就是android-ndk-r10d/samples/hello-jni/。
$ cd android-ndk-r10d/samples/hello-jni/$ ndk-build[arm64-v8a] Gdbserver : [aarch64-linux-android-4.9] libs/arm64-v8a/gdbserver[arm64-v8a] Gdbsetup : libs/arm64-v8a/gdb.setup[x86_64] Gdbserver : [x86_64-4.9] libs/x86_64/gdbserver[x86_64] Gdbsetup : libs/x86_64/gdb.setup[mips64] Gdbserver : [mips64el-linux-android-4.9] libs/mips64/gdbserver[mips64] Gdbsetup : libs/mips64/gdb.setup[armeabi-v7a] Gdbserver : [arm-linux-androideabi-4.8] libs/armeabi-v7a/gdbserver[armeabi-v7a] Gdbsetup : libs/armeabi-v7a/gdb.setup[armeabi] Gdbserver : [arm-linux-androideabi-4.8] libs/armeabi/gdbserver[armeabi] Gdbsetup : libs/armeabi/gdb.setup[x86] Gdbserver : [x86-4.8] libs/x86/gdbserver[x86] Gdbsetup : libs/x86/gdb.setup[mips] Gdbserver : [mipsel-linux-android-4.8] libs/mips/gdbserver[mips] Gdbsetup : libs/mips/gdb.setup[arm64-v8a] Install : libhello-jni.so => libs/arm64-v8a/libhello-jni.so[x86_64] Install : libhello-jni.so => libs/x86_64/libhello-jni.so[mips64] Install : libhello-jni.so => libs/mips64/libhello-jni.so[armeabi-v7a] Install : libhello-jni.so => libs/armeabi-v7a/libhello-jni.so[armeabi] Install : libhello-jni.so => libs/armeabi/libhello-jni.so[x86] Install : libhello-jni.so => libs/x86/libhello-jni.so[mips] Install : libhello-jni.so => libs/mips/libhello-jni.so$ cd libs/armeabi$ lsgdb.setup gdbserver libhello-jni.so
libhello-jni.so 就是要分析的so。
Java_com_example_hellojni_HelloJni_stringFromJNI 就是要分析的函数。
IDA反汇编后,如下:
.text:00000C18 ; =============== S U B R O U T I N E =======================================.text:00000C18.text:00000C18 ; Attributes: bp-based frame.text:00000C18.text:00000C18 EXPORT Java_com_example_hellojni_HelloJni_stringFromJNI.text:00000C18 Java_com_example_hellojni_HelloJni_stringFromJNI.text:00000C18.text:00000C18 var_C = -0xC.text:00000C18 var_8 = -8.text:00000C18.text:00000C18 STMFD SP!, {R11,LR}.text:00000C1C ADD R11, SP, #4.text:00000C20 SUB SP, SP, #8.text:00000C24 STR R0, [R11,#var_8].text:00000C28 STR R1, [R11,#var_C].text:00000C2C LDR R3, [R11,#var_8].text:00000C30 LDR R3, [R3].text:00000C34 LDR R3, [R3,#0x29C].text:00000C38 LDR R0, [R11,#var_8].text:00000C3C LDR R2, =(aHelloFromJniCo - 0xC48).text:00000C40 ADD R2, PC, R2 ; "Hello from JNI ! Compiled with ABI arm"....text:00000C44 MOV R1, R2.text:00000C48 BLX R3.text:00000C4C MOV R3, R0.text:00000C50 MOV R0, R3.text:00000C54 SUB SP, R11, #4.text:00000C58 LDMFD SP!, {R11,PC}.text:00000C58 ; End of function Java_com_example_hellojni_HelloJni_stringFromJNI
jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz )
有2个参数,分别存入了r0和r1里面。
n可以重命名。;, 则可以给一行操作注释。经过重命名和简单的注释之后,代码如下:
.text:00000C18 EXPORT Java_com_example_hellojni_HelloJni_stringFromJNI.text:00000C18 Java_com_example_hellojni_HelloJni_stringFromJNI.text:00000C18.text:00000C18 jobject = -0xC.text:00000C18 env = -8.text:00000C18.text:00000C18 STMFD SP!, {R11,LR}.text:00000C1C ADD R11, SP, #4.text:00000C20 SUB SP, SP, #8 ; 拓展栈空间4*2(用来保存r0和r1).text:00000C24 STR R0, [R11,#env] ; 将r0压栈.text:00000C28 STR R1, [R11,#jobject] ; 将r1压栈.text:00000C2C LDR R3, [R11,#env].text:00000C30 LDR R3, [R3].text:00000C34 LDR R3, [R3,#0x29C] ; r3=env + 0x29c.text:00000C38 LDR R0, [R11,#env] ; r0 = *evn.text:00000C3C LDR R2, =(aHelloFromJniCo - 0xC48).text:00000C40 ADD R2, PC, R2 ; "Hello from JNI ! Compiled with ABI arm"....text:00000C44 MOV R1, R2 ; r1 = "Hello from JNI ! Compiled with ABI arm".text:00000C48 BLX R3 ; r3为(*env)->NewStringUTF函数地址.text:00000C4C MOV R3, R0.text:00000C50 MOV R0, R3.text:00000C54 SUB SP, R11, #4.text:00000C58 LDMFD SP!, {R11,PC}.text:00000C58 ; End of function Java_com_example_hellojni_HelloJni_stringFromJNI
函数地址是通过基于寄存器的偏移值传递的。
从上面的代码指定,r3保存了(*env)->NewStringUTF的地址,而地址为env + 0x29c。
(*env)->NewStringUTF这个函数的定义在jni.h中,位置在android-ndk-r10d\platforms\android-19\arch-arm\usr\include\jni.h。
在 jni.h 中,有这么一段声明。
struct _JNIEnv;struct _JavaVM;typedef const struct JNINativeInterface* C_JNIEnv;#if defined(__cplusplus)typedef _JNIEnv JNIEnv;typedef _JavaVM JavaVM;#elsetypedef const struct JNINativeInterface* JNIEnv;typedef const struct JNIInvokeInterface* JavaVM;#endif
如果使用 C++, JNIEnv 就定义为 _JNIEnv 结构体,第一个字段就是 JNINativeInterface 结构体的指针。
如果使用 C, JNIEnv 就直接定义为 JNINativeInterface 结构体的指针。
所有的函数都是在 JNINativeInterface 和 JNIInvokeInterface 的结构体中;而 NewStringUTF 这个函数是在 JNINativeInterface 中。
*env 的首地址,就是JNINativeInterface结构体的首地址,这意味着通过首地址加上索引值,就可以找到这个的函数了。
每个地址占用4字节的空间。
0x29c/4 = 668/4 = 167,也就是说 JNIInvokeInterface 的第167项的函数,就是以上的 IDA 的调用函数。
观察结构体,从0开始算,第167项,是NewStringUTF函数。
这里保存了JNI的函数序号,可作为参考:
https://github.com/jnr/jnr-ffi/blob/master/src/main/java/jnr/ffi/provider/jffi/JNINativeInterface.java
IDA Pro 支持结构化的数据显示,而且支持从 C/C++ 头文件直接导入结构体定义。
可以通过File->Load file>Parse C header file...来导入头文件。
jni.h 导入前需要修改:
1. 注释掉#include <stdarg.h>
2. 修改#define JNIEXPORT __attribute__ ((visibility ("default")))为#define JNIEXPORT
不过,JNI结构体IDA已经自带,点击界面的Structures标签。
按Insert键,选择JNINativeInterface和JNIInvokeInterface导入即可。


导入结构体之后,不妨Ctrl-+展开结构的偏移,观察NewStringUTF的偏移值。

将偏移值显示为函数有2种方式:
1. 右键偏移值。
2. 选中偏移值,按字母t。

修改后,显示如下:
.text:00000C18.text:00000C18 EXPORT Java_com_example_hellojni_HelloJni_stringFromJNI.text:00000C18 Java_com_example_hellojni_HelloJni_stringFromJNI.text:00000C18.text:00000C18 jobject = -0xC.text:00000C18 env = -8.text:00000C18.text:00000C18 STMFD SP!, {R11,LR}.text:00000C1C ADD R11, SP, #4.text:00000C20 SUB SP, SP, #8 ; 拓展栈空间4*2(用来保存r0和r1).text:00000C24 STR R0, [R11,#env] ; 将r0压栈.text:00000C28 STR R1, [R11,#jobject] ; 将r1压栈.text:00000C2C LDR R3, [R11,#env].text:00000C30 LDR R3, [R3].text:00000C34 LDR R3, [R3,#JNINativeInterface.NewStringUTF] ; r3=env + 0x29c.text:00000C38 LDR R0, [R11,#env] ; r0 = *evn.text:00000C3C LDR R2, =(aHelloFromJniCo - 0xC48).text:00000C40 ADD R2, PC, R2 ; "Hello from JNI ! Compiled with ABI arm"....text:00000C44 MOV R1, R2 ; r1 = "Hello from JNI ! Compiled with ABI arm".text:00000C48 BLX R3 ; r3为(*env)->NewStringUTF函数地址.text:00000C4C MOV R3, R0.text:00000C50 MOV R0, R3.text:00000C54 SUB SP, R11, #4.text:00000C58 LDMFD SP!, {R11,PC}.text:00000C58 ; End of function Java_com_example_hellojni_HelloJni_stringFromJNI
这样,就清晰了,上面代码就是调用了JNINativeInterface.NewStringUTF,并返回字符串的结果。
再观察一下PseudocodeF5的结果对比,这个结果与显示插件有关。
显示函数之前:
int __fastcall Java_com_example_hellojni_HelloJni_stringFromJNI(int a1){return (*(int (__cdecl **)(int, _DWORD))(*(_DWORD *)a1 + 668))(a1, "Hello from JNI ! Compiled with ABI armeabi.");}
显示函数之后:
int __fastcall Java_com_example_hellojni_HelloJni_stringFromJNI(int a1){return (*(int (__cdecl **)(int, _DWORD))(*(_DWORD *)a1 + offsetof(JNINativeInterface, NewStringUTF)))(a1,"Hello from JNI ! Compiled with ABI armeabi.");}