re-angr

Helps security researchers automatically solve puzzles in code using smart analysis.

Installation
Run `npx skills add "https://github.com/dslsdzc/rev-skills" --skill "re-angr"` to install this skill, then follow its SKILL.md instructions for my next request.

Paste this into Claude Code, Cursor, or any agent that can run commands.

What this skill does
What it does: - Helps solve puzzles in computer programs by treating input as unknown values and tracking what happens to them - Finds the right input that makes a program reach a specific location or avoid a bad location - Works with input from keyboard, files, network, or memory When to use it: - The program checks your input byte by byte in a long loop and you need to find the right answer - You know where the program should go but figuring out the input by hand is too hard - The input comes from different places like command line arguments or files - Do not use it for simple checks you can read in a few lines of code - Do not use it if the check uses random numbers or things from the computer that cannot be tracked - Do not use it if you just need to understand the rules instead of finding one answer
SKILL.mdShow the author's original SKILL.md (not in English)
---
name: re-angr
description: angr 符号执行:符号化输入、求解。触发词:angr、符号执行、自动解题、constraint
---

# angr 符号执行(符号化输入 / 自动求解)

## 何时使用 / 何时不用

- 用:CTF 逆向题——输入在长循环 / 深比较链里逐字节校验(校验通过地址已知或可定位),人工逆推繁琐易错
- 用:已知"输入必须到达某地址 / 避开某地址",想把路径条件交给求解器(find / avoid 模式)
- 用:输入来源复杂(argv / 文件 / 标准输入),需要按通道符号化后求解
- 不用:简单 XOR / 明文比较(一屏伪代码内)——人工更快([[re-binary-core]])
- 不用:校验含不可符号化内容(环境相关值、随机数、未符号化输入)——会无解或误解(见坑 3)
- 不用:需要精确约束集合而非一条路径时——纯约束求解用 [[re-z3]] 更轻(见坑 2)
- 注意:符号执行是把双刃剑——先人工定位关键校验函数缩小符号化范围(见坑 2);运行验证走沙箱([[platform-tips]] 最高原则)

## 工具准备

参考 [[platform-tips]] 最高原则——用求解结果运行目标验证时默认沙箱;angr 本身是进程内仿真器,不接触真实系统调用,求解过程无需沙箱。

### angr(pip 安装)

- `pip install angr`(Linux / macOS / Windows 均可;Windows 下 pip 装 win32 wheel,WSL 内安装 Linux 版亦可)
- **Python 版本兼容性(重点)**:按 angr 主版本对号入座(以 PyPI 元数据为准)——**angr 9.2.x 支持 Python 3.8–3.11;angr 9.3.0 起要求 Python 3.12+**(`requires-python >=3.12`)。装前先 `pip index versions angr`(或查 PyPI)确认当前主版本,再按系统 Python 选:
  - 系统自带 Python 3.12+(Ubuntu 24.04 默认 3.12)→ 直接 `pip install angr`(最新 9.3.x 线)
  - 系统为 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` 使缓冲符号化:`state.memory.store(buf_addr, BVS('pkt', len*8))` 或 hook 返回符号指针——网络解析器目标通用 |
     | 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=0x4011xx, avoid=[0x4012xx, 0x4013xx])`(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=0x4011xx, avoid=[0x4012xx, 0x4013xx])
   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]]:求解输入的运行验证沙箱([[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 线支持 3.8–3.11,在过旧解释器上装新版(或在 3.13 上装无 wheel 的旧依赖)会现场编译甚至失败;对策——先查当前主版本对应的 Python 要求(PyPI `requires-python`),9.2 线用 3.11 及以下、9.3+ 用 3.12+,建独立 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]]

Ships with 2 supporting files:

  • references/commands.md
  • references/gotchas.md

Mirrored from the author's public source. Install counts from the open skills registry.

The systems behind these skills get built for partners every week.

Partner with us