# Ghidra Core

> Ghidra headless 执行底座（execution engine）——rpc_driver/ghidra-rpc 命令、daemon 起停、doctor 环境自检、脚本清单。当需要实际运行任何 Ghidra 命令（import/decompile/disassemble/rename/patch/version-track）、环境排障、或其他逆向 skill 里的操作不知道具体命令时加载。不含分析方法论。动手前必须先用 skill 工具加载本 skill 全文并遵守其流程；一切观察/结论用 ledger.py 落账。

- **Type:** Skill
- **Install:** `agentstack add skill-cristallin2006-ghidra-skill-for-dsh-ghidra-core`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Cristallin2006](https://agentstack.voostack.com/s/cristallin2006)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Cristallin2006](https://github.com/Cristallin2006)
- **Source:** https://github.com/Cristallin2006/ghidra-skill-for-dsh/tree/main/ghidra-core

## Install

```sh
agentstack add skill-cristallin2006-ghidra-skill-for-dsh-ghidra-core
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 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 于是去 `\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 \...\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 个手写拟合脚本；**升级前先查依赖图**——存在可剥离递推/可逆算子时优先手工剥离（Reverse-chal：掩码二阶递推逐层反解成功，z3 60s 超时反而没用上）；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/模乘类密文升级阶梯（禁止跳档）**：① 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  --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 机械拦截原地打转）——先命中哪条执行哪条。台账 = `/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 等专用工具。
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_.gpr`，junction 路径）、binary key 自动替换（不用记 `/name-hash6` 后缀）、`--project` 注入、`@out` 落盘、环境变量自给（GHIDRA_INSTALL_DIR/JAVA_HOME/LOCALAPPDATA 重定向/USERNAME=dsh）。
子命令：`ensure|status|stop `（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  ` / `function-diff    ` 会自动把 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_ 项目
python "$SK/launch_gui.py" --project dsh_ # 按项目名打开
```

detached 启动，脚本立即返回 PID，GUI 输出进 `logs/gui-launch.log`；关闭用 `taskkill //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_）
"$PY" -m pyghidra --project-path "$WS/projects" --project-name dsh_ \
  "C:/abs/path/binary.exe" "$SK/triage_scan.p

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Cristallin2006](https://github.com/Cristallin2006)
- **Source:** [Cristallin2006/ghidra-skill-for-dsh](https://github.com/Cristallin2006/ghidra-skill-for-dsh)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-cristallin2006-ghidra-skill-for-dsh-ghidra-core
- Seller: https://agentstack.voostack.com/s/cristallin2006
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
