---
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`
- 本 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 升级**；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；没有直线程序才跳过本档）→ ② 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 种方式重试同一路径。字节只能通过工具解读（反汇编、反编译、脚本输出）；肉眼/裸 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**）。**只验变换不验方程 = 没验**。
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——自相矛盾正是诱饵的指纹）；存在 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 等专用工具。**工具处方闸（判例：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`），关键量存疑时先用它们，不要继续读码。

## 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 要求同项目）。

### 长任务（后台执行）

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；**非地址型题目用 `--locus region:/channel:/proto:` 前缀**）/卡点/render；**铁律 4 假设对象** `hypothesis --text --if-true --if-false --test`（关闭 confirmed/killed 必须 `--evidence`，缺证据 exit 2）；**铁律 13 轴矩阵** `plan --axes --budget` 落账；**铁律 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（种子实测值）后运行即逐层反解；无干净结构才放行 SMT。exit 0 判定完成 / 2 输入错或骨架缺料（指名缺哪个变量）。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） |
