Skip to main content
Glama
README.md
# GhidraMCP+ — Ghidra to MCP Bridge with Evidence Capture

An upgraded fork of [LaurieWired/ghidraMCP](https://github.com/LaurieWired/ghidraMCP), extended for static malware analysis workflows that require reproducible evidence: annotated screenshots, verifiable findings, and structured reports.

> **Credit:** This project is built on top of LaurieWired/ghidraMCP (Apache 2.0). The original Ghidra plugin and MCP bridge are LaurieWired's work; this fork keeps that foundation and adds the features listed below. Thank you, LaurieWired, for open-sourcing the groundwork.

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)

![GhidraMCP Evidence Capture](images/evidence-highlight.png)

*Automatic red highlight on the exact evidence line — the new `/evidence` endpoint (address-range mode shown).*

## What This Fork Adds

| Feature | Original (LaurieWired) | This fork |
|---|---|---|
| Screenshot of the Ghidra window | no | `/screenshot` |
| Go to an address (like pressing G) | no | `/focus` |
| Red highlight on evidence plus capture in one call | no | `/evidence` (uses official `FieldPanel.getCursorBounds()`) |
| Red caption above the box | no | `label` parameter |
| Range highlight (multiple lines) | no | `to` parameter |
| HTTP server binding | all interfaces | loopback only (`127.0.0.1`) |
| Request body limit | none | 1 MB max |
| Output path validation in the bridge | none | blocks `/etc`, `/proc`, `/sys`, `/dev`, `C:\Windows`, `..` |
| One-command launcher (Linux/macOS) | no | `start-mcp.sh` |
| One-command launcher (Windows) | no | `start-mcp.ps1` |
| Wait for the plugin HTTP to be alive | no | polls up to 30 s, has `--check` mode |
| Build without Maven | requires Maven | `.dsh-build.sh` (plain javac + jar) |
| Low-RAM GUI launcher | no | `ghidra-safe.sh` (heap capped at 1.2 GB) |

## Architecture

```
+-------------+   MCP stdio    +----------------------+   HTTP 127.0.0.1:8080   +------------------+
| dsh/Claude  | -------------> | bridge_mcp_ghidra.py  | ---------------------> | Ghidra GUI       |
| (AI client) |    30 tools    |       (FastMCP)       |   GhidraMCP+ plugin    | + CodeBrowser    |
+-------------+                +----------------------+                        +------------------+
```

- The **plugin** (Java) runs inside the Ghidra GUI. Its HTTP server is only alive while a CodeBrowser tool has a program open; headless mode does not load it.
- The **bridge** (Python) exposes 30 MCP tools: decompile, rename, cross-references, strings, segments, imports, and three imaging tools: `focus_address`, `screenshot_tool`, `capture_evidence`.
- HTTP listens on loopback only; no other machine on the LAN can drive your Ghidra instance.

## Installation

### Requirements

- [Ghidra](https://ghidra-sre.org) (tested with 11.3.1)
- Python 3.10 or newer
- JDK 21 (only when building from source)

### Step 1 — Build the plugin (no Maven needed)

```bash
./.dsh-build.sh
# Output: target/GhidraMCP.jar and target/GhidraMCP-1.0-SNAPSHOT.zip
```

The script copies the required Ghidra jars into `lib/`, compiles with `javac`, and packages the extension zip in the layout Ghidra expects.

### Step 2 — Install the extension into Ghidra

GUI route: `File -> Install Extensions -> (+) -> select target/GhidraMCP-1.0-SNAPSHOT.zip -> restart Ghidra`

Terminal route (Linux):

```bash
SETTINGS=~/.config/ghidra/ghidra_11.3.1_PUBLIC
mkdir -p $SETTINGS/Extensions/GhidraMCP/lib
cp target/GhidraMCP.jar $SETTINGS/Extensions/GhidraMCP/lib/
cp src/main/resources/{extension.properties,Module.manifest} $SETTINGS/Extensions/GhidraMCP/
```

Enable the plugin: `File -> Configure -> Developer -> GhidraMCPPlugin`.

### Step 3 — Run the full pipeline (one command)

```bash
# Linux / macOS
./start-mcp.sh                 # waits for the plugin HTTP, then runs the MCP bridge (stdio)

# Windows (PowerShell 5.1+)
.\start-mcp.ps1                # same behavior
```

Options: `--check` (verify connectivity, do not run the bridge) | `--install-deps` (install `requests` + `mcp`) | `--transport sse` (run SSE on port 8081 instead of stdio).

Both launchers pick Python automatically (project `.venv`, then `py -3` / `python3`, then `python`), poll `http://127.0.0.1:8080/` for up to 30 seconds, and print clear guidance when Ghidra or the CodeBrowser tool is not running yet.

### Low-RAM machines

```bash
./ghidra-safe.sh        # runs the Ghidra GUI with the heap capped at 1.2 GB (override with _GHIDRA_MAXMEM=800M)
```

## Evidence Capture

The headline feature of this fork: capture a CodeBrowser screenshot with a red highlight box drawn around the exact evidence line. It uses Ghidra's official API (`CodeViewerService -> ListingPanel -> FieldPanel.getCursorBounds()`), so the box lands on the real address line every time, without guessing.

```
GET http://127.0.0.1:8080/evidence?address=0x0044c51e&to=0x0044c534&label=EX-01%20Entry&lines=6
```

| Parameter | Purpose |
|---|---|
| `address` | evidence address to focus (required); the listing scrolls as if G was pressed |
| `to` | end address of a range; the red box then covers the whole range |
| `label` | red caption drawn above the box |
| `lines` | context lines (default 6) |

Returns a PNG of the CodeBrowser window with:

- a 3 px red border plus a translucent red fill over the evidence line(s)
- the red caption above the box when `label` is given

From an MCP client (dsh, Claude, and others), call the tool:

```python
capture_evidence(
    address="0x0044c51e",
    output_path="reports/exhibits/EX-01.png",
    label="Entry stub jmp _CorExeMain",
    to="0x0044c534"   # optional range
)
```

![Evidence single address](images/evidence-single.png)

*Single-line highlight example — the high-entropy IL region (entropy 7.6) from a Lokibot analysis.*

## RE Workflow Training — How to Drive This Toolchain

This section documents the reverse-engineering methodology this fork was built for. It assumes an AI agent (or a disciplined human analyst) drives Ghidra through the MCP tools. The workflow has six phases.

### Phase 0 — Safety first

- Never execute malware samples on a real machine. Static analysis or an isolated sandbox only.
- Hash the sample before anything else (`md5sum`, `sha256sum`) and reference it by hash, not by filename, in every report.
- The HTTP endpoints of this plugin can rename and annotate program data. Keep the loopback binding; do not expose it to a network.

### Phase 1 — Triage before Ghidra

Do not open the binary in Ghidra first. Triage the raw bytes first:

```bash
md5sum sample.exe && sha256sum sample.exe    # record hashes before anything else
file sample.exe                              # PE32? .NET? packed?
strings -n 8 sample.exe | head -50           # quick IOC scan
```

For .NET binaries, parse the metadata before Ghidra: find the `BSJB` metadata root, walk the streams (`#~`, `#Strings`, `#US`, `#GUID`, `#Blob`), and enumerate MethodDef tokens and resources. Ghidra shows managed code as meaningless x86 data, so the metadata is the real entry point for .NET analysis. A large `__StaticArrayInitTypeSize` value combined with reflection names (`GetTypes`, `Invoke`, `InitializeArray`) indicates an obfuscator that moved IL bodies into a static array.

Core rule of the whole workflow: **every claim must trace back to a real byte region with an address. If you cannot point to it, it does not go in the report.**

### Phase 2 — Choose the right approach

Match the target to the right technique before touching tools:

| Target type | Approach |
|---|---|
| Native x86/x64 PE | Ghidra MCP tools: `list_imports`, `list_strings`, `decompile_function`, `get_xrefs_to` |
| .NET assembly | Python metadata parsing first; Ghidra MCP only for the native stub and embedded data |
| Packed / obfuscated | Identify the packer layer by layer; each layer gets its own evidence exhibit |
| APK / mobile | Decompile the managed layer; Ghidra only for native `.so` libraries |

### Phase 3 — Evidence collection

Work exhibit-first. For each finding:

1. Locate the exact address or byte range that proves the claim.
2. Capture it: `capture_evidence(address="0x...", label="F-01 XOR loop", to="0x...")`.
3. Verify the capture programmatically: count red pixels in the PNG. A valid evidence image has roughly 500-900 red pixels plus dark text pixels inside the box. Zero red pixels means the fallback banner fired and the highlight failed; retake the shot.
4. Excerpts from tools go into the report verbatim. Never edit decompiler output; put analyst notes in separate annotation bullets below the code block.

### Phase 4 — Analysis discipline

- Follow the call chain: entry point first, then each hop, collecting one exhibit per meaningful step.
- Name things as you go: `rename_function`, `rename_local_variable`, and `set_decompiler_comment` make later exhibits self-explanatory.
- Record what you tried and failed (for example: XOR scan of 256 single-byte keys, RC4 with candidate keys, zlib inflate). Failed decryption attempts are findings too; they tell the next analyst what not to repeat.
- Time-box static analysis. If a payload cannot be decrypted statically within the box, stop and document the boundary instead of guessing.

### Phase 5 — Report structure

A complete RE report has nine sections:

1. Executive summary — what the sample is, what it does, what remains unknown, plus a capability table with evidence links
2. Sample information — hashes, type, sections, entry point
3. Exhibits — each with a repro command, an address, a verbatim excerpt, an annotated screenshot, and an interpretation
4. Technical analysis — identification, mechanism by phase, crypto/algorithms, dynamic results (or why they are absent)
5. Findings table — each finding mapped to an exhibit and a MITRE ATT&CK technique, with a confidence level
6. IOC table — every indicator traced back to an exhibit
7. Callflow — numbered steps from entry point to final behavior, unverified steps clearly marked
8. Open questions — everything that did not make it into findings, with next-step proposals
9. Appendix — environment, full reproduction commands, optional YARA rule

The two hard rules: **no finding without an exhibit, and no claim without an address.**

## HTTP Endpoints

| Endpoint | Method | Purpose |
|---|---|---|
| `/methods` | GET | list functions (paginated) |
| `/classes` | GET | list namespaces/classes |
| `/segments` `/imports` `/exports` `/strings` | GET | list program data |
| `/xrefs` `/calls` | GET | cross-references |
| `/decompile` | POST | decompile a function by name |
| `/renameFunction` `/renameData` and related | POST | renames |
| `/comment` `/setFunctionPrototype` | POST | annotations |
| `/focus` | GET | scroll the listing to an address (G key) |
| `/screenshot` | GET | PNG of the CodeBrowser window |
| `/evidence` | GET | red-highlight capture (new) |
| `/windows` | GET | debug: list Ghidra windows |

See `src/main/java/com/lauriewired/GhidraMCPPlugin.java` for the complete list.

## MCP Tools (30)

Analysis: `list_methods` | `list_classes` | `list_segments` | `list_imports` | `list_exports` | `list_strings` | `list_data_items` | `list_namespaces` | `decompile_function` | `disassemble_function` | `search_functions_by_name` | `get_xrefs_to` | `get_xrefs_from` | `get_function_xrefs` | `get_callers` | `get_function_by_address` | `get_current_address` | `get_current_function`

Editing: `rename_function` | `rename_data` | `rename_local_variable` | `rename_global_variable` | `rename_function_by_address` | `set_function_prototype` | `set_decompiler_comment` | `set_disassembly_comment` | `set_local_variable_type`

Imaging (new): `focus_address` | `screenshot_tool` | `capture_evidence`

## Mounting an MCP Client

**Claude Desktop** (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "ghidra": {
      "command": "python",
      "args": ["/ABSOLUTE_PATH/bridge_mcp_ghidra.py", "--ghidra-server", "http://127.0.0.1:8080/"]
    }
  }
}
```

**Cline / SSE mode:**

```
python bridge_mcp_ghidra.py --transport sse --mcp-host 127.0.0.1 --mcp-port 8081 --ghidra-server http://127.0.0.1:8080/
```

Then add the remote server `http://127.0.0.1:8081/sse` in Cline.

**dsh (DeepSeek Harness)** — profile row:

```yaml
- id: mcp-ghidra
  name: '@deepseek-ai/dsh-mcp-client'
  config:
    serverName: ghidra
    transport: stdio
    command: '/path/to/ghidraMCP/.venv/bin/python'
    args: ['/path/to/ghidraMCP/bridge_mcp_ghidra.py', '--ghidra-server', 'http://127.0.0.1:8080/']
    toolCallTimeoutMs: 120000
    failOnStartupError: false
```

## Security Notes

- **Loopback only.** The HTTP server binds `127.0.0.1`; other machines on the LAN cannot drive Ghidra. Renames and annotations mutate real program data, so do not relax this binding.
- **Body limit.** POST bodies over 1 MB are rejected to avoid memory pressure on the Ghidra JVM.
- **Path validation.** The bridge refuses to write screenshots to `/etc`, `/proc`, `/sys`, `/dev`, `C:\Windows`, any path containing `..`, and any non-`.png` output.

## Acknowledgments

- **[LaurieWired/ghidraMCP](https://github.com/LaurieWired/ghidraMCP)** — the original plugin and bridge this fork builds on. Apache 2.0.
- The Ghidra project for the public API, in particular `FieldPanel.getCursorBounds()`, which made precise evidence highlighting possible.

## License

Apache 2.0 — inherited from the original project.