[机翻] [ABD] 06_二进制分析与中间表示

06 二进制分析与中间表示

https://github.com/malrev/ABD

本文档介绍二进制分析工具的总体架构、Frontend/Backend 设计模式、中间表示(IR)的作用与组成、静态单赋值形式(SSA),以及 IR 的局限性。

⚠️ API 版本说明:本文档中的代码示例已于 2026-07 更新至最新版本。Miasm 更新至 0.1.5+,Triton 更新至 v0.9+(master 分支),angr 更新至 9.2.x。课程原始材料使用的是 2020 年版本,部分 API 已发生 breaking change。更新处以 【更新】 标注,旧版用法以 【旧版】 标注。


目录

  1. 二进制分析工具列表
  2. 二进制分析架构:Frontend 与 Backend
  3. 通用设计模式
  4. 中间表示(IR)
  5. 静态单赋值形式(SSA)
  6. IR 的组成
  7. IR 的局限性
  8. 参考资料

一、二进制分析工具列表

以下工具覆盖了从商业产品到开源框架、从交互式反汇编器到自动化分析平台的广泛谱系:

工具 类型 特点
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(TritonContextInstructionprocessing()、求解器接口)完全向后兼容,2020 年代码可直接运行。主要变化:新增 Bitwuzla 求解器、Instruction 分析方法、LLVM/SMT 提升功能。依赖 Capstone 从 4.x 升级到 5.x。可通过 pip install triton-library 安装。

2. Angr 设计模式

Angr 是一个大型符号执行框架,核心抽象是 ProjectSimulationManager

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)。关键迁移:PathGroupSimulationManagerstate.sestate.solverCFGAccurateCFGEmulatedclaripy.BVclaripy.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_1reg_01_2 两个不同的变量。
  • reg_02 = reg_01 - 3 中的 reg_01 明确指向 reg_01_1,不存在歧义。
  • 数据流关系变得显式:reg_01_1reg_02_1reg_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):如 0x1842
    • 变量标识(Id):如 EAXR0
    • 内存引用(Mem):如 @32[EAX](从地址 EAX 读取 32 位值)
    • 条件表达式(Cond):如 cond ? a : b
    • 切片(Slice):如 EAX[8:16](取 EAX 的第 8-15 位)
    • 组合(Compose):将多个切片拼接为新值

可组合性是关键:操作数可以是任意复杂的表达式,操作码可以任意嵌套,形成表达式树。

# 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 的过程中,部分信息会丢失(如原始的高级类型信息、源代码结构)。
  • 近似建模:对于复杂指令(如 cpuidrdtsc、系统调用),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.
posted @ 2026-08-05 15:55  DirWangK  阅读(15)  评论(0)    收藏  举报