本节摘要:当样本从一变成一百,对话式逆向必须升级成流水线。本节先把 2.0 的 headless 能力用起来:GhidraMCP 基于 PyGhidra(Python 3.10~3.13)支持无界面运行,配合 analyzeHeadless(第 1.3 节)把目录里的样本逐个「导入、分析、出函数清单」。然后是本节核心的命名落库流程:脚本收集无名函数的伪代码,交模型生成命名建议,先落 name_suggestions.json(含理由与置信度),抽样复核后由回写脚本批量改名——把 3.2 节「不让模型直接批量改名」的边界变成工程化动作。最后给一张成本与提速的示意账本与六条批处理工程化清单。所有数字均为教学示意,非实测承诺。
三个真实场景把 GUI 逼上绝路:样本集初筛(一批 CTF 样本先出清单再挑重点)、入库流水线(新样本自动分析归档)、定时任务(夜间跑白天看结果)。GhidraMCP 2.0 的 headless 支持让 MCP 能力与批处理同源:GUI 里模型调的工具,headless 里脚本同样能调——区别只是有没有人坐在屏幕前。
环境核对:headless 链路要求 Python 3.10~3.13(本书记 3.12),且 PyGhidra 按官方文档安装(第 1.3 节)。
复用第 1.3 节的 analyzeHeadless,把单文件换成目录循环:
# bash —— 目录批处理:每个样本出一份函数清单(写法示意,以官方文档为准) GHIDRA=/opt/ghidra_12.1.4_PUBLIC mkdir -p ghidra_proj out for f in bin/*; do base=$(basename "$f") "$GHIDRA/support/analyzeHeadless" ghidra_proj ReLab \ -import "$f" \ -scriptPath scripts \ -postScript export_functions.py "$base" \ -deleteProject || echo "FAIL: $base" >> out/failures.log done
脚本侧只需小改:输出文件名带样本名(out/functions_{样本名}.json),失败落 failures.log 而不是中断整批。GhidraMCP 的 headless 形态则更进一步:不启 GUI 也能让 MCP 客户端连上来逐个样本对话(启动方式以官方文档为准),批处理与交互在同一个能力面上。
批处理的两个实务问题提前说:失败样本——failures.log 里逐条排查,常见原因是格式不支持或样本损坏,单独用 GUI 复核一次即可定性;并行度——多个 analyzeHeadless 同时跑会争内存与 CPU,入门先串行,确有吞吐需求再按机器内存控制并发数(每个进程预留足量堆,具体以 Ghidra 官方性能建议为准)。
| 批处理故障 | 多半原因 | 处理 |
|---|---|---|
| 某样本反复失败 | 格式不支持 / 加壳 / 损坏 | GUI 单点复核定性,记录后跳过 |
| 整批越跑越慢 | 并发争用内存 | 降并发;每个进程的堆按官方建议预留 |
| 导出 JSON 缺字段 | 脚本异常吞错 | 脚本里对空对象兜底,缺字段记样本名 |
| 重跑样本重名冲突 | 每轮 -import 新建程序 | 删旧工程或改用 -process 复用(以官方文档为准) |
批处理里最值钱的产物是命名建议。流程三段,段段有闸门:
第一段:生成。 脚本收集无名函数(FUN_ 开头)的伪代码,交给模型,要求输出结构化建议:
# out/name_suggestions.json —— 命名建议文件(示意,由模型生成后落盘) [ { "entry": "00401160", "old_name": "FUN_00401160", "new_name": "check_key", "reason": "调用 deobfuscate 后与参数 strcmp,语义为许可校验", "confidence": "high" }, { "entry": "00401189", "old_name": "FUN_00401189", "new_name": "deobfuscate", "reason": "逐字节与 0x5a 异或的循环", "confidence": "medium" } ]
第二段:复核。 人工抽样规则:high 全查或抽三成,medium 以上全查,low 直接丢弃。复核看两样:理由引用的地址是否真实存在、名字是否与行为一致(不与「听起来像」妥协)。第 0.1 节的幻觉案例在这里就是拦截对象。
第三段:回写。 复核通过的建议由脚本批量落库:
# apply_names.py —— 读命名建议 JSON,批量回写 Ghidra 工程(示意,API 以官方文档为准) import json from ghidra.program.model.symbol import SourceType # noqa SUGGESTIONS = "out/name_suggestions.json" with open(SUGGESTIONS, encoding="utf-8") as fh: items = json.load(fh) fm = currentProgram.getFunctionManager() af = currentProgram.getAddressFactory() applied, skipped = [], [] for item in items: addr = af.getAddress(item["entry"]) func = fm.getFunctionAt(addr) if addr is not None else None if func is None: skipped.append(item["entry"]) continue func.setName(item["new_name"], SourceType.USER_DEFINED) func.setComment(item.get("reason", "")) applied.append(item["new_name"]) print("applied %d, skipped %d" % (len(applied), len(skipped)))
回写脚本的两个防御点:地址解析失败跳过并记录(不中断整批);改名来源标 USER_DEFINED,与自动分析产出的名字区分,日后可整体回滚。
| 项 | 量级(示意) | 说明 |
|---|---|---|
| 单样本 Ghidra 分析 | 小程序数秒、中型样本数分钟 | 与 1.1 节 GUI 内观察同源 |
| 单函数命名建议 | 数百至数千 token | 含伪代码输入与理由输出 |
| 百样本批处理 | 小时级,瓶颈在分析不在模型 | 并行度受内存与授权限制 |
| 人工复核 | 每函数十秒级 | 抽样策略决定总量 |
读这张表的正确姿势:所有数字是教学示意,真实数字取决于样本体量、模型档位与并行度;但结构关系稳定——批处理把人的时间从「逐函数读」压缩到「抽样复核」,模型成本与分析时间用机器时间换。 先用十几个样本跑通全流程再放量,是唯一正确的放量方式。
Ghidra 侧的工具流到此闭环。下一章换 r2 侧:r2ai 的角色预设与 ReAct 自动模式,r2mcp 的自建流水线——你会看到同一套「假设、验证、落库」思想换了一套更轻的武器。