动态调试是什么
简单说,就是运行程序,运行中监控程序的运行流程,数据变化,和静态分析的对比如下
| 场景 | 动态调试(推荐) | 静态分析(推荐) |
|---|---|---|
| 确认参数寄存器 / 调用约定 | ✅ 直接观察寄存器值变化 | ❌ 容易因编译器差异判断错误 |
| 提取解密后的中间数据 | ✅ 内存中直接追踪字符串 | ❌ 需逆向所有加密步骤,易出错 |
| 绕过校验 / 触发 flag | ✅ 改内存 / 寄存器直接满足条件 | ❌ 可能需要 patch 二进制,步骤繁琐 |
| 理解整体代码结构 | ❌ 太细节,容易陷入指令级泥潭 | ✅ 快速看函数调用、逻辑分支 |
先说一些基础概念
- 断点:程序执行到此处会停下
- 单步(F8):程序执行一步
- 运行(F9):运行程序,到断点停下
为什么要动态调试
- 快速观察程序运行流程,变量流向。
- 程序有加密流程,追踪字符串,观察加密方法
- 需要控制程序走向
怎么动态调试
今天我们只讲IDA的动态调试。首先我们要先明确一点,动态调试本质是要让程序运行,不同车保程序的运行平台也是不一样的,因此,IDA动态调试分为本地调试和远程调试两种。具体怎么远程调试这里不展开,网上教程很多
1.动态调试代替思考
对于动态调试,我想用一题简单的例题来引入,这是一道十分基础的XOR题

那么已知主函数如下,密文已经显示,且只存在XOR计算,如何最快解出flag。因为这个XOR函数计算比较简单,大多数人会选择用python写一个反推脚本(flag+1=x⬅️flag=x-1),这很好理解
如果从另一个角度来想,XOR运算是可逆运算(a异或b=c,a异或c=b),我们用动态调试会怎么样
这里为了方便大家观察,因此我下了两个断点,正常来说只要在图一的if判断处下一个就可以了

选择Local Window debugger后点击绿色小箭头(F9)执行程序

接着在弹出的命令窗输入主函数看到的密文回车,此时程序停止在我们的第一个断点,我们可以打开字符串表(Locals)更方便看变化的字符串。(菜单栏Debugger➡️debugger windows➡️Locals)此时我们可以看到Str1和Str2内的值都是一样的,而下一步就是进行异或运算

此时我们点击F8,程序会执行下一步,边执行边观察Locals窗口,发现Str1的值变红,证明发生了变化,观察就是flag

此时,你是否意识到,我们根本没有看XOR中的加密流程,而是直接利用动态调试绕过计算帮我们输出了结果,这就是动态调试最大的好处之一。回忆一下,自己品味一下为什么可以这样
2.动态调试劫持控制
接下来我们来看第二题,一道利用动态调试实现非预期解的题目,不过由于这题的base64加密没有魔改,因此直接解会更快,但是我们学习重点是如何用动态调试的方法来帮我们跳过加密的步骤。
看主函数,可以发现是base64加密,密文也摆在眼前了,但是注意到只执行了encode函数,还有一个decode的函数并没有被执行

那么,我们可不可以让这个decode函数帮我们解密呢,当然。那我们需要什么?
1.decode函数地址
2.密文地址(打开shift+F12找)
提前准备好,准备开始劫持!
首先,我们在call encode函数处下个断点,先不让程序执行加密函数

接下来,来到decode函数,在其将解密的值传回处,下个断点,先不让程序结束

接下来观察,输入encode函数Str的哪个寄存器,一般来说是RDI,不过这里是RCX

F9运行程序,随便输点东西。程序停在call encode前,这时候我们要让它执行call decode。在register窗口找到RIP,(储存程序要跳转的下一个地址)双击并修改地址为decode的地址

此时成功就会跳转到decode函数,记得刚刚说过RCX是要输入到Str的寄存器吗,双击它,将地址改成密文的地址

F9执行程序,我们就会发现,RDX寄存器的内容已经发生变化,这就是我们要的结果。看起来不像flag是因为,题目还有一步调换字符,这里就不展示了。

至此,我们又能发现动态调试一个强大的功能,就是控制流劫持。我们可以构造有利于我们解题的条件。

动态调试是 “让程序‘说话’” 的工具 —— 它将静态代码转化为可交互的运行过程,既能验证分析结论,又能直接获取关键数据、绕过复杂逻辑,是逆向中 “从理论到实操” 的核心桥梁,尤其在静态分析陷入困境时,动态调试往往是破局的关键
相信你已经掌握动态调试了,快去练几题试试吧!


