第 40 章 混淆技术原理深解(OLLVM / MBA / VM)
第 8 章讲"怎么绕过混淆",本章讲"混淆是怎么实现的"。懂实现才能高效反混淆——知道每个混淆在汇编层的特征,反混淆工具的选择和手动还原就有依据。
📍 知识点地图 | 主题:混淆原理 | 前置:第8章 | 后续:— | 核心概念:CFF、MBA、字符串加密、VM混淆
40.1 混淆的四大目标(分类学)
1. 控制流混淆: 让程序"怎么走"看不懂 → 平坦化/不透明谓词/花指令
2. 数据混淆: 让数据"是什么"看不懂 → 字符串加密/常量编码/MBA
3. 语义混淆: 让逻辑"在干什么"看不懂 → VM 化/代码虚拟化
4. 结构混淆: 让程序"长什么样"看不懂 → 命名混淆/合并拆分
对应反混淆: 去平坦化 → 字符串解密/化简 → 还原 VM → 符号恢复
40.2 控制流平坦化(CFF)—— 最经典的混淆
原理(OLLVM 实现)
原始控制流:
A → B → C
A → D → C
平坦化后:
A: 设置 state=1
B: 设置 state=2
C: 设置 state=3
...
dispatcher: switch(state) { case 1: 跳 A; case 2: 跳 B; ... }
→ 所有基本块都被压平到一个循环 + switch 分发器
汇编特征:
main 函数巨大化(所有逻辑并进一个函数)
一个"分发块"反复出现(取 state → 跳表跳转)
大量 jmp [table + offset]
为什么难读
原始逻辑顺序被打散,每个基本块的"前后关系"藏在 state 里
静态阅读需要: 还原 state 图 → 重建控制流
反平坦化原理(工具怎么做到的)
1. 识别分发器(找循环里的跳表)
2. 解析每个基本块的 state 值(常量传播)
3. 建立 state → 基本块映射
4. 还原真实边(A 的出口 state 指向谁)
5. 重建控制流图 → 输出正常函数
工具: D-810(IDA)、GOOMBA(Ghidra)、obpo、ollvm-unflattener
40.3 不透明谓词(Opaque Predicates)
原理
问题: 普通分支一眼看穿
方案: 插入"看似是分支、实际恒定"的判断
if (x == 0) { /* 死代码 */ } ; x 恒为 0 → 分支恒真
等价形式: x^2 + x + 41 恒为奇数(欧拉多项式)
汇编特征:
分支前有一个复杂计算
跳转两边都有代码,但一边永远不执行
编译器优化难以消除(值分析不出恒定)
识别与去除
识别: 统计分支实际方向(动态运行一次全记录)→ 恒定向的分支 = 候选
去除: 把恒定分支替换为直接跳转(D-810 的 opaque predicate removal)
40.4 MBA(混合布尔算术)—— 数据混淆
原理
目标: 把简单的算术/逻辑表达式替换成"看起来很复杂的等价式"
核心恒等式(记忆这 5 条就能认):
x + y == (x ^ y) + 2 * (x & y)
x + y == (x | y) + (x & y)
x ^ y == (x | y) - (x & y)
x | y == x + y - (x & y)
x - y == x + (~y) + 1
多层嵌套: MBA 可以套 MBA(每层倍增复杂度)
识别特征
反编译出现大量"无意义"的 and/or/xor/add 组合
同样的值被用多种方式重复计算
常量被展开成复杂表达式
化简工具
D-810 的 MBA simplification(IDA)
SiMBA(pip install simba-simplifier)—— 自动识别替换
angr/z3 符号化简: 约束重写为最简形式
手工: 背核心恒等式,逐式替换
40.5 字符串加密
原理
目标: 让 strings/静态搜索失效
实现: 字符串加密存储 → 运行时解密到栈上使用
常见: XOR key / 单字节 / 自定义算法
反混淆
方法 1: 动态观察(最快)—— 断在字符串使用点,dump 已解密的栈内容
方法 2: 静态还原 —— 找到解密函数 → 批量解密 .rodata
方法 3: Frida hook 字符串函数(打印解密后的实参)
工具: FLOSS(自动识别+解密恶意软件字符串)
40.6 代码虚拟化(VM 混淆)—— 最高级对抗
原理
把原始指令翻译成自定义 VM 的字节码
运行时: VM 解释器(handler)循环执行字节码
结构:
字节码(加密/编码的原始操作)
handler 表(每种 opcode 一个处理函数)
分发循环(取 opcode → 找 handler → 执行)
识别特征:
入口处有一个"读取-分派-执行"循环
大量 handler 函数(每个很小)
原函数逻辑全部消失,只剩 VM 循环
代表: VMProtect、Themida、自研 VM
反 VM 的四个层次
| 层次 | 方法 | 成本 |
|---|---|---|
| 1. 行为级 | 动态观察"它做了什么"(Hook 输出/API) | 低 |
| 2. 指令级 | 跟踪 VM 执行流(Hook 分发点,记录 opcode+操作数) | 中 |
| 3. 语义级 | 把字节码提升到 IR(Miasm/自定义)→ 优化还原 | 高 |
| 4. 完整还原 | 逆向整个 VM 架构,写反编译器 | 很高 |
实战建议:CTF/日常分析中,层次 1-2 通常够用(拿到行为+数据即可),除非目标是复用算法才做 3-4。
40.7 混合对抗(现实世界)
真实样本通常是多层组合:
字符串加密 + CFF + MBA + 反调试 + 自修改
应对策略:
1. 分层剥离(每层一种混淆 → 逐个击破)
2. 动态优先(先动态观察,再静态还原)
3. 工具链配合(D-810 → SiMBA → 动态 dump → 符号恢复)
4. 判断 ROI(虚拟化 → 行为级理解往往足够)
40.8 反混淆工作流(综合)
1. 侦察: 识别混淆类型(特征表 40.1-40.6)
2. 先动态: 运行观察/Hook/内存 dump(绕过静态混淆)
3. 静态工具: 按类型选(CFF→D-810;MBA→SiMBA;字符串→FLOSS)
4. 手动兜底: 高价值函数人工还原(配合 LLM 第 29 章)
5. 验证: 还原结果与动态行为一致
动手练习
- 用 ollvm 编译一个小程序(启用 CFF + MBA + 字符串加密),对比混淆前后的反编译输出。
- 用 D-810 对平坦化函数去平坦化,对比还原前后控制流。
- 手写 5 个 MBA 恒等式的替换练习(40.4 表),训练识别能力。
- 分析一个 VMProtect 样本:只做"行为级"理解(观察它调用了什么 API、处理什么数据),不尝试完整还原。
深入阅读
- 仓库:
skills/reverse-engineering/anti-analysis.md(94 种反分析技术全集) - 仓库:
skills/reverse-engineering/tools-advanced.md(D-810/obpo/Miasm/GOOMBA) - 仓库:
skills/reverse-engineering/references/ollvm-deobfuscation.md(OLLVM 去混淆专项) - 仓库:
skills/reverse-engineering/patterns.md(自定义 VM、Nanomites、自修改代码) - 仓库:
skills/reverse-engineering/tools-advanced.md(VMProtect/Themida 分析)