diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md b/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md new file mode 100644 index 0000000..26e534d --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md @@ -0,0 +1,154 @@ +# 13 — Ghidra control bridge + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +The bridge lets a GhidraScript drive a live green-eye session: execute the +target until a point **chosen in the decompiler**, set/clear breakpoints, read +memory and registers, make remote calls, and export hit lists back into the +Ghidra project. Nothing about the firmware has to be rebuilt or re-hosted to +answer "does execution actually reach this function?". + +``` +Ghidra (Java script) ── JSON lines over localhost ──► greeneye gdbridge ──► Session ──► QEMU + ▲ │ + └──────── greeneye_hits.tsv ── MarkGreenEyeHits.java ◄──── recorded session ┘ +``` + +## 1. Quickstart + +```bash +# Terminal 1: a live target + bridge (records by default) +./greeneye.sh ghidra --kernel firmware/uproc_demo/build/uproc_demo.elf --bridge-port 7000 +# prints: ghidra bridge on 127.0.0.1:7000 + +# Terminal 2: Ghidra headless, run until the decompiler's address +analyzeHeadless <proj> <name> -process <program> -noanalysis \ + -scriptPath open-iov/debug/risc-v-gsp-rm-vm/ghidra \ + -postScript GreenEyeControl.java 7000 0x800000ce +``` + +In the GUI, load `GreenEyeControl.java` from the Script Manager and supply the +port in the script arguments; with no address argument it uses the current +cursor (`currentAddress`). + +## 2. `GreenEyeControl.java` functions + +All functions exist on the script class and can be called from the script's +own `run()` or from other scripts via the bridge `send()` method. + +| Function | Effect | +|---|---| +| `ping()` | `pong` if the bridge is alive | +| `info()` | session name, pc, symbol count, recording path | +| `resolve(name)` | symbol or `name+0xNN` → runtime address | +| `setBreakpointAt(hexAddr)` / `setBreakpointHere()` | install a hook (prints on hit) | +| `clearBreakpointAt(longAddr)` | remove breakpoints at an address | +| `runUntil(hexAddr)` / `runUntilHere()` | resume and stop at the address; returns `{addr, symbol, kind}` | +| `readMemoryHere(size)` | hex bytes at the cursor | +| `readRegister(name)` / `writeRegister(name, value)` | register access | +| `callFunction(hexAddr, args...)` | Frida-like remote call; returns `a0` | +| `snapshotHere(size)` | record a memory snapshot in the session | +| `listHits()` | breakpoint hit counts from the session | +| `exportHits(path)` | write `greeneye_hits.tsv` for `MarkGreenEyeHits.java` | +| `send(cmd, argsJson)` / `resultOf(response)` | raw bridge access for new workflows | + +Example additions inside `run()`: + +```java +setBreakpointAt("0x800000ce"); +println(runUntil("0x800000ce")); // {addr:..., symbol:"rpc_sum", kind:"breakpoint"} +println(readMemoryHere(16)); +println(callFunction("0x80000048", 0x1111L)); // rpc_ping -> 0xaffe +println(exportHits("/tmp/greeneye_hits.tsv")); +``` + +## 3. Bridge protocol (specification) + +One JSON object per line; one response per request, same `id`. + +Request: `{"id": <int>, "cmd": "<name>", "args": {...}}` +Response: `{"id": <int>, "ok": true, "result": ...}` or +`{"id": <int>, "ok": false, "error": "..."}`. + +| `cmd` | args | result | +|---|---|---| +| `ping` | — | `"pong"` | +| `info` | — | `{name, pc, symbols, recording}` | +| `resolve` | `{name}` | address (int) | +| `bp_set` | `{target, kind?}` | address | +| `bp_clear` | `{addr}` | `true` | +| `bp_list` | — | `[{addr, label, hits, kind}]` | +| `watch_set` | `{target, size?, kind?}` | address | +| `run_until` | `{target, timeout?}` | `{addr, symbol, kind}` | +| `run` | `{duration?, max_stops?}` | stops | +| `step` | `{count?}` | `[pc, ...]` | +| `read_mem` | `{addr, size?}` | hex string | +| `write_mem` | `{addr, hex}` | `true` | +| `read_reg` | `{name}` | int | +| `write_reg` | `{name, value}` | `true` | +| `call` | `{target, args?, timeout?}` | return value | +| `snapshot` | `{addr, size, label?}` | label | +| `note` | `{text}` | `true` | +| `hits` | — | `[{addr, hits, symbol}]` | +| `export_hits` | `{path}` | absolute path | +| `close` | — | `true` (closes the connection and stops the bridge) | + +`target`/`addr` accept `"0x..."`, a symbol name, or `name+0xNN`. + +## 4. Workflows + +### 4.1 Execute until here +1. Start `greeneye ghidra` on the firmware image and load symbols + (`symbols export` from the Ghidra index) so hits are named. +2. Put the cursor on a function in Ghidra and run `GreenEyeControl.java`. +3. `runUntilHere()` sets a temporary breakpoint, services intervening stops + (including your hooks/watchpoints), and reports the symbol resolved at the + stop. If the function is never reached, the bridge returns a timeout error. + +### 4.2 Annotate the project with runtime evidence +1. Run a session (any CLI/scripted path) and `exportHits("/tmp/hits.tsv")`. +2. `MarkGreenEyeHits.java /tmp/hits.tsv` adds plate comments and bookmarks in + the Ghidra project. +3. Optionally export a symbol DB from Ghidra (`ExportForGreenEye.java`) and + feed it back with `symbols export --index`. + +### 4.3 Remote-call driven experiments +Use `callFunction()` to invoke firmware or injected-service functions from +the decompiler (unit-test style), then inspect memory with +`readMemoryHere()` and the recorded session. + +## 5. Address conventions + +Ghidra shows **program addresses** (e.g. libcuda file vaddr + image base). +green-eye resolves **runtime addresses**. For firmware linked at its load +address these coincide; otherwise export symbols with the right bias +(docs/05-GHIDRA-BRIDGE.md) and prefer `resolve(name)` over hardcoded numbers. + +## 6. Limitations (v1) + +* Request/response only: the script pulls state; the bridge does not push + asynchronous hit events. Poll `hits`/`bp_list` between commands. +* One command at a time per connection; multiple clients are accepted but are + not synchronised with each other. +* `watch_set` exists on the bridge but has no dedicated Java wrapper yet — + call `send("watch_set", ...)` directly. +* The bridge is unauthenticated **by design**: localhost only, never expose + it on a network interface. + +## 7. Troubleshooting + +| Symptom | Cause / fix | +|---|---| +| `Connection refused` | bridge not started, wrong port, or it exited (`close`) | +| `green-eye bridge closed the connection` | the server was stopped or the target session closed | +| timeout on `run_until` | address not executed; check symbol bias and that the target is not wedged | +| empty `hits` | the session was started without recording; use the CLI (records by default) | +| Java compile error on script load | Ghidra API drift; the script targets Ghidra 11/12 `GhidraScript` | + +## 8. Roadmap + +* Push notifications for breakpoint hits (server → Ghidra). +* Decompiler annotations from live watchpoints (value changes per field). +* A bridge client that lets multiple scripts share a session with a command + queue. +* Direct MCP exposure of the same command set (roadmap R3). |
