10、动态分析工具(下):内存查看与修改、堆栈跟踪、Run Trace

好,咱们接着聊动态分析。上一章我们把断点和单步执行讲透了,这一章要聊的,是真正让你「看见」程序灵魂的东西——内存、堆栈,还有执行轨迹。

我个人觉得,这三样东西才是逆向工程的硬功夫。断点只是敲门砖,真正的高手,是能通过内存和堆栈的变化,把程序的逻辑「读」出来的。

10.1 内存查看与修改:直接动程序的「内脏」

你想想看,程序跑起来之后,所有的变量、对象、标志位,最终都落在内存里。能直接看内存,就等于能透视程序的内部状态。

10.1.1 内存窗口怎么用

大部分调试器(比如 x64dbg、OllyDbg、IDA Pro)都有内存窗口。用法其实很简单:

  • 地址输入:直接输入十六进制地址,或者输入表达式(比如 ebp-0x4
  • 数据解读:默认显示十六进制字节,右侧是 ASCII 字符
  • 修改数据:双击字节,直接改,回车生效

核心要点:内存修改是动态分析的「核武器」。改一个字节,可能就跳过了验证、解锁了功能。

10.1.2 实战:改内存跳过密码验证

我在项目中遇到过这样一个场景:一个老旧的 Windows 程序,登录框死活找不到正确的密码。我懒得去逆向算法,直接动态调试,找到密码比对后的标志位。

// 伪代码示意
if (strcmp(input, password) == 0) {
    flag = 1;  // 这个 flag 在内存地址 0x00401234
} else {
    flag = 0;
}

我在内存窗口找到 0x00401234,看到 flag 是 0。输入错误密码,断点命中后,我直接把 0 改成 1。程序就认为验证通过了。

小技巧:改内存比改汇编代码更隐蔽。有些程序会做代码完整性校验,但很少校验数据段。

10.1.3 内存搜索技巧

有时候你不知道变量在哪个地址,怎么办?搜!

  • 精确搜索:搜具体的数值,比如搜 0x12345678
  • 模糊搜索:搜字符串,比如搜 "password"
  • 变化搜索:先搜一次,让程序运行一下,再搜变化后的值

嗯,这里要注意:内存搜索在大型程序里会很慢。我建议你先缩小范围,比如只搜某个 DLL 的内存空间。

10.2 堆栈跟踪:看穿函数的「前世今生」

堆栈跟踪(Stack Trace),说白了就是告诉你「当前这行代码是谁调过来的」。这玩意儿在分析崩溃、理解调用流程时,简直是神器。

10.2.1 堆栈的结构

堆栈是后进先出的。每次函数调用,都会压入返回地址、参数、局部变量。堆栈跟踪就是把这些信息一条条列出来。

// 堆栈跟踪示例(从顶部到底部)
0x00401234 - main() 调用了 funcA()
0x00405678 - funcA() 调用了 funcB()
0x004089AB - funcB() 调用了 funcC()  <-- 当前在这里

你看,从下往上读,就是完整的调用链。

10.2.2 怎么用好堆栈跟踪

我个人习惯是:当程序崩溃时,第一件事就是看堆栈。它能告诉你崩溃发生在哪个函数,以及是谁调了它。

避坑指南:我曾经遇到一个程序,堆栈被破坏了(栈溢出攻击),显示的调用链完全乱掉。这时候别信堆栈,得靠内存分析来还原。

10.2.3 手动分析堆栈

有时候调试器给的堆栈跟踪不准(比如优化过的代码),你就得手动看:

  1. 找到当前 ESP(栈顶指针)
  2. 往上读,找到返回地址(通常是 call 指令的下一条)
  3. 根据返回地址,反推是哪个函数调用的

这活儿有点累,但很锻炼人。我刚开始学的时候,经常手动画堆栈图,画着画着就懂了。

10.3 Run Trace:程序的「行车记录仪」

Run Trace,也叫执行轨迹。它会把程序执行过的每一条指令都记录下来。你想想看,这得有多大信息量?

10.3.1 什么时候用 Run Trace

  • 分析复杂算法:比如加密算法,你单步跟会累死,用 Run Trace 跑一遍,然后分析日志
  • 找隐藏功能:有些代码只在特定条件下执行,Run Trace 能帮你找到那些「暗门」
  • 对比分析:跑两次,一次正常,一次异常,对比轨迹差异

10.3.2 怎么用 x64dbg 做 Run Trace

x64dbg 的 Run Trace 功能很强大。我简单说一下步骤:

  1. 设置好断点,让程序跑到你感兴趣的位置
  2. 打开 Run Trace 窗口,点击「开始记录」
  3. 让程序继续跑(或者用 Trace Into)
  4. 停止记录,查看日志

注意:Run Trace 会产生海量数据。跑几秒钟可能就几百万条指令。我建议你设置过滤条件,比如只记录某个模块的指令。

10.3.3 分析 Run Trace 日志

日志里每一行都是一条指令。你会看到:

地址        指令                    备注
0x00401000  mov eax, dword ptr [ebp-4]
0x00401003  add eax, 0x1
0x00401006  mov dword ptr [ebp-4], eax

你可以搜索特定的指令模式,比如搜索 call 指令,看看程序调了哪些函数。

嗯,这里有个坑:Run Trace 日志太大,普通文本编辑器打不开。我建议用专门的日志分析工具,或者写脚本过滤。

10.4 知识体系总览

为了让你更直观地理解这三样工具的关系,我画了一张图:

动态分析工具(下)核心知识体系 内存查看与修改 • 查看内存数据 • 修改内存值 • 内存搜索(精确/模糊) • 变化搜索 • 修改标志位 • 绕过验证 ⚠ 注意代码校验 堆栈跟踪 • 查看调用链 • 分析崩溃原因 • 理解函数调用关系 • 手动分析堆栈 • 定位返回地址 • 反推调用者 ⚠ 堆栈可能被破坏 Run Trace • 记录执行轨迹 • 分析复杂算法 • 寻找隐藏功能 • 对比正常/异常 • 过滤模块指令 • 搜索特定模式 ⚠ 数据量巨大 三者配合:内存定位数据 → 堆栈追踪调用 → Run Trace 还原全貌

10.5 三者配合的实战思路

光会单个工具不够,你得把它们串起来用。我举个例子:

假设你在分析一个恶意软件,它解密了一段数据。你怎么搞?

  1. 用内存搜索:找到解密后的数据在内存中的位置
  2. 下内存断点:看谁在读写这块内存
  3. 看堆栈跟踪:找到读写内存的函数调用链
  4. 用 Run Trace:记录这个函数的执行轨迹,分析解密算法

你看,一套组合拳下来,再复杂的逻辑也能给你拆得明明白白。

我的经验:别指望一次就能搞定。动态分析是个反复迭代的过程——改内存、看效果、跟踪堆栈、再改内存。多试几次,规律就出来了。

好了,这一章的内容就到这儿。内存查看、堆栈跟踪、Run Trace,这三样东西你练熟了,逆向工程就算入了门。剩下的,就是多练、多踩坑、多总结。


公众号:蓝海资料掘金营,微信 deep3321