Files

167 lines
7.4 KiB
Docker

# syntax=docker/dockerfile:1
#
# ChipBench — self-contained reverse-engineering environment for 8-bit
# (and small embedded) chip dumps. Bundles: Ghidra 11.3.2 + GhidraMCP 1.4,
# radare2, binwalk v3, binutils for several targets, sdcc, gputils,
# capstone/unicorn/keystone, plus a firmware-hacker toolset (flashrom,
# avrdude, openocd, srecord, simavr, unpacking tools, serial consoles).
# Optional VNC/noVNC desktop so the Ghidra GUI also runs headlessly.
#
# Nothing runs as root; runtime user is mapped to host uid/gid so
# bind-mounted artifacts keep their owner.
#
# This project is AGPLv3 (see LICENSE). It builds tooling from public
# sources; no binaries are hosted by the project.
# ---- Stage 1: build binwalk v3 (Rust) in an isolated builder ----------------
FROM rust:1-slim-bookworm AS binwalk-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config libfontconfig1-dev ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN cargo install --root /out --version 3.1.0 binwalk
# ---- Stage 2: main image ----------------------------------------------------
ARG BUILD_UID=1001
ARG BUILD_GID=1001
FROM ubuntu:24.04
# ARGs above do not cross FROM boundaries; re-declare with defaults for this
# stage (overridable at build time: --build-arg BUILD_UID=$(id -u) etc.)
ARG BUILD_UID=1001
ARG BUILD_GID=1001
ENV DEBIAN_FRONTEND=noninteractive \
TZ=UTC \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
DISPLAY=:0 \
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 \
GHIDRA_HOME=/opt/ghidra \
GHIDRAMCP_HOME=/opt/ghidramcp \
VENV_HOME=/opt/venv \
GHIDRA_SCRIPTS=/opt/ghidra-scripts \
PATH="${JAVA_HOME}/bin:/opt/ghidra:/opt/ghidra/support:/opt/venv/bin:/usr/local/bin:${PATH}"
# -----------------------------------------------------------------------------
# 1. System packages
# - GUI/VNC stack (optional desktop for the Ghidra GUI)
# - core RE CLI: radare2, binutils-multiarch, binutils-avr, sdcc, gputils
# - chip-programming & debug: flashrom, avrdude, openocd, stlink-tools
# - EPROM/SREC/HEX wrangling: srecord
# - simulators: simavr (AVR), gpsim (PIC)
# - firmware-unpacking: binwalk deps (7z, cabextract, lzma, cpio, squashfs,
# unar), u-boot-tools (mkimage), device-tree-compiler (dtc)
# - serial: tio, picocom
# - misc: esptool, z80dasm, vbindiff
# NOTE: openjdk-21-jdk is UNPINNED — in-process javac for Ghidra .java
# user scripts has broken before on point-release drift. The supported
# script path here is Jython (.py); the .java helper scripts are not shipped.
# -----------------------------------------------------------------------------
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates wget git unzip xz-utils \
openjdk-21-jdk \
xvfb x11vnc fluxbox supervisor \
novnc websockify \
xterm dbus-x11 \
libfontconfig1 fonts-dejavu-core \
python3 python3-venv python3-pip \
file xxd hexyl binutils \
binutils-avr \
binutils-multiarch \
gputils \
sdcc \
radare2 \
net-tools iproute2 procps less vim-tiny \
flashrom avrdude openocd stlink-tools \
srecord \
simavr gpsim \
z80dasm \
squashfs-tools p7zip-full cabextract lzma cpio unar \
u-boot-tools device-tree-compiler \
tio picocom \
esptool \
vbindiff \
&& rm -rf /var/lib/apt/lists/*
# binwalk v3 binary (Rust) from the builder stage.
COPY --from=binwalk-builder /out/bin/binwalk /usr/local/bin/binwalk
# -----------------------------------------------------------------------------
# 2. Ghidra 11.3.2 (pinned: latest version the GhidraMCP plugin supports)
# -----------------------------------------------------------------------------
ARG GHIDRA_VERSION=11.3.2
ARG GHIDRA_ZIP=ghidra_11.3.2_PUBLIC_20250415.zip
ARG GHIDRA_URL=https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.3.2_build/${GHIDRA_ZIP}
ARG GHIDRA_SHA256=99d45035bdcc3d6627e7b1232b7b379905a9fad76c772c920602e2b5d8b2dac2
RUN cd /tmp \
&& wget -q -O ghidra.zip "${GHIDRA_URL}" \
&& echo "${GHIDRA_SHA256} ghidra.zip" | sha256sum -c - \
&& unzip -q ghidra.zip -d /opt \
&& rm -f ghidra.zip \
&& mv /opt/ghidra_* "${GHIDRA_HOME}"
# -----------------------------------------------------------------------------
# 3. GhidraMCP 1.4 plugin (optional REST server inside the Ghidra GUI).
# The GitHub release ships a NESTED zip; unpack both layers.
# (Harmless "Module manifest file error" warnings at headless startup are
# known noise from this plugin's manifest formatting.)
# -----------------------------------------------------------------------------
ARG GHIDRAMCP_VERSION=1.4
ARG GHIDRAMCP_ZIP=GhidraMCP-release-1-4.zip
ARG GHIDRAMCP_URL=https://github.com/LaurieWired/GhidraMCP/releases/download/${GHIDRAMCP_VERSION}/${GHIDRAMCP_ZIP}
ARG GHIDRAMCP_SHA256=b81ca5240fdde57ea4e899170dcd9fdee6ed29246280c74263a509bfcfc7e734
RUN cd /tmp \
&& wget -q -O mcp-outer.zip "${GHIDRAMCP_URL}" \
&& echo "${GHIDRAMCP_SHA256} mcp-outer.zip" | sha256sum -c - \
&& mkdir -p outer && unzip -q mcp-outer.zip -d outer \
&& inner_zip="$(find outer -name 'GhidraMCP-*.zip' | head -n1)" \
&& test -n "$inner_zip" \
&& mkdir -p inner && unzip -q "$inner_zip" -d inner \
&& extdir="$(dirname "$(find inner -name extension.properties | head -n1)")" \
&& test -n "$extdir" \
&& mkdir -p "${GHIDRA_HOME}/Ghidra/Extensions" \
&& cp -r "$extdir" "${GHIDRA_HOME}/Ghidra/Extensions/GhidraMCP" \
&& rm -rf /tmp/*
ARG BRIDGE_URL=https://raw.githubusercontent.com/LaurieWired/GhidraMCP/1.4/bridge_mcp_ghidra.py
# Non-fatal: the MCP bridge is optional (GUI-only convenience). GitHub raw
# occasionally rate-limits (429); retries + a warning keep builds reproducible.
RUN mkdir -p "${GHIDRAMCP_HOME}" \
&& ( wget -q --tries=3 --timeout=30 -O "${GHIDRAMCP_HOME}/bridge_mcp_ghidra.py" "${BRIDGE_URL}" \
|| echo "[warn] GhidraMCP bridge download failed; GUI MCP bridge disabled (headless workflow unaffected)" )
# -----------------------------------------------------------------------------
# 4. Python tooling (isolated venv; respects PEP 668)
# -----------------------------------------------------------------------------
RUN python3 -m venv "${VENV_HOME}" \
&& "${VENV_HOME}/bin/pip" install --no-cache-dir --upgrade pip \
&& "${VENV_HOME}/bin/pip" install --no-cache-dir \
"mcp==1.5.0" "requests==2.32.3" "pyserial>=3.5" \
capstone unicorn \
&& ( "${VENV_HOME}/bin/pip" install --no-cache-dir keystone-engine \
|| echo "[warn] keystone-engine unavailable on this platform; skipping (non-fatal)" )
# -----------------------------------------------------------------------------
# 5. Runtime user matching host uid/gid (no root needed at runtime)
# -----------------------------------------------------------------------------
RUN groupadd -g "${BUILD_GID}" chip \
&& useradd -m -u "${BUILD_UID}" -g "${BUILD_GID}" -s /bin/bash chip
COPY scripts/ghidra-scripts/ "${GHIDRA_SCRIPTS}/"
COPY conf/supervisord.conf /opt/conf/supervisord.conf
COPY conf/profile.d/chipbench.sh /etc/profile.d/chipbench.sh
COPY scripts/entrypoint.sh /opt/entrypoint.sh
RUN chmod +x /opt/entrypoint.sh \
&& chown -R chip:chip "${GHIDRA_SCRIPTS}" /opt/entrypoint.sh /opt/conf
USER chip:chip
WORKDIR /data
EXPOSE 5900 6080 8081
ENTRYPOINT ["/opt/entrypoint.sh"]
CMD ["supervisor"]