[机翻] [ABD] 06_二进制分析与中间表示
06 二进制分析与中间表示
本文档介绍二进制分析工具的总体架构、Frontend/Backend 设计模式、中间表示(IR)的作用与组成、静态单赋值形式(SSA),以及 IR 的局限性。
⚠️ API 版本说明:本文档中的代码示例已于 2026-07 更新至最新版本。Miasm 更新至 0.1.5+,Triton 更新至 v0.9+(master 分支),angr 更新至 9.2.x。课程原始材料使用的是 2020 年版本,部分 API 已发生 breaking change。更新处以
【更新】标注,旧版用法以【旧版】标注。
目录
一、二进制分析工具列表
以下工具覆盖了从商业产品到开源框架、从交互式反汇编器到自动化分析平台的广泛谱系:
| 工具 | 类型 | 特点 |
|---|---|---|
| IDA | 商业 / 交互式 | 业界标杆,支持反汇编、反编译(Hex-Rays),插件生态丰富 |
| Ghidra | 开源(NSA) | 免费反汇编/反编译框架,支持插件扩展,内置 SLEIGH 处理器描述语言 |
| radare2 | 开源 | 命令行驱动的逆向工程框架,轻量灵活,支持脚本自动化 |
| Binary Ninja | 商业 | 现代化 UI,强大的 IL(BNIL)中间表示,API 友好 |
| angr | 开源 | Python 框架,专注于符号执行与自动化漏洞发现(v9.2.x) |
| BINSEC | 开源 | 基于 SMT 的静态/动态二进制分析平台,关注代码安全 |
| Triton | 开源 | 动态符号执行框架,提供程序分析与反混淆能力(v0.9+) |
| Miasm | 开源 | 逆向工程框架,支持符号执行、JIT 模拟、IR 翻译(v0.1.5+) |
| McSema | 开源(Trail of Bits) | 将二进制提升为 LLVM IR,复用 LLVM 生态进行分析与重编译 |
【更新】上述工具版本信息(2026-07):angr 9.2.223、Triton v0.9+(master活跃开发中)、Miasm 0.1.5。
二、二进制分析架构:Frontend 与 Backend
现代二进制分析工具普遍采用"前端 + 后端"的分层架构,与编译器的三段式架构(前端-优化-后端)高度相似。
1. Frontend 架构
前端负责将原始二进制文件转换为可分析的中间表示:
Binary file(二进制文件)
│
▼
Disassembler(反汇编器)─── 将机器码字节序列解码为汇编指令
│
▼
Disassembly(反汇编结果)─── 汇编指令序列 / AsmCFG
│
▼
Lifter(提升器)─────────── 将汇编指令的语义提升为 IR
│
▼
Intermediate Representation(中间表示 / IR)
各组件职责:
- Disassembler(反汇编器):从二进制字节流中识别指令边界,解码机器码为汇编指令。需处理变长指令(x86)、对齐指令(ARM)等架构差异。
- Lifter(提升器):将每条汇编指令的语义翻译为 IR 操作。例如,
mov eax, ebx被提升为EAX = EBX的 IR 赋值。提升器是架构相关的——每种架构(x86, ARM, MIPS...)有自己的 lifter。
2. Backend 架构
后端在 IR 上执行各类分析与变换:
IR(中间表示)
│
▼
Emulator(模拟器)───────── 在 IR 上执行具体或符号化的计算
│
▼
IR Translator(IR 翻译器)── 将 IR 翻译为其他表示(如 LLVM IR、Z3 表达式)
│
├──────────────────────────────────────────┐
▼ ▼
CFG Recovery(控制流图恢复) Type Inference(类型推断)
│ │
▼ ▼
SMT Queries ──────────────► SMT Solver(SMT 求解器)
│
▼
Dataflow Analysis(数据流分析)/ Trace(轨迹)/ Symbolic Execution Engine(符号执行引擎)
│
▼
Decompiler(反编译器)/ Program Synthesis(程序综合)
各组件职责:
- Emulator(模拟器):在 IR 层面模拟指令执行,支持具体执行(Concrete Execution)与符号执行(Symbolic Execution)。
- IR Translator(IR 翻译器):将 IR 翻译为其他形式,如 LLVM IR(复用 LLVM 优化 pass)、Z3 表达式(约束求解)。
- CFG Recovery(控制流图恢复):识别基本块边界、解析跳转目标,构建完整的控制流图。需处理间接跳转(Indirect Jump)、虚表调用等。
- Type Inference(类型推断):从指令使用模式推断变量与内存对象的类型。
- SMT Queries → SMT Solver:将分析问题编码为 SMT 约束,交由求解器(如 Z3)判定可满足性。用于路径可行性检查、等价性检查、不透明谓词检测。
- Dataflow Analysis / Trace / Symbolic Execution Engine:数据流分析(常量传播、活跃度分析等)、执行轨迹记录、符号执行引擎。
- Decompiler / Program Synthesis:最终将优化后的 IR 还原为高级语言代码。
三、通用设计模式
尽管各工具的 API 不同,但它们的使用模式高度一致。以下展示三大主流框架的核心使用范式。
1. Triton 设计模式
Triton 是一个动态符号执行框架,核心是 TritonContext 与指令级符号化:
from triton import TritonContext, ARCH, Instruction, CALLBACK, SOLVER
# 1. 初始化 TritonContext,指定架构
ctx = TritonContext(ARCH.X86_64)
# 2. (可选)配置符号化寄存器/内存
ctx.setConcreteRegisterValue(ctx.registers.rax, 0x1000)
# 3. 构造指令并处理
inst = Instruction()
inst.setOpcode(b"\x48\x89\xe5") # mov rbp, rsp
ctx.processing(inst) # 处理指令,更新符号状态
# 4. 与 solver 通信,查询约束可满足性
# Triton 内置 SMT solver,可直接查询
ast = ctx.getAstContext()
constraint = ast.equal(ctx.getSymbolicRegisterValue(ctx.registers.rax), 0x2000)
print(ctx.isSat(constraint)) # 判定是否可满足
# 【更新 v0.9+】Triton 新增功能和 API(向后兼容,不影响现有代码)
# 1. 新增 Bitwuzla 求解器后端
ctx.setSolver(SOLVER.BITWUZLA) # 【更新 v0.9+】可选 Z3 或 Bitwuzla
# 2. Instruction 新增分析方法(v0.9+)
inst = Instruction(0x40000, b"\x48\x89\xe5")
ctx.processing(inst)
print(inst.isBranch()) # 【更新 v0.9+】是否为分支指令
print(inst.isMemoryRead()) # 【更新 v0.9+】是否读取内存
print(inst.getReadRegisters()) # 【更新 v0.9+】获取读取的寄存器列表
print(inst.getLoadAccess()) # 【更新 v0.9+】获取内存读取访问
# 3. 提升到不同格式(v0.9+)
# ctx.liftToLLVM(ast) # 【更新 v0.9+】提升到 LLVM IR
# ctx.liftToPython(ast) # 【更新 v0.9+】提升到 Python 代码
# ctx.liftToSMT(ast) # 【更新 v0.9+】提升到 SMT 公式
# 4. 路径谓词查询(v0.9+)
# ctx.getPredicatesToReachAddress(0x401000) # 【更新 v0.9+】获取到达地址的路径条件
要点:
TritonContext是全局上下文,管理架构、符号状态、约束。Instruction封装单条机器码指令,通过processing()处理。- 与 solver 的通信通过
TritonContext内置接口完成。
【更新 v0.9+】Triton 核心 API(
TritonContext、Instruction、processing()、求解器接口)完全向后兼容,2020 年代码可直接运行。主要变化:新增 Bitwuzla 求解器、Instruction分析方法、LLVM/SMT 提升功能。依赖 Capstone 从 4.x 升级到 5.x。可通过pip install triton-library安装。
2. Angr 设计模式
Angr 是一个大型符号执行框架,核心抽象是 Project 与 SimulationManager:
import angr
# 1. 初始化 Project,加载二进制
proj = angr.Project('./target.bin', auto_load_libs=False)
# 2. 生成 CFG(控制流图恢复)
cfg = proj.analyses.CFGFast()
# 3. 创建初始符号状态
state = proj.factory.entry_state()
# 4. 使用 SimulationManager 管理符号状态
simgr = proj.factory.simulation_manager(state)
# 5. 符号执行,探索到达目标地址的路径
simgr.explore(find=lambda s: b'Success' in s.posix.dumps(1),
avoid=lambda s: b'Fail' in s.posix.dumps(1))
# 6. 获取满足条件的状态,提取输入
if simgr.found:
found_state = simgr.found[0]
print("Solution:", found_state.posix.dumps(0))
# 【更新 angr 9.2.x】以下是 angr 7+ 的 API。与 2020 年课程版本的差异:
# 【旧版 angr 6.x】proj = angr.Project('./target.bin', load_options={'auto_load_libs': False})
# 【更新 angr 8+】简化为 auto_load_libs=False 关键字参数
#
# 【旧版 angr 6.x】simgr = proj.factory.path_group(state) # PathGroup
# 【更新 angr 7+】simgr = proj.factory.simulation_manager(state) # SimulationManager
#
# 【旧版 angr 6.x】state.se.eval(expr) # 通过 state.se 访问求解器
# 【更新 angr 8+】state.solver.eval(expr) # state.se 已弃用,使用 state.solver
#
# 【旧版 angr 7-】cfg = proj.analyses.CFGAccurate() # 精确 CFG
# 【更新 angr 8+】cfg = proj.analyses.CFGEmulated() # 重命名为 CFGEmulated
#
# 【旧版 angr 7-】sym_arg = claripy.BV("sym_arg", 32) # 符号位向量
# 【更新 angr 8+】sym_arg = claripy.BVS("sym_arg", 32) # BV 已弃用,使用 BVS
要点:
Project是二进制加载与分析的入口。- CFG 生成是分析的基础步骤。
SimulationManager管理一组符号状态(state),支持explore()进行导向式路径探索。
【更新 angr 9.2.x】angr 经历了多次 breaking changes(angr 7/8/9.1)。关键迁移:
PathGroup→SimulationManager、state.se→state.solver、CFGAccurate→CFGEmulated、claripy.BV→claripy.BVS。本文档中的代码示例已使用最新 API。当前版本:9.2.223(2026-07)。
3. Miasm 设计模式
Miasm 是一个灵活的逆向工程框架,核心是 AsmCFG → IRCFG → 符号执行的流水线:
from miasm.analysis.binary import Container
from miasm.analysis.machine import Machine
from miasm.ir.symbexec import SymbolicExecutionEngine
from miasm.core.locationdb import LocationDB
# 1. 加载二进制
loc_db = LocationDB()
with open('target.bin', 'rb') as fstream:
cont = Container.from_stream(fstream, loc_db)
machine = Machine(cont.arch)
# 2. 反汇编生成 AsmCFG
mdis = machine.dis_engine(cont.bin_stream, loc_db=cont.loc_db)
asmcfg = mdis.dis_multiblock(cont.entry_point)
# 3. 提升为 IRCFG
# 【更新】machine.ir() → machine.lifter(),变量名 ir → lifter
# 【旧版 v0.1.3】ir = machine.ir(cont.loc_db)
lifter = machine.lifter(cont.loc_db)
# 【更新】ir.new_ircfg_from_asmcfg() → lifter.new_ircfg_from_asmcfg()
ircfg = lifter.new_ircfg_from_asmcfg(asmcfg)
# 4. 符号执行
cont.loc_db.add_location(offset=cont.entry_point, name='entrypoint')
# 【更新】SymbolicExecutionEngine(ir, ...) → SymbolicExecutionEngine(lifter)
# 【旧版 v0.1.3】sb = SymbolicExecutionEngine(ir, ...) # 传入符号化信息
# 【更新】v0.1.5+ 中 symbols 参数不再在构造函数中传递,改用 sb.symbols 或 sb.update_state() 设置
sb = SymbolicExecutionEngine(lifter)
sb.run_block_at(ircfg, cont.entry_point)
要点:
Container加载二进制,Machine选择架构。- 反汇编引擎生成
AsmCFG(汇编级控制流图)。 - 【更新】
lifter.new_ircfg_from_asmcfg()将 AsmCFG 提升为 IRCFG(IR 级控制流图)。【旧版 v0.1.3】ir.new_ircfg_from_asmcfg() SymbolicExecutionEngine在 IRCFG 上执行符号执行。
四、中间表示(IR)
1. IR 的角色:粘合剂
IR(Intermediate Representation,中间表示)是二进制代码与分析方法之间的粘合剂:
┌───────────────────────────────────────────────────────┐
│ 不同架构的机器码 │
│ x86 x64 ARM MIPS ... │
│ 机器码 机器码 机器码 机器码 │
│ \ \ \ / │
│ \ \ \ / │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 反汇编 + Lifter │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 统一的中间表示(IR) │ │
│ │ (架构无关的操作码与操作数) │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ 数据流分析 符号执行 反编译 │
│ Dataflow Symbolic Decompilation │
│ Analysis Execution │
└───────────────────────────────────────────────────────┘
2. IR 的核心价值
- 架构无关性:使我们能在单个界面处理不同架构的代码(x86, x64, ARM, MIPS 等)。分析算法只需针对 IR 编写一次,即可应用于所有架构。
- 语义明确性:IR 的每条操作有明确的操作语义,消除了机器码的歧义性。
- 可组合性:IR 操作可自由组合,便于构建复杂分析。
- 可翻译性:IR 可翻译为 LLVM IR(复用编译器优化)、Z3 表达式(约束求解)等其他表示。
# 示例:同一分析逻辑适用于不同架构,因为它们共享相同的 IR 抽象
# 无论是 x86 还是 ARM,提升后的 IR 都是 "dst = src" 的赋值形式
# 因此 DeadRemoval 等分析 pass 无需关心底层架构
# x86: mov eax, ebx → EAX = EBX
# ARM: mov r0, r1 → R0 = R1
# IR 层面:均为 ExprAssign(dst, src),统一处理
# 【更新】ExprAff 已弃用,使用 ExprAssign
# 【旧版 v0.1.3】IR 层面:均为 ExprAff(dst, src),统一处理
五、静态单赋值形式(SSA)
1. SSA 的定义
SSA(Static Single Assignment,静态单赋值)是一种 IR 形式,具有以下性质:
- 每个变量只被赋值一次:变量在 IR 中只在一条指令中被定义(赋值)。
- 在使用之前已定义:变量的每次使用都对应唯一的定义点。
- 通过 φ(phi)函数处理控制流汇合:在多个定义到达同一使用点时,用 φ 函数选择对应的定义。
2. 为什么需要 SSA
SSA 形式极大简化了数据流分析:
- 显式的定义-使用链:每个使用点直接关联到唯一的定义点,无需迭代求解可达定义。
- 简化优化 pass:常量传播、死代码消除、公共子表达式消除等在 SSA 上更高效、更易实现。
- 支持稀疏分析:分析信息沿定义-使用链传播,无需对每个程序点重新计算。
3. SSA 转换示例
原始形式(非 SSA)
在非 SSA 形式中,同一个变量(如 reg_01)可以被多次赋值,导致定义-使用关系模糊:
// 原始形式
reg_01 = 5 // 定义 1:reg_01 = 5
reg_02 = reg_01 - 3 // 使用定义 1 的 reg_01
reg_01 = reg_01 * 2 // 定义 2:reg_01 = reg_01 * 2(覆盖定义 1)
SSA 形式
在 SSA 形式中,每次赋值引入新的版本号(下标),消除歧义:
// SSA 形式
reg_01_1 = 5 // 定义 1 → 版本 1
reg_02_1 = reg_01_1 - 3 // 使用 reg_01_1(明确指向定义 1)
reg_01_2 = reg_01_1 * 2 // 定义 2 → 版本 2(新变量)
关键变化:
reg_01被拆分为reg_01_1和reg_01_2两个不同的变量。reg_02 = reg_01 - 3中的reg_01明确指向reg_01_1,不存在歧义。- 数据流关系变得显式:
reg_01_1→reg_02_1和reg_01_2。
4. φ 函数(Phi Function)
当控制流从多条路径汇合时,SSA 引入 φ 函数选择对应路径的定义:
// 控制流汇合
if (cond) {
x = 1; // 路径 1: x_1 = 1
} else {
x = 2; // 路径 2: x_2 = 2
}
// 汇合点
y = x; // SSA: x_3 = φ(x_1, x_2); y_1 = x_3
φ 函数 x_3 = φ(x_1, x_2) 表示:若从路径 1 到达则 x_3 = x_1,若从路径 2 到达则 x_3 = x_2。
六、IR 的组成
一个完整的 IR 由以下两部分定义:
1. Syntax(语法)
语法定义了 IR 中可用的可组合的操作码(opcodes)和操作数(operands):
- 操作码(Opcodes):IR 支持的运算,如
+、-、*、<<、>>>、&、|、==、?:(条件)等。 - 操作数(Operands):运算的对象,包括:
- 整数常量(Int):如
0x18、42 - 变量标识(Id):如
EAX、R0 - 内存引用(Mem):如
@32[EAX](从地址 EAX 读取 32 位值) - 条件表达式(Cond):如
cond ? a : b - 切片(Slice):如
EAX[8:16](取 EAX 的第 8-15 位) - 组合(Compose):将多个切片拼接为新值
- 整数常量(Int):如
可组合性是关键:操作数可以是任意复杂的表达式,操作码可以任意嵌套,形成表达式树。
# Miasm IR 中的表达式组合示例
# 表达式:(EAX + EBX) >> 2
expr = ExprOp('>>',
ExprOp('+', ExprId('EAX', 32), ExprId('EBX', 32)),
ExprInt(2, 32))
2. Operational Semantics(操作语义)
操作语义定义了每个操作码如何更新操作数,即每个 IR 操作的计算效果:
+:将两个操作数相加,结果为二者之和。>>:将左操作数逻辑右移右操作数指定的位数。@N[addr]:从地址addr读取 N 位内存值。cond ? a : b:若cond非零则结果为a,否则为b。x[a:b]:取x的第a位到第b-1位。
操作语义是 IR 的"含义"——它使得模拟器、符号执行引擎、SMT 翻译器等能够一致地解释 IR 的行为。
// 操作语义示例
dst = src + 1
// 语义:将 src 的当前值加 1,结果赋给 dst
// 模拟器:执行 dst = src + 1 的具体计算
// 符号执行引擎:记录 dst ≡ src + 1 的符号关系
// SMT 翻译器:生成 (assert (= dst (+ src 1)))
七、IR 的局限性
IR 并非万能,它在建模某些架构特性时存在固有局限:
1. 难以建模的架构特性
| 特性 | 困难所在 |
|---|---|
| Flag registers(标志寄存器) | x86 的 CF/ZF/SF/OF 等标志位由算术指令隐式设置,需在 IR 中显式建模,增加 IR 复杂度 |
| Floating points(浮点数) | 浮点运算的精度、舍入模式、特殊值(NaN, Inf)难以在 IR 中精确表达 |
| SIMD(单指令多数据) | SSE/AVX 等向量指令一次操作多个数据,IR 需展开为多条标量操作,效率低且语义复杂 |
2. IR 不会等同于原始代码语义
IR 只是对原始代码语义的近似(Approximation),而非完全等价。
- 信息丢失:反汇编→提升为 IR 的过程中,部分信息会丢失(如原始的高级类型信息、源代码结构)。
- 近似建模:对于复杂指令(如
cpuid、rdtsc、系统调用),IR 通常用近似语义代替,可能不完全准确。 - 未定义行为:某些机器码序列的行为在架构手册中是未定义的(UB),IR 提升器只能选择某种解释。
实践影响:
- 在反混淆中,不能假设 IR 完全忠实于原始语义——需验证分析结果。
- 对于依赖标志位、浮点、SIMD 的代码,IR 分析可能不准确,需结合动态分析验证。
八、参考资料
- Schwartz, Avgerinos, Brumley. All You Ever Wanted to Know About Dynamic Taint Analysis and the Syntactic Side-Channel. IEEE S&P (Oakland) 2010.
- Kim, Burrow, Dickerson, Snow, Cifuentes. Testing Intermediate Representations for Binary Analysis. ASE 2017.
- Jung, Lee, Jang, Kim. B2R2: Building an Efficient and Fast Front-End for Binary Analysis. BAR 2019.
- Cytron, Ferrante, Rosen, Wegman, Zadeck. Efficiently Computing Static Single Assignment Form and the Control Dependence Graph. TOPLAS 1991.
- Yadegari, Johannes, Debray, Samet. A Generic Approach to Automatic Deobfuscation of Executable Code. IEEE S&P 2015.

浙公网安备 33010602011771号