4. 污点追踪实战(一):基于x64dbg的简单污点标记与追踪(追踪用户输入)
好,咱们今天来点真家伙。
前面几章讲了符号执行和污点追踪的理论,说实话,光看理论容易犯困。我当年刚接触这些概念时,也是看了好几遍文档,总觉得隔着一层窗户纸。直到有一天,我在分析一个CTF题目时,手动在x64dbg里标记了一个输入字节,看着它一路传播到比较指令——那一刻,我才真正理解了什么叫「污点追踪」。
所以这一章,咱们就动手做。用x64dbg,从一个最简单的场景开始:追踪用户输入。
4.1 为什么选x64dbg?
市面上调试器不少,OllyDbg、WinDbg、GDB……但我个人习惯用x64dbg做逆向分析。原因有三:
- 插件生态好:x64dbg有现成的污点追踪插件,虽然功能不算特别强大,但做教学演示足够了。
- 界面直观:内存布局、寄存器状态、反汇编窗口一目了然,适合初学者建立直觉。
- 跨平台支持:虽然主要跑在Windows上,但分析Linux程序也能用(配合WSL或远程调试)。
说白了,x64dbg就像一把瑞士军刀——不一定最锋利,但什么都能干一点。
核心思路:我们手动标记用户输入所在的缓冲区为「污点源」,然后观察这个污点如何传播到其他内存和寄存器。最终,我们想看到的是:用户输入是否影响了某个关键判断条件。
4.2 准备工作:搭建实验环境
先准备好工具和样本:
- x64dbg(建议用最新版,我用的v2024.12.30)
- 一个简单的C程序(接收用户输入,做字符串比较)
- 污点追踪插件(x64dbg自带或社区版)
咱们的测试程序长这样:
#include <stdio.h>
#include <string.h>
int main() {
char input[64];
printf("Enter password: ");
fgets(input, 64, stdin);
input[strcspn(input, "\n")] = 0; // 去掉换行符
if (strcmp(input, "secret123") == 0) {
printf("Access granted!\n");
} else {
printf("Access denied!\n");
}
return 0;
}
嗯,很经典的密码验证程序。我们的目标就是追踪用户输入的字符串,看它怎么跟"secret123"比较的。
小提示:编译时记得关掉优化(/Od),不然编译器可能把strcmp内联掉,追踪起来反而不直观。
4.3 手动标记污点源
启动x64dbg,加载编译好的exe。在fgets调用之后下断点——具体位置是fgets返回后的第一条指令。
怎么找这个位置?我一般用两种方法:
- 在反汇编窗口找到
call fgets,然后在下一行下断点。 - 或者直接在
main函数入口下断,然后单步跟到fgets返回。
我个人更推荐第一种,省时间。
断下来后,查看input缓冲区的地址。在x64dbg的「内存布局」窗口里,找到栈上对应的位置。通常input数组就在rbp-0x40附近(64位程序)。
现在,我们要手动标记这个缓冲区为污点源。x64dbg的污点追踪插件提供了SetTaint命令:
SetTaint 0x0019FEC0 64 1
参数解释:
0x0019FEC0:input缓冲区的起始地址64:标记的字节数(缓冲区大小)1:标记为污点(0是清除污点)
注意:地址每次运行都可能不同,因为ASLR(地址空间布局随机化)会改变栈地址。建议在x64dbg里直接右键复制地址,别手打。
4.4 单步追踪:看污点怎么传播
标记完污点源后,我们开始单步执行。每走一步,观察污点追踪窗口的变化。
这里有个关键点:污点传播的规则。简单来说:
- 如果一条指令的源操作数包含污点,那么目的操作数也会被标记为污点。
- 例如:
mov eax, [rbp-0x40],如果[rbp-0x40]是污点,那么eax也会变成污点。 - 算术指令(add、sub、xor等)同样会传播污点。
- 控制流指令(jmp、call)不会直接传播污点,但会影响后续指令的上下文。
我当年踩过一个坑:以为所有指令都会传播污点。实际上,像lea指令(加载有效地址)只计算地址,不读取内存内容,所以不会传播污点。但mov指令会。这个区别在分析复杂程序时特别重要。
咱们继续单步。当执行到call strcmp时,注意看参数:
mov rcx, rbp-0x40 ; 第一个参数:input缓冲区(污点)
mov rdx, 0x00401000 ; 第二个参数:硬编码的"secret123"(非污点)
call strcmp
此时,rcx已经被标记为污点(因为来自input缓冲区)。rdx是干净的。进入strcmp内部后,你会看到污点随着字节比较逐步传播到eax(返回值)。
关键观察:当strcmp比较到第一个不匹配的字符时,eax的污点状态会发生变化。如果输入的第一个字符是's'(匹配),那么eax可能暂时保持干净;如果输入的是'a'(不匹配),eax会立即被标记为污点。
4.5 污点追踪的实战意义
你可能会问:手动单步追踪,效率是不是太低了?
嗯,确实。但这是理解污点传播机制的最好方式。就像学开车,先得在驾校练倒库,虽然慢,但能建立肌肉记忆。
在实际的漏洞分析中,污点追踪的价值体现在:
- 定位输入处理路径:快速找到用户输入影响了哪些代码分支。
- 发现未预期的传播:比如输入数据被复制到了堆上,然后被某个函数使用——这可能是漏洞触发点。
- 辅助模糊测试:知道哪些字节影响了关键判断,可以更有针对性地变异输入。
我曾经分析过一个网络服务的缓冲区溢出漏洞。服务端接收用户输入后,先做了一次base64解码,然后才复制到栈缓冲区。如果不用污点追踪,我根本想不到输入数据会经过解码函数后仍然保持「污点」属性——但事实上,解码后的数据依然来自用户输入,所以污点会沿着数据流传播。最终,我在解码后的数据上发现了溢出点。
4.6 常见问题与避坑指南
做污点追踪时,有几个地方容易翻车:
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 污点丢失 | 某些指令(如xor eax, eax)会清零寄存器,导致污点消失 |
注意观察清零指令,必要时重新标记 |
| 污点爆炸 | 污点传播到大量内存区域,追踪窗口刷屏 | 缩小追踪范围,只关注关键寄存器或地址 |
| 插件不工作 | x64dbg版本不匹配或插件未加载 | 检查插件目录,或使用内置的Taint功能 |
我的经验:如果污点突然丢失,先检查是不是遇到了rep movs这类串指令。x64dbg的污点追踪插件对串指令的支持有时不完善,需要手动跟进。
4.7 本章小结
这一章我们做了三件事:
- 在x64dbg里手动标记用户输入为污点源。
- 单步追踪污点传播,观察它如何影响寄存器和内存。
- 理解了污点追踪在实战中的意义——定位输入处理路径、发现漏洞触发点。
说实话,手动追踪确实有点累。但这是建立直觉的必经之路。下一章我们会引入自动化工具,让污点追踪更高效。
最后,送大家一句话:逆向分析没有捷径,但污点追踪是那条最近的路。