使用IDA Pro调试Android原生程序

1 背景

距离写上一篇博客已经有将近5个月了,在这期间不仅发表了小论文,还成功参加了护网行动,也算挺圆满的啦🤭🤭🤭

开学也已经两周了,到校隔离完成后就开始干活了,导师真是把我当成生产队的驴了,让我扛起实验室的大梁/(ㄒoㄒ)/~~

不过,也不能忘了自己的主业,还得接着搞Android逆向,今天开始使用IDA Pro调试Android原生程序的探索吧。

2 简介

今年来,由于Android原生程序的软件保护技术日趋成熟,市面上很多软件和病毒也开始用加密和混淆技术强化自身。对此,静态分析已很难奏效,要用到动态调试。
随着软件安全技术的对抗升级,对逆向分析人员的技能要求也达到了一个新高度。使用C、C++开发的原生程序,其语言的先天特性决定其二进制代码的分析难度比Java开发的DEX高得多,加上高强度的代码加密和混淆技术,逆向分析更加困难。
因此,使用调试器配合脚本自动化技术,实现原生程序的自动化分析、自动化去除混淆与动态脱壳,已成为逆向分析中必须掌握的技能。

gdb调试器和lldb调试器的命令行操作略显不足,因此,这里主要介绍使用IDA Pro调试Android原生程序。

3 调试流程

3.1 用Clang编译C语言代码

首先,编写如下程序demo.c,以此来为后面调试原生程序做准备。

1
2
3
4
5
6
7
8
9
// demo.c
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main(int argc, char *argv[]) {
printf("add: %d\n", add(1, 2));
return 0;
}

然后,我们进入到AndroidSDK\ndk\21.4.7075529\toolchains\llvm\prebuilt\windows-x86_64\bin文件夹下,并打开cmd,输入下面的命令编译生成demo程序。

aarch64-linux-android21-clang ../../../../../../../../../../tmp/tmp/test/demo.c -o demo -fPIE -pie

接着,将生成的demo程序上传到连接到电脑的手机的/data/local/tmp目录下,命令如下所示。

adb push demo /data/local/tmp
adb shell
cd /data/local/tmp
chmod 777 demo

最后,可以使用NDK Toolchain查看ELF的文件头信息,结果如图1所示。

图1 使NDK Toolchain查看demo的文件头信息

3.2 IDA Pro配置

3.2.1 启动调试服务器

IDA Pro从6.1版本开始引入了Android原生程序的调试功能。与gdb和lldb一样,在使用IDA Pro调试Android原生程序时,也需要在设备上通过调试服务端进行远程调试。IDA Pro的dgbsrv目录下存放着所有的远程调试服务器程序,如图2所示。

图2 IDA Pro远程调试服务器程序

目前,IDA Pro支持调试x86、x86-64、ARM、ARM64共四种类型的Android原生程序,我的电脑是64位的,需要将android_server64上传到手机的/data/local/tmp目录下,如下面所示。

adb push android_server64 /data/local/tmp
adb shell
cd /data/local/tmp
chmod 777 demo

上传android_server64完成并赋予rwx的权限后,执行图3中的命令启动调试服务器。

图3 启动调试服务器

另外,打开新的cmd,执行如下命令,开启端口转发。

adb forward tcp:23946 tcp:23946

3.2.2 配置IDA Pro的选项

首先,将demo拖入IDA Pro程序的主界面,待IDA Pro分析完成后,在main()的printf()调用上按“F2”键设置断点,如图4所示。

图4 在printf()处设置断点

点击菜单栏Debugger->Select Debugger,打开调试器选择对话框,选择Remote ARM Linux/Android debugger,点击OK。再次点击Debugger->Debugger options…,勾选如下图所示选项,点击OK,如图5所示。

图5 Debugger setup设置

点击菜单栏Debugger->Process options…,如图6所示,Hostname处输入localhost或者127.0.0.1均可。

图6 Debug applicationo setup: armlinux设置

至此,配置工作就完成了,下面就该开始正式搞活了。

3.2.3 开始调试

首先,点击菜单栏Debugger->Start process或按F9,开始调试。但此时会弹出一个提示直接执行程序可能会对设备有风险的提示,点击Yes后出现图7的界面。

图7 调试demo的主界面

由上图可看出,此时寄存器窗口尚未显示寄存器的值,按F9让程序运行。此时可能会弹出一个提示,关于调试路径和linker64有问题之类的,点击Yes即可。程序会中断在linker64的代码空间中,寄存器与堆栈都已被正确显示,如图8所示。

图8 demo中断在linker64的代码空间

Ctrl+S,打开段选择对话框,找到.got并双击,定位到第一项printf的内存地址0x0000005D6DA93FC0,如图9&10所示。

图9 找到.got段

图 10 找到printf的内存地址

点击菜单栏Debugger->Debugger windows->Watch view,打开监视面板。在其中单击右键,选择Add watch,输入如下内容,监视该地址的内存变化,如图11所示。

图11 设置watch view来监视地址的内存变化

图12 查看IDA View-PC界面

现在,该地址存放的值为0x560。按F9继续运行,程序会断在libdl.so的代码空间,而且上述地址的值已变化。从Output window窗口输出的内容可看出,libdl.so在加载后改写了0x0000005D6DA93FC0处的内容0x560,改成了0x00000079F547F9C8,如图13所示。

图13 修改地址值

在反汇编窗口按G,输入0x00000079F547F9C8,点击OK,将会看到此处的伪代码(书上是这么说,但此处的伪代码并没有识别为printf,不过从内存窗口可看出)正是printf()的内存地址,如图14所示。

图14 反汇编窗口

3.2.4 小结

通过上述操作,可得出结论,ELF.got的符号填充过程是在linker64加载和链接依赖库的过程中完成的,ELF的所有外部函数符号的真实地址会在依赖库加载后全部设置完成,这意味着Android系统的链接器没有使用延迟绑定技术(Lazy Binding)。

3.2.5 扩展

如图15所示,可按TabF5切换到伪代码模式进行调试。点击菜单栏Debugger->Debugger windows->Locals,打开本地变量窗口,可查看动态调试过程中临时变量的信息。
接下来可按F7 单步步入F8 单步步过(伪代码窗口貌似还是会步入),配合伪代码和本地变量窗口,即可很方便地调试(源码级别动态调试)。

图15 伪代码分析

4 总结

本文介绍了Android平台上常用的动态调试工具GDB、LLVB和IDA Pro基本配置与操作,而且主要通过demo.c进行原生程序的分析,体验了IDA Pro的强大功能。
接下来,准备向第十章《Hook与注入》进发。

5 参考文献

[1]丰生强. Android软件安全权威指南[M]. 电子工业出版社, 2019.


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。

×

喜欢就点赞,疼爱就打赏