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>
This commit is contained in:
+182
-16
@@ -1,3 +1,128 @@
|
||||
# Ghidra from the official GitHub release, checked against the SHA-256 in the
|
||||
# release notes: https://github.com/NationalSecurityAgency/ghidra/releases
|
||||
# 12.1.4 because that is the version the GhidraMCP release below is built
|
||||
# for; Ghidra only loads extensions built for its exact version.
|
||||
ARG GHIDRA_VERSION=12.1.4
|
||||
ARG GHIDRA_BUILD_DATE=20260921
|
||||
ARG GHIDRA_SHA256=ddac49f903da9d5bac833e5cc79395098b9c33cfd3279be5f31bd00387d2d4db
|
||||
|
||||
|
||||
# --- Stage: Ghidra distribution ----------------------------------------------
|
||||
# Downloaded once, used by the final image and to build extensions against.
|
||||
FROM ubuntu:24.04 AS ghidra-dist
|
||||
ARG GHIDRA_VERSION
|
||||
ARG GHIDRA_BUILD_DATE
|
||||
ARG GHIDRA_SHA256
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates curl unzip \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& curl -fsSLo /tmp/ghidra.zip \
|
||||
"https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${GHIDRA_VERSION}_build/ghidra_${GHIDRA_VERSION}_PUBLIC_${GHIDRA_BUILD_DATE}.zip" \
|
||||
&& echo "${GHIDRA_SHA256} /tmp/ghidra.zip" | sha256sum -c - \
|
||||
&& unzip -q /tmp/ghidra.zip -d /opt \
|
||||
&& rm /tmp/ghidra.zip
|
||||
|
||||
|
||||
# --- Build stage: ESP32 Flash Loader for Ghidra --------------------------------
|
||||
# https://github.com/dynacylabs/ghidra-esp32-flash-loader: loads ESP32 flash
|
||||
# dumps / app images (partition table, segments at their addresses, chip
|
||||
# ROM, peripherals from Espressif's SVD files). Its tags ship no binaries,
|
||||
# so it's built against this exact Ghidra. Pinned to a master commit (Ghidra
|
||||
# 12 API, newer than the v12.0 tag) and its submodules' commits. Gradle is
|
||||
# downloaded directly, checked against the SHA-256 gradle.org publishes,
|
||||
# because Ghidra's own wrapper doesn't verify its download.
|
||||
FROM ubuntu:24.04 AS esp32-loader-build
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG GHIDRA_VERSION
|
||||
ARG ESP32_LOADER_COMMIT=59d88034d07010fb3a3601053554baf02e7be2e4
|
||||
ARG ESP32_SVD_COMMIT=be20aa12560889d6125d144cdb48cf615ac17628
|
||||
ARG GHIDRA_SVD_LOADER_COMMIT=8fb5601c3444111159cf1e056cbe9d8457985237
|
||||
ARG GRADLE_VERSION=9.7.1
|
||||
ARG GRADLE_SHA256=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates curl git unzip openjdk-21-jdk-headless \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=ghidra-dist /opt/ghidra_${GHIDRA_VERSION}_PUBLIC /opt/ghidra
|
||||
RUN set -e \
|
||||
&& fetch() { git init -q "$2"; git -C "$2" fetch -q --depth 1 "$1" "$3"; git -C "$2" checkout -q FETCH_HEAD; } \
|
||||
&& fetch https://github.com/dynacylabs/ghidra-esp32-flash-loader.git /src/esp32 "${ESP32_LOADER_COMMIT}" \
|
||||
&& rmdir /src/esp32/data/svd /src/esp32/ghidra_scripts \
|
||||
&& fetch https://github.com/espressif/svd.git /src/esp32/data/svd "${ESP32_SVD_COMMIT}" \
|
||||
&& fetch https://github.com/dynacylabs/ghidra-svd-loader.git /src/esp32/ghidra_scripts "${GHIDRA_SVD_LOADER_COMMIT}" \
|
||||
&& curl -fsSLo /tmp/gradle.zip "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
|
||||
&& echo "${GRADLE_SHA256} /tmp/gradle.zip" | sha256sum -c - \
|
||||
&& unzip -q /tmp/gradle.zip -d /opt \
|
||||
&& cd /src/esp32 \
|
||||
&& GHIDRA_INSTALL_DIR=/opt/ghidra "/opt/gradle-${GRADLE_VERSION}/bin/gradle" --no-daemon -q buildExtension \
|
||||
&& unzip -q dist/ghidra_${GHIDRA_VERSION}_PUBLIC_*_esp32.zip -d /out \
|
||||
&& grep -qx "version=${GHIDRA_VERSION}" /out/esp32/extension.properties
|
||||
|
||||
|
||||
# --- Build stage: binwalk and its helpers -------------------------------------
|
||||
# Based on binwalk's own Dockerfile (https://github.com/ReFirmLabs/binwalk,
|
||||
# Dockerfile at the pinned commit), adapted to Ubuntu 24.04 and with every
|
||||
# source pinned. Only the results are copied into the final image, so
|
||||
# compilers and the Rust toolchain stay out of it.
|
||||
FROM ubuntu:24.04 AS binwalk-build
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
# binwalk: last tag (v3.1.0) is from 2024; master is 3.1.1 with later fixes.
|
||||
ARG BINWALK_COMMIT=26713972e3f9f52dc37d4a421c4554b0bd9e82ef
|
||||
ARG RUST_VERSION=1.99.1
|
||||
ARG DUMPIFS_COMMIT=b7bac90e8312eca2796f2003a52791899eb8dcd9
|
||||
ARG LZFSE_COMMIT=e634ca58b4821d9f3d560cdc6df5dec02ffc93fd
|
||||
ARG DMG2IMG_COMMIT=a3e413489ccdd05431401357bf21690536425012
|
||||
ARG VMLINUX_TO_ELF_COMMIT=36da74ded9cd486f294c8852e0dcab9ef6a48c6b
|
||||
ARG ESPTOOL_VERSION=5.6.0
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates curl git build-essential clang pkg-config \
|
||||
zlib1g-dev liblzma-dev liblzo2-dev libucl-dev liblz4-dev libbz2-dev \
|
||||
libssl-dev libsrecord-dev libfontconfig1-dev \
|
||||
python3 python3-venv python3-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Shallow-fetch one exact commit of a git repository.
|
||||
RUN printf '%s\n' '#!/bin/sh' 'set -e' \
|
||||
'git init -q "$2"' \
|
||||
'git -C "$2" fetch -q --depth 1 "$1" "$3"' \
|
||||
'git -C "$2" checkout -q FETCH_HEAD' > /usr/local/bin/fetch-commit \
|
||||
&& chmod +x /usr/local/bin/fetch-commit
|
||||
|
||||
# binwalk itself, with a pinned Rust toolchain and the locked crate versions.
|
||||
RUN fetch-commit https://github.com/ReFirmLabs/binwalk.git /src/binwalk "${BINWALK_COMMIT}" \
|
||||
&& curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain "${RUST_VERSION}" \
|
||||
&& . /root/.cargo/env \
|
||||
&& cargo build --release --locked --manifest-path /src/binwalk/Cargo.toml \
|
||||
&& install -D -m 755 /src/binwalk/target/release/binwalk /out/bin/binwalk
|
||||
|
||||
# Extractors binwalk calls that Ubuntu doesn't package (upstream src.sh).
|
||||
# dumpifs ships a prebuilt binary in its repo; it's removed and rebuilt.
|
||||
RUN fetch-commit https://github.com/askac/dumpifs.git /src/dumpifs "${DUMPIFS_COMMIT}" \
|
||||
&& rm /src/dumpifs/dumpifs \
|
||||
&& make -C /src/dumpifs dumpifs \
|
||||
&& fetch-commit https://github.com/lzfse/lzfse.git /src/lzfse "${LZFSE_COMMIT}" \
|
||||
&& make -C /src/lzfse install \
|
||||
&& fetch-commit https://github.com/Lekensteyn/dmg2img.git /src/dmg2img "${DMG2IMG_COMMIT}" \
|
||||
&& make -C /src/dmg2img dmg2img vfdecrypt HAVE_LZFSE=1 \
|
||||
&& install -m 755 /src/dumpifs/dumpifs /usr/local/bin/lzfse \
|
||||
/src/dmg2img/dmg2img /src/dmg2img/vfdecrypt /out/bin/
|
||||
|
||||
# Python extractors binwalk calls (upstream requirements.txt, versions
|
||||
# pinned), and esptool. Each gets its own venv at its final path; the final
|
||||
# image uses the same base and Python, so the venvs can be copied as-is.
|
||||
RUN python3 -m venv /opt/binwalk-py \
|
||||
&& CC=clang /opt/binwalk-py/bin/pip install --no-cache-dir --disable-pip-version-check \
|
||||
uefi_firmware==1.16 jefferson==0.4.8 ubi-reader==0.8.16 \
|
||||
lz4==4.4.5 zstandard==0.25.0 \
|
||||
"vmlinux-to-elf @ git+https://github.com/marin-m/vmlinux-to-elf@${VMLINUX_TO_ELF_COMMIT}" \
|
||||
&& python3 -m venv /opt/esptool \
|
||||
&& /opt/esptool/bin/pip install --no-cache-dir --disable-pip-version-check \
|
||||
"esptool==${ESPTOOL_VERSION}"
|
||||
|
||||
|
||||
# --- Final image -------------------------------------------------------------
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
@@ -33,24 +158,15 @@ RUN apt-get update \
|
||||
fonts-dejavu-core \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Ghidra from the official GitHub release, checked against the SHA-256 in the
|
||||
# release notes: https://github.com/NationalSecurityAgency/ghidra/releases
|
||||
# 12.1.4 because that is the version the GhidraMCP release below is built
|
||||
# for; Ghidra only loads extensions built for its exact version.
|
||||
# Ghidra 12.1.4 needs JDK 21 (application.java.min=21). The full JDK, not the
|
||||
# headless one, because Ghidra's GUI needs the AWT/X11 libraries.
|
||||
ARG GHIDRA_VERSION=12.1.4
|
||||
ARG GHIDRA_BUILD_DATE=20260921
|
||||
ARG GHIDRA_SHA256=ddac49f903da9d5bac833e5cc79395098b9c33cfd3279be5f31bd00387d2d4db
|
||||
# Ghidra (downloaded and verified in the ghidra-dist stage). Ghidra 12.1.4
|
||||
# needs JDK 21 (application.java.min=21). The full JDK, not the headless one,
|
||||
# because Ghidra's GUI needs the AWT/X11 libraries.
|
||||
ARG GHIDRA_VERSION
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends openjdk-21-jdk unzip \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& curl -fsSLo /tmp/ghidra.zip \
|
||||
"https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${GHIDRA_VERSION}_build/ghidra_${GHIDRA_VERSION}_PUBLIC_${GHIDRA_BUILD_DATE}.zip" \
|
||||
&& echo "${GHIDRA_SHA256} /tmp/ghidra.zip" | sha256sum -c - \
|
||||
&& unzip -q /tmp/ghidra.zip -d /opt \
|
||||
&& rm /tmp/ghidra.zip \
|
||||
&& ln -s "/opt/ghidra_${GHIDRA_VERSION}_PUBLIC" /opt/ghidra \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=ghidra-dist /opt/ghidra_${GHIDRA_VERSION}_PUBLIC /opt/ghidra_${GHIDRA_VERSION}_PUBLIC
|
||||
RUN ln -s "/opt/ghidra_${GHIDRA_VERSION}_PUBLIC" /opt/ghidra \
|
||||
&& ln -s /opt/ghidra/ghidraRun /usr/local/bin/ghidra
|
||||
ENV GHIDRA_INSTALL_DIR=/opt/ghidra
|
||||
|
||||
@@ -72,6 +188,11 @@ RUN set -e; cd /tmp \
|
||||
&& jar uf "$jar_file" defaultTools/CodeBrowser.tool \
|
||||
&& rm -r /tmp/defaultTools /tmp/codebrowser-developer.xml
|
||||
|
||||
# ESP32 Flash Loader (built above), installed like GhidraMCP into Ghidra's
|
||||
# own Extensions directory. It's a loader, not a plugin, so no tool config is
|
||||
# needed: it shows up as a format in File > Import File.
|
||||
COPY --from=esp32-loader-build /out/esp32 /opt/ghidra/Ghidra/Extensions/esp32
|
||||
|
||||
# GhidraMCP (https://github.com/LaurieWired/GhidraMCP), official release zip
|
||||
# checked against the SHA-256 GitHub lists for it. It holds:
|
||||
# - the Ghidra extension, unpacked into Ghidra's own Extensions directory so
|
||||
@@ -109,6 +230,51 @@ RUN set -e \
|
||||
&& /opt/ghidra-mcp/bin/ghidra-mcp --help >/dev/null \
|
||||
&& rm -rf /tmp/ghidramcp /tmp/ghidramcp.zip /tmp/uv.lock /tmp/constraints.txt /tmp/uvlock-to-constraints.py
|
||||
|
||||
# binwalk (built above) with the extraction tools its Dockerfile installs.
|
||||
# sasquatch (SquashFS variants) comes as a .deb from onekey-sec; the release
|
||||
# publishes no checksums, so the SHA-256 was taken when it was pinned.
|
||||
# The Python tools' entry points are linked into /usr/local/bin; the venvs'
|
||||
# python/pip are not, so the system python3 isn't shadowed.
|
||||
ARG SASQUATCH_RELEASE=sasquatch-v4.5.1-5
|
||||
ARG SASQUATCH_SHA256_AMD64=23191cb530bed01e9f00b9a28a74d03db057fb111e13ce5fb383356c1fdf7e6c
|
||||
# binwalk -E -p (entropy graph as PNG) renders through Kaleido. binwalk's
|
||||
# build downloads it into the build stage's /root, which the image doesn't
|
||||
# have, so -p would crash; it's fetched here instead (same release the crate
|
||||
# uses, no upstream checksum, SHA-256 taken when pinned) and found through
|
||||
# KALEIDO_PATH at runtime.
|
||||
ARG KALEIDO_VERSION=0.2.1
|
||||
ARG KALEIDO_SHA256_AMD64=3eaa3efd41a00db05dd71add7f54ad4e9d4259552e3f7470ac4c1a8594cea214
|
||||
COPY --from=binwalk-build /out/bin/ /usr/local/bin/
|
||||
COPY --from=binwalk-build /opt/binwalk-py /opt/binwalk-py
|
||||
COPY --from=binwalk-build /opt/esptool /opt/esptool
|
||||
RUN set -e \
|
||||
&& arch="$(dpkg --print-architecture)" \
|
||||
&& [ "$arch" = amd64 ] || { echo "sasquatch checksum only pinned for amd64" >&2; exit 1; } \
|
||||
&& curl -fsSLo /tmp/sasquatch.deb \
|
||||
"https://github.com/onekey-sec/sasquatch/releases/download/${SASQUATCH_RELEASE}/sasquatch_1.0_${arch}.deb" \
|
||||
&& echo "${SASQUATCH_SHA256_AMD64} /tmp/sasquatch.deb" | sha256sum -c - \
|
||||
&& curl -fsSLo /tmp/kaleido.zip \
|
||||
"https://github.com/plotly/Kaleido/releases/download/v${KALEIDO_VERSION}/kaleido_linux_x64.zip" \
|
||||
&& echo "${KALEIDO_SHA256_AMD64} /tmp/kaleido.zip" | sha256sum -c - \
|
||||
&& unzip -q /tmp/kaleido.zip -d /opt/kaleido \
|
||||
&& rm /tmp/kaleido.zip \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
/tmp/sasquatch.deb \
|
||||
7zip 7zip-standalone zstd srecord tar unzip sleuthkit cabextract \
|
||||
lz4 lzop unrar unyaffs cpio device-tree-compiler \
|
||||
liblzo2-2 libucl1 libfontconfig1 \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/sasquatch.deb \
|
||||
&& for f in /opt/binwalk-py/bin/* /opt/esptool/bin/*; do \
|
||||
case "$(basename "$f")" in python*|pip*|activate*|Activate*) continue ;; esac; \
|
||||
ln -s "$f" /usr/local/bin/; \
|
||||
done \
|
||||
&& binwalk --version && esptool version
|
||||
# Binwalk leaves a symlink to the input file in its extraction directory. The
|
||||
# target is a container path, which is dangling when viewed from the host.
|
||||
ENV BINWALK_RM_EXTRACTION_SYMLINK=1 \
|
||||
KALEIDO_PATH=/opt/kaleido
|
||||
|
||||
# Ubuntu 24.04 ships a default "ubuntu" user on UID 1000; replace it.
|
||||
RUN if id ubuntu >/dev/null 2>&1; then userdel -r ubuntu; fi \
|
||||
&& groupadd -g "${USER_GID}" "${USER_NAME}" \
|
||||
|
||||
Reference in New Issue
Block a user