ghidra-core · git:20261009.2063994 · 2026-10-09 · sha256 2fe4ecff21c45a99
ghidra-core git:20261009.2063994A
Immutable. This exact content never changes and is served at /api/v1/blob/2fe4ecff21c45a99.
---
name: ghidra-core
description: Ghidra headless 执行底座(execution engine)——rpc_driver/ghidra-rpc 命令、daemon 起停、doctor 环境自检、脚本清单。当需要实际运行任何 Ghidra 命令(import/decompile/disassemble/rename/patch/version-track)、环境排障、或其他逆向 skill 里的操作不知道具体命令时加载。不含分析方法论。动手前必须先用 skill 工具加载本 skill 全文并遵守其流程;一切观察/结论用 ledger.py 落账。
whenToUse: 需要执行 Ghidra 命令、查命令参数、daemon 起停/排障、环境自检(doctor)、拉起 Ghidra GUI 时;re-triage/ghidra-static/vuln-audit 里的操作步骤缺少具体命令时
---
# Ghidra Core(执行底座)
唯一的代码与命令知识层。场景 skill(re-triage / ghidra-static / vuln-audit)只含方法论,具体命令一律回本文件查。
- 场景路由:未知样本分诊 → `~/.dsh/skills/re-triage`;脱壳与验证 → `~/.dsh/skills/re-unpack`;静态深挖/patch/交付 → `~/.dsh/skills/ghidra-static`;漏洞模式排查 → `~/.dsh/skills/vuln-audit`
- 非二进制出口:题面是 URL/HTTP 服务 → `~/.dsh/skills/ctf-web`;纯数学密码结构(n/e/c、曲线、PRNG)→ `~/.dsh/skills/ctf-crypto`;pyjail/编码/语言沙箱/杂项 → `~/.dsh/skills/ctf-misc`。pcap → `~/.dsh/skills/traffic-analysis`
- 本 skill 内容:§0 环境 / §1 十四条铁律 / §2 快速上手(含长任务、GUI、doctor)/ §4 legacy 裸命令 / §5 能力清单(三层)/ §7 移植坑 / §8 能力边界
- engine 内部补丁细节不在本文件:见 `engine/VENDOR.md`
## 0. 环境(本机已配好)
> **WSL/Linux 部署的路径映射**(当 `uname` 是 Linux 时,本节常量按下表替换;脚本内部已自动按平台分支,此处供人工/排障参考):
> - `GHIDRA_HOME = /opt/ghidra`,JDK = `/usr/lib/jvm/java-21-openjdk-amd64`(apt openjdk-21)
> - 引擎 venv = `~/ghidra-rpc-venv`(bin/python,无 .exe),legacy venv = `~/pyghidra-venv`
> - re-tools venv = `~/re-tools-venv`,unpacker venv = `~/unpacker-venv`,pwn venv = `~/re-pwn-venv`
> - 工具目录 = `~/tools/`(goresym、jadx、pyinstxtractor);系统工具(file/upx/gdb/tshark/pycdc…)全在 PATH,直接裸名调用
> - junction 在 Linux 是 symlink `~/dsh-ghidra-workspace`,语义相同
> - `win_gui_drive.py`、pywin32、Android SDK/emulator 为 Windows-only,Linux 下不可用
- `GHIDRA_HOME = C:\t001s\ghidra_12.1.3_PUBLIC_20260817\ghidra_12.1.3_PUBLIC`(含 `support/` 的内层目录)
- 执行引擎:**ghidra-rpc 常驻 daemon**(vendor 在 `engine/ghidra-rpc/`,上游 Cellebrite Labs 0.2.0 + dsh 补丁,见 `engine/VENDOR.md`)。统一入口 `scripts/rpc_driver.py`;GUI 用 `scripts/launch_gui.py`(见 §2「GUI」节)
- **JDK 21+**(本机 `C:\Java`,Java 25),且必须是 JDK 而非 JRE——PyGhidra 通过 JPype 起 JVM
- **Python 环境 ×2**:
- 引擎 venv:`$HOME/Desktop/src/ghidra-bridge/ghidra-rpc-venv`(Python 3.12;ghidra-rpc 0.2.0 **editable** 安装自 `engine/ghidra-rpc/`,pyghidra 3.1.0 + JPype1 1.5.2)——引擎代码改动即时生效
- legacy venv:`$HOME/Desktop/src/ghidra-bridge/pyghidra-venv`(Python 3.10,pyghidra 3.1.0)——仅供 `driver.py` 后路使用
- 工作区:`~/.dsh/ghidra-workspace/`(`projects/` legacy 项目缓存、`projects-rpc/` daemon 项目、`out/` 产物、`logs/` 日志)
- **传给 Ghidra 的路径必须走 junction `~/dsh-ghidra-workspace`**:Ghidra 的 `ProjectLocator` 拒绝任何以 `.` 开头的路径元素(`~/.dsh/...` 会 abort)。上游 ghidra-rpc 会 `resolve()` 掉 junction——已打补丁(`engine/VENDOR.md` 补丁 2),junction 路径全程可用
### 为什么不是 Jython / analyzeHeadless(重要:原设计其实没错)
**Jython 扩展本身是装好的**,就在 `%APPDATA%\ghidra\ghidra_12.1.3_PUBLIC\Extensions\Jython`(内含 `jython-2.7.4\Lib`)。用 GUI(`ghidraRun.bat`,不重定向 APPDATA)跑,原本的 `@runtime Jython` 脚本是能用的。
真正的坑是 **APPDATA 重定向**:为了满足 DSH 的文件沙箱(只允许写工作区),driver 把 `APPDATA`/`LOCALAPPDATA`/`USERPROFILE`/`TMP` 指到工作区内的 settings 目录。Ghidra 于是去 `<被重定向的 settings>\ghidra\ghidra_12.1.3_PUBLIC\Extensions\` 找扩展——**那里没有 Jython**,于是报:
```
ghidra.app.script.JythonStubScriptProvider$JythonStubException:
In order to use Jython based scripts, you must install the Jython Ghidra Extension,
or (recommended) port your script to PyGhidra or Java.
```
一句话:**不是 skill 的脚本有问题,是重定向把一个已装好的扩展藏起来了**。两个可选解法:
- **想用 Jython**:在被重定向的 settings 目录下建 junction 指到真实扩展
`mklink /J <redirected>\...\Extensions\Jython %APPDATA%\ghidra\ghidra_12.1.3_PUBLIC\Extensions\Jython`
(实测可行;但 PyGhidra 流程下仍不建议回退,见下)
- **继续用 PyGhidra**(当前选择):不依赖扩展、脚本是真正的 Python 3、错误信息更清楚。代价是要过下面的启动/API 关口。
另外注意:**PyGhidra 启动器下 `analyzeHeadless` 完全不可用**——把 `.py` 交给它会得到 `Ghidra was not started with PyGhidra. Python is not available`。所以一旦选 PyGhidra,就必须整条链走 `driver.py`。
上游 19 个 + 本 skill 早期的 `triage_scan`/`decompile_all` 共 21 个脚本已全部移植为 `@runtime PyGhidra` 并统一由 `driver.py` 启动(原 `run-headless.sh` 入口已废弃并删除,见 §2),此后又新增 4 个。**2026-09-15 起执行引擎迁移为 ghidra-rpc 常驻 daemon,这 25 个脚本冻结为 legacy 备查**(见 §5 第 3 层),`driver.py` 保留为 daemon 挂掉时的后路。
## 1. 十四条铁律(先读这个再动手)
1. **开工先 ensure;批量场景用批量工具**:daemon 温热后单次命令 ~0.2s,"一个函数一次调用"不再是罪。但全量反编译/全文搜索仍优先 `decompile-all` / `search-decompiled` 这类服务端批量工具——别 for 循环 1000 次单条 `decompile`(每条都要序列化过锁)。
2. **导入分析一次做足**:`rpc_driver.py ensure` 的 load 只做一次全量分析;之后所有查询/写操作都打在同一个已分析程序上,不会重跑分析。
3. **大输出落文件**:所有脚本约定首个参数 `@绝对路径` = 完整结果写该文件(JSON/文本),stdout 只留状态行。agent 用 Read 读文件,不要从 stdout 抠大输出。
4. **Triage 硬门**:未记录 imports(DLL/SYS 还要 exports)+ 语言/壳判定之前,MUST NOT 进入深挖或动态分析。导入表只有 kernel32/ntdll 且极少 → 高度怀疑 `LoadLibrary`+`GetProcAddress` 动态加载,禁止宣称"无网络/无文件能力"。
5. **确认即标注**:搞清一个函数立即 `rename-function` 改成语义名 + `set-comment --type plate` 写 plate comment(地址/作用/依据)。结论必须带地址和可复现命令。daemon 写操作即刻生效并自动存盘。
6. **时间盒**:静态深挖 ~15 分钟无关键路径 → 转动态(Frida/GDB/Qiling/angr,选型见 ghidra-static `references/ctf-patterns.md` §6);同一路径失败 2 次 → 换工具,禁止空转。**量化判据(什么算"卡住")**:同一假设上连续 3 次工具调用没有产出新观测(新地址/新常量/新结构),即判定卡住——不许靠"再试一次"拖延,立即按铁律 7 的菜单升级;给同一思路"换措辞重试"(改脚本变量名/换种写法跑同一模型)计入失败次数,不重置计数。**拟合/接线类推断熔断(chal session:wiring→wiring2→fitwiring 连错 5 次)**:手写拟合/接线求解脚本连错 2 次 → 强制升级 z3/SMT 求解或 `emulate_blob`/`emulate-function` 仿真取数,禁止写第 3 个手写拟合脚本;**升级前先跑 `peel_inverse.py` 查依赖图**(吃 `ssa_reconstruct.py` 的直线程序,0.25s 出判词)——检出可剥离递推段就逐层手工剥离,无干净结构才放行 SMT(Reverse-chal:掩码二阶递推逐层反解成功,z3 60s 超时反而没用上;be20d7dc:recon 文件在手却跳过本闸直接 z3,40 min 全部 unknown,flag 丢失)——**手上有直线程序时这是闸不是建议**;**反向闸(47246ce9 判例:检出后 0 次剥离直奔 z3,模型连修 4 次未收敛,47 min 丢 flag)**:检出递推段的下一步是 `peel_inverse.py <recon> --emit-solver peel_solve.py` 生成剥离骨架,填 KNOWN/CALL_TABLES/SEEDS 后逐层反解,检出后直奔 z3/SMT 视为违规;**z3/SMT 模型接线连错 2 次(unsat 无法解释/换输入不泛化/污点断链)→ 禁止修第 3 次模型,强制回到剥离路径或 stuck 升级**;**骨架使用闸(4ccec36c 判例:emit-solver 骨架生成后 head -60 一眼弃用、0 次填写 0 次运行,z3 长征 91 min 后把 peel 路径写进 stuck escalation 直接交卷,丢 flag)**:骨架生成后 0 次填写/运行 = 视同未过闸;主张「检出段与求解目标不重合/骨架不适用」是否定性论断,必须 conclude 带证据(剥离全部检出段后目标仍不可达的实证,或扰动实验证明检出段纯 RNG/常量-only)才放行 SMT;递推段确实不是目标 ⇒ 转 `cone_invert.py` 锥形反推(阶梯 ①'),别直接跳 SMT;hooks 层 `gate_churn.py` 会在脚本堆积(目录近 24h ≥8 个 .py)且台账零 stuck 时机械阻断。**探索运行熔断(f862c151 session:97 次 heredoc 探索、128 个手写结构变体盲搜,gdb/frame_map/model_diff/emulate 全程 0 次)**:heredoc/`python -c`/stdin 管道这类**不落盘的探索运行不过 churn 闸门**(载体盲区),由 hooks 层 `gate_explore.py` 计数封堵——30 分钟 ≥25 次且台账零 stuck → 机械阻断;脚本体与窗内 ≥2 次历史高度相似(换措辞重试的指纹)且台账无新条目 → 阻断并直指 `model_diff.py` 分歧指纹分类。**ARX/模乘类密文升级阶梯(禁止跳档)**:① 手上有 `ssa_reconstruct` 直线程序 → `peel_inverse.py` 剥离掩码递推(检出即 `--emit-solver peel_solve.py` 生成骨架,填 KNOWN/CALL_TABLES/SEEDS 逐层反解,产物按铁律 7 过原程序验证,无需 SMT;没有直线程序才跳过本档)→ ①' 无干净递推段、或递推段经落账证伪不是求解目标 → `cone_invert.py` 锥形反推(`--known` 锚点起步,每语句单操作数精确反解 + 查表调用爆破 2^16;矛盾检出 exit 1 分两类判词——锥内含未知量 = 方程错了按铁律 9 换方程禁止续命;锥体全被字面量决定、无未知量 = 输入符号化不完整,跑 `--diff-symbolize`(同长度不同输入两份 recon 按值 diff,自动符号化全部输入相关字面量,行数错位也能用)补符号化后重跑,禁止据此弃路径——29bf99c0 只符号化 4/8+ 个常量、锥体全可正算报矛盾后误弃本路径交卷)——①①' 都不通才进 ② → ② gdb 断轮体读**帧槽位真值**(先 `frame_map.py` 定位槽位)→ ③ `model_diff.py` 分歧指纹分类 → ④ `emulate-function`/p-code 插桩取语句级真值 → ⑤ 把已有 trace 当方程组**解**接线(数据够时差的是"解"不是"枚举")→ ⑥ angr 殿后且须先论证对本密文可行(16 轮 × 68 次 mod-65537 乘 + 192 次查表的规模必爆)——未走完前五档就调 angr 会被 `gate_explore.py` 机械阻断。**Cython/CPython 扩展前置**:triage 报 `python_ext=true` 时,建模轮函数之前必须先跑 `const_scan.py --binary`(§12 元数据常量重建)+ `frame_map.py`(栈帧槽位)并落账,否则深挖被机械阻断。
7. **死循环断路器(机械触发,不靠自觉)**:循环签名 = 第二次回到同一字节区域/同一假设继续分析。执行载体是 `scripts/ledger.py`:每个区域级观察 MUST 走 `ledger.py observe` 落账——同一区域第二次 observe 时脚本**拒绝入账(exit 2)**,除非 `--delta` 回答"这次观测和上次差在哪";答不出 = 断路器触发,按脚本打印的菜单升级:换工具 / 转动态 / 问用户,并用 `ledger.py stuck` 留痕——禁止换第 3 种方式重试同一路径。**stuck 是升级的起点不是终点(4ccec36c:--escalate 写出能力范围内的可执行路径后 0 次执行直接交卷)**:--escalate 在能力范围内(机械步骤/已有部分验证)必须先执行——执行成功走 conclude,失败把该路径并入 --tried 重新 stuck,确实需要用户/缺工具才把 escalate 写成「问用户/需安装 X」;hooks 层 stop_check 收尾机械拦截「末条 = stuck 且 escalate 未出现在 tried」。**升级未收敛 ≠ 升级穷尽(29bf99c0:binv.py 一轮 +38 未收敛后以「上下文将尽」交卷,实测 1M 窗口占用不足三成,19 min 丢 flag)**:escalate 执行后未收敛禁止默默交卷——补规则重跑 / 重新 stuck 换新路径 / ask_user_question 报告卡点,三选一;「上下文将尽/预算不足」不是出口,上下文压力的正确响应是窗口化读取(grep/sed 段读)继续推进;stop_check 机械拦截未收敛投降(用户在 stuck 之后发过话视为已问,放行)。字节只能通过工具解读(反汇编、反编译、脚本输出);肉眼/裸 hex 仅用于验证工具输出,脚本对同一区域只放行 2 次,第 3 次直接拒绝。packed/加密字节在信息论上是噪声,内容级死磕一律禁止——先脱壳(re-unpack)或仿真(`emulate-function`)。**断路器的盲区自查**:observe 熔断只统计同 binary 同 region——给同一思路"换措辞重试"(36 个脚本各写各的 region/文件名)一次都响不了(DEFCON26 复盘 R3)。外部信号:work 目录脚本数 ÷ 台账条目数 > 5 = 记账死掉/同路径重试,用 `ledger.py status <bin> --workdir <题目目录>` 机械统计,命中立即 stuck + 审视。**定时器(chal 复盘 E4)**:单样本目录下每 ~20 分钟主动跑一次 `status --workdir`——本次 churn 12 倍于红线而没人去跑;注意比值 >5 **只触发一次记账**(落 observe/stuck),是否停手由当时的 delta 决定,**不由比值决定**;阈值 5 仅在单样本目录有意义,多文件/协议/多阶段题型天然"脚本多、样本少",只当记账提醒。非地址型题目(协议/算法/多方件)没有自然 binary 键:用题目名当 binary 键、`conclude --locus proto:/channel:/region:` 前缀落账。
铁律 6 管时间(多久没进展就换路),铁律 7 管循环签名(ledger.py 机械拦截原地打转)——先命中哪条执行哪条。台账 = `<ws>/out/<名>.ledger.jsonl`(机器真相,append-only)+ 每次入账自动重建的 `<名>.ledger.md`(人读视图):权威结论写入即锁定(同 id 覆盖必须 `--overturn`+新证据)、先查后析(`ledger.py query`)、推翻留痕——机制细节见 `references/evidence-ledger.md`。
8. **读数纪律(第一嫌疑人是读数,不是程序)**:关键常量(密文/密钥/换表/魔数)的唯一权威读数 = `scripts/read_views.py` 三视图(hexdump + 带字节数的 fromhex-ready hex + cstr),读到立即 `ledger.py conclude` 锁定(注明地址+长度+工具);**禁止从终端手工转录 hex**(可信度最低的通道不能承载最关键的事实)。反编译器/IDA 的字符串与 hex 渲染只是视图——会把 `0x01`/`0x0E` 渲染成 `1`/`E` 吞掉前导 0——凡要引用渲染文本,先 `read_views.py --expect-hex "<渲染文本>"` 与真实字节对照。观测矛盾(伪码声明长度 vs 实测处理长度、同一事实两次读出不同值、`strcmp` 对任何输入都不等)→ **先怀疑读数**:任何"这段逻辑是坏的/反的"的结论,必须先排除读数错误才允许提出;同一事实第二次读出不同结果 = 熔断信号,立即停止推断、用 read_views 建立权威读数。**求逆之前先正向验证**:拿到疑似密钥/密文后,先用已知输入把完整流水线正向跑一遍(`oracle.py` / `emulate-function`)确认模型能复现已知输出,再求逆——直接求逆错了也不知道错在哪。字段/读数冲突时,在宣称"题目设计有矛盾"之前,必须先排除"该值是多字段复合函数"(如 tsval ^ payload ^ seq 片段)这一可能——同键冲突恰恰证明单字段不是明文。
9. **缓冲区归属(字节是谁的)**:从 `MOV [EBP+disp], imm` 序列重建栈上字符串时,必须**按 disp 区间归属变量**,禁止按指令出现顺序拼接——相邻变量的写入区间相接(`end_A + 1 == start_B`)时极易把别人的字节拼进自己的常量(encode 复盘 E1:28 字节密文被读成 49 字符)。拼接结果 MUST `read_views.py --expect-len` 与该变量声明长度核对。任何常量长度不符合其用途(hex 必须偶数、base64 必须 4 的倍数、XOR/RC4 密文必须等于明文长度)→ **以「读数可疑」中止,禁止进入求逆**。机械门 = `scripts/crypto_sanity.py`:求逆前 MUST 过 `check`,求逆后 MUST 过 `check-result`——49 字符的 base64、28≠21 的密文、不可打印的反推结果都会被它 exit 2 拦下。**候选合法性 = 方程校验信号(92a0a107 临门一脚)**:反演候选全部不可打印 / 尾部不符合填充格式(PKCS#7 等),且原程序对非 ASCII 输入直接崩溃时,判定本轮**方程错了**(常量或比对对象选错),禁止在同一方程上加补丁续命、禁止归因「还有未建模的编码分支」继续探测——先核对方程两边各是什么(trace 里输入构造用的常量与输出比对的常量未必是同一个)。`check-result` 的不可打印判定就是这条的机械触发器。
10. **验证独立性(同源验证 = 没验证)**:用自己 patch 的进程、自己写的 harness、自己算的偏移来验证自己对程序的理解,三者一致不构成任何证据(encode 复盘 E2/E3)。① **被 patch 过的运行态只能用于探索控制流,禁止用于验证数据模型**——验证数据模型要求进程未修改(或修改点与测量点无数据依赖)+ 至少一个独立来源(静态常量/第二输入/已知明文)交叉印证;被迫在 patch 后测量的结论必须 `conclude --independent no` 标注。② **自建 harness(投喂/读数脚本)在支撑结论前必须用已知答案的输入自检**;自检失败或结果不稳定 → 该 harness 全部输出作废;「偶发命中」必须复跑 ≥100 次确认可复现才算发现。**凡「由观测反推中间量」的脚本(白化/搬运公式、角色字反推、偏移表)无已知答案自检 ⇒ 其输出标 ⚠UNVERIFIED 且不得用于否定模型**——「模型像坏的」时第一嫌疑人永远是 harness 自己:差异呈现规律(如低半字全对、高半字共用一个常数)是"接口/搬运写错"的典型指纹,不是"程序少了运算"。③ `ledger.py conclude` 强制标注 `--source`/`--independent`(self-script 必须给 `--harness` 路径),无独立来源的结论在 render 里标 ⚠UNVERIFIED,禁止原样交付。④ **否定性结论门槛更高**:说「不可满足/程序是坏的」之前,必须先用已知输入正向复现成功(铁律 8),且结论必须能指认一个「如果它错了,结论就崩」的外部事实——指认不出就不许交付。**「这是诱饵/假 check/作者逻辑坏了」同属否定性结论**:判某分支为假之前,必须先做判定性实验——钉随机源为不同值看输出是否变(判 mask vs target)、读校验涉及的状态量(`dir()`/`__dict__`/hook 目标函数)并用已知输入拟合其表达式;实验做不了就不许判。**混淆实验不算判定性实验**(f862c151 诱饵门事故:stub `_p3` 返回 `_var2` 得到 "yes" 是人为满足第一道门,推不出"真门是 `_var2`";真指纹是 `_p3` 输出被 `os.urandom` 掩码 ⇒ `_p3(...)==_var2` 恒不可满足——恒不可满足的"判定条件"本身就是诱饵签名)。**「工具不可用/装不上」同属否定性结论**(chal 复盘 §8.3):下此结论前必须换第二种方式复核(离线 pip 缓存 / 系统包 / 独立可执行 / 另一包管理器)——检查器自身也会假阴性(tag 重建错、缺 `--target`),"不可用"的结论被信了之后,整条工具升级路线都会被误关。细则见 re-dynamic §4。⑤ **flag 类结论的唯一合法 VERIFIED 证据 = 未修改的原程序/平台接受候选输入**(chal session 循环论证事故:把自写探针 `[ord(c) for c in _var1] == _var3` 当程序判定,交付错 flag 并标 `--independent yes`——探针里 `_var1` 就是输入本身,恒真)。自写探针/等价式只能支撑 `--kind finding`;flag/答案类结论必须 `conclude --kind flag --program-accept "投喂命令 + 原程序成功响应原文"`(缺证据 **exit 2**,且禁止 `--source self-script`),远程题则为平台接受回执。⑥ **自检强度分级 + 闭合锚定**(f862c151/92a0a107 双会话临门一脚:用恒真的右逆自检确认了一个错的方程):**L1** `fwd(inv(B))==B` 恒真——任何逆函数实现都构造性满足,零信息量,禁止落账为「已验证」;**L2** `inv(fwd(X_known))==X_known` 左逆锚定——能抓常量/分支/接线错,手握 ground truth(如 `'A'*L` 采的 trace,反演结果前 L 字节必须是 `0x41`)必须拿它当锚,一行 assert 的事;**L3** 未修改原程序接受候选 = 唯一终审(⑤)。宣布「模型/反演已闭合」的结论必须 `conclude --kind model --anchor "L2 断言+实测输出"`(缺证据 **exit 2**)。**只验变换不验方程 = 没验**。⑦ **「观测缺失 ≠ 不存在」——可达性/存在类否定判词的专属硬门**(判例:4307cc95 Glitched:watchpoint 全程未触发 + hbreak 不命中 + Ghidra 无 xref,三条证据误判「flag 路径不可达」交卷,实际是 UD2 的 SIGILL handler 推进保存的 RIP 使调用正常返回,86 min 白走、靠 wp 才纠正):「watchpoint 未触发 / hbreak 未命中 / Ghidra 无 xref / 反编译器不显示调用者」**全部只是「未观测到」,证明不了「不存在」**。这类结论只有两种合法证据——① **静态证明**:给出入口到目标每条路径上不可满足的约束(写出约束本身);② **动态证明**:patch 掉中间闸门强制到达后目标仍不执行。落账必须 `conclude --kind negative --proof "static-constraint: …" 或 "dynamic-forced: …"`(缺 proof 或前缀不符 **exit 2**;用 observe 落账充数视同未落账,交付视同无证据否定结论)。**样本带自定义信号处理器(sigaction/signal/SIGILL/SIGSEGV/SIGTRAP)或裸 UD2(`0f 0b`) 时,反编译器的 non-returning/死代码/调用图截断判定一律不可信**——handler 可改写保存的 RIP/ucontext 让「非法指令」正常返回;下任何可达性结论前 MUST 先回答「handler 是否推进了保存的 RIP」,并把该问题落 `ledger.py hypothesis`(triage 的 `selfmod_signal` 块命中 = 本检查升级为硬门)。
11. **手写解析器必须双源验证(工具缺失 ≠ 自造轮子的许可证)**:自行实现的格式/字节码解析器(opcode 表、结构体偏移、指令解码、文件格式 parser),在据此下任何结论前,MUST 与第二个独立实现逐条比对至少一次——官方实现优先(SDK 自带工具),其次成熟第三方库;比对不上就装工具,装不了就标 ⚠UNVERIFIED(铁律 10③)禁止交付。**「输出看起来合理」不构成正确性证据**——表偏移类错误恰恰只产生语法合法、语义自洽、看似合理的输出(五题复盘:DEX opcode 表在 0x2d 多塞一个条目,`if-lt` 被读成 `if-ne`,标准 ROT13 显示成残废实现,差点自信交付)。已知的静默偏移陷阱(手写同类代码前先当自检清单过一遍):`scripts/pe_info.py` 的 **PE32 ImageBase**(PE32 在 opt+28,opt+24 是 BaseOfData;PE32+ 才在 opt+24)与 **PE32+ 导入 thunk 宽度**(8 字节 QWORD——按 4 字节读会撞上全零高半部,把导入列表静默截断成「每 DLL 1 个函数」)、AXML 字符串池偏移、DEX opcode 表条目数。配套纪律:**oracle 必须成对**(证明「正确输入被接受」之外,必须给出「近似错值被拒绝」的负对照,否则无法排除「凡输入皆通过」);**解空间可枚举时穷举优先于公式**(穷举同时产出答案与唯一性证明)。
12. **异常即约束**:观测到的不一致(重复键冲突、伪码长度 vs 实测、同一事实两次读数不同)必须 `ledger.py anomaly --consequence "..."` 转成可检验假设落账,禁止降级为"噪声/歧义待枚举";**台账自身两条结论互相矛盾同样是异常**(f862c151:`_var2` 32 项 ⇒ flag 17–32 字节 与 `len(_p3res)==48` ⇒ flag 33–48 字节并存却 0 条 anomaly——自相矛盾正是诱饵的指纹);**对工具判词的否定性论断(「检出段不适用/是 red herring/与目标无关」)本身是一条结论,必须 conclude 落账并给证据**;后续观测与之矛盾(判「纯 RNG-only」后发现数据相关)必须 anomaly + 同 id --overturn 标 killed——4ccec36c:自证 mask chain 数据相关、前提已崩却未更新,后续 70 min 按「peel 不适用」走 z3 交卷,禁止带已崩前提继续决策;存在 open anomaly 时 stuck 必须 `--ack` 引用或先 `resolve --waive`;工具缺失导致的客观不可查走 waive,不许硬卡。待检验的模型/推断(不止观测到的不一致)落 `ledger.py hypothesis --text --if-true --if-false --test`——"不一致必须转成可检验假设"的假设对象就是它,open 在 render 单列置顶,关闭(confirmed/killed)必须 `--evidence`(与 resolve 反向门同构,缺证据 exit 2),客观不可检验走 `--status waived --waive "理由"`(豁免理由强制入账,render 单列可见)。**写下就必须有下文:stop_check 收尾时机械检查 open hypothesis,带着未闭环假设交卷直接 deny**(92a0a107 临门一脚:H1 精确写出真因却没跑,恢复轮把最后 3 分钟花在写报告上)。**关闭异常的机械门(反向门,比正向门更重要)**:`resolve --note` 必须同时给 `--evidence`(复核命令/地址/读数),否则 **exit 2**——叙述性机制解释不是证据,不许用它解除约束;waive 通道豁免 `--evidence`(豁免理由强制入账),但 `waived` 在 status/render 里与 `resolved` 单独可见。
13. **轴必须交叉(限枚举类解码/搜索任务)**:仅适用于候选空间可枚举的解码/搜索任务(隐信道解码、爆破类)——开跑前先出轴矩阵+候选预算(候选数 = Σ C(字段数,k) × 算子数 × 顺序源 × 打包 × 后变换),且**轴矩阵+预算必须 `ledger.py plan --axes --budget` 落账,禁止只写在聊天里**(复盘实证:不落账的轴矩阵永远不会被执行);预算算得出却仍剪轴必须写明理由;任一轴恒为 identity/常量 = 未覆盖,不得声称"试过"。与铁律 6 时间盒联动:预算 > 时间盒承受能力时升级方法(找 oracle/换数学洞察)而不是硬跑。**不适用于需要数学洞察的密码题——那里穷举是最后手段。** **模型内拟合搜索预算门(chal 复盘 G2)**:在自己写的模型(Python 拟合脚本)里做 >1e5 次候选搜索前,先自证输入数据(铁律 10②——"用错的观测数据做对的搜索"是最大时间黑洞,本次 10^7 组搜索全部建立错数据上),再找可分离未知量或检查点把搜索变求解;都不行 → 换观测手段(机器级取数/打桩/仿真);该题型不存在观测手段时退回纯推断 + ⚠UNVERIFIED,**不停手**。正当爆破(弱 KDF/口令/小密钥空间异或)不归此门——走 hashcat/GPU 等专用工具。**约束系统路由闸(285db822 判例:BV32 方程组 ror/add/mul,z3 毫秒出唯一解,agent 全程 z3 零调用、连写 4 个 OpenMP C 枚举器烧 20+ min——失败触发的断路器对「永远有产出」的枚举器结构性失效,它一直在跑所以从不"卡住")**:识别到**约束系统**(等式/不等式组求未知输入)→ 第一工具 = z3/SMT(手上有 ssa_reconstruct 直线程序时仍先过铁律 6 的 peel_inverse 闸),**禁止把手写枚举器当作约束系统的第一解**。大枚举(候选空间 ≥ 2^28)仅两种情况合法:① 密码学黑盒比对(KDF/加解密输出比对,SMT 结构性不适用);② SMT 已证不可行(unsat/超时证据落账)。开跑前必须 `ledger.py brute <binary> --keyspace --eta --why-no-smt` 登记——把「无感开烧」变成「有账本的开烧」;hooks 层 `gate_bruteforce.py` 对未登记的自建大枚举器(C 循环上界 ≥2^28 / omp 并行大循环 / range(≥2^28) / itertools.product repeat≥4)机械阻断,hashcat/john 等专用工具不过该门。**工具处方闸(判例:42 min 无 flag)**:工具输出末尾的 advice/下一步命令(conflict_oracle→decode_engine 接力等)必须照抄执行,或 `ledger.py --note` 落账不执行的理由——无视处方转手搓脚本是本轮最贵错误(decode_engine 零调用,>2e5 手搓候选全在缺轴空间)。两个流量解码认知闸:① **真复合不一定消除冲突**——冲突由重传重生字段(tsval 等)携带时,真组合继承冲突,conflict_oracle 排名结构性收敛不到真解,此时走工具处方里的「铺轴 + oracle 过滤」(全部窄字段池喂 decode_engine 全交叉);② **键的确定函数 ≠ 无信息**——某字段可由键拟合(如某载荷半字节 `=(a-b·k)%16`)且重复键上存在冲突时,该字段是复合值的 XOR/ADD **抵消项**,必须纳入组合,禁止剔除(剔除后整条搜索不在解空间)。
14. **判定性实验优先于继续读数**:任一关键量(参数角色 / 通道归属 / 某因子是否参与计算)在 30 分钟或 3 次工具调用内未被**扰动实验**确认(钉住可疑因子为常量看输出是否变、翻转一个输入看哪路输出动),必须停止读码转做打桩实验,并按 `--source runtime-oracle` 落账。**参数角色不得仅由调用约定/寄存器推断**——"第 N 参数按惯例是输出缓冲区"是假设不是读数(DEFCON26 复盘:`FUN_13d74` 第一参数实为文件缓冲区,被读成输出,错模型持有数小时;推翻它只需一次几分钟的打桩实验)。瓶颈不是算力是"到证伪的距离":最高杠杆的两个动作是 oracle 打桩隔离单因子(`oracle_family.py`)与模型差分校验(`model_diff.py`),关键量存疑时先用它们,不要继续读码。
15. **禁止以提问结束回合(c50c8812 判例:07CTF Glitched,stage 1–4 已解出验证,stage 5 卡点自述被 gate_stuck 拦 2 次后始终不落账,改用 ask_user_question 绕道——还把违反用户「依次进行」明示指令的选项列进选项表;提问被空选驳回后回合空转结束,web 端死寂收不了场)**:ask_user_question 仅限三类——缺凭据/缺授权/用户必须裁决的偏好分叉,且选项不得与用户显式指令冲突(用户说"依次进行"就不许列"先做下一题");**返回空选/驳回 = 按推荐选项立即自主继续**,禁止空转收尾;有卡点自述未 `ledger.py stuck` 落账时禁止提问——gate_stuck 对 ask_user_question 同样机械拦截。真卡死时的正确出口是 stuck 落账 + --escalate 继续执行,不是把选择题丢给用户。
## 2. 快速上手(3 行)
```bash
RD="$HOME/.dsh/skills/ghidra-core/scripts/rpc_driver.py"
# 1) 开工:daemon 没起就起、二进制没 load 就 load(幂等),含全量分析
python "$RD" ensure /path/to/binary
# 2) 一键分诊 → @out 落盘;之后所有命令直接跟二进制路径,key 自动映射
python "$RD" "@$HOME/.dsh/ghidra-workspace/out/app.triage.json" triage /path/to/binary
python "$RD" decompile /path/to/binary main
python "$RD" xrefs-to /path/to/binary check_flag
# 3) 写操作(重命名/注释/patch)即刻生效并自动存盘,无需 exec-w
python "$RD" rename-function /path/to/binary FUN_00401000 check_flag
```
`rpc_driver.py` 做的事:项目映射(`~/dsh-ghidra-workspace/projects-rpc/dsh_<sha256前16>.gpr`,junction 路径)、binary key 自动替换(不用记 `/name-hash6` 后缀)、`--project` 注入、`@out` 落盘、环境变量自给(GHIDRA_INSTALL_DIR/JAVA_HOME/LOCALAPPDATA 重定向/USERNAME=dsh)。
子命令:`ensure|status|stop <binary>`(ensure 支持 `--timeout N` 秒,等待期间每 12s 向 stderr 打心跳,大二进制首载不再像死掉)+ 其余全部透传给 `ghidra-rpc` CLI(`decompile`/`functions`/`strings`/`disassemble`/`assemble`/`write-bytes`/`triage`/`exec-code`/`export-binary`/`emulate-function`/`version-track`…,全量见 `ghidra-rpc --help`)。
二进制比对:`version-track <A> <B>` / `function-diff <A> <f1> <B> <f2>` 会自动把 B 也 load 进 A 的项目(VT 要求同项目)。
### ledger.py 速查(查用法看这个,禁止为查语法去读 ledger.py 源码或逐命令 --help 轮询——c50c8812 两次因此各烧 1 min)
```bash
L="$HOME/.dsh/skills/ghidra-core/scripts/ledger.py"
python3 "$L" query <bin> [--region R] # 先查后析
python3 "$L" observe <bin> --region R --tool T --note N [--delta "与上次差在哪"]
python3 "$L" conclude <bin> --conclusion C (--address 0x… | --locus region:) --source S --independent yes|no
# conclude 变体闸门:--kind flag 须 --program-accept;--kind negative 须 --proof "static-constraint: …"|"dynamic-forced: …";
# --kind model 须 --anchor "L2 左逆锚定实测";--source self-script 须 --harness;覆盖同 id 须 --overturn
python3 "$L" anomaly <bin> --region R --note N --consequence C
python3 "$L" resolve <bin> --anomaly A1 (--note N --evidence E | --waive W)
python3 "$L" hypothesis <bin> --text H --if-true P --if-false Q --test T # 开假设
python3 "$L" hypothesis <bin> --resolve-id H1 --status confirmed|killed --evidence E # 关假设(waived 用 --waive 理由)
python3 "$L" plan <bin> --axes "轴矩阵" --budget N [--note]
python3 "$L" brute <bin> --keyspace "2**31" --eta "25min @ 8线程" --why-no-smt R [--tool T]
python3 "$L" stuck <bin> --at 位置 --tried 已试 --escalate 下一步 [--ack A1,A3]
python3 "$L" status <bin> [--workdir 题目目录] # churn 信号:脚本数÷条目数 >5 报警
python3 "$L" render <bin> ; python3 "$L" validate <bin>
# 长文本一律 --conclusion-file/--evidence-file/--proof-file/... 走 UTF-8 文件,绕 shell 引号
```
### 长任务(后台执行)
daemon 常驻后日常命令都是亚秒级,真正的长任务只剩 **`load`(大文件导入+分析)和 `version-track`(全函数关联)**——这类用 dsh Bash 工具的 `run_in_background` 跑,靠 `@out` 落盘拿结果:
```bash
# 后台跑 version-track(大样本对),结果落 @out 文件
python "$RD" "@$HOME/.dsh/ghidra-workspace/out/vt.json" version-track old.exe new.exe --changed-only
# → 拿到 task_id 后继续干别的;完成通知到达后 Read 该 json
```
判断完成的信号是 `@out` 文件出现且 JSON 有效;stdout 摘要行只有几行,不要从前台输出抠大结果。
**本地计算型长任务(爆破/拟合/搜索,timeout ≥300s)必须走 `scripts/guarded_run.py`**(chal session 事故:`nohup timeout 1200 python3 bf.py > log &` ——python 块缓冲 + 到点 SIGTERM,20 分钟计算日志 0 字节):
```bash
python3 "$SK/guarded_run.py" --timeout 1200 --log out/job.log -- python3 -u bf.py
```
逐行 tee 落盘(每行 flush,杀掉也有)、静默 30s 心跳、到点先 terminate 宽限 5s 再 kill、结束打印 rc/耗时/log 路径;超时退出码 124 对齐 timeout(1)。hooks 层 `gate_longrun.py` 会拦下裸 `timeout ≥300s + python 脚本` 的命令。
### GUI(launch_gui.py)
```bash
python "$SK/launch_gui.py" # 裸启动 Ghidra GUI
python "$SK/launch_gui.py" --open /path/to/bin # 打开该二进制的 dsh_<hash> 项目
python "$SK/launch_gui.py" --project dsh_<hash> # 按项目名打开
```
detached 启动,脚本立即返回 PID,GUI 输出进 `logs/gui-launch.log`;关闭用 `taskkill //PID <pid> //F` 或 GUI 内退出。GUI 以 `USERNAME=dsh` 启动(与 headless 创建的项目的属主一致,不会 NotOwnerException),settings 用真实用户目录。
**何时该去 GUI**:交互式 CFG/函数图深挖、人工比对多个函数、type archive(FIDB)制作、plugin 类交互工具(GOOMBA 等)。**headless 已覆盖的事别去 GUI**:导出 patched 二进制(`export_binary.py`)、patch(`patch_bytes.py`)、批量反编译、分诊。
### 环境自检(doctor.py)
```bash
python "$SK/doctor.py" # 全绿 exit 0;任一 fail exit 1
```
输出分两块:
- **Ghidra 核心 8 项**(决定 exit code,fail 即 exit 1):安装与两个启动器、JAVA_HOME/java 版本、pyghidra-venv、ghidra-rpc-venv、ghidra-rpc 包(editable 自 engine/)、工作区可写 + junction 解析、现有项目清单、daemon 起停冒烟(`--quick` 可跳过这项慢的)
- **toolchain 三层**(独立成节,不进 exit code):Tier A 轻量高频(checksec/GoReSym/pyinstxtractor/frida/z3/rust-demangler/upx/unpacker,缺失=warn 按 hint 补装);Tier B 按需重装(angr/qiling/speakeasy/unipacker/ghidriff/SiMBA/strings/readelf);Tier C GUI/手工(dnSpyEx/de4dot/DIE/x64dbg/GOOMBA/golang-loader)
**任何 skill 的路由表提到外部工具时,可用性以 doctor 的 toolchain 节为准**——那是唯一真相源。换机器/升级 Ghidra/排查"怎么又起不来"时先跑它。
**外来旧项目**:从别处拷来的项目若 `project.prp` 的 `OWNER` 不是 `dsh`,headless `open_project` 首次打开会自动改写属主;GUI 则要求属主一致,手动把 `OWNER VALUE="..."` 改成 `dsh` 即可。
## 4. 裸命令模板(legacy 排障专用;日常用 `rpc_driver.py`)
daemon 整体不可用时的后路是 `driver.py`(pyghidra-venv,每次调用一个冷启动 JVM):
```bash
PY="$HOME/Desktop/src/ghidra-bridge/pyghidra-venv/Scripts/python.exe"
GH="C:/t001s/ghidra_12.1.3_PUBLIC_20260817/ghidra_12.1.3_PUBLIC"
WS="$HOME/dsh-ghidra-workspace" # junction → ~/.dsh/ghidra-workspace;Ghidra 拒绝含 . 的路径元素,必须用 junction
export GHIDRA_INSTALL_DIR="$GH"
export JAVA_HOME="C:/Java"
# 首次:导入 + 全量分析 + 分诊(项目名 dsh_<sha256前16>)
"$PY" -m pyghidra --project-path "$WS/projects" --project-name dsh_<hash> \
"C:/abs/path/binary.exe" "$SK/triage_scan.py" "@$WS/out/binary.exe.triage.json"
# 注意:pyghidra 3.1.0 已支持脚本参数透传(脚本路径后的参数全部进 script_args)。
# 但日常仍推荐 driver.py:脚本名解析(skill 目录/cwd/ws scripts)、exec-w 存盘、日志落盘。
```
`analyzeHeadless.bat` 仍然可用,但**只能跑 `@runtime` 不是 PyGhidra 的脚本(即 `.java`)**;对 `.py` 会报 Jython 缺失。全部参数细节与 Windows 坑 → `references/headless.md`。
## 5. 能力清单(三层)
通用约定:`rpc_driver.py` 的 `[@out文件]` 恒为第一个参数;函数定位支持 `0x地址` / 精确名 / 模糊子串;写操作即刻生效并自动存盘。
### 第 1 层:rpc 命令(主力,`python rpc_driver.py <命令> <binary> [参数]`)
| 命令 | 用途 |
|---|---|
| `ensure` / `status` / `stop` | daemon 与二进制生命周期(ensure 幂等) |
| `triage` ◆ | 一键分诊报告(本 skill 入口) |
| `metadata` / `imports` / `exports` / `memory-map` / `relocations` | 程序元数据 |
| `functions` / `decompile`(`--format text` 顶层直接给 `text` 键,失败 `error` 带原始异常原文)/ `decompile-all` / `search-decompiled` | 函数与伪码 |
| `disassemble`(`--force` 在请求地址强制解码——跳转表 target 未反汇编时用,否则只会前跳到下一条已有指令 + warning)/ `assemble` ✎ / `basic-blocks` / `pcode` | 汇编/CFG/P-code |
| `strings` / `symbols` / `find-bytes` | 搜索 |
| `xrefs-to`(`--follow-fatptr` 自动追 Rust/Go {ptr,len} 描述符第二跳,hop=2 + via=描述符地址)/ `xrefs-from` | 交叉引用 |
| `read-bytes` / `read-pointers` / `write-bytes` ✎ | 内存读写(write-bytes 已对齐 patch_bytes 语义) |
| `rename-function` ✎ / `rename-symbol` ✎ / `batch-rename` ✎ / `set-comment` ✎ / `batch-set-comment` ✎ / `set-signature` ✎ | 标注 |
| `create-struct` ✎ / `create-enum` ✎ / `modify-struct` ✎ / `set-data-type` ✎ / `apply-data-type-range` ✎ / `set-equate` ✎ | 数据类型 |
| `retype-variable` ✎ / `rename-variable` ✎ / `batch-edit-variables` ✎ / `set-calling-convention` ✎ / `set-thunk` ✎ / `create-function` ✎ / `create-label` ✎ | 深度修改 |
| `version-track` / `function-diff` / `match-function` | 二进制比对(Auto VT + BSim) |
| `list-vtable` / `get-processor-context` / `set-processor-context` ✎ | C++/ISA 上下文 |
| `tag-function` ✎ / `list-tags` / `functions-by-tag` / `set-bookmark` ✎ / `list-bookmarks` | 进度标记 |
| `exec-code` ◆⚠ | 逃生舱:daemon 内 exec Python 文件(预置 program/find_function/output_json;无沙箱) |
| `export-binary` ◆ | Original File 导出 + md5 对比 |
| `emulate-function` ◆ | EmulatorHelper P-code 仿真(寄存器/内存预置,call-depth 追踪) |
| `exec-code` + `scripts/unreferenced_funcs.py` ◆ | 零调用方函数清单(藏 flag 的第二函数):`python rpc_driver.py [@out] exec-code <binary> "$SK/unreferenced_funcs.py"`;`--reachable-from <root>` 做可达性过滤——daemon 下脚本参数走 `DSH_UNREF_ARGS` 环境变量(须在 ensure/启动 daemon 前导出);排除 thunk/external/entry-export 根 |
| `exec-code` + `scripts/unreferenced_data.py` ◆ | 零引用数据块清单 + 置换/密钥材料分类 + 相邻块跨度合并(藏置换表/S-box):`python rpc_driver.py exec-code <binary> "$SK/unreferenced_data.py"`;daemon 下脚本参数走 `DSH_UNREF_DATA_ARGS` 环境变量(须在 ensure/启动 daemon 前导出);`--min-size <n>` 调阈值,`--include-referenced` 全量 |
◆ = dsh 自定义工具(`engine/ghidra-rpc/ghidra_rpc/server/tools/dsh_tools.py`)。✎ = 写操作。⚠ = 无沙箱。
完整命令与参数:`~/Desktop/src/ghidra-bridge/ghidra-rpc-venv/Scripts/ghidra-rpc.exe --help`,或 `engine/ghidra-rpc/docs/` + `engine/ghidra-rpc/README.md`。
### 第 2 层:宿主工具脚本(◈ 直接 python 运行,不经 daemon/driver)
| 脚本 | 用途 |
|---|---|
| `rpc_driver.py` | 统一入口:项目映射/key 替换/@out/环境自给 |
| `ledger.py` | **铁律 7 机械断路器**:观察落账(同区回访强制 `--delta`)/权威结论锁定(`conclude` 强制 `--source`/`--independent` 来源标注,无独立来源标 ⚠UNVERIFIED;**铁律 10⑤**:`--kind flag` 必须 `--program-accept`(未修改原程序/平台接受候选的实测记录)且禁止 self-script 来源,缺证据 exit 2;**铁律 10⑦**:`--kind negative`(可达性/存在类否定结论)必须 `--proof "static-constraint: …"|"dynamic-forced: …"`——观测缺失 ≠ 不存在,缺 proof 或前缀不符 exit 2;**非地址型题目用 `--locus region:/channel:/proto:` 前缀**)/卡点/render;**铁律 4 假设对象** `hypothesis --text --if-true --if-false --test`(关闭 confirmed/killed 必须 `--evidence`,缺证据 exit 2);**铁律 13 轴矩阵** `plan --axes --budget` 落账;**铁律 13 约束系统路由闸** `brute --keyspace --eta --why-no-smt`(候选空间 ≥2^28 的自建枚举开跑前强制登记 keyspace/预计耗时/SMT 不可行理由,keyspace 支持 `2**31`/`95*8` 表达式,gate_bruteforce 查此账);**铁律 12 反向门**:`resolve --note` 必须同时给 `--evidence`(可检验证据),缺证据 exit 2,waive 通道豁免但 `waived` 单列可见;`validate` 机器校验台账最小 schema(CI 判据用,违规 exit 2;历史无 evidence 的 resolve 条目不判违规);`status` 在 ≥5 观察 0 异常时打印铁律 12 提示,`status --workdir <题目目录>` 统计脚本 churn(脚本数÷条目数 >5 警告换措辞重试) |
| `read_views.py` | **铁律 8 权威读数**:三视图读字节(hexdump/fromhex-ready hex/cstr)+ `--expect-len`/`--expect-hex` 一致性检查(失败 exit 2) |
| `crypto_sanity.py` | **铁律 9 求逆闸门**:`check`(常量长度/格式合法性:hex 偶数、base64 %4、流密码等长)+ `check-result`(反推结果可打印性/格式),违规 exit 2 禁止求逆 |
| `launch_gui.py` | detached 拉起 Ghidra GUI,可直接打开项目 |
| `doctor.py` | 环境自检(8 项:安装/JDK/双 venv/工作区/项目/rpc 包/daemon 起停),全绿 exit 0 |
| `guarded_run.py` | **长任务保护执行器(timeout ≥300s 的计算任务必须走它)**:逐行 tee 落盘(杀掉也有部分结果)+ 静默心跳 + terminate→kill 两段终止;`--timeout N --log F -- cmd`;超时退出码 124。替代裸 `timeout N python3 x.py > log`(块缓冲+SIGTERM 丢光输出,chal 复盘) |
| `pe_info.py` ◈ | PE 分诊:头/节表/熵/导入表/资源;PE32 与 PE32+ 双路径(ImageBase 与导入 thunk 宽度都按位数分派)。`python pe_info.py <exe>` |
| `decomp_lint.py` | **伪码体检(读伪码之前先跑)**:输入 decompile-all 的 @out JSON,输出 fatal 函数清单(跳转表未恢复等,这些函数禁止读伪码)+ 危险渲染计数;有 fatal exit 2。加 `--binary <bin>` 启用**尾调用甄别**(审计 A5:尾声 `ADD RSP,imm`/`POP`×N/`LEAVE` 后的 `JMP reg` 是良性尾调用,Ghidra 误报 jumptable,单列不计入 fatal;happyVm 实测基线由 28/468=6.0% 修正为 13/468=2.8%,剔除 15 个尾调用函数) |
| `call_histogram.py` | CALL/JMP 目标直方图:从反汇编里数出"重复调用点"(≥3 次高亮 = 疑似分发器/校验体骨架),Rust/内联 main 场景的开工第一个动作(ctf-patterns §4) |
| `emulate_blob.py` | 裸 blob Unicorn 仿真骨架:`--base/--entry/--rsp/--max-insns/--reg/--map-file/--dump-dir`(`--entry` 是相对 `--base` 的偏移),停止原因分类 + dirty 页 dump;exit 0 跑完 / 2 用法错 / 3 unicorn 缺失(提示进 re-tools-venv)/ **4 harness 配置错**(blob 不可读或为空、未知 `--reg`、映射失败、**入口未映射**——不误报 RET 飞出)(多层载荷题用,ctf-patterns §10) |
| `const_scan.py` | Cython/C 常量重建:扫反编译 C(或 decompile-all @out JSON)的 `PyList_New(n)`/`PyTuple_New(n)` + 随后常量写入,重建 Python 字面量;小整数聚集自动告警"疑似字节级常量表";`--binary <样本>`(须先 ensure)把 `<DAT_…>` 缓存 PyLong 引用还原成 Python 值,CPython 布局自动投票(`--py 311/312` 强制)(chal 复盘 T3:L 表 48 项实测定位+还原) |
| `xor_scan.py` | blob 变换搜索:单字节 XOR 全扫 + 可选滚动密钥/ADD/SUB,按可打印率+magic(MZ/ELF/PK/UPX!)+flag 正则综合评分排 Top N;`--dump <key>` 落地解密产物(fakePE 复盘缺口 4,不要再现场手写第 5 版) |
| `emulate_program.py` | **整程序仿真**(emulate_blob 的上层):PE 装载 + IAT 桩(88 个内建 Win32 stub,ABI 陷阱已正确实现)+ 喂 stdin + 成功/失败断点;`--trace-api` 排障、`--dump-state` 取状态。console 型校验程序专用(边界见 docstring);happyVm 实测 0.047s 跑到 `You Get FLAG!`(unicorn-harness.md 的可执行形态) |
| `jt_resolve.py` | 跳转表一键解析:LEA+JMP 线索 ∪ decompile 警告 ∪ 伪码残留三路并集找**所有**表基址,rel32/abs32 双模式解 target,basic-blocks CFG 交叉验证;只解一张 = 解错一半(happyVm 实测 0x442b90+0x442ba0 双表全解) |
| `frame_map.py` | 栈帧槽位归属表:`SUB RSP` 定帧 → 扫 `[RSP+off]` 首写/宽度/读写次数;同槽多宽度标可疑(伪码 `local_XXXX` 重叠命名的真身) |
| `const_audit.py` | 伪码常量对照审计:`&DAT_0000xxxx` 小整数嫌疑(happyVm 实测抓到 `&DAT_00000007` = 长度 7)、`DAT_` 引用实际字节对照、指针化字面值漂移判定(注意:指向字符串中部的真实 `LEA` 立即数**不是**漂移,工具已加反汇编引用抑制) |
| `model_diff.py` ◈ | **铁律 10② 分歧指纹产出**:模型实现 vs 真实 oracle 命令随机对拍 N 组(`--input-gen hex:N/dec/ascii:N`,确定性 `--seed`),首个分歧给 hex 对照 + 指纹分类 + mismatch 率汇总。判词优先级**宽度级 > 字节级 > 无规律**:宽度级(按 2/4 字节块)高低半块交换 / 低半块全对+高半块 XOR·加法恒定(chal 形状)/ 单侧半块恒为常量;字节级低 nibble 全对 / nibble 交换 / 单常量差 / 全体偏移固定值 —— 以上全部判疑似接口/搬运错,宽度级命中**绝不**落"疑似算法错";仅"完全无关"判疑似算法错。exit 0 全一致 / 2 有分歧 / 3 命令错误。纯本地子进程工具,不经 daemon |
| `oracle_family.py` ◈ | **铁律 14 打桩 oracle 家族(单因子隔离)**:binary + `--stub 0xaddr=0xval`(可重复)批量产 patched 副本(x86-64 函数头写 `mov eax,imm32;ret`,`--rax` 用 imm64 形式)逐个运行出差分表:输出变 ⇒ 该因子参与计算;任意常量不变 ⇒ 无关通道。`--addr-is va`(默认,ELF 按 program header、PE 按节表+ImageBase 换算,可用 `--dry-run` 只验换算不 patch)/offset;非 x86-64 exit 3;exit 0 全部跑完 / 2 有副本运行失败 / 3 用法/架构错误 |
| `ssa_trace.py` ◈ | **Cython/CPython 扩展 SSA 数据流追踪器**(ctf-patterns §12.2 工具形态):int 子类包裹(`+ - * ^ & | << >> % //` 自动落账成事件流)+ import 前 builtins 记录包装(`--observe-builtins`)+ `random.*`/`os.urandom` 钩子(`--pin-urandom`/`--seed` 做 §9 角色判别);`--class CLS --ctor-input 'A*48' --wrap m1,m2 --out trace.json`。exit 0 正常 / 2 用法错 / 3 加载失败。stdlib only,不经 daemon(Reverse-chal 复盘分水岭:8 min 拿 ARX 递推结构,替代手读生成 C) |
| `ssa_reconstruct.py` ◈ | **SSA 事件流→直线程序重建 + 裸字面量解析**(§12.2 后半链,吃 ssa_trace 的 trace.json):产出可读直线程序(行格式与 peel_inverse 输入约定一致),高频裸字面量用 identity/sw/sw<<16/位移/*16 + `sw(a)<<16|sw(b)` 打包四式逐一试解,解不出标 ?? 交人工不猜——Cython 值 C 化断链处"神奇常数"的归位器。exit 0 正常 / 2 输入错。stdlib only,不经 daemon |
| `peel_inverse.py` ◈ | **铁律 6 工具形态:求逆前先查依赖图**(吃 ssa_reconstruct 的直线程序):找 `& 0xffffffff` 掩码变量的依赖结构,检出"滞后-2 递推段"(mask_i 只依赖前两个)→ `--emit-solver peel_solve.py` 生成可运行剥离骨架(f0/f1 探针自动判加法/异或反解,前向重放自验),填 KNOWN(段末两掩码目标值)/CALL_TABLES(查表函数)/SEEDS(种子实测值)后运行即逐层反解;**骨架 0 次填写/运行 = 未过闸(4ccec36c)**;无干净结构转 cone_invert.py,仍不通才放行 SMT。exit 0 判定完成 / 2 输入错或骨架缺料(指名缺哪个变量)。stdlib only,不经 daemon |
| `cone_invert.py` ◈ | **阶梯 ①' 通用锥形反推**(peel 无递推段/段非目标时的下一档,4ccec36c 复盘产物):吃同一份直线程序,`--known 名=值` 锚点起步逐语句反向传播——`d=a OP b` 已知 d+单操作数精确反解另一个(+/-/^/全 1 掩码 & 精确;<</>>/|/%/非全 1 & 有损指名交人工),`d=fn(args)` 缺一个参数用 `--calls` 的 CALL_TABLES 前向实现爆破(默认域 2^16,`--brute fn=N` 可调);模派生值自动带 mod 传播防误杀;**矛盾检出 exit 1 分两类判词**:① 矛盾锥含未知量 = 方程/常量错了(铁律 9 禁止续命);② 矛盾锥全被字面量决定、无未知量 = **输入符号化不完整**(29bf99c0 判例),指引走 `--diff-symbolize reconB --out asym.txt`(同长度不同输入两份 recon 按值集合 diff——A 有 B 没有的字面量即随输入变化,自动符号化为 IN0..INK;不按行号,控制流差异导致行数错位也能用;`--min-lit` 默认 256 防误符号化结构小常量)补全后重跑;断链点(缺 CALL_TABLES/爆破域小)无论变量是否已从他路解出都单列缺料警告。exit 0 收敛 / 1 矛盾 / 2 输入错。stdlib only,不经 daemon |
| `layer_dump.py` ◈ | **分层运行时解密样本的 dump 一条龙**(gdb 模板;c50c8812 同一模式手写 heredoc ×4 的沉淀):`--break 0x加载器返回点 --dump VA:LEN:OUT`(可重复)→ 断点首次命中即 dump 全层并退出 + sha256/尺寸校验;`--pass-signals`(SIGILL/SIGSEGV/SIGBUS 穿透,自修改样本用;**SIGTRAP 禁止 pass**——gdb 断点/单步内部复用它,pass 即程序死,实测)、`--input` 喂 stdin、`--disasm`(objdump 线性反汇编按 VA 对齐成 OUT.dis,读时以地址为锚别信 CFG)、`--strings`。只管取层,不含解密层重载分析器。exit 0 成功 / 2 用法 / 3 gdb、objdump 缺失 / 4 断点未命中零产出。stdlib only,不经 daemon |
### 第 3 层:legacy(冻结备查,被 rpc 取代)
`scripts/` 下 25 个 PyGhidra 脚本 + `driver.py` + `analysis_config.py`:**冻结,仅备查**。`driver.py` 仍是两条后路——(a) daemon 整体挂掉时的直接执行通道(`driver.py export/exec/exec-w`,用 pyghidra-venv);(b) 调试脚本行为差异时的对照组。不要在新工作流里再用它们;行为差异以 rpc 为准。
✎ 注意:legacy 时代"写操作必须 exec-w 才存盘"的纪律**已由 daemon 的自动存盘取代**——rpc 写命令每次写完即 `project.save()`,`stop` 时再全量保存。
### 执行模型(engine 内部,排障时读)
- daemon 进程 = rpc-venv 的 python + 进程内 JVM(pyghidra);transport = 127.0.0.1 TCP + token(endpoint 文件在 `<ws>/rpc-localappdata/ghidra-rpc/`)
- session/registry 在 `<ws>/rpc-state/`(`GHIDRA_RPC_STATE_DIR`);daemon 日志在 `<ws>/rpc-localappdata/ghidra-rpc/*.log`
- 所有 handler 由全局锁串行化——**并发客户端不会并行执行**,长命令会挡住其他命令
- daemon 崩溃后下一条命令自动按 session 重启(auto-restart);`stop` 不干净时删 endpoint 文件再起
- **改了 `engine/` 下任何代码必须重启 daemon 才生效**(daemon 是长驻进程,启动时一次性加载 engine 代码;scripts/ 与文档改动不用):`pkill -f 'ghidra_rpc[.]daemon'` 后下一条 rpc 命令自动拉起新 daemon。否则会出现"改了引擎、测了还是旧行为"的假缺陷——排障前先核对 daemon 启动时间晚于引擎改动时间
## 7. PyGhidra 移植须知
写新脚本、移植旧 Jython 脚本、排障启动/API 形状问题时读 `references/pyghidra-migration.md`(启动顺序、`LoadResults`、回调式 `walk_programs`、持久化限制与 `export` 绕法、实测覆盖表)。
## 8. 能力边界(什么不做、去哪补)
| 能力 | 状态 |
|---|---|
| 动态调试(Ghidra Debugger/TraceRmi) | **不做**。动态分析外包给 Frida/GDB/Qiling/angr,选型见 `references/ctf-patterns.md` §6 |
| 二进制比对 / Version Tracking | **内置**:`version-track`(Auto VT + BSim)/ `function-diff` / `match-function`。⚠ 灵敏度坑:单字节 patch 级别的差异在默认相似度下会被判 `identical`(`changed_functions: 0`)——找小改动要去掉 `--changed-only` 看 similarity 分数,或调高 `--min-similarity`;确认差异用 `function-diff` |
| 汇编级 patch | **内置**:`assemble`(SLEIGH,助记符大小写敏感,小写自动重试一次)/ `write-bytes`(自动清冲突指令+授写权限) |
| 伪码全文搜索 | **内置**:`search-decompiled`(跨函数正则) |
| P-code 分析 | **内置**:`pcode`(raw listing)/ `pcode --high`(SSA) |
| 批量变量改名/改型 | **内置**:`batch-edit-variables`(同一反编译快照上一次事务改多个局部变量) |
| Function ID / FIDB | 分析器由 daemon 的 `load` 全量分析自带;FIDB 签名库的制作是低频 GUI 操作(File → Batch Import 到 FIDB),不在本 skill 范围 |
| 数据类型深度操作 | `create-struct`/`create-enum`/`modify-struct`/`set-data-type`/`apply-data-type-range` 覆盖定义与套用;type archive 管理走 GUI |
| 协作式项目(shared repository) | **不做**。Ghidra Server 运维范畴,与单机 agent 场景无关 |
| 导出 patched 二进制 | `export-binary`(多 FileBytes 的固件镜像未测,可能按目录导出) |
| 分析器选项调优 | daemon `load` 固定全量分析;需要 minimal 档时用 legacy `driver.py import --analysis` 或改 `analysis_config.py` |
| 清单外 API 调用 | `exec-code` 逃生舱 |
| `@out`/导出/输入文件路径无白名单 | **已接受风险**(本地单机威胁模型)。`exec-code` 是无沙箱 exec、`@out` 可写任意路径——不要把本 skill 暴露给不可信调用方 |
(按需加载,别一次全读)
| 文件 | 何时读 |
|---|---|
| `references/headless.md` | 要调参数、排障、理解项目缓存机制时 |
| `references/scripting.md` | 要写自定义 Ghidra 脚本(Jython/PyGhidra 惯用法、事务、仿真模板) |
| `~/.dsh/skills/re-triage/references/triage.md` | 分诊细节:语言识别特征、壳检测、高危 API 组合、平台速查(属 re-triage) |
| `~/.dsh/skills/re-triage/references/anti-analysis.md` | 命中反调试/反混淆/自校验时的识别与绕过对照表(属 re-triage) |
| `references/evidence-ledger.md` | 铁律 7 执行机制:ledger.py 命令、断路器语义、台账格式(任何区域级分析前必读) |
| `references/pyghidra-migration.md` | 写新脚本/移植 Jython 脚本/排障启动与 API 形状时(PyGhidra 坑全集+实测覆盖表) |
| `references/crypto-ident.md` | 求逆前先认算法:常量指纹(AES S-Box/ChaCha20/MD5/SHA 初值)、CryptoAPI/CNG/OpenSSL 对照表、弱点清单(铁律 9 的上游) |
| `references/unicorn-harness.md` | 要跑"整程序级"仿真时(映 PE/ELF 进 Unicorn、填 IAT 桩、喂 stdin):可套用骨架 + Win32 ABI 陷阱清单(IO_STATUS_BLOCK/NtReadFile/HeapReAlloc) |
| `~/.dsh/skills/ghidra-static/references/ctf-patterns.md` | CTF 模式库与 flag 狩猎启发式(XOR/期望值/oracle/自定义 VM/魔数,属 ghidra-static) |