今天通过两个调试例子:Windows的exe程序和Android的so库,来了解IDA的调试界面和一些调试命令。
调试 Windows 程序
在静态分析篇中 ,我们在示例文件中猜测 sub_140001834
这个函数是将用户的输入存放到一个字符数组中,现在我们就可以使用动态调试来验证一下我们的猜想。
IDA 不仅可以调试汇编,还可以调试它反编译的伪代码,这就意味着我们可以将目标对象当作一个吊毛同事写的代码,然后进行调试。
首先, 我们在需要断点的地方下一个断点,就跟平时调试一样,没啥区别:
在想要下断点的行数左边那个青色小点那里点一下,当前行就会变红,表明这行下了一个断点,这个操作和普通的 IDE 是一样的,或者你也可以按 F2。
由于 IDA 可以调试的程序类型非常之多(但是每个的操作都是统一的,没有学习成本,很吊啊),所以在调试前,我们需要选择调试器的类型:
选择调试器: 菜单项 [Debugger] → [Select debugger...] → 选择 [Local Windows debugger]
选择了之后,我们再看菜单项 [Debugger] :
首先,最下面是切换调试器,当你不小心点错了,那么就选这个切换一下就行。
然后我们介绍两个最常用的选项:
- Start process,这个会重新运行目标程序
- Attach to process,这个选项会根据不同的调试程序类型有不同的行为。
-
- 如果是一个可执行文件,IDA将显示所有与被调试程序同名的进程列表。如有一个都没有的话,会显示每一个进程列表让你自己选择。
- 如果是一个共享库,在 Windows 平台上,IDA 会过滤出所有加载了该共享库的进程。在其他平台上,不会过滤,会让你自行选择。
Start to process 与 Attach to process,还有一个区别,就是我们平时调试经常会遇到的,当我们想调试一个启动阶段的代码时,就只能选择 Start to process。
Process options,这个就是给程序设置一些启动参数,远程调试的时候,可能还需要设置一下密码等。
暂时先介绍这些,我们选择 Start process 重新启动一个程序,在我们的例子中,选择两个都行,有兴趣的可以自己都试试。
当我们点击 Start to process 后,会弹出一个警告窗口,不要慌,不是我们哪里操作错了,而是 IDA 给了一个友善的提醒:
这是因为,当使用IDA来分析恶意软件的时候,调试机器很容易受到感染。所以任何时候,以 Start to process 的方式启动新进程,都会弹这个窗。
如果你比较慌,选择了 NO,那么菜单里面的 Debugger 选项会消失,直到你重新启动 IDA,所以我们直接 Yes,虚拟机怕啥,而且还有快照功能,大不了时光回溯。
点击 Yes 之后,IDA启动了这个程序,我们输入用户名和密码:
按 enter 键,等待程序运行到断点位置:
可以看到,这一行变色了,那么我们如何看 v8 与 v9 的值呢?非常的简单,对准变量,双击左键就行了:
我们可以看到栈上有存我们输入的用户名(得了解C/C++变量在内存中储存方式),以同样的方式也可以看到密码。所以,我们的猜测是得到了验证。
IDA 调试界面的窗口
当IDA进入调试模式的时候,将会显示出6个默认窗口和一个调试工具栏。
7 窗口是调试工具栏,在调试模式中,调试工具栏会替换反汇编工具栏。它里面有很多标准工具可以使用,包括进程控制工具与断点操纵工具。
1 窗口是默认的反汇编窗口,我们从伪代码模式切换成文本模式可以看到当前指令的左边标记了一个 RIP 寄存器:
这是因为 IDA 检测到了有寄存器的指向了该窗口的一个内存位置。
默认情况下,IDA 以红色突出显示断点,以蓝色突出显示下一条要执行的指令,上图中,标记的就是下一条待执行的指令。
2 窗口是栈窗口,主要用于显示进程运行时栈的数据内容。所有指向栈的寄存器,在窗口3中也都会标记出来:
如果栈里面是内存地址,IDA会尝试将该地址解析为函数地址。如果栈里面是数据,则会显示引用数据。
如果我们想跳转到栈顶,可以点击通用寄存器中的 rsp 的蓝色拐弯箭头,栈底就是 rbp 咯。
3 窗口是寄存器窗口,默认显示通用寄存器,但是我们可以在窗口空白位置右键,查看其他寄存器:
寄存器的右边有的会有一个蓝色的拐弯箭头,这个可以用来跳转到具体的地址位置,比如,我先在 IDA View-RIP 窗口里鼠标左键点一下:
然后点一下寄存器 RDX 的蓝色箭头:
我们跳转到了 RDX 指向的位置。这个操作对 4 窗口也生效。
双击寄存器,还可以修改该寄存器的值:
4 窗口是十六进制窗口,表示的是程序的内存。使用频率会很高。
数据窗口跟随寄存器自动切换地址:右键 → [sycnc with] → 选择跟随寄存器,当我们发现某个寄存器一直在操作内存,我们就可以使用该方式看它搞了啥。
5 窗口是当前进程中加载的模块列表,双击其中任何一个,可以看到该模块的导出函数。
6 窗口是当前进程中的线程列表,双击其中任何一个线程,可以跳转到所选线程中的当前指令,同时寄存器窗口会更新为选中线程的。
进程控制
其实和我们平时调试程序是差不多的。
- 继续/F9,继续执行一个暂停的进程,直到遇到下一个断点,用户暂停/终止或者进行自行终止
- 暂停,这个需要在工具栏上操作,暂停一个正在运行的进程。
- 终止(Ctrl + F2),终止一个正在运行的进程。
- 步过(F7):执行一条指令(反汇编窗口) / 执行一行语句(伪代码),不进入函数调用
- 步入(F8):执行一条指令(反汇编窗口) / 执行一行语句(伪代码),进入函数调用
- 执行到返回(Ctrl+F7):执行到当前函数的返回后位置(步入了xx函数,快速步出的方法)
- 执行到光标(F4):执行到光标位置停下来(适用于跳出循环等操作...)
- 运行直到断点(F9):运行
- 设置程序计数器(Ctrl + N):强制代码从光标位置处开始执行,不太清楚会不会导致程序错误,暂未尝试,因为只设置了 RIP,没正确设置其他寄存器,当进入函数时,有可能因为参数等问题报错,除非是用于函数内部走分支。
调试动态链接库
动态链接库调试很简单,只需要调试动态链接库的宿主程序即可,IDA 会自动识别动态链接库。
我们以 Android so 调试为例,首先进入 ida 的 dbgsrv 目录,这是因为IDA它不能直接调试Android程序,而是需要用代理的方式来调试,这个和 frida 是差不多的。
我们选择一个对应平台的文件,我这里是 arm64:
上传到手机的 /data/local/tmp(这个目录没要求,想放哪里放哪里)。
上传完成后,进入该目录,切换到 root 用户(因为调试其他程序需要root权限),添加可执行权限,运行起来:
bash
./android_server64
输出结果如下:
scss
IDA Android 64-bit remote debug server(ST) v7.7.27. Hex-Rays (c) 2004-2022
Listening on 0.0.0.0:23946 (my ip 172.217.160.106)...
如果我们的手机与IDA在同一个局域网内,是可以直接通过 ip + 23946 端口连上并调试的,但是网络调试的方式还是比较慢的,所以我们可以搞一个端口转发,让 adb 自动帮我们传输调试数据,这样就不用通过网络传输,而是通过 adb 传输。
adb forward tcp:23946 tcp:23946
我们先使用 IDA 加载需要分析的 so 文件,最好是等 IDA 分析完成后再调试比较好,因为这样反汇编代码会更精确。
调试程序放到了 p16
我们选择调试下面这个函数,因为它点击按钮后会运行,比较好操作:
IDA 分析完 so 之后,我们在上面的函数里面,选择一个感兴趣的地方打上断点,在 debugger 菜单下选择附加到进程(因为这里我暂时还不清楚如何启动一个android程序),这里还需要设置启动参数,看下一篇内容:
Ctrl + F 搜索目标进程名,选中进程,点击OK即可。经过一段时间的等待(主要是我用的 wifi adb),IDA 弹出一个窗口,询问它在进程找到的 so 是不是你想调试的那个 so,点击 same 就ok了。
Android 的 so 调试时,会先在 libc.so 的这里停下,暂时不知道为啥,我们继续程序。
操作 app,发现程序运行到了我们下断点的位置:
整个过程还是比较简单的。