MASTG-TECH-0024: 审查反汇编的原生代码
按照“反汇编本机代码”中的示例,我们将使用不同的反汇编器来审查反汇编的本机代码。
radare2¶
一旦你在 radare2 中打开了你的文件,你应该首先获得你正在寻找的函数的地址。你可以通过列出或获取关于符号 s
的信息 i
(is
) 并使用 grep (~
radare2 的内置 grep) 搜索一些关键词来做到这一点,在我们的例子中,我们正在寻找 JNI 相关的符号,所以我们输入 "Java"
$ r2 -A HelloWord-JNI/lib/armeabi-v7a/libnative-lib.so
...
[0x00000e3c]> is~Java
003 0x00000e78 0x00000e78 GLOBAL FUNC 16 Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI
该方法可以在地址 0x00000e78
找到。要显示它的反汇编,只需运行以下命令
[0x00000e3c]> e emu.str=true;
[0x00000e3c]> s 0x00000e78
[0x00000e78]> af
[0x00000e78]> pdf
╭ (fcn) sym.Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI 12
│ sym.Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI (int32_t arg1);
│ ; arg int32_t arg1 @ r0
│ 0x00000e78 ~ 0268 ldr r2, [r0] ; arg1
│ ;-- aav.0x00000e79:
│ ; UNKNOWN XREF from aav.0x00000189 (+0x3)
│ 0x00000e79 unaligned
│ 0x00000e7a 0249 ldr r1, aav.0x00000f3c ; [0xe84:4]=0xf3c aav.0x00000f3c
│ 0x00000e7c d2f89c22 ldr.w r2, [r2, 0x29c]
│ 0x00000e80 7944 add r1, pc ; "Hello from C++" section..rodata
╰ 0x00000e82 1047 bx r2
让我们解释一下前面的命令
e emu.str=true;
启用 radare2 的字符串模拟。有了这个,我们可以看到我们正在寻找的字符串 ("Hello from C++")。s 0x00000e78
是一个寻址到地址s 0x00000e78
,我们的目标函数位于那里。我们这样做是为了使以下命令应用于该地址。pdf
意味着打印函数的反汇编。
使用 radare2,你可以通过使用标志 -qc '<commands>'
快速运行命令并退出。从之前的步骤中,我们已经知道该怎么做,所以我们将简单地把所有东西放在一起
$ r2 -qc 'e emu.str=true; s 0x00000e78; af; pdf' HelloWord-JNI/lib/armeabi-v7a/libnative-lib.so
╭ (fcn) sym.Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI 12
│ sym.Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI (int32_t arg1);
│ ; arg int32_t arg1 @ r0
│ 0x00000e78 0268 ldr r2, [r0] ; arg1
│ 0x00000e7a 0249 ldr r1, [0x00000e84] ; [0xe84:4]=0xf3c
│ 0x00000e7c d2f89c22 ldr.w r2, [r2, 0x29c]
│ 0x00000e80 7944 add r1, pc ; "Hello from C++" section..rodata
╰ 0x00000e82 1047 bx r2
请注意,在这种情况下,我们不是从 -A
标志开始,也没有运行 aaa
。相反,我们只是告诉 radare2 通过使用分析函数 af
命令来分析该函数。在某些情况下,我们可以加快我们的工作流程,因为你专注于应用程序的某些特定部分。
通过使用 r2ghidra 可以进一步改进工作流程,它是 Ghidra 反编译器与 radare2 的深度集成。r2ghidra 生成反编译的 C 代码,这可以帮助快速分析二进制文件。
IDA Pro¶
我们假设你已经在 IDA pro 中成功打开了 lib/armeabi-v7a/libnative-lib.so
。文件加载完成后,点击左侧的 "Functions" 窗口,按 Alt+t
打开搜索对话框。输入 "java" 并点击回车。这应该会高亮显示 Java_sg_vantagepoint_helloworld_ MainActivity_stringFromJNI
函数。双击该函数跳转到反汇编窗口中的地址。"Ida View-A" 现在应该显示该函数的反汇编。
那里没有太多代码,但你应该分析它。你需要知道的第一件事是,传递给每个 JNI 函数的第一个参数是一个 JNI 接口指针。接口指针是指向指针的指针。这个指针指向一个函数表:一个包含更多指针的数组,每个指针都指向一个 JNI 接口函数(你的头晕了吗?)。函数表由 Java VM 初始化,并允许本机函数与 Java 环境交互。
考虑到这一点,让我们看看每一行汇编代码。
LDR R2, [R0]
记住:第一个参数(在 R0 中)是指向 JNI 函数表指针的指针。LDR
指令将这个函数表指针加载到 R2 中。
LDR R1, =aHelloFromC
这条指令将字符串 "Hello from C++" 的 PC 相关偏移量加载到 R1 中。请注意,这个字符串直接位于偏移量 0xe84 处的函数块的末尾。相对于程序计数器寻址允许代码独立于它在内存中的位置运行。
LDR.W R2, [R2, #0x29C]
这条指令从偏移量 0x29C 处将函数指针加载到 R2 指向的 JNI 函数指针表中。这是 NewStringUTF
函数。你可以在 Android NDK 中包含的 jni.h 中的函数指针列表中查看。函数原型如下所示
jstring (*NewStringUTF)(JNIEnv*, const char*);
该函数接受两个参数:JNIEnv 指针(已经在 R0 中)和一个字符串指针。接下来,将 PC 的当前值添加到 R1,从而得到静态字符串 "Hello from C++" 的绝对地址(PC + 偏移量)。
ADD R1, PC
最后,程序执行一个分支指令,跳转到加载到 R2 中的 NewStringUTF
函数指针
BX R2
当这个函数返回时,R0 包含一个指向新构造的 UTF 字符串的指针。这是最终的返回值,因此 R0 保持不变,函数返回。
Ghidra¶
在 Ghidra 中打开库后,我们可以在 **Symbol Tree** 面板中的 **Functions** 下看到所有定义的函数。当前应用程序的本机库相对来说非常小。有三个用户定义的函数:FUN_001004d0
、FUN_0010051c
和 Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI
。其他符号不是用户定义的,而是为共享库的正确功能生成的。在前面的章节中已经详细讨论了函数 Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI
中的指令。在本节中,我们可以查看该函数的反编译。
在当前函数中,调用了另一个函数,该函数的地址是通过访问 JNIEnv
指针(显示为 plParm1
)中的偏移量来获得的。上面的图表中也以图解方式演示了这个逻辑。反汇编函数的相应 C 代码显示在 **Decompiler** 窗口中。这种反编译的 C 代码使得理解正在进行的函数调用变得更加容易。由于这个函数很小而且非常简单,所以反编译输出非常准确,但在处理复杂函数时,这种情况可能会发生剧烈变化。