第九章 Android 原生程序动态调试(三)(使用 IDA Pro 调试 Android 原生程序)
使用 IDA Pro 调试 Android 原生程序
-
和 gdb、lldb 一样,用 IDA 调试原生程序时也要在设备上通过调试服务端进行远程连接。IDA 的 dbdsrv 目录存放着所有远程调试服务端程序:
-
目前 IDA 支持调试
x86
、x86-64
、ARM
、ARM64
四种类型的 Android 原生程序。android_server_nonpie
运行在低版本的设备上,用于调试没开启 PIE 的 ELF。因为设备是 64 位的,故执行如下命令 push -
仍然调试已 push 的 app6
-
将 app6 拖入 IDA,等待分析完成,然后在 main() 的 printf() 调用上按 F2 设断点
-
点击菜单栏
Debugger
->Select Debugger
,打开调试器选择对话框,选择Remote ARM Linux/Android debugger
,点击OK
。再次点击Debugger
->Debugger options...
,勾选如下图所示选项,点击OK
-
启动调试服务端
-
点击菜单栏
Debugger
->Process options...
,如下图所示输入 -
端口转发
-
至此,配置完成
-
点击菜单栏
Debugger
->Start process
或按 F9,开始调试。但此时会弹出一个提示直接执行程序可能会对设备有风险的提示,点击Yes
即可 -
这还没完,接下来会提示找不到文件(忘记截图),点击
Yes
即可。然后又会弹出没找到远端的文件,并询问是否复制一份新的 -
点击
Use found
,IDA 会返回主界面并切换到调试界面。至此,所有调试准备工作都已完成 -
由上图可看出,此时寄存器窗口尚未显示寄存器的值,按 F9 让程序运行。此时可能会弹出一个提示,关于调试路径和 linker64 有问题之类的,点击
Yes
即可。程序会中断在 linker64 的代码空间中,寄存器与堆栈都已被正确显示 -
按
Ctrl+S
,打开段选择对话框,找到.got
并双击,定位到第一项printf
的内存地址 0x0000005559D82FB8 -
点击菜单栏
Debugger
->Debugger windows
->Watch view
,打开监视面板。在其中单击右键,选择Add watch
,输入如下内容,监视该地址的内存变化 -
现在,该地址存放的值为 0x5A0。按 F9 继续运行,程序会再次断在 linker64 的代码空间,但上述地址的值已变化。从
Output window
窗口输出的内容可看出,linker64 在加载/system/lib64/libc.so
后改写了 0x0000005559D82FB8 处的内容,改成了 0x0000007FA9DB22E4。在反汇编窗口按G
,输入 0x0000007FA9DB22E4,点击OK
,将会看到此处的伪代码(书上是这么说,但此处的伪代码并没有识别为 printf,不过从内存窗口可看出)正是 printf() 的内存地址 -
通过上述操作,可得出结论,ELF 的
.got
的符号填充过程是在 linker64 加载和链接依赖库的过程中完成的,ELF 的所有外部函数符号的真实地址会在依赖库加载后全部设置完成,这意味着 Android 系统的链接器没有使用延迟绑定技术(Lazy Binding) -
要想知道具体的符号填充细节,要对 linker64 的代码进行跟踪
-
也可按 Tab 或 F5 切换到伪代码模式进行调试。点击菜单栏
Debugger
->Debugger windows
->Locals
,打开本地变量窗口,可查看动态调试过程中临时变量的信息 -
接下来可按 F7 单步步入,F8 单步步过(伪代码窗口貌似还是会步入),配合伪代码和本地变量窗口,即可很方便地调试(源码级别动态调试)