[关闭]
@oro-oro 2015-08-19T18:38:58.000000Z 字数 6172 阅读 2678

二、hello-jni

IDAForAndroid


第一个分析的so就是android-ndk-r10d/samples/hello-jni/

1. 编译 hello-jni

  1. $ cd android-ndk-r10d/samples/hello-jni/
  2. $ ndk-build
  3. [arm64-v8a] Gdbserver : [aarch64-linux-android-4.9] libs/arm64-v8a/gdbserver
  4. [arm64-v8a] Gdbsetup : libs/arm64-v8a/gdb.setup
  5. [x86_64] Gdbserver : [x86_64-4.9] libs/x86_64/gdbserver
  6. [x86_64] Gdbsetup : libs/x86_64/gdb.setup
  7. [mips64] Gdbserver : [mips64el-linux-android-4.9] libs/mips64/gdbserver
  8. [mips64] Gdbsetup : libs/mips64/gdb.setup
  9. [armeabi-v7a] Gdbserver : [arm-linux-androideabi-4.8] libs/armeabi-v7a/gdbserver
  10. [armeabi-v7a] Gdbsetup : libs/armeabi-v7a/gdb.setup
  11. [armeabi] Gdbserver : [arm-linux-androideabi-4.8] libs/armeabi/gdbserver
  12. [armeabi] Gdbsetup : libs/armeabi/gdb.setup
  13. [x86] Gdbserver : [x86-4.8] libs/x86/gdbserver
  14. [x86] Gdbsetup : libs/x86/gdb.setup
  15. [mips] Gdbserver : [mipsel-linux-android-4.8] libs/mips/gdbserver
  16. [mips] Gdbsetup : libs/mips/gdb.setup
  17. [arm64-v8a] Install : libhello-jni.so => libs/arm64-v8a/libhello-jni.so
  18. [x86_64] Install : libhello-jni.so => libs/x86_64/libhello-jni.so
  19. [mips64] Install : libhello-jni.so => libs/mips64/libhello-jni.so
  20. [armeabi-v7a] Install : libhello-jni.so => libs/armeabi-v7a/libhello-jni.so
  21. [armeabi] Install : libhello-jni.so => libs/armeabi/libhello-jni.so
  22. [x86] Install : libhello-jni.so => libs/x86/libhello-jni.so
  23. [mips] Install : libhello-jni.so => libs/mips/libhello-jni.so
  24. $ cd libs/armeabi
  25. $ ls
  26. gdb.setup gdbserver libhello-jni.so

libhello-jni.so 就是要分析的so。

2. 分析

Java_com_example_hellojni_HelloJni_stringFromJNI 就是要分析的函数。

IDA反汇编后,如下:

  1. .text:00000C18 ; =============== S U B R O U T I N E =======================================
  2. .text:00000C18
  3. .text:00000C18 ; Attributes: bp-based frame
  4. .text:00000C18
  5. .text:00000C18 EXPORT Java_com_example_hellojni_HelloJni_stringFromJNI
  6. .text:00000C18 Java_com_example_hellojni_HelloJni_stringFromJNI
  7. .text:00000C18
  8. .text:00000C18 var_C = -0xC
  9. .text:00000C18 var_8 = -8
  10. .text:00000C18
  11. .text:00000C18 STMFD SP!, {R11,LR}
  12. .text:00000C1C ADD R11, SP, #4
  13. .text:00000C20 SUB SP, SP, #8
  14. .text:00000C24 STR R0, [R11,#var_8]
  15. .text:00000C28 STR R1, [R11,#var_C]
  16. .text:00000C2C LDR R3, [R11,#var_8]
  17. .text:00000C30 LDR R3, [R3]
  18. .text:00000C34 LDR R3, [R3,#0x29C]
  19. .text:00000C38 LDR R0, [R11,#var_8]
  20. .text:00000C3C LDR R2, =(aHelloFromJniCo - 0xC48)
  21. .text:00000C40 ADD R2, PC, R2 ; "Hello from JNI ! Compiled with ABI arm"...
  22. .text:00000C44 MOV R1, R2
  23. .text:00000C48 BLX R3
  24. .text:00000C4C MOV R3, R0
  25. .text:00000C50 MOV R0, R3
  26. .text:00000C54 SUB SP, R11, #4
  27. .text:00000C58 LDMFD SP!, {R11,PC}
  28. .text:00000C58 ; End of function Java_com_example_hellojni_HelloJni_stringFromJNI

2.1 重命名和注释

jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz )
有2个参数,分别存入了r0和r1里面。

经过重命名和简单的注释之后,代码如下:

  1. .text:00000C18 EXPORT Java_com_example_hellojni_HelloJni_stringFromJNI
  2. .text:00000C18 Java_com_example_hellojni_HelloJni_stringFromJNI
  3. .text:00000C18
  4. .text:00000C18 jobject = -0xC
  5. .text:00000C18 env = -8
  6. .text:00000C18
  7. .text:00000C18 STMFD SP!, {R11,LR}
  8. .text:00000C1C ADD R11, SP, #4
  9. .text:00000C20 SUB SP, SP, #8 ; 拓展栈空间4*2(用来保存r0和r1)
  10. .text:00000C24 STR R0, [R11,#env] ; r0压栈
  11. .text:00000C28 STR R1, [R11,#jobject] ; r1压栈
  12. .text:00000C2C LDR R3, [R11,#env]
  13. .text:00000C30 LDR R3, [R3]
  14. .text:00000C34 LDR R3, [R3,#0x29C] ; r3=env + 0x29c
  15. .text:00000C38 LDR R0, [R11,#env] ; r0 = *evn
  16. .text:00000C3C LDR R2, =(aHelloFromJniCo - 0xC48)
  17. .text:00000C40 ADD R2, PC, R2 ; "Hello from JNI ! Compiled with ABI arm"...
  18. .text:00000C44 MOV R1, R2 ; r1 = "Hello from JNI ! Compiled with ABI arm"
  19. .text:00000C48 BLX R3 ; r3为(*env)->NewStringUTF函数地址
  20. .text:00000C4C MOV R3, R0
  21. .text:00000C50 MOV R0, R3
  22. .text:00000C54 SUB SP, R11, #4
  23. .text:00000C58 LDMFD SP!, {R11,PC}
  24. .text:00000C58 ; End of function Java_com_example_hellojni_HelloJni_stringFromJNI

2.2 函数地址

函数地址是通过基于寄存器的偏移值传递的。
从上面的代码指定,r3保存了(*env)->NewStringUTF的地址,而地址为env + 0x29c

(*env)->NewStringUTF这个函数的定义在jni.h中,位置在android-ndk-r10d\platforms\android-19\arch-arm\usr\include\jni.h

在 jni.h 中,有这么一段声明。

  1. struct _JNIEnv;
  2. struct _JavaVM;
  3. typedef const struct JNINativeInterface* C_JNIEnv;
  4. #if defined(__cplusplus)
  5. typedef _JNIEnv JNIEnv;
  6. typedef _JavaVM JavaVM;
  7. #else
  8. typedef const struct JNINativeInterface* JNIEnv;
  9. typedef const struct JNIInvokeInterface* JavaVM;
  10. #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

2.3 显示JNI函数

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键,选择JNINativeInterfaceJNIInvokeInterface导入即可。
structure

structure

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

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

修改后,显示如下:

  1. .text:00000C18
  2. .text:00000C18 EXPORT Java_com_example_hellojni_HelloJni_stringFromJNI
  3. .text:00000C18 Java_com_example_hellojni_HelloJni_stringFromJNI
  4. .text:00000C18
  5. .text:00000C18 jobject = -0xC
  6. .text:00000C18 env = -8
  7. .text:00000C18
  8. .text:00000C18 STMFD SP!, {R11,LR}
  9. .text:00000C1C ADD R11, SP, #4
  10. .text:00000C20 SUB SP, SP, #8 ; 拓展栈空间4*2(用来保存r0和r1)
  11. .text:00000C24 STR R0, [R11,#env] ; r0压栈
  12. .text:00000C28 STR R1, [R11,#jobject] ; r1压栈
  13. .text:00000C2C LDR R3, [R11,#env]
  14. .text:00000C30 LDR R3, [R3]
  15. .text:00000C34 LDR R3, [R3,#JNINativeInterface.NewStringUTF] ; r3=env + 0x29c
  16. .text:00000C38 LDR R0, [R11,#env] ; r0 = *evn
  17. .text:00000C3C LDR R2, =(aHelloFromJniCo - 0xC48)
  18. .text:00000C40 ADD R2, PC, R2 ; "Hello from JNI ! Compiled with ABI arm"...
  19. .text:00000C44 MOV R1, R2 ; r1 = "Hello from JNI ! Compiled with ABI arm"
  20. .text:00000C48 BLX R3 ; r3为(*env)->NewStringUTF函数地址
  21. .text:00000C4C MOV R3, R0
  22. .text:00000C50 MOV R0, R3
  23. .text:00000C54 SUB SP, R11, #4
  24. .text:00000C58 LDMFD SP!, {R11,PC}
  25. .text:00000C58 ; End of function Java_com_example_hellojni_HelloJni_stringFromJNI

这样,就清晰了,上面代码就是调用了JNINativeInterface.NewStringUTF,并返回字符串的结果。

再观察一下PseudocodeF5的结果对比,这个结果与显示插件有关。
显示函数之前:

  1. int __fastcall Java_com_example_hellojni_HelloJni_stringFromJNI(int a1)
  2. {
  3. return (*(int (__cdecl **)(int, _DWORD))(*(_DWORD *)a1 + 668))(a1, "Hello from JNI ! Compiled with ABI armeabi.");
  4. }

显示函数之后:

  1. int __fastcall Java_com_example_hellojni_HelloJni_stringFromJNI(int a1)
  2. {
  3. return (*(int (__cdecl **)(int, _DWORD))(*(_DWORD *)a1 + offsetof(JNINativeInterface, NewStringUTF)))(
  4. a1,
  5. "Hello from JNI ! Compiled with ABI armeabi.");
  6. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注