@oro-oro
2015-08-19T18:38:58.000000Z
字数 6172
阅读 2678
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
$ ls
gdb.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;
#else
typedef 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
,并返回字符串的结果。
再观察一下Pseudocode
F5的结果对比,这个结果与显示插件有关。
显示函数之前:
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.");
}