From 5667a17b64c750bd6ec87ac0f8a66a5bba3103d6 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 7 Jul 2026 10:28:11 -0400 Subject: [PATCH] docs: design spec for Ghidra+MCP native-RE workflow (target u00428010) Stand up bethington/ghidra-mcp loop; prove it by reversing SCJUMP's native decision->scene resolver. Raw-dump-first, pe-sieve fallback; findings -> docs/engine-re.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-07-07-ghidra-mcp-native-re-design.md | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-07-ghidra-mcp-native-re-design.md diff --git a/docs/superpowers/specs/2026-07-07-ghidra-mcp-native-re-design.md b/docs/superpowers/specs/2026-07-07-ghidra-mcp-native-re-design.md new file mode 100644 index 0000000..655493b --- /dev/null +++ b/docs/superpowers/specs/2026-07-07-ghidra-mcp-native-re-design.md @@ -0,0 +1,146 @@ +# Ghidra + MCP native-RE workflow (proven on `u00428010`) — design + +**Date:** 2026-07-07 +**Status:** approved (brainstorming) → ready for implementation plan +**Slice:** native-engine RE enablement; first target = SCJUMP's decision→scene resolver. + +--- + +## 1. Problem & goal + +We keep hitting the same wall: logic compiled into `AGE.EXE` that scripts call as native ops — +**decision→scene (`u00428010`), call-script dispatch, op 0x60 (the rand-like SCJUMP gate), the gfx +command-buffer.** Frida (dynamic observation) has taken us far but stalls where the native logic is a +complex state machine that must be *read*, not just watched. The blocker that previously ruled out +static analysis (packed exe) is gone — we have the in-place-unpacked engine dumped +(`build/engine-dump/`, module @0x400000, validated at the AGF landmark 0x474f1f). + +**Goal:** stand up a repeatable "Claude drives Ghidra over MCP" loop and **prove it end-to-end on one +native function — `u00428010`, the SCJUMP decision→scene resolver** — to a concrete result: either +recover the `0x62ccf → SCxxxx.BIN` mapping (finishing the SCJUMP slice), or establish precisely what +makes it runtime-only. Success de-risks the whole approach and leaves a documented, reusable process +for the other native walls. + +## 2. Non-goals (this slice) + +- **Not** reversing call-script dispatch, op 0x60, or the gfx command-buffer — those are follow-on + slices that reuse the proven loop. One target only. +- **Not** building custom RE tooling — we use an off-the-shelf Ghidra MCP server; we don't write our + own bridge. +- **No redistribution** of engine binaries — the dump/PE stay local, for analysis only. +- Not committing to reimplementing `u00428010` in the VM this slice; the deliverable is the + *understanding* (the mapping/table or a clear runtime verdict). Reimplementation is a later step. + +## 3. Environment (confirmed 2026-07-07) + +- **Have:** Ghidra 12.1.2 installed; the game runs locally (JP locale); Frida 17.15.3; + `build/engine-dump/` (raw runtime dump: `manifest.json` + `range_.bin`, module base 0x400000). +- **Need (this slice sets up):** the bethington/ghidra-mcp server (plugin + bridge) registered in + Claude Code; possibly pe-sieve.exe (only if raw-dump analysis is inadequate). +- **Target facts:** consumers do `lookup-array(ptr, 0x5f0ed, 0x62ccf)` then `u00428010(ptr)`. Kelebek + names the op by its handler VA `0x428010` (may drift in our build → resolve via the dispatch table + `[esi+idx*120+0x53d88]` / proximity). The interpreter executes from the **module @0x400000** + (stable base) — not the per-run heap (an earlier worry, since corrected). + +## 4. The MCP server — bethington/ghidra-mcp + +Chosen over LaurieWired/GhidraMCP because it explicitly exposes the primitives this analysis needs: +`decompile_function`, `get_xrefs_to`/`get_xrefs_from`, `get_function_by_address`, `read_memory`, +`list_data_items`, `search_byte_patterns`, struct/enum tools. Actively maintained (v5.14.2, Jun 2026), +stdio-MCP transport designed for Claude Code. Requires Ghidra 12.1.2 (matches ours); prereqs Java 21, +Maven 3.9+, Python 3.10+. The 256-tool surface is a non-issue for Claude Code — tools are deferred/ +searchable, so only the handful used are loaded via ToolSearch. + +**Setup (user, one-time):** build + deploy the Ghidra extension (`python -m tools.setup ...`), start +the HTTP-serving plugin inside Ghidra (127.0.0.1:8089), run the MCP bridge, and register the server in +Claude Code's MCP config. A connectivity check (I call `list_functions` and get a non-empty result) +gates progress to analysis. + +**Fallback:** if the bridge proves painful, I can still work from decompiler output the user pastes, +or a Ghidra headless export — degraded (no automated xrefs) but not blocked. + +## 5. Analyzable image — raw-dump-first, pe-sieve as escalation + +For reading one function's logic, a perfect IAT is often unnecessary (unnamed API calls are fine when +the function is a table lookup). So: + +1. **Stage A — try the existing raw dump** (`build/engine-dump/range_<0x400000>.bin`), imported into + Ghidra as a raw 32-bit x86 binary at base 0x400000, auto-analyzed. Zero new setup. If the decompiler + renders `u00428010`'s region legibly, prep is done. +2. **Stage B — escalate to pe-sieve only if Stage A is inadequate:** with the game running, + `pe-sieve.exe /pid /imp 3` rebuilds the IAT into a clean PE (recorded in + `vm-mapping-plan.md` appendix; pe-sieve is a single portable exe, Bash-drivable while the user has + the game up). Validate the dump by confirming plaintext `SYS4422`/`.BIN`/`DATA1` strings appear. + +The escalation is triggered by observation (illegible/incomplete decompilation), not assumed up front. + +## 6. Ghidra load parameters + +32-bit x86 (`x86:LE:32:default`), image base `0x400000`. For the raw dump: import as "Raw Binary", +set the language + base, run auto-analysis (decompiler, function ID, data references). For a pe-sieve +PE: standard PE import. Confirm the AGF landmark (VA 0x474f1f contains the `BM`/0x4D42 check) reads as +real code — the recorded dump-sanity anchor. + +## 7. Analysis plan for `u00428010` + +Driven by me over MCP: +1. **Locate the real handler.** Try `get_function_by_address(0x428010)`; if it lands mid-function or on + non-handler code (Kelebek build drift), find it via the interpreter's op dispatch table + (`[esi+idx*120+0x53d88]`) or by xrefs from the operand-fetch path (`call 0x41b940`). +2. **Decompile it** and read how it consumes its pointer argument (the `&0x5f0ed[decision]` reference) + — what fields it reads, what it returns, what it calls. +3. **Chase the data.** Follow xrefs/reads to any static table in `.data`: does the decision index map, + directly or via `0x5f0ed`, to a scene id / filename / SYS4INI section? `list_data_items` + + `read_memory` on the referenced table; cross-check candidate scene ids against the real corpus + (SC#### filenames) and `build/asset-sections.json`. +4. **Verdict.** Either (a) recover the `decision → scene` mapping (ideally a static table we can export + → a *static* win finishing SCJUMP), or (b) determine it's computed at runtime (e.g. from live state + the table doesn't encode) and document exactly what's needed to resolve it (a Frida hook point). + +**Success criterion:** a concrete, evidence-backed answer to "what scene does decision N load?", or a +precise statement of why it can't be answered statically + the identified runtime hook. + +## 8. Findings capture + +- **New canonical doc `docs/engine-re.md`** — "native-engine reverse engineering": the home for + decompiled native-op findings. This slice writes the `u00428010` section (handler address, decompiled + behavior, the `0x5f0ed`/table structure, the decision→scene verdict). Future native walls (call-script, + op 0x60, gfx buffer) get sections here. Includes a short "how to run the Ghidra+MCP loop" runbook + (setup + connect) so it's repeatable. +- **`vm-map/opcodes.toml`** — if `u00428010` (and any native op identified along the way) gets real + semantics, record them there (its existing home); rebuild via `opcodes_build.py --build`. +- **`docs/scjump-progression.md`** — update the "native decision→scene boundary" section with the + result (mapping recovered, or the precise runtime dependency). +- **CLAUDE.md canonical-documents map** — add a row: `Native-engine RE (decompiled AGE.EXE) → + docs/engine-re.md`. +- If pe-sieve is used, note the dump-prep recipe in `docs/engine-re.md` (and reference the existing + `vm-mapping-plan.md` appendix rather than duplicating). + +## 9. Risks & fallbacks + +- **MCP setup friction / version mismatch** → fallback to pasted decompiler output or headless export + (degraded, not blocked). A connectivity gate (§4) catches this before analysis. +- **Anti-debug / protector** → attach post-title (already works for our Frida dump); pe-sieve is a + read-only scan (low risk). Not expected to affect Ghidra static analysis of an already-captured image. +- **VA drift (Kelebek build ≠ ours)** → resolve the handler via the dispatch table, not the raw VA. +- **The answer is genuinely runtime** → that is still a *successful* outcome for this slice: it + validates the workflow and pinpoints the exact Frida hook, converting an open wall into a defined task. + +## 10. Definition of done + +1. bethington/ghidra-mcp connected in Claude Code; I can `list_functions` / `decompile_function` on the + loaded engine image. +2. `u00428010` located and decompiled; its use of `0x5f0ed` and any static table understood. +3. A verdict on decision→scene: recovered mapping (with evidence) **or** a precise runtime-hook + statement. +4. `docs/engine-re.md` written (findings + repeatable runbook); `scjump-progression.md` and (if + applicable) `opcodes.toml` + CLAUDE.md updated; status memory updated. + +## 11. Implementation order (for the plan) + +1. Set up bethington/ghidra-mcp (user) + register in Claude Code; connectivity gate. +2. Load the raw engine dump in Ghidra (32-bit, base 0x400000); confirm the AGF landmark; assess decomp + quality. Escalate to pe-sieve `/imp 3` only if inadequate. +3. Locate + decompile `u00428010`; chase `0x5f0ed` + referenced tables; reach the decision→scene verdict. +4. Write `docs/engine-re.md` (findings + runbook); update `scjump-progression.md`, `opcodes.toml` (if + applicable), CLAUDE.md map, status memory.