第 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 重新枚举线索
动手练习
- 找一道单字节 XOR 题,先算空间,再写枚举脚本,最后用已知明文方式各解一次,比较思路。
- 找一道有 Wrong/Correct 输出的验证题,先评估"整串爆破"的复杂度(写下来),再用侧信道思路设计逐字节方案。
- 阅读仓库
patterns.md里 SIGFPE 侧信道案例,画出它的"观测信号→逐字节确定"流程。 - 练习选型:给出 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 等密码破解工具)