逆向工程实战手册 第 22 章

第 22 章 枚举与爆破:逆向的通用解题技术

本章核心是方法论与决策,不是代码。目标:知道"什么时候该枚举、什么时候该爆破、什么时候换工具",以及怎么把枚举/爆破做得更聪明。

📍 知识点地图 | 主题:枚举与爆破 | 前置:第9章 | 后续:— | 核心概念:枚举分类、爆破空间、侧信道、决策树

22.1 为什么这是逆向的"地基技术"

逆向中一切手段的终点都可以归结为两件事:

缩小空间 → 枚举(系统性地列出候选)
验证候选 → 比较/计算(判断哪个是对的)
  • 静态分析:本质上是在枚举"程序里有什么"(字符串、函数、常量、xref)
  • 动态观察:是在枚举"运行时发生了什么"
  • 爆破:是枚举"所有可能的答案"
  • 符号执行:是"用数学枚举替代穷举"(约束求解)

理解枚举与爆破 = 理解逆向的底层逻辑。 高级工具只是把这两件事自动化了。

22.2 枚举(Enumeration):先穷举"有什么"

枚举是分析的起点。不同类型的"枚举清单":

枚举对象 工具/手段 获得的信息
字符串 strings / rabin2 -z / IDA 线索、格式、硬编码值
导入/导出 rabin2 -i/-E / readelf 行为分类(网络/文件/加密)
函数 afl / IDA Functions 逻辑地图、可疑点
交叉引用 axt / xrefs_to 谁用了什么
类/方法(Java/ObjC) jadx / class-dump 业务结构(元数据=金矿)
opcode(自定义 VM) 反汇编器 + 分派表 指令集架构
节区/段 iS / readelf -S 代码 vs 数据分布
配置/C2 配置提取器 恶意软件全家桶

枚举的原则: 1. 先全量再定向:先看全貌(survey),再深入单点 2. 元数据优先:有符号/有元数据的枚举(Go pclntab、ObjC 类、.NET IL)永远比硬逆向便宜 3. 枚举是持续的:每深入一步,都要重新枚举新发现的对象(新字符串→新函数→新常量→新枚举)

枚举的架构化流程

拿到未知样本
  ↓
枚举文件层: file / magic / 节区 / 壳检测
  ↓
枚举内容层: 字符串 / 导入 / 符号 / 函数列表
  ↓
枚举关系层: xref / 调用图 / 数据流
  ↓
枚举语义层: 类/方法/opcode/协议字段
  ↓
确定"枚举空间已收敛" → 转入深挖或求解

什么时候枚举结束? 当新增枚举不再产生新线索、候选空间小到可穷举、或已定位到唯一可疑点时。

22.3 爆破(Brute-Force):枚举所有可能答案

爆破的空间分类

爆破对象 典型空间 可行性
单字节 XOR key 256 ✅ 秒级
双字节 XOR key 65536 ✅ 秒级
短 key 循环 XOR(≤4 字节) 4 字节全空间 2^32 ⚠️ 视明文约束
逐字节 flag(有 oracle) 每个位置 ≤95 字符 ✅ 线性!
全 flag 空间(无 oracle) 95^32 ❌ 不可能
校验和/短值 2^16~2^32 ✅~⚠️
弱密码(hashcat) 字典 ✅ 用规则集

核心判断:爆不爆得动,取决于两个变量——空间大小和 oracle 有无。

无 oracle + 大空间  → 爆破不可行 → 换约束求解/符号执行/侧信道
有 oracle + 任意空间 → 爆破总是可行(每位置只试候选数)
有已知明文 + 小空间 → 爆破可行(推 key 再验证)

爆破的决策树

要破解的"答案"空间多大?
├─ ≤ 2^16 → 直接爆破(纯离线,写脚本)
├─ ≤ 2^32 → 有约束/已知明文 → 爆破;否则评估耗时
├─ > 2^32 → 有没有 oracle(程序/网络能告诉你"对不对")?
│   ├─ 有 → 逐位置爆破(线性!)——见 22.4
│   └─ 没有 → 能不能缩小空间?
│       ├─ 已知明文前缀(flag{)→ 推 key
│       ├─ 可打印字符/格式约束 → 乘性缩小
│       └─ 都做不到 → 换符号执行/angr/Z3(第 7 章)

22.4 侧信道:让爆破从"指数"变"线性"

爆破最大的敌人是指数空间。侧信道(Side Channel)能把"验证整串答案"变成"一次猜一个字节"——从 O(空间^n) 降到 O(95·n)。

四种经典侧信道

侧信道 原理 攻击形态
时序(timing) 正确前缀越多,程序跑得越久/越快 逐字节爆破
比较返回值(memcmp/strcmp) 返回"前几个字节匹配"的差值 逐字节爆破
执行路径(分支/信号/调用) 不同输入走不同分支 用 strace/ltrace 观察
指令计数(Pin/Stalker) 不同输入执行指令数不同 逐字节爆破

逐字节爆破的通用架构

for 位置 i in 0..n-1:
    for 候选 c in 字符集(≈95):
        输入 = 已知前缀 + c + 填充
        观测 = 程序行为(耗时/返回值/路径/计数)
        if 观测最强: 前缀 += c; break

观测信号的选择优先级:返回值字节数 > 路径/分支 > 指令计数 > 时序(噪声大)。

经典案例:memcmp 逐字节爆破

场景: 程序用 memcmp(输入, 期望, 32) 校验,但程序有反调试、不打印 flag
信号: memcmp 返回 (第一个不同字节的索引差) —— 前缀越对,返回越大
攻击: LD_PRELOAD hook memcmp → 记录返回值 → 逐字节确定
       每位置 95 次尝试, 32 位置 → ~3000 次运行搞定

侧信道在本书中的分布(知识地图)

时序侧信道          → patterns.md / field-notes.md(Timing Side-Channel)
memcmp 返回值       → tools-dynamic.md(LD_PRELOAD memcmp Side-Channel)
SIGFPE 信号计数     → anti-analysis.md / patterns.md(信号处理器侧信道)
Brainfuck 读次数    → languages.md(Read Count Oracle)
指令计数(movfuscated) → tools-dynamic.md(Intel Pin Instruction Counting)
INT3 + coredump     → patterns.md(INT3 Patch + Coredump Brute-Force Oracle)

22.5 枚举/爆破 vs 其他技术的选型架构

拿到一个问题(例:找到让程序输出 Correct 的输入)
  ↓
□ 空间小(≤2^16)?            → 爆破
□ 有字符串/硬编码?            → 枚举 → 直接读答案
□ 程序有 oracle(能验证对错)? → 侧信道爆破(22.4)
□ 逻辑是纯函数式校验?         → 符号执行 angr(第 7 章)
□ 逻辑是数学约束?             → Z3 建模(第 7 章)
□ 逻辑藏在变换链里?           → 枚举层序 → 逐层反推(第 9 章)
□ 以上都失败?                 → 回到 22.2 重新枚举,找漏掉的线索

原则:先枚举(找捷径),再爆破(兜底),用约束(提效)。

22.6 两个完整思维演练(重方法,轻代码)

演练 1:XOR 密文的破解决策

发现: .rodata 有一段高熵数据 + 代码里 c ^= K
问: 怎么破?
1. 枚举: key 长度未知?看代码循环结构(单字节/循环/位置相关)
2. 已知明文: 有 flag{ 前缀 → 直接推前 5 字节 key,验证剩余
3. 无已知明文 → 单字节: 256 枚举,可打印率过滤
4. 循环多字节 key → 用频率分析/分组统计,或暴力长度 1-16
决策点: 空间 = 256^len, len≤2 直接爆, len>2 必须用明文/统计/其他线索

演练 2:验证类程序的选择

题目: 32 字符 flag 校验,未知算法,有 Correct/Wrong 输出
候选方案排序:
1. 枚举字符串/常量: 5 分钟,可能直接见 flag/表
2. angr 符号执行: 逻辑纯 → 几分钟自动出
3. 侧信道爆破: 程序用 memcmp/逐字符比较 → 线性爆破
4. 手动逆向+反推: 逻辑复杂时最后手段
排序依据: 每个方案先做"5 分钟侦察",不行立刻换,不要死磕

22.7 爆破工程的纪律

□ 永远先算空间(2^n),写下来,再决定方案
□ 有 oracle 优先侧信道,不要硬爆整串
□ 爆破脚本要可中断、可续跑(保存进度)
□ 用字符集约束(可打印/flag 格式)缩小空间
□ 验证结果:解出的答案跑程序确认,不要只看脚本输出
□ 爆破失败 ≠ 无解:回到 22.2 重新枚举线索

动手练习

  1. 找一道单字节 XOR 题,先算空间,再写枚举脚本,最后用已知明文方式各解一次,比较思路。
  2. 找一道有 Wrong/Correct 输出的验证题,先评估"整串爆破"的复杂度(写下来),再用侧信道思路设计逐字节方案。
  3. 阅读仓库 patterns.md 里 SIGFPE 侧信道案例,画出它的"观测信号→逐字节确定"流程。
  4. 练习选型:给出 5 种不同形态的题,写出各自的第一方案和第二方案。

深入阅读

  • 仓库:skills/reverse-engineering/patterns.md(Timing Side-Channel、INT3+Coredump Oracle、信号侧信道)
  • 仓库:skills/reverse-engineering/tools-dynamic.md(LD_PRELOAD memcmp 逐字节爆破、Intel Pin 指令计数)
  • 仓库:skills/reverse-engineering/patterns-ctf*.md(前缀哈希爆破、决策树爆破、VM 顺序密钥爆破)
  • 仓库:skills/reverse-engineering/field-notes.md(Iterative Solver、Expected Values Tables)
  • 仓库:skills/reverse-engineering/crypto-decode-tools.md(hashcat 等密码破解工具)