018、反汇编代码的重建与编译
返回:全书总览 · 上一章:WannaCry 分析
重建的目标
重建不是追求编译出完全相同的机器字节,而是恢复一个在关注输入范围内行为等价、结构清晰、可验证的实现。
三种目标要分清:
| 目标 | 关注点 |
|---|---|
| 阅读伪代码 | 帮助理解原程序 |
| 行为重实现 | 输入、输出与副作用等价 |
| 二进制级复现 | 编译器、ABI、布局和构建选项高度一致 |
多数学习场景以第二种为主。
从边界开始
第一步不是立刻写 C++,而是建立可靠边界:
- 模块和节区。
- 函数起止地址。
- 基本块与控制流边。
- 调用点与返回点。
- 全局数据、常量和导入函数。
- 间接调用的候选目标。
边界错误会让后续类型和控制流全部错位。
恢复函数原型
对每个函数记录:
1 | 候选名称: |
调用者说明“怎样传”,被调用者说明“怎样用”,两边一致时置信度才高。
恢复类型
类型恢复顺序建议:
- 先确定访问宽度。
- 再判断整数、浮点或地址语义。
- 从步长恢复数组元素类型。
- 从固定偏移恢复结构体字段。
- 从
this和函数聚类恢复类。 - 从虚表与指针调整恢复继承。
未知字段先使用中性名称和字节填充,不要为了“看起来完整”虚构含义。
恢复控制流
先写低层但准确的版本:
1 | if (condition) { |
确认基本块和边之后,再逐步提升为 if、switch、while、for。编译器优化可能让多个高级写法生成同一种控制流,所以应优先恢复等价语义,而不是执着于原作者使用的语法。
先求控制流正确,再求代码漂亮;先保留不确定性,再逐步重命名。
恢复对象模型
将第 9–12 章的证据组合起来:
- 固定成员偏移形成候选结构体。
- 构造函数确认初值和子对象顺序。
- 析构函数确认资源所有权。
- 虚表确认动态接口和槽位。
this调整确认多重继承子对象。
先用纯结构体加普通函数重建也可以,等证据足够后再提升为 C++ 类,往往更稳。
保留副作用
行为等价不仅是返回值一致,还包括:
- 修改全局变量和对象字段。
- 文件、注册表、网络和日志操作。
- 错误码与异常行为。
- 资源申请和释放顺序。
- 线程同步与回调顺序。
遗漏副作用是“看起来一样、运行却不一样”的主要原因。
建立验证夹具
为合法、良性的目标准备测试:
1 | 选取可控输入 |
对于无法直接调用的原函数,可以在自编译的实验程序上先验证推导规则。
Debug 与 Release 对照
同一份源码至少比较:
- x86 Debug。
- x86 Release。
- x64 Debug。
- x64 Release。
观察内联、常量折叠、栈帧省略、循环变换、返回值优化和去虚拟化。这样可以区分“语法固有特征”和“某个构建的偶然形态”。
置信度标注
1 | [确认] 有符号、动态行为或多个交叉引用支持 |
把不确定性写进笔记,比给每个函数编一个看似准确的名字更有价值。
合法与伦理边界
只分析自己编写、明确授权、用于互操作研究或防御响应的程序。保存来源和授权记录,不发布第三方私有代码、密钥、敏感数据或可直接滥用的攻击实现。
全书复盘清单
| 检查项 | 状态 |
|---|---|
| 能先恢复函数与基本块边界 | 待复盘 |
| 能从调用者和被调用者共同恢复函数原型 | 待复盘 |
| 能把数组、结构体、类和继承证据串起来 | 待复盘 |
| 能优先重建等价控制流而非猜原始语法 | 待复盘 |
| 能建立测试夹具验证返回值与副作用 | 待复盘 |
| 能为每个重要结论标注证据和置信度 | 待复盘 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ruiqy~!





















