agentleFS
Cristallin2006Cristallin2006/ghidra-skill-for-dsh/ghidra-core/SKILL.md

ghidra-core

Skill11 starsChanged 49 days agoMITView on GitHub

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

The file

Files
SKILL.md

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 丢失)——手上有直线程序时这是闸不是建议,hooks 层 gate_smt.py 机械执行:z3 指纹命中而台账无 peel/cone 痕迹 → exit 2;反向闸(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」;换路校验(机械拒收):本样本已有 stuck 落账时,新 stuck 的 tried/escalate 与上次雷同(相似度>0.8)或 --escalate 的技术类全部落在已试/已升级清单内 → exit 2 拒收——落账不是补账,必须包含换路;正当的同类重试(换模型/换常量)加 --note "差异点" 放行;否定矛盾门(a0f3be66:「目标随 run 变」落账后 136 秒仍升级离线 angr 求解,35 min 空转):存在活跃 negative conclude 时 stuck 必须 --ack-negatives 逐条说明升级路径为何不被这些否定覆盖,否则 exit 2——被否定杀死的路线类不许再作为 escalate;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/文件名)一次都响不了(判例D·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。

  1. 读数纪律(第一嫌疑人是读数,不是程序):关键常量(密文/密钥/换表/魔数)的唯一权威读数 = 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 片段)这一可能——同键冲突恰恰证明单字段不是明文。
  2. 缓冲区归属(字节是谁的):从 MOV [EBP+disp], imm 序列重建栈上字符串时,必须按 disp 区间归属变量,禁止按指令出现顺序拼接——相邻变量的写入区间相接(end_A + 1 == start_B)时极易把别人的字节拼进自己的常量(判例E·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 的不可打印判定就是这条的机械触发器。
  3. 验证独立性(同源验证 = 没验证):用自己 patch 的进程、自己写的 harness、自己算的偏移来验证自己对程序的理解,三者一致不构成任何证据(判例E·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: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 块命中 = 本检查升级为硬门)。
  4. 手写解析器必须双源验证(工具缺失 ≠ 自造轮子的许可证):自行实现的格式/字节码解析器(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 必须成对(证明「正确输入被接受」之外,必须给出「近似错值被拒绝」的负对照,否则无法排除「凡输入皆通过」);解空间可枚举时穷举优先于公式(穷举同时产出答案与唯一性证明)。
  5. 异常即约束:观测到的不一致(重复键冲突、伪码长度 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 单独可见。
  6. 轴必须交叉(限枚举类解码/搜索任务):仅适用于候选空间可枚举的解码/搜索任务(隐信道解码、爆破类)——开跑前先出轴矩阵+候选预算(候选数 = Σ 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 抵消项,必须纳入组合,禁止剔除(剔除后整条搜索不在解空间)。
  7. 判定性实验优先于继续读数:任一关键量(参数角色 / 通道归属 / 某因子是否参与计算)在 30 分钟或 3 次工具调用内未被扰动实验确认(钉住可疑因子为常量看输出是否变、翻转一个输入看哪路输出动),必须停止读码转做打桩实验,并按 --source runtime-oracle 落账。参数角色不得仅由调用约定/寄存器推断——"第 N 参数按惯例是输出缓冲区"是假设不是读数(判例D:FUN_13d74 第一参数实为文件缓冲区,被读成输出,错模型持有数小时;推翻它只需一次几分钟的打桩实验)。瓶颈不是算力是"到证伪的距离":最高杠杆的两个动作是 oracle 打桩隔离单因子(oracle_family.py)与模型差分校验(model_diff.py),关键量存疑时先用它们,不要继续读码。
  8. 禁止以提问结束回合(c50c8812 判例:前序阶段已解出验证,末段卡点自述被 gate_stuck 拦 2 次后始终不落账,改用 ask_user_question 绕道——还把违反用户「依次进行」明示指令的选项列进选项表;提问被空选驳回后回合空转结束,web 端死寂收不了场):ask_user_question 仅限三类——缺凭据/缺授权/用户必须裁决的偏好分叉,且选项不得与用户显式指令冲突(用户说"依次进行"就不许列"先做下一题");返回空选/驳回 = 按推荐选项立即自主继续,禁止空转收尾;有卡点自述未 ledger.py stuck 落账时禁止提问——gate_stuck 对 ask_user_question 同样机械拦截。真卡死时的正确出口是 stuck 落账 + --escalate 继续执行,不是把选择题丢给用户。

2. 快速上手(3 行)

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)

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] [--note 同类重试的差异点] [--ack-negatives "升级路径为何不被活跃否定结论覆盖"]
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 落盘拿结果:

# 后台跑 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 字节):

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)

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)

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/排查"怎么又起不来"时先跑它。

venv 健康:doctor 查的是工具在不在,scripts/venv_health.py 查的是装了能不能 import(装半截的包 pip show 得到、import 才崩)。判例 a0f3be66:re-tools-venv 的 angr 链做题中途才暴露问题,现场 pip 手术烧掉十余分钟——要用 angr/z3 的局开工前先跑 venv_health.py --quick,exit≠0 先修再打。注意 angr ≥10 的 claripy 是内置的(from angr import claripy),PyPI 独立 claripy 止步 9.x,别去装不存在的 claripy 10。

外来旧项目:从别处拷来的项目若 project.prp 的 OWNER 不是 dsh,headless open_project 首次打开会自动改写属主;GUI 则要求属主一致,手动把 OWNER VALUE="..." 改成 dsh 即可。

4. 裸命令模板(legacy 排障专用;日常用 rpc_driver.py)

daemon 整体不可用时的后路是 driver.py(pyghidra-venv,每次调用一个冷启动 JVM):

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 / stopdaemon 与二进制生命周期(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.pydetached 拉起 Ghidra GUI,可直接打开项目
doctor.py环境自检(8 项:安装/JDK/双 venv/工作区/项目/rpc 包/daemon 起停),全绿 exit 0
venv_health.pyvenv 健康(装了≠能 import):各工具 venv 关键包真实 import 探针 + angr≥10 内置 claripy 自洽检查,附可照抄修复命令;要用 angr/z3 的局开工先跑 --quick,exit≠0 先修(判例 a0f3be66)
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;判例H 实测基线由 28/468=6.0% 修正为 13/468=2.8%,剔除 15 个尾调用函数)
call_histogram.pyCALL/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.pyCython/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.pyblob 变换搜索:单字节 XOR 全扫 + 可选滚动密钥/ADD/SUB,按可打印率+magic(MZ/ELF/PK/UPX!)+flag 正则综合评分排 Top N;--dump <key> 落地解密产物(判例F·缺口4,不要再现场手写第 5 版)
emulate_program.py整程序仿真(emulate_blob 的上层):PE 装载 + IAT 桩(88 个内建 Win32 stub,ABI 陷阱已正确实现)+ 喂 stdin + 成功/失败断点;--trace-api 排障、--dump-state 取状态。console 型校验程序专用(边界见 docstring);判例H 实测 0.047s 跑到成功分支(unicorn-harness.md 的可执行形态)
jt_resolve.py跳转表一键解析:LEA+JMP 线索 ∪ decompile 警告 ∪ 伪码残留三路并集找所有表基址,rel32/abs32 双模式解 target,basic-blocks CFG 交叉验证;只解一张 = 解错一半(判例H 实测双表全解)
frame_map.py栈帧槽位归属表:SUB RSP 定帧 → 扫 [RSP+off] 首写/宽度/读写次数;同槽多宽度标可疑(伪码 local_XXXX 重叠命名的真身)
const_audit.py伪码常量对照审计:&DAT_0000xxxx 小整数嫌疑(判例H 实测抓到 &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 'A48' --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)<<16sw(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。取层后要 xref/反编译走重载管线 rpc_driver.py import-raw <原样本> OUT --base 0xVA [--entry 0x…](dump 按原 VA 导入为新程序,共享原样本 language)。exit 0 成功 / 2 用法 / 3 gdb、objdump 缺失 / 4 断点未命中零产出。stdlib only,不经 daemon
noreturn_fix.py ◈铁律 10⑦ 的修复侧(4307cc95:UD2 被误判 non-returning → 调用图截断 → 误判 flag 路径不可达丢 86 min;铁律只教「判定不可信」,本脚本补「怎么修」):无参体检 = 列 NoReturn 函数 + 函数体内 UD2/INT3 流切断点(terminal 含 UD2/INT3 才可疑,exit/abort 合法 no-return 不用修);--fix <func> 清 NoReturn 标志(调用者 CALL 之后代码恢复);--flow <addr> 在 UD2/INT3 处补 fall-through override 到下一条(--flow-clear 撤销);修后重反编译调用者验证,下游仍 undefined 用 rpc disassemble --force 补。经 exec-code 通道,exit 0 成功 / 1 用法 / 2 目标未找到 / 3 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.mdCTF 模式库与 flag 狩猎启发式(XOR/期望值/oracle/自定义 VM/魔数,属 ghidra-static)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.