本节摘要:r2pipe 是 radare2 的编程绑定:以
r2 -q0方式拉起一个 r2 进程,通过一根管道收发命令,Python、Go 等多语言皆可接。本节先讲清这根管子的模型——r2 变成一个「命令进、文本或 JSON 出」的服务,cmd 与 cmdj 两种调用形态;然后给最小示例验证连通;核心是实战脚本 extract_functions.py:aflj 拿函数清单、pdc 逐个取伪代码文本、按字段组装并截断,落成 demo_functions.json——与第 1.3 节 Ghidra 侧导出同构的备料。最后讨论备料形态的设计原则:函数级切分为什么是对的(对照《语义代码检索》第 3 章)、体量预算怎么定、截断要留什么痕迹——这份 JSON 将在第 04 章进入 RAG 向量库与模型提示词。
第 2.1 节的命令都是人敲的;r2pipe 让程序来敲。模型很简单:
┌──────────────┐ 命令字符串(如 "aflj") ┌──────────────┐ │ Python 脚本 │ ─────────────────────────▶ │ r2 进程 │ │ (r2pipe) │ ◀───────────────────────── │ (r2 -q0) │ └──────────────┘ 文本 或 JSON(cmdj 解析) └──────────────┘ 同一进程内可反复收发;结束时 r2.quit() 收摊
关键设计:r2 不嵌入 Python,Python 也不嵌入 r2,两个进程用管道对话。好处是解耦——r2 崩了重启即可,语言随意换(官方提供多语言绑定);代价是每次 cmd 都是字符串协议,命令拼写错误要到运行时才暴露。
安装:pip install r2pipe(第 0.2 节环境清单已含),r2 本体在 PATH 里即可。
# r2pipe_min.py —— 最小示例:列函数,验证管道连通 import r2pipe r2 = r2pipe.open("bin/demo_stripped", flags=["-A"]) print(r2.cmd("afl")) # 文本形态:人看的 funcs = r2.cmdj("aflj") # JSON 形态:程序用的 print("函数数量:", len(funcs)) print("第一个函数:", funcs[0].get("name"), hex(funcs[0].get("offset", 0))) r2.quit()
对 demo_stripped 预期输出十几行函数列表,函数数量在十个上下(含库导入桩,视平台与链接方式浮动)。如果这里报错,九成是 r2 不在 PATH——回第 0.2 节自检 r2 -v。
目标:产出与第 1.3 节 Ghidra 侧同构的数据——每条记录含名字、地址、体量,再加大模型真正要读的东西:伪代码文本。
# extract_functions.py —— r2pipe 批量导出函数与伪代码,为模型备料 import json import r2pipe TARGET = "bin/demo_stripped" OUT = "out/demo_functions.json" MAX_TEXT = 4000 # 每个函数伪代码的截断上限(字符,示意) r2 = r2pipe.open(TARGET, flags=["-A", "-q"]) funcs = r2.cmdj("aflj") or [] records = [] for item in funcs: name = item.get("name", "") offset = item.get("offset", 0) size = item.get("size", 0) if size < 16 or name.startswith(("sym.imp.", "loc.")): continue # 跳过导入桩与太小的碎片 text = r2.cmd("pdc @ %s" % name) or "" text = text.strip() records.append({ "name": name, "offset": offset, "size": size, "pseudocode": text[:MAX_TEXT], "truncated": len(text) > MAX_TEXT, }) r2.quit() with open(OUT, "w", encoding="utf-8") as fh: json.dump(records, fh, ensure_ascii=False, indent=2) total = sum(len(rec["pseudocode"]) for rec in records) print("exported %d functions, %d chars -> %s" % (len(records), total, OUT))
运行后 out/demo_functions.json 里每条记录形如:
# out/demo_functions.json —— 导出结果示例(节选,示意) [ { "name": "fcn.00401189", "offset": 4198792, "size": 76, "pseudocode": "do {\n var_2h = ... ^ 0x5a;\n} while (...);", "truncated": false } ]
工程细节三个:其一,-q 安静模式减少管道噪音;其二,过滤规则(size 下限、名字前缀)按对象调,CTF 样本常见大量小碎片函数;其三,pdc 是内置伪反编译,若需 Ghidra 级质量可装 r2ghidra 插件换 pdg,脚本只需改一个命令字符串。
这份 JSON 的最终读者是第 04 章的大模型。备料好坏直接决定那边的上限,三条原则:
| 原则 | 做法 | 理由 |
|---|---|---|
| 函数级切分 | 一函数一条记录 | 恰好装进一次提示词;函数是行为的最小完整单元 |
| 体量预算 | 每条截断并打 truncated 标 | 超长文本会撑爆上下文;截断必须可发现 |
| 保留地址 | offset 字段不能丢 | 模型说「fcn.00401189 可疑」时,人要能一秒跳回 r2 / Ghidra |
关于函数级切分,与源码侧对照:《语义代码检索》第 3 章《代码切分》讲 AST 级切分——按语法树切块,兼顾语义完整与检索粒度。二进制侧没有语法树,只有分析器恢复的函数边界,所以本书直接取函数级:粒度更粗,但边界由调用约定与分析器保证,且与 r2 / Ghidra 的交互单元天然对齐。
体量预算的量级感(示意,随模型代际浮动):单函数伪代码数百到数千字符;一个中型样本全部函数合计几万到几十万字符——后者显然不能整包塞进提示词,这正是第 4.2 节要用 RAG 检索的原因:入库的是本章的 JSON,查询时只取最相关的几个函数。
本章结束了「人驱动工具」的阶段,手里多了两样东西:
(文字流程图)demo_stripped ──▶ r2 -A ──▶ aflj 函数清单 ──▶ pdc 伪代码 ──▶ demo_functions.json ──▶(第 04 章)RAG 入库 / 假设验证。
地基篇(第 00~02 章)到此完工:Ghidra 立起工程化阅读,r2 立起命令行与备料。下一章起把工具交给模型——先从 GhidraMCP 开始,让模型直接列出函数、读伪代码、改名字、追引用。