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 | // demo.c |
然后,我们进入到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所示。
3.2 IDA Pro配置
3.2.1 启动调试服务器
IDA Pro从6.1版本开始引入了Android原生程序的调试功能。与gdb和lldb一样,在使用IDA Pro调试Android原生程序时,也需要在设备上通过调试服务端进行远程调试。IDA Pro的dgbsrv目录下存放着所有的远程调试服务器程序,如图2所示。
目前,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中的命令启动调试服务器。
另外,打开新的cmd,执行如下命令,开启端口转发。
adb forward tcp:23946 tcp:23946
3.2.2 配置IDA Pro的选项
首先,将demo拖入IDA Pro程序的主界面,待IDA Pro分析完成后,在main()的printf()调用上按“F2”键设置断点,如图4所示。
点击菜单栏Debugger->Select Debugger,打开调试器选择对话框,选择Remote ARM Linux/Android debugger,点击OK。再次点击Debugger->Debugger options…,勾选如下图所示选项,点击OK,如图5所示。
点击菜单栏Debugger->Process options…,如图6所示,Hostname处输入localhost或者127.0.0.1均可。
至此,配置工作就完成了,下面就该开始正式搞活了。
3.2.3 开始调试
首先,点击菜单栏Debugger->Start process或按F9,开始调试。但此时会弹出一个提示直接执行程序可能会对设备有风险的提示,点击Yes后出现图7的界面。
由上图可看出,此时寄存器窗口尚未显示寄存器的值,按F9让程序运行。此时可能会弹出一个提示,关于调试路径和linker64有问题之类的,点击Yes即可。程序会中断在linker64的代码空间中,寄存器与堆栈都已被正确显示,如图8所示。
按Ctrl+S,打开段选择对话框,找到.got并双击,定位到第一项printf的内存地址0x0000005D6DA93FC0,如图9&10所示。
点击菜单栏Debugger->Debugger windows->Watch view,打开监视面板。在其中单击右键,选择Add watch,输入如下内容,监视该地址的内存变化,如图11所示。
现在,该地址存放的值为0x560。按F9继续运行,程序会断在libdl.so的代码空间,而且上述地址的值已变化。从Output window窗口输出的内容可看出,libdl.so在加载后改写了0x0000005D6DA93FC0处的内容0x560,改成了0x00000079F547F9C8,如图13所示。
在反汇编窗口按G,输入0x00000079F547F9C8,点击OK,将会看到此处的伪代码(书上是这么说,但此处的伪代码并没有识别为printf,不过从内存窗口可看出)正是printf()的内存地址,如图14所示。
3.2.4 小结
通过上述操作,可得出结论,ELF的.got的符号填充过程是在linker64加载和链接依赖库的过程中完成的,ELF的所有外部函数符号的真实地址会在依赖库加载后全部设置完成,这意味着Android系统的链接器没有使用延迟绑定技术(Lazy Binding)。
3.2.5 扩展
如图15所示,可按Tab或F5切换到伪代码模式进行调试。点击菜单栏Debugger->Debugger windows->Locals,打开本地变量窗口,可查看动态调试过程中临时变量的信息。
接下来可按F7 单步步入,F8 单步步过(伪代码窗口貌似还是会步入),配合伪代码和本地变量窗口,即可很方便地调试(源码级别动态调试)。
4 总结
本文介绍了Android平台上常用的动态调试工具GDB、LLVB和IDA Pro基本配置与操作,而且主要通过demo.c进行原生程序的分析,体验了IDA Pro的强大功能。
接下来,准备向第十章《Hook与注入》进发。
5 参考文献
[1]丰生强. Android软件安全权威指南[M]. 电子工业出版社, 2019.
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。