Files
claude-re-setup/CLAUDE.md
T
LinusandClaude Opus 5.5 dbce497f95 Add binwalk, esptool and the ESP32 flash loader for Ghidra
binwalk: new binwalk-build stage adapted from binwalk's own Dockerfile
to Ubuntu 24.04, with every source pinned: binwalk master (3.1.1),
Rust 1.99.1, dumpifs/lzfse/dmg2img commits, and the Python extractors
(jefferson, ubi-reader, uefi_firmware, vmlinux-to-elf, lz4, zstandard)
in /opt/binwalk-py. The final image adds upstream's apt extractors,
sasquatch (SHA-256 pinned), and Kaleido via KALEIDO_PATH so that
binwalk -E -p works (binwalk's build otherwise leaves it in /root of the
build stage and -p panics). BINWALK_RM_EXTRACTION_SYMLINK=1 avoids
dangling links to container paths.

esptool 5.6.0 in /opt/esptool. Entry points of both venvs are linked
into /usr/local/bin without shadowing the system python3.

ESP32 flash loader: dynacylabs/ghidra-esp32-flash-loader, built in an
esp32-loader-build stage against Ghidra 12.1.4 (no binaries are
published, and the older forks don't compile against Ghidra 12),
pinned to a master commit with its svd and ghidra_scripts submodules,
Gradle checked against gradle.org's SHA-256. Installed into
Ghidra/Extensions/esp32. Ghidra itself is now downloaded once in a
ghidra-dist stage and copied into the final image and the build stage.

Verified: binwalk extracted squashfs/gzip/cpio/7z from a test image and
rendered an entropy PNG; the loader imported a test ESP32 app image and
a flash dump with bootloader at 0x0 (partition table, bootloader and
factory app, SVD peripherals, ROM symbols). Original-ESP32 dumps with
the bootloader at 0x1000 are not supported; documented in CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:02:22 +01:00

11 KiB

CLAUDE.md

Guidance for Claude Code when working in this repository.

Purpose

This repo provides a reproducible, Docker-managed reverse-engineering workstation on Linux, built around Claude Desktop with two MCP integrations:

  1. Reverse Engineer Anything MCP: general-purpose RE tooling exposed to Claude over MCP.
  2. Ghidra + GhidraMCP: Ghidra with the GhidraMCP plugin, so Claude can query and annotate an open program (functions, decompilation, xrefs, renames, comments).

The goal is that docker compose up gives you a working setup where Claude Desktop can drive both toolsets against binaries in a shared workspace, without installing Ghidra, a JDK or RE tooling on the host.

Status

Early stage. What exists: a single image (Dockerfile, launch-apps.sh) with Claude Desktop, Ghidra (plus GhidraMCP and an ESP32 loader), binwalk and esptool, started by run.sh; Claude Desktop's MCP config is claude-desktop/claude_desktop_config.json; Ghidra config files are in ghidra/. No compose setup yet. The layout below is the intended structure. Update this file when the real structure differs.

Intended layout

.
├── CLAUDE.md
├── README.md                 # user-facing setup and usage
├── docker-compose.yml        # orchestrates all services
├── .env.example              # versions, ports, UID/GID, display settings
├── claude-desktop/
│   ├── Dockerfile            # Claude Desktop (Linux build) + GUI deps
│   └── claude_desktop_config.json  # MCP server definitions (template)
├── ghidra/
│   ├── Dockerfile            # JDK + Ghidra + GhidraMCP plugin
│   └── scripts/              # plugin install / headless helpers
├── re-mcp/
│   └── Dockerfile            # Reverse Engineer Anything MCP server
├── scripts/                  # host helpers (xhost setup, start/stop, health checks)
└── workspace/                # shared, git-ignored: binaries + Ghidra projects

Architecture

  • Claude Desktop and Ghidra share one container (the root Dockerfile, started by run.sh), at the user's request. Ghidra is the official GitHub release (version, build date and SHA-256 are global build args, downloaded once in the ghidra-dist stage and copied into the final image and into build stages that compile extensions against it), is pinned to 12.1.4 because that's what the GhidraMCP release is built for (Ghidra only loads extensions built for its exact version), runs on Ubuntu's openjdk-21-jdk, lives in /opt/ghidra and starts together with Claude Desktop. The Developer plugin package is enabled in both the project window and the CodeBrowser. A bare <PACKAGE NAME="Developer" /> only adds the package's RELEASED plugins, so the STABLE ones are listed with <INCLUDE CLASS=...>. CodeBrowser: the build inserts ghidra/codebrowser-developer.xml into defaultTools/CodeBrowser.tool inside Public_Release.jar. Project window: it has no default to patch, so ghidra/FrontEndTool.xml is copied into the user settings, along with ghidra/preferences (USER_AGREEMENT=ACCEPT, because a pre-created settings dir otherwise makes Ghidra ask for its user agreement). To check a config really loaded, close Ghidra cleanly and see whether the plugins survive in the file it rewrites; Ghidra drops what it didn't load.

  • Shared workspace volume. Every container mounts the workspace at /workspace, so a file path means the same thing to Claude, Ghidra and the RE MCP. run.sh asks for the host folder on each start in the desktop's folder picker (zenity, or kdialog on KDE; terminal prompt if neither exists), opening at the last folder used, else ./workspace. Skip it with --workspace DIR or WORKSPACE=DIR. It refuses / and $HOME. The image symlinks ~/workspace to /workspace for Claude Desktop's file picker.

  • GUI forwarding. Claude Desktop and Ghidra are GUI apps. Default to X11 forwarding (/tmp/.X11-unix + DISPLAY). Support Wayland through XWayland. Keep a noVNC/VNC fallback in mind for headless hosts. Never solve display access with a blanket xhost +. Scope it (xhost +local:docker or +SI:localuser:...).

  • GhidraMCP wiring. LaurieWired's GhidraMCP (https://github.com/LaurieWired/GhidraMCP), release 2.0, installed from the official release zip (SHA-256 checked). It has two parts:

    • a Ghidra extension, unpacked into /opt/ghidra/Ghidra/Extensions/GhidraMCP. Its GhidraMCPPlugin is a RELEASED plugin in the Developer package, so the CodeBrowser config loads it. While a CodeBrowser is open it serves an HTTP backend on 127.0.0.1:8080 (port: Edit → Tool Options → GhidraMCP HTTP Server).
    • the ghidra-mcp Python MCP server (2.0 replaced bridge_mcp_ghidra.py), installed from the release wheel into the venv /opt/ghidra-mcp. Its dependencies are pinned to GhidraMCP's uv.lock at the release commit via ghidra/uvlock-to-constraints.py. CLI: ghidra-mcp --ghidra-server http://127.0.0.1:8080/ (stdio by default; also --transport streamable-http, --headless).

    A program must be open in the CodeBrowser for the tools to return data. Claude Desktop only starts MCP servers after sign-in.

  • ESP32 Flash Loader (https://github.com/dynacylabs/ghidra-esp32-flash-loader): a Ghidra loader ("ESP32 Flash Image" in File → Import File) for ESP32 app images and flash dumps. It reads the partition table at 0x8000, maps segments to their addresses, adds peripherals from Espressif's SVD files and loads the chip's ROM ELF for symbols. A flash dump is only recognized when it starts with the bootloader (ESP32-S/C layouts). Original-ESP32 dumps (bootloader at 0x1000) are rejected, and trimming the first 0x1000 bytes doesn't help: the loader then finds no partition table at 0x8000, loads only the bootloader and throws a NullPointerException while still reporting success. Workaround: cut the app partition out of the dump (offsets from the partition table) and import it as an app image. Its tags ship no binaries and the older forks (tslater2006, Ebiroll, saibotk v1.1.0) don't compile against Ghidra 12, so the esp32-loader-build stage builds dynacylabs' master at a pinned commit, with its data/svd and ghidra_scripts submodules fetched over HTTPS at pinned commits and Gradle checked against gradle.org's SHA-256. It is unpacked into /opt/ghidra/Ghidra/Extensions/esp32; loaders need no tool config.

  • binwalk and esptool are CLI tools in the same image (no MCP server yet). The Dockerfile has a binwalk-build stage adapted from binwalk's own Dockerfile (https://github.com/ReFirmLabs/binwalk) to Ubuntu 24.04, with every source pinned: binwalk at a master commit (3.1.1; the last tag, v3.1.0, is from 2024), Rust toolchain, dumpifs/lzfse/dmg2img commits, and the Python extractors (jefferson, ubi-reader, uefi_firmware, vmlinux-to-elf, ...) in /opt/binwalk-py. esptool is in /opt/esptool. The venvs' entry points are symlinked into /usr/local/bin (not their python/pip). The final stage adds upstream's runtime extractors from apt, sasquatch from onekey-sec's .deb, and Kaleido for binwalk -E -p (PNG entropy graphs), found via KALEIDO_PATH. Upstream publishes no checksums for sasquatch or Kaleido; their SHA-256s were recorded when pinned, amd64 only. BINWALK_RM_EXTRACTION_SYMLINK=1 avoids dangling symlinks to container paths in extraction output. esptool needs the serial device passed into the container (--device) to talk to hardware; that isn't set up.

  • MCP transport. Claude Desktop starts local MCP servers as stdio subprocesses. GhidraMCP runs in the same container, so ghidra-mcp is a plain stdio entry in claude_desktop_config.json that reaches Ghidra on 127.0.0.1. For future servers in other containers, pick one approach and apply it consistently:

    • Preferred: run them with an HTTP/SSE transport in their own containers and bridge them into Claude Desktop's stdio config with a small proxy (e.g. mcp-remote or mcp-proxy) installed in the Claude Desktop image.
    • Alternative: install the client side inside the Claude Desktop image and point it at the other container over the network.
    • Avoid mounting the Docker socket into the Claude Desktop container just to docker exec MCP servers.

Conventions

  • Pin versions. Ghidra version, GhidraMCP release, JDK version, Claude Desktop build and RE MCP commit go in .env / build args, not latest. The GhidraMCP release must match the Ghidra version it was built for.
  • Run as non-root inside containers. Map the host UID/GID (via .env) so files in workspace/ stay owned by the user.
  • Persist state in named volumes or workspace/: Ghidra user settings and projects. Rebuilding an image must not lose analysis.
  • Claude Desktop sign-in is not persisted. No keyring is installed and ~/.config/Claude is not mounted, so the user logs in on every start. Don't add a keyring or config volume back without being asked.
  • No secrets in the repo. Credentials, session data and workspace/ contents are git-ignored. Ship .env.example, never .env.
  • Untrusted binaries. Samples under analysis may be malicious. Never execute them on the host or in the Claude Desktop container. Containers that might run samples (dynamic analysis) should have no host mounts beyond workspace/ and should allow network isolation (network_mode: none or an internal-only network).
  • Linux only. Target recent Docker Engine + Compose v2 (docker compose, not docker-compose). Make the setup work under both Docker and rootless Podman where practical. On SELinux hosts such as Fedora, bind mounts need :z/:Z.

Common commands

These are expected once the compose setup exists. Keep this list in sync with reality.

cp .env.example .env            # then adjust UID/GID, versions, DISPLAY
xhost +local:docker             # allow containers to use the X server
docker compose build
docker compose up -d
docker compose logs -f <service>
docker compose down

Things to verify, not assume

  • Claude Desktop on Linux. Check whether an official Linux build is available at the time of work. If not, use a maintained community repackaging and document which one and why. Confirm the config path, usually ~/.config/Claude/claude_desktop_config.json.
  • Reverse Engineer Anything MCP. Confirm the upstream repository, install method, runtime (Python/Node), required external tools and supported transports before writing its Dockerfile. Record the source URL in the README.
  • GhidraMCP. Before bumping GhidraMCP or Ghidra, confirm the release's supported Ghidra version (version= in the extension's extension.properties; the build checks it), the ghidra-mcp CLI flags and its default ports from the upstream README. The Developer plugin lists in ghidra/ were checked against 12.1.4 and 12.2; re-check them for other versions.

When making changes

  • After changing a Dockerfile or compose file, run docker compose config to validate it, then build the affected service.
  • When adding an MCP server, update claude-desktop/claude_desktop_config.json, docker-compose.yml, README.md and the Architecture section above together.