executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
re-angrangr 符号执行:符号化输入、求解。触发词:angr、符号执行、自动解题、constraint
Independent GitHub snapshot · Automated classification · Original documentation governs
参考 [[re-analyze/platform-tips]] 最高原则——用求解结果运行目标验证时默认沙箱;angr 本身是进程内仿真器,不接触真实系统调用,求解过程无需沙箱。
pip install angr(Linux / macOS / Windows 均可;Windows 下 pip 装 win32 wheel,WSL 内安装 Linux 版亦可)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 选:
pip install angr(当前主线 10.x;Python >=3.12)pip install "angr<9.3"(9.2.x 线),别硬装 9.3+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(避免原生组件编译失败)binutils(angr 处理 / 重写二进制会调 objcopy):apt install binutilspython3 -c "import angr; print(angr.__version__)"apt install python3(多数自带);macOS: brew install python;Windows: 官方安装包 / choco install pythonpython3 --versionfile target 确认架构与位数(arm 目标 angr 支持,注意字节序)按顺序执行,每步记录结果(地址 / 约束 / 求解脚本 / flag,证据路径见 [[re-triage]])。先人工后自动化:符号化范围越小越稳(见坑 2)。
输入适配(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)
到达目标地址 / 避开地址建模(find / avoid):
simgr.explore(find=<入口地址>, avoid=[<失败分支地址>])(find 可多地址或 lambda lambda s: b"flag{" in s.posix.dumps(0))if (check(input)))→ find 设在 check 返回后的成功分支地址,avoid 设在失败分支路径约束与求解(solver):
found = simgr.found[0]flag = found.solver.eval(inp, cast_to=bytes)(inp 即步骤 1 创建的 stdin/argv 符号)或按符号变量名求:found.solver.eval(flag_sym, cast_to=bytes)for c in flag_sym.chop(8): found.solver.add(0x20 <= c, c <= 0x7e)
open('flag.bin','wb').write(flag),同时打印 repr 检查(非打印字符常是符号化长度问题,见坑 3)路径爆炸应对(hook / 限制深度 / 分段):
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 选项控制典型模板(find / avoid 模式):
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 只符号化校验真正读取的字节);仍不行就分段探索或转人工分析blank_state + 手动设置寄存器 / 内存),跳过前面无关代码;校验是纯等式集合时直接换 [[re-z3]]explore 秒回 no path found,或求解出的值跑原程序不通过;原因——输入没被符号化(读取的是真实 stdin / 文件内容)、符号化字节数 < 实际读取长度(fgets(buf, 0x40) 却只符号化 16 字节)、输入含运行时才能确定的量(随机数 / 时间戳);对策——对照反编译确认读取点与长度(步骤 1),stdin 长度按读取上限符号化;随机值点用 hook 固定(hook_symbol("rand", ...))再符号化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 wheelrepr() 检查再原样重放(见步骤 3 验证);拿多个解逐一跑目标验证0x00101231)填 find,angr 永远找不到;原因——PIE 二进制 Ghidra 按 0x100000 基址显示,angr 从 0x400000 装载;对策——地址换算(差 0x300000 之类固定偏移),或先查 angr 内模块实际装载基址再定 find 地址,脚本头部注释标明换算关系explore 结果明显违反直觉(错误返回值、绕过校验),或同一逻辑换种跑法结果不一致;原因——angr 用 Python 摘要(SimProcedure)替代库函数以避免路径爆炸(strlen/malloc 等),但摘要有 bug 或不完整;对策——行为异常时优先怀疑 SimProcedure:禁用对应摘要(注意输入约束不紧时可能路径爆炸)或用 project.hook 按实际场景自定义摘要(如针对单一格式串自写 scanf hook)0xFFFFFFFF 之类,跑原程序必挂;原因——Z3 对 a/b(分母为 0)求值为全 1(4294967295),angr 只在部分路径(VEX 侧出口)拦截除零,SimProcedure/自定义分析代码可能漏过去;对策——涉及除法的符号表达式显式加"分母非零"约束,别依赖 angr 自动处理entry_state 对未初始化的寄存器/内存生成未约束符号变量,把无关状态也符号化了;对策——按告警设置状态选项 ZERO_FILL_UNCONSTRAINED_MEMORY/ZERO_FILL_UNCONSTRAINED_REGISTERS(或 SYMBOL_FILL_* 抑制告警),或显式设置已知初值,缩小求解空间state.copy() 两份(一份执行 Move:setattr(regs, dst, getattr(regs, src)),一份不执行),并手动跳过 cmov 指令(state.regs.ip += ins.size)防止 angr 再次执行,再各自 simgr 步进找后继