reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
133
vendor-new/rev-skills/.claude/skills/re-angr/SKILL.md
Normal file
133
vendor-new/rev-skills/.claude/skills/re-angr/SKILL.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
name: re-angr
|
||||
description: angr 符号执行:符号化输入、求解。触发词:angr、符号执行、自动解题、constraint
|
||||
capabilities: [symbolic-execution]
|
||||
---
|
||||
|
||||
# angr 符号执行(符号化输入 / 自动求解)
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:CTF 逆向题——输入在长循环 / 深比较链里逐字节校验(校验通过地址已知或可定位),人工逆推繁琐易错
|
||||
- 用:已知"输入必须到达某地址 / 避开某地址",想把路径条件交给求解器(find / avoid 模式)
|
||||
- 用:输入来源复杂(argv / 文件 / 标准输入),需要按通道符号化后求解
|
||||
- 不用:简单 XOR / 明文比较(一屏伪代码内)——人工更快([[re-binary-core]])
|
||||
- 不用:校验含不可符号化内容(环境相关值、随机数、未符号化输入)——会无解或误解(见坑 3)
|
||||
- 不用:需要精确约束集合而非一条路径时——纯约束求解用 [[re-z3]] 更轻(见坑 2)
|
||||
- 注意:符号执行是把双刃剑——先人工定位关键校验函数缩小符号化范围(见坑 2);运行验证走沙箱([[re-analyze/platform-tips]] 最高原则)
|
||||
|
||||
## 工具准备
|
||||
|
||||
参考 [[re-analyze/platform-tips]] 最高原则——用求解结果运行目标验证时默认沙箱;angr 本身是进程内仿真器,不接触真实系统调用,求解过程无需沙箱。
|
||||
|
||||
### angr(pip 安装)
|
||||
|
||||
- `pip install angr`(Linux / macOS / Windows 均可;Windows 下 pip 装 win32 wheel,WSL 内安装 Linux 版亦可)
|
||||
- **Python 版本兼容性(重点)**:**angr 9.3.0 起要求 Python 3.12+**(`requires-python >=3.12`;当前主线 10.x 同);**9.2.x 不是固定区间**——下限随补丁版本抬高(9.2.91 要求 ≥3.8,9.2.203 已要求 ≥3.10),不能按「9.2.x」整线判断。装前以目标版本的 PyPI `requires-python` 为准(`pip index versions angr` 看可用版本),再按系统 Python 选:
|
||||
- 系统自带 Python 3.12+(Ubuntu 24.04 默认 3.12)→ 直接 `pip install angr`(当前主线 10.x;Python >=3.12)
|
||||
- 系统为 Python 3.11 及以下 → `pip install "angr<9.3"`(9.2.x 线),**别硬装 9.3+**
|
||||
- 多版本并存用 venv 隔离:`python3.12 -m venv ~/venvs/angr && source ~/venvs/angr/bin/activate`(或 `brew install python@3.12` / `py -3.12 -m venv angr-venv`)
|
||||
- 装前先升级基础工具:`pip install --upgrade pip setuptools wheel`(避免原生组件编译失败)
|
||||
- Linux 建议补 `binutils`(angr 处理 / 重写二进制会调 objcopy):`apt install binutils`
|
||||
- 验证: `python3 -c "import angr; print(angr.__version__)"`
|
||||
|
||||
### python3
|
||||
|
||||
- Linux: `apt install python3`(多数自带);macOS: `brew install python`;Windows: 官方安装包 / `choco install python`
|
||||
- 验证: `python3 --version`
|
||||
|
||||
### 目标二进制
|
||||
|
||||
- 原始样本副本(**先备份**:符号执行不修改文件,但建模要反复对照,见坑 5)+ [[re-triage]] 初勘产物(架构 / 位数 / 是否静态链接 / 是否带壳)
|
||||
- 验证: `file target` 确认架构与位数(arm 目标 angr 支持,注意字节序)
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步记录结果(地址 / 约束 / 求解脚本 / flag,证据路径见 [[re-triage]])。**先人工后自动化**:符号化范围越小越稳(见坑 2)。
|
||||
|
||||
1. **输入适配(Input Adapter):确定符号源(symbol source)**:
|
||||
- 反编译定位输入读取点:`read` / `scanf` / `fgets` / `getline` / `main(int argc, char **argv)` 的 argv 使用处([[re-ghidra]] / [[re-ida]] / [[re-radare2]])
|
||||
- 按**符号源类型**选择接入方式(CTF 的 stdin 只是其中一种——真实目标输入可能是网络包/文件映射/JNI 参数/自定义字节码):
|
||||
|
||||
| 符号源 | 接入方式 |
|
||||
|---|---|
|
||||
| stdin(标准输入) | `project.factory.full_init_state(stdin=claripy.BVS('in', 32*8))` |
|
||||
| argv(命令行参数) | `project.factory.full_init_state(args=["./target", claripy.BVS("arg1", 64*8)])` |
|
||||
| file(文件输入) | `state.fs.insert('/tmp/in', angr.SimFile('in', content=claripy.BVS('in', 32*8)))`——fopen 后即符号化;按 fd 操作用 `state.posix.fd[0]`(fd 0=stdin;fd 1/2 是 stdout/stderr,勿混) |
|
||||
| memory(mmap/堆/全局缓冲区) | 直接符号化目标内存区:`state.memory.store(addr, claripy.BVS('buf', n*8))`——程序从该地址读入即符号化(mmap 文件、解密缓冲、共享内存通用) |
|
||||
| network buffer(网络包/recv) | hook `recv`/`read`:**按调用约定取 buf 参数**(x86-64 SysV 是 RSI=buf、RDX=len),把符号字节写进该地址,**返回值是 ssize_t 字节数而不是 buffer 指针**——返回符号长度才能让 `if (recv(...) > 0)` 一类控制流可变。照"返回符号指针"实现会把后续控制流全部建歪 |
|
||||
| jni argument(Android native 入参) | 对 `JNIEnv` 方法参数做符号化:从 `GetByteArrayElements`/`GetStringUTFChars` 返回处符号化(配合 [[re-android-native]]/[[re-frida]] 确认入参形态) |
|
||||
| custom VM bytecode(自定义 VM 指令流) | 把字节码缓冲区整体符号化 + 约束操作码范围(0x00-0x0F 类)——VM 逆向的符号执行路径([[re-deobfuscate]] 联动) |
|
||||
|
||||
- 记下:符号化通道 + 字节数 + 读取点地址(后续 find / hook 用);N 字节长度对照读取点逻辑确认(见坑 3)
|
||||
|
||||
2. **到达目标地址 / 避开地址建模(find / avoid)**:
|
||||
- 反编译确认**成功路径地址**(校验通过后打印 flag 的地址)与**失败路径地址**(打印 "wrong" 等处,有多个失败点要列全)
|
||||
- 建模:`simgr.explore(find=<入口地址>, avoid=[<失败分支地址>])`(find 可多地址或 lambda `lambda s: b"flag{" in s.posix.dumps(0)`)
|
||||
- find 选地址的技巧:选**校验循环出口**而非程序 exit——找到"经过校验通过分支"的状态即可,不必等打印(见坑 5)
|
||||
- 校验是通过调用函数返回判定(`if (check(input))`)→ find 设在 check 返回后的成功分支地址,avoid 设在失败分支
|
||||
|
||||
3. **路径约束与求解(solver)**:
|
||||
- 找到状态后,约束即该状态路径上累积的条件:`found = simgr.found[0]`
|
||||
- 求解:直接对符号变量求值(不依赖 posix 插件):`flag = found.solver.eval(inp, cast_to=bytes)`(inp 即步骤 1 创建的 stdin/argv 符号)或按符号变量名求:`found.solver.eval(flag_sym, cast_to=bytes)`
|
||||
- 多解时按需加约束(长度 / 可打印字符,见坑 4):
|
||||
```python
|
||||
for c in flag_sym.chop(8): found.solver.add(0x20 <= c, c <= 0x7e)
|
||||
```
|
||||
- 输出到文件:`open('flag.bin','wb').write(flag)`,同时打印 repr 检查(非打印字符常是符号化长度问题,见坑 3)
|
||||
|
||||
4. **路径爆炸应对(hook / 限制深度 / 分段)**:
|
||||
- **hook 无关调用**:把与校验无关的重型 / 系统调用替换成轻量过程:
|
||||
```python
|
||||
project.hook(addr_of_sleep_or_memset_impl, angr.SIM_PROCEDURES["stubs"]["ReturnUnconstrained"]())
|
||||
```
|
||||
`angr.SIM_PROCEDURES` 自带库(`libc.sleep`、`linux_kernel` 等)优先:`project.hook_symbol("sleep", angr.SIM_PROCEDURES["libc"]["sleep"])`(sleep 属 libc 类目,不是 posix)
|
||||
- **限制探索规模**:`simgr = proj.factory.simgr(state, veritesting=True)`(veritesting 合并路径,长循环题常用,见坑 2);`simgr.explore(find=..., avoid=..., num_find=1)` 找到即停;`stash` 上限 / `lazy_solves` 选项控制
|
||||
- **分段探索**:长循环把入口地址 hook 住,先探索到循环边界,再对循环体单独符号化展开(人工定位循环不变量后缩小范围)
|
||||
- 仍爆炸 → 回到步骤 1 缩小符号化范围 / 结合人工分析(见坑 2),或换 [[re-z3]] 对已展开的循环体建模
|
||||
|
||||
5. **典型模板(find / avoid 模式)**:
|
||||
```python
|
||||
import angr, claripy
|
||||
p = angr.Project("./target", auto_load_libs=False) # 关库加载,快且稳
|
||||
inp = claripy.BVS("inp", 32 * 8) # 32 字节符号化输入
|
||||
state = p.factory.full_init_state(stdin=inp)
|
||||
simgr = p.factory.simgr(state, veritesting=True)
|
||||
simgr.explore(find=0x401100, avoid=[0x401200, 0x401300]) # 示意地址:按目标实际入口/分支替换
|
||||
if simgr.found:
|
||||
sol = simgr.found[0].solver.eval(inp, cast_to=bytes)
|
||||
print(sol) # 直接保存进证据目录
|
||||
else:
|
||||
print("no path found") # 排查:地址错 / 输入长度 / 未符号化
|
||||
```
|
||||
- 模板参数化:`find` / `avoid` / 输入长度 / 符号化通道写进脚本头部注释,逐个换参跑(见坑 5)
|
||||
|
||||
**验证**:沙箱内([[re-sandbox]])用求解出的输入原样跑目标(stdin 重定向 `./target < flag.bin` 或按 argv / 文件通道),必须打印 `flag{...}`;与 [[re-z3]] / 人工还原结果交叉对照(见坑 4)。
|
||||
|
||||
## 跨域联合
|
||||
- [[re-address-space]]:mapped_base/rebase 与目标地址对齐
|
||||
|
||||
- [[re-ctf]]:本技能是 re-ctf 网关工作流第 3 步的自动化解题路径(逐字节长循环校验题)
|
||||
- [[re-binary-core]]:前置工作台——反编译定位输入读取点 / 成功失败分支地址([[re-ghidra]] / [[re-ida]] / [[re-radare2]]);[[re-triage]] 初勘决定架构 / 位数 / 壳
|
||||
- [[re-deobfuscate]]:混淆先还原再符号执行(花指令 / 平坦化函数直接符号执行会路径爆炸)
|
||||
- [[re-z3]]:姊妹技能——无循环 / 已人工展开的约束集合用 z3 更轻更快;angr 求解慢时对约束子集转 z3
|
||||
- [[re-crypto-decrypt]]:其「工具准备」将 angr 列为可选的解密仿真方案(还原算法失败时符号化执行解密函数)
|
||||
- [[re-sandbox]]:求解输入的运行验证沙箱([[re-analyze/platform-tips]] 最高原则)
|
||||
- [[re-gdb]] / [[re-tracing]]:动态交叉验证(断点看校验分支实际走向,与 angr 路径结论对照)
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **路径爆炸**:现象——`explore` 跑十几分钟状态数飞涨,内存吃满;原因——符号化范围过大(整段程序都符号化)、无关分支(strlen / memcpy 展开、错误处理分支)被逐一探索、长循环不合并;对策——hook 无关系统调用(步骤 4)、`veritesting=True` 合并路径、`num_find=1` 找到即停、缩小符号化输入范围(步骤 1 只符号化校验真正读取的字节);仍不行就分段探索或转人工分析
|
||||
- **复杂校验慢 → 结合人工分析**:现象——一个看似简单的题 angr 几分钟没结果;原因——校验链中夹着查表 / 随机数 / 系统调用副作用,符号执行在这些点低效;对策——先人工反编译定位**关键校验函数**,把符号化入口设到校验函数入口(`blank_state` + 手动设置寄存器 / 内存),跳过前面无关代码;校验是纯等式集合时直接换 [[re-z3]]
|
||||
- **未符号化输入 → 无解**:现象——`explore` 秒回 `no path found`,或求解出的值跑原程序不通过;原因——输入没被符号化(读取的是真实 stdin / 文件内容)、符号化字节数 < 实际读取长度(`fgets(buf, 0x40)` 却只符号化 16 字节)、输入含运行时才能确定的量(随机数 / 时间戳);对策——对照反编译确认读取点与长度(步骤 1),stdin 长度按读取上限符号化;随机值点用 hook 固定(`hook_symbol("rand", ...)`)再符号化
|
||||
- **python 版本兼容**:现象——`pip install angr` 报编译错误 / import 即崩(`ImportError` 指向 C 扩展);原因——angr 9.3.0+ 官方要求 Python 3.12+,9.2.x 线下限随补丁版本抬高(9.2.91 要求 ≥3.8、9.2.203 已要求 ≥3.10),在过旧解释器上装新版(或在 3.13 上装无 wheel 的旧依赖)会现场编译甚至失败;对策——查**目标版本**的 Python 要求(PyPI `requires-python`):9.3+ 用 3.12+,9.2 按具体补丁版的元数据选解释器,建独立 venv 安装(工具准备节);装前 `pip install --upgrade pip setuptools wheel`
|
||||
- **find 地址选错 → 求解出的"flag"跑不通**:现象——求解成功但输出含非打印字符 / 程序不打印 flag;原因——find 设在错误处理循环(失败也经过)、多失败点只 avoid 了一个、符号化长度与程序读取不一致;对策——find 选**校验循环出口**(用反编译确认唯一成功路径),avoid 列全所有失败分支;输出先 `repr()` 检查再原样重放(见步骤 3 验证);拿多个解逐一跑目标验证
|
||||
- **PIE 基址偏移 → find 地址填错**:现象——按 Ghidra 显示的地址(如 `0x00101231`)填 find,angr 永远找不到;原因——PIE 二进制 Ghidra 按 0x100000 基址显示,angr 从 0x400000 装载;对策——地址换算(差 0x300000 之类固定偏移),或先查 angr 内模块实际装载基址再定 find 地址,脚本头部注释标明换算关系
|
||||
- **自定义 VM/重度混淆题直接符号执行低效**:现象——校验包在自定义字节码 VM(Tigress 等)里,angr 状态爆炸 / 求解无果;原因——VM 的 dispatch 循环反复符号化 + 混淆路径分支(先消 opaque predicate,见 [[re-deobfuscate]]);对策——VM handler 用 Unicorn 实例化仿真(angr 只负责外层控制流),或探索到死路径后导出每条路径的 SMT 方程交给 [[re-z3]] 生成输入(Tigress 案例达到 100% 分支覆盖)
|
||||
- **SimProcedure 摘要不准 → 结果诡异**:现象——`explore` 结果明显违反直觉(错误返回值、绕过校验),或同一逻辑换种跑法结果不一致;原因——angr 用 Python 摘要(SimProcedure)替代库函数以避免路径爆炸(strlen/malloc 等),但摘要有 bug 或不完整;对策——行为异常时优先怀疑 SimProcedure:禁用对应摘要(注意输入约束不紧时可能路径爆炸)或用 `project.hook` 按实际场景自定义摘要(如针对单一格式串自写 scanf hook)
|
||||
- **除零约束缺失 → 求解出 4294967295 类怪值**:现象——求解出的输入里出现 `0xFFFFFFFF` 之类,跑原程序必挂;原因——Z3 对 `a/b`(分母为 0)求值为全 1(4294967295),angr 只在部分路径(VEX 侧出口)拦截除零,SimProcedure/自定义分析代码可能漏过去;对策——涉及除法的符号表达式显式加"分母非零"约束,别依赖 angr 自动处理
|
||||
- **未初始化寄存器/内存被自动符号化 → 求解空间虚胖**:现象——求解极慢或结果含垃圾值,日志有 unconstrained 告警;原因——`entry_state` 对未初始化的寄存器/内存生成未约束符号变量,把无关状态也符号化了;对策——按告警设置状态选项 `ZERO_FILL_UNCONSTRAINED_MEMORY`/`ZERO_FILL_UNCONSTRAINED_REGISTERS`(或 `SYMBOL_FILL_*` 抑制告警),或显式设置已知初值,缩小求解空间
|
||||
- **cmovxx 不会自动分裂分支(ollvm 后继还原)**:现象——angr 模拟 ollvm 真实块找后继时 cmov 分支丢失,后继关系不完整;原因——angr 对 cmov 类指令通过**积累约束**实现而非分裂两个状态;对策——手动分裂:`state.copy()` 两份(一份执行 Move:`setattr(regs, dst, getattr(regs, src))`,一份不执行),并**手动跳过 cmov 指令**(`state.regs.ip += ins.size`)防止 angr 再次执行,再各自 simgr 步进找后继
|
||||
- **分发器 hook 要 unhook + 地址偏移校准**:现象——hook 主分发器末条指令跳真实块后执行又跳回分发器死循环,或 angr 解码出混乱指令;原因——hook 未卸掉(执行回来再次进 hook);capstone 从错误边界解码(指令错位);对策——跳到目标块后立即 unhook;解码混乱时调整 hook 地址(经验偏移如 -6,逐位试并输出汇编对照确认,见下条方法)
|
||||
- **ollvm 真实块识别与 patch 顺序**:现象——静态插件(D-810)面对变异 ollvm(双循环头、汇聚块与循环头合并)乏力;原因——静态分析难处理变体;对策——动态执行找真实顺序:BFS 找循环头(回到已访问块即循环头)→ 循环头两前驱=序言+汇聚块(多前驱者为汇聚块)→ 汇聚块前驱们=真实块,另找 ret 块(无后继且末指令 retn);**patch 顺序固定**:先收集全部无用块列表(含起止地址)→ 再 patch 控制流 → 最后按先前列表 nop 无用块(先 patch 再找无用块会找错)
|
||||
- API 速查(装载/状态/求解/hook 各族)与组合套路见 [[commands]];版本差异、求解语义与装载边界见 [[gotchas]]
|
||||
@@ -0,0 +1,141 @@
|
||||
# angr 命令速查与操作序列
|
||||
|
||||
angr 是 Python 库,没有 CLI 命令——"命令"即 API 调用。全部用法以官方文档(docs.angr.io)与目标版本源码为准;版本差异见 [[gotchas]]。
|
||||
|
||||
## 装载族(Project)
|
||||
|
||||
```python
|
||||
import angr, claripy
|
||||
p = angr.Project("./target", auto_load_libs=False) # 关库加载:快且稳(默认行为按版本有差异)
|
||||
p.loader.main_object.mapped_base # 实际装载基址(PIE 目标先查它)
|
||||
p.loader.main_object.entry # 入口点
|
||||
p.factory # 状态/块工厂
|
||||
p.factory.block(addr).vex # 取 VEX IR(汇编级核对)
|
||||
```
|
||||
|
||||
## 状态族(State)
|
||||
|
||||
```python
|
||||
s = p.factory.entry_state() # 从入口
|
||||
s = p.factory.full_init_state(stdin=claripy.BVS('in', 32*8)) # 标准输入符号化
|
||||
s = p.factory.full_init_state(args=["./target", claripy.BVS("arg1", 64*8)]) # argv 符号化
|
||||
s = p.factory.blank_state(addr=0x401000) # 任意地址起步(校验函数入口常用)
|
||||
s.regs.rax = claripy.BVS('rax', 64) # 设符号寄存器
|
||||
s.mem[0x601000].int32 = ... # 符号内存写
|
||||
s.posix.dumps(0) # 当前 stdin 内容(find 条件常用)
|
||||
s.solver.eval(...) / s.solver.add(...) # 该状态的求解器
|
||||
# 状态选项(未初始化内存/寄存器行为)
|
||||
opts = {angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS}
|
||||
s = p.factory.entry_state(add_options=opts)
|
||||
```
|
||||
|
||||
## 文件系统族
|
||||
|
||||
```python
|
||||
s.fs.insert('/tmp/in', angr.SimFile('in', content=claripy.BVS('in', 32*8))) # 符号文件
|
||||
# 程序 fopen('/tmp/in') 后读取即符号化;按 fd 操作走 s.posix.fd[0](0=stdin,1/2 是 stdout/stderr 勿混)
|
||||
```
|
||||
|
||||
## SimManager 族(路径探索)
|
||||
|
||||
```python
|
||||
simgr = p.factory.simgr(s, veritesting=True) # veritesting 合并路径(长循环题常用)
|
||||
simgr.explore(find=0x401100, avoid=[0x401200, 0x401300], num_find=1) # 示意地址:按目标实际入口/分支替换;找到即停
|
||||
simgr.found / simgr.deadended / simgr.active # 各 stash
|
||||
simgr.move('deadended', 'active') # stash 操作
|
||||
simgr.run(n=100) # 手动步数上限
|
||||
simgr.prune() # 剪掉已无解路径
|
||||
```
|
||||
|
||||
## 求解族(claripy / solver)
|
||||
|
||||
```python
|
||||
inp = claripy.BVS('inp', 32*8) # 符号变量(位宽=字节数*8)
|
||||
claripy.BVV(0x41, 8) # 具体值
|
||||
solver = found.solver
|
||||
solver.add(inp.get_byte(0) == ord('f')) # 加约束(逐字节)
|
||||
flag = solver.eval(inp, cast_to=bytes) # 求值
|
||||
solver.satisfiable() # 可满足性检查
|
||||
solver.min(inp) / solver.max(inp) # 极值(长度约束常用)
|
||||
for c in flag_sym.chop(8): solver.add(0x20 <= c, c <= 0x7e) # 可打印字符约束
|
||||
# eval 超时与数量: solver.eval(expr, n=..., cast_to=bytes, timeout=...) 按需
|
||||
```
|
||||
|
||||
## Hook 族(路径爆炸应对)
|
||||
|
||||
```python
|
||||
project.hook(addr, angr.SIM_PROCEDURES["stubs"]["ReturnUnconstrained"]()) # 地址处替换
|
||||
project.hook_symbol("sleep", angr.SIM_PROCEDURES["libc"]["sleep"]) # 按符号替换
|
||||
project.hook_symbol("rand", angr.SIM_PROCEDURES["libc"]["rand"]) # 固定随机值
|
||||
project.unhook(addr) / project.unhook_symbol("sleep") # 还原
|
||||
# SIM_PROCEDURES 类目: "stubs" 通用替身 / "libc" / "linux_kernel" / "posix" 等
|
||||
```
|
||||
|
||||
## 分析族(其他内置分析)
|
||||
|
||||
```python
|
||||
p.analyses.CFGFast() # 快速控制流图(找关键函数/循环边界)
|
||||
p.analyses.CFGEmulated() # 模拟 CFG(重/慢)
|
||||
p.analyses.BackwardSlice(...) # 后向切片(定位影响输出的输入字节)
|
||||
p.analyses.VariableRecovery(...) # 变量恢复
|
||||
```
|
||||
|
||||
## 操作序列(组合套路)
|
||||
|
||||
### 1. CTF find/avoid 标准模板(argv 变体)
|
||||
|
||||
```python
|
||||
import angr, claripy
|
||||
p = angr.Project("./target", auto_load_libs=False)
|
||||
arg = claripy.BVS("arg1", 64*8)
|
||||
s = p.factory.full_init_state(args=["./target", arg])
|
||||
simgr = p.factory.simgr(s, veritesting=True)
|
||||
simgr.explore(find=0x401100, avoid=[0x401200, 0x401300]) # 示意地址:按目标实际入口/分支替换
|
||||
if simgr.found:
|
||||
print(simgr.found[0].solver.eval(arg, cast_to=bytes))
|
||||
```
|
||||
|
||||
### 2. 文件输入符号化(fopen 读取型)
|
||||
|
||||
```python
|
||||
s = p.factory.full_init_state()
|
||||
s.fs.insert('/tmp/in', angr.SimFile('in', content=claripy.BVS('in', 0x40*8))) # 长度按读取上限
|
||||
simgr = p.factory.simgr(s)
|
||||
simgr.explore(find=..., avoid=...)
|
||||
sol = simgr.found[0].solver.eval(s.fs.get('/tmp/in').content, cast_to=bytes)
|
||||
open('flag.bin','wb').write(sol) # 沙箱内 ./target < flag.bin 验证
|
||||
```
|
||||
|
||||
### 3. blank_state 从校验函数入口起步(跳过无关代码)
|
||||
|
||||
```python
|
||||
# 反编译定位校验函数入口([[re-ghidra]])与参数约定
|
||||
s = p.factory.blank_state(addr=check_entry, add_options={angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS})
|
||||
s.regs.rdi = claripy.BVS('inp', 32*8) # 按调用约定放参数
|
||||
s.mem[s.regs.rsp] = 0x0 # 栈帧最小化(返回地址)
|
||||
simgr = p.factory.simgr(s)
|
||||
simgr.explore(find=success_addr, avoid=fail_addr)
|
||||
```
|
||||
|
||||
### 4. 循环题提速(hook + veritesting + 分段)
|
||||
|
||||
```
|
||||
hook 无关调用(sleep/memset/rand)→ simgr(veritesting=True)
|
||||
仍慢: 探索到循环边界地址 → 对循环体单独符号化展开(人工定位循环不变量)
|
||||
仍爆炸 → 换 [[re-z3]] 对已展开约束建模
|
||||
```
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- `auto_load_libs=False` 是默认建议:库加载既慢又让求解空间虚胖;样本带壳先脱壳([[re-deobfuscate]])再符号执行
|
||||
- find/avoid 的地址**按 angr 装载基址**填(PIE 与 Ghidra 显示基址差固定偏移),脚本头部注释标明换算关系
|
||||
- 求解出的值先 `repr()` 检查再重放:非打印字符常是符号化长度 < 实际读取长度
|
||||
- 涉及除法的表达式显式加"分母非零"约束(Z3 对 a/0 求值全 1)——见 [[gotchas]]
|
||||
- 探索超时用 `simgr.explore(..., timeout=...)` 或 `s.solver.eval(..., timeout=...)` 设秒级上限,别无限等
|
||||
- `eval(expr, cast_to=bytes)` 只对整字节宽符号有意义;逐字节用 `get_byte(i)`/`chop(8)`
|
||||
- 一个目标多轮求解时,脚本参数化(find/avoid/长度/通道放头部注释)逐参跑,别改一处全重写
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 求解过程在进程内仿真器完成,无需沙箱;**用求解结果运行目标验证时默认沙箱**([[re-analyze/platform-tips]] 最高原则)
|
||||
- 结果(地址/约束/求解脚本/flag)存档 sha256 + 路径([[re-triage]] 证据链);结论入 [[re-analyze/analysis-contract]]
|
||||
@@ -0,0 +1,45 @@
|
||||
# angr 工具特有坑与边界
|
||||
|
||||
## 版本与安装坑
|
||||
|
||||
- **Python 版本线**:**当前主线为 10.x;9.3.0 起要求 Python 3.12+**(PyPI `requires-python` 实测);**9.2.x 下限随补丁版本抬高**(9.2.91 要求 ≥3.8、9.2.203 已要求 ≥3.10),不能按「9.2.x」整线判断——装前以目标版本的 PyPI `requires-python` 为准(`pip index versions angr` 看可用版本);9.3 装到 3.11 会直接拒绝或编译失败,老的 9.2 装到 3.13 也可能无 wheel
|
||||
- **主版本间 API 变更**:angr 9.1(迁移文档有专页)改过 `SimState`/`state.posix` 等细节;网上旧教程(8.x 时代)的 `state.mem`、`simprocedures` 写法可能失效——以目标版本 docs 的 "Migrating to angr X" 为准
|
||||
- **binutils 依赖**:angr 处理/重写二进制(`angr.Project` 的 back-end 或 `angr.SimProcedure` 无关但 binary rewriting 相关操作)会调 `objcopy`——Linux 装 `binutils`,缺了报 `objcopy: command not found` 类错误
|
||||
- **Windows 支持边界**:Windows 下 pip 装 win32 wheel 可用,但部分 cle/ELF 后端与 fork 类操作受限;需要 ELF/arm 目标分析时优先 WSL 内装 Linux 版
|
||||
- **多环境共存**:angr 依赖树大(pyvex/unicorn/claripy 等),别往系统解释器塞——venv 隔离,升级先看依赖是否连带升
|
||||
|
||||
## 装载与地址坑
|
||||
|
||||
- **PIE 基址**:angr 默认按 0x400000 装载,Ghidra 按 0x100000 显示——find/avoid 地址必须换算(`p.loader.main_object.mapped_base` 核对实际基址)
|
||||
- **auto_load_libs 默认值**:新版本默认 `auto_load_libs=False` 而旧版本为 True——行为差异导致同一脚本结果不同;显式写参数,别依赖默认
|
||||
- **壳/静态链接目标**:带壳目标先脱壳([[re-unpack-simple]]);静态链接大二进制装载后状态数爆炸——hook 掉无关库函数入口
|
||||
- **架构/字节序**:arm/armeb/mips 目标 angr 支持但要注意字节序(`archinfo` 按 ELF header 自动选);`file target` 先确认再建 Project
|
||||
|
||||
## 求解语义坑
|
||||
|
||||
- **除零约束缺失**:Z3 对 `a/b` 分母为 0 求值为全 1(4294967295)——涉除法表达式显式加"分母非零"约束
|
||||
- **未初始化寄存器/内存被自动符号化**:`entry_state` 对未初始化值生成无约束符号,求解空间虚胖、结果含垃圾——`ZERO_FILL_UNCONSTRAINED_MEMORY`/`ZERO_FILL_UNCONSTRAINED_REGISTERS` 或显式设已知初值
|
||||
- **float/NaN**:浮点路径 Z3 与实机可能不一致(FP 舍入/NaN 语义)——浮点校验题优先人工还原或换 [[re-z3]] 单独建模
|
||||
- **eval 返回值的陷阱**:`eval(expr, cast_to=bytes)` 对非整字节宽符号(如 6 字节长度符号)按位宽对齐返回,多解时 `n=` 取多个逐一重放验证
|
||||
- **求解超时**:复杂约束集 eval 可能分钟级无回——`timeout=` 设上限,配合 `solver.min/max` 缩小搜索
|
||||
- **约束集合 ≠ 路径语义**:angr 求解器拿的是该状态累积路径约束,不是"程序全部行为"——求解出的输入必须沙箱内原样重放验证([[re-analyze/platform-tips]] 最高原则)
|
||||
|
||||
## SimProcedure 与 hook 坑
|
||||
|
||||
- **摘要不准**:strlen/malloc 等 SimProcedure 替代实现有 bug 或不完整,结果反直觉(错误返回值/绕过校验)——异常先怀疑摘要:禁用对应 hook 或 `project.hook` 自定义
|
||||
- **hook 未卸掉**:hook 分发器后执行跳回再次进 hook 死循环——跳到目标块后立即 unhook
|
||||
- **hook_symbol 与库调用路径**:`hook_symbol` 只影响按符号解析的调用;地址间接调用要 `project.hook(addr, ...)` 按地址挂
|
||||
- **cmovxx 不分裂分支**:angr 对 cmov 通过积累约束实现而非分裂两个状态——需要后继关系时手动 `state.copy()` 分裂并跳过 cmov 指令
|
||||
|
||||
## 性能与路径爆炸坑
|
||||
|
||||
- **符号化范围**:整段程序符号化是爆炸主因——只符号化校验真正读取的字节,入口尽量后移(blank_state)
|
||||
- **veritesting 双刃**:合并路径省状态但可能丢失分支精度(结果与不合并不同)——两个都跑对照
|
||||
- **stash 无上限**:默认 stash 不限制状态数——主动 `simgr.step()` 循环内检查 `len(active)` 或限制步数
|
||||
- **lazy_solves 与 prune**:路径多了先 `simgr.prune()` 剪无解路径,或开 `lazy_solves` 延后求解
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 求解在进程内仿真器完成(免沙箱);**用求解结果运行目标时默认沙箱**
|
||||
- 地址/约束/脚本/flag 全量存档([[re-triage]] 证据链),结论入 [[re-analyze/analysis-contract]]
|
||||
- 与 [[re-z3]] 的分工:需要精确约束集合而非一条路径时 z3 更轻;angr 求解慢时对约束子集转 z3
|
||||
Reference in New Issue
Block a user