🎨 颜色标记说明

  • 粉色:特别重要、主线必掌握、优先复盘的内容。
  • 蓝色:核心概念、术语、分类名。
  • 橙色:需要关注的边界、易错点、注意事项。
  • 绿色:解释类内容、帮助理解知识点。

返回:全书总览 · 上一章:WannaCry 分析

重建的目标

重建不是追求编译出完全相同的机器字节,而是恢复一个在关注输入范围内行为等价、结构清晰、可验证的实现。

三种目标要分清:

目标 关注点
阅读伪代码 帮助理解原程序
行为重实现 输入、输出与副作用等价
二进制级复现 编译器、ABI、布局和构建选项高度一致

多数学习场景以第二种为主。

从边界开始

第一步不是立刻写 C++,而是建立可靠边界:

  • 模块和节区。
  • 函数起止地址。
  • 基本块与控制流边。
  • 调用点与返回点。
  • 全局数据、常量和导入函数。
  • 间接调用的候选目标。

边界错误会让后续类型和控制流全部错位。

恢复函数原型

对每个函数记录:

1
2
3
4
5
6
7
8
候选名称:
调用约定:
参数数量与位置:
参数访问宽度:
返回值:
this / 隐藏返回缓冲区:
修改的外部状态:
所有调用点是否一致:

调用者说明“怎样传”,被调用者说明“怎样用”,两边一致时置信度才高。

恢复类型

类型恢复顺序建议:

  1. 先确定访问宽度。
  2. 再判断整数、浮点或地址语义。
  3. 从步长恢复数组元素类型。
  4. 从固定偏移恢复结构体字段。
  5. this 和函数聚类恢复类。
  6. 从虚表与指针调整恢复继承。

未知字段先使用中性名称和字节填充,不要为了“看起来完整”虚构含义。

恢复控制流

先写低层但准确的版本:

1
2
3
4
if (condition) {
goto block_a;
}
goto block_b;

确认基本块和边之后,再逐步提升为 ifswitchwhilefor。编译器优化可能让多个高级写法生成同一种控制流,所以应优先恢复等价语义,而不是执着于原作者使用的语法。

先求控制流正确,再求代码漂亮;先保留不确定性,再逐步重命名。

恢复对象模型

将第 9–12 章的证据组合起来:

  • 固定成员偏移形成候选结构体。
  • 构造函数确认初值和子对象顺序。
  • 析构函数确认资源所有权。
  • 虚表确认动态接口和槽位。
  • this 调整确认多重继承子对象。

先用纯结构体加普通函数重建也可以,等证据足够后再提升为 C++ 类,往往更稳。

保留副作用

行为等价不仅是返回值一致,还包括:

  • 修改全局变量和对象字段。
  • 文件、注册表、网络和日志操作。
  • 错误码与异常行为。
  • 资源申请和释放顺序。
  • 线程同步与回调顺序。

遗漏副作用是“看起来一样、运行却不一样”的主要原因。

建立验证夹具

为合法、良性的目标准备测试:

1
2
3
4
5
选取可控输入
记录原程序输出和状态变化
运行重建实现
比较返回值、缓冲区、文件和日志
覆盖边界值与错误路径

对于无法直接调用的原函数,可以在自编译的实验程序上先验证推导规则。

Debug 与 Release 对照

同一份源码至少比较:

  • x86 Debug。
  • x86 Release。
  • x64 Debug。
  • x64 Release。

观察内联、常量折叠、栈帧省略、循环变换、返回值优化和去虚拟化。这样可以区分“语法固有特征”和“某个构建的偶然形态”。

置信度标注

1
2
3
4
[确认] 有符号、动态行为或多个交叉引用支持
[高] 多条静态证据一致,尚未动态验证
[中] 结构合理,但存在多个等价解释
[低] 仅根据单条指令或单一调用点猜测

把不确定性写进笔记,比给每个函数编一个看似准确的名字更有价值。

合法与伦理边界

只分析自己编写、明确授权、用于互操作研究或防御响应的程序。保存来源和授权记录,不发布第三方私有代码、密钥、敏感数据或可直接滥用的攻击实现。

全书复盘清单

检查项 状态
能先恢复函数与基本块边界 待复盘
能从调用者和被调用者共同恢复函数原型 待复盘
能把数组、结构体、类和继承证据串起来 待复盘
能优先重建等价控制流而非猜原始语法 待复盘
能建立测试夹具验证返回值与副作用 待复盘
能为每个重要结论标注证据和置信度 待复盘