Add unblob so REA can extract firmware

unblob 26.6.4 (the version REA is verified with) is installed from its
PyPI wheel into /opt/unblob, with dependencies pinned to its uv.lock at
the release commit. A wrapper puts that venv first on PATH, and
REA_UNBLOB_COMMAND points REA's extract_firmware at it.

Its extractors follow upstream's install-deps.sh: Ubuntu packages,
sasquatch v4.5.1-6 (also used by binwalk) and onekey-sec's e2fsprogs
fork, whose debugfs rdump options the ext2/3/4 handler needs. The build
checks that unblob finds every extractor.

The uv.lock converter moves to scripts/ and skips the locked project
itself generically, since ghidra-mcp and unblob both use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 18:20:01 +01:00
co-authored by Claude Opus 5.5
parent e61c004431
commit ab9bb3eae9
4 changed files with 80 additions and 26 deletions
+62 -11
View File
@@ -121,6 +121,28 @@ RUN python3 -m venv /opt/binwalk-py \
&& /opt/esptool/bin/pip install --no-cache-dir --disable-pip-version-check \
"esptool==${ESPTOOL_VERSION}"
# unblob (https://github.com/onekey-sec/unblob), the firmware extractor REA
# uses for extract_firmware; REA is verified with 26.6.4. Installed from the
# PyPI wheel (SHA-256 from PyPI) into its own venv, with every dependency
# pinned to unblob's uv.lock at the release tag's commit, like ghidra-mcp.
ARG UNBLOB_VERSION=26.6.4
ARG UNBLOB_COMMIT=1fcc7a0a584a70a96c31f5a276c20944d199a089
ARG UNBLOB_WHEEL_SHA256=0fc16e9c7dc92fcf86b8b61475700c9dd16e1dddacf34467574ea965a5d3584e
ARG UNBLOB_UVLOCK_SHA256=0a88ed397d60e0624bdef34c7a0b7329a6d278dfdedb86f940c6ee25401c0317
COPY scripts/uvlock-to-constraints.py /tmp/uvlock-to-constraints.py
RUN set -e \
&& whl="unblob-${UNBLOB_VERSION}-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" \
&& curl -fsSLo "/tmp/${whl}" "https://files.pythonhosted.org/packages/cp39/u/unblob/${whl}" \
&& echo "${UNBLOB_WHEEL_SHA256} /tmp/${whl}" | sha256sum -c - \
&& curl -fsSLo /tmp/uv.lock "https://raw.githubusercontent.com/onekey-sec/unblob/${UNBLOB_COMMIT}/uv.lock" \
&& echo "${UNBLOB_UVLOCK_SHA256} /tmp/uv.lock" | sha256sum -c - \
&& python3 -I /tmp/uvlock-to-constraints.py /tmp/uv.lock > /tmp/constraints.txt \
&& python3 -m venv /opt/unblob \
&& /opt/unblob/bin/pip install --no-cache-dir --disable-pip-version-check \
--constraint /tmp/constraints.txt "/tmp/${whl}"
# (unblob needs libmagic to start, which this stage lacks; the final stage
# runs it.)
# --- Final image -------------------------------------------------------------
FROM ubuntu:24.04
@@ -207,7 +229,7 @@ ARG GHIDRAMCP_VERSION=2.0
ARG GHIDRAMCP_SHA256=ebf904fd6782e429e95a60a24fe0724b20c0acb2dedb304135a5281f6b5f2e72
ARG GHIDRAMCP_COMMIT=1e980252087b2479a21b9ca6d82d5829538651a7
ARG GHIDRAMCP_UVLOCK_SHA256=f80ffe8df8167535f253976a1dfb702a9a7f8836d884084017dd7c04e1444837
COPY ghidra/uvlock-to-constraints.py /tmp/uvlock-to-constraints.py
COPY scripts/uvlock-to-constraints.py /tmp/uvlock-to-constraints.py
RUN set -e \
&& apt-get update \
&& apt-get install -y --no-install-recommends python3 python3-venv \
@@ -230,13 +252,23 @@ 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.
# binwalk and unblob (built above) with the extraction tools their install
# scripts (binwalk's Dockerfile, unblob's install-deps.sh) put in place.
# sasquatch (SquashFS variants) comes as a .deb from onekey-sec, at the
# release unblob 26.6.4 uses. unblob's ext2/3/4 extraction needs the rdump
# options from onekey-sec's e2fsprogs fork, so its .debs replace Ubuntu's
# e2fsprogs (same upstream 1.47.0). These releases publish no checksums;
# the SHA-256s were taken when 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
# python/pip are not, so the system python3 isn't shadowed. unblob gets a
# wrapper that puts its own venv first on PATH, so it calls its own locked
# jefferson/ubireader rather than binwalk's.
ARG SASQUATCH_RELEASE=sasquatch-v4.5.1-6
ARG SASQUATCH_SHA256_AMD64=efbc92bd27594e01211e1b582f105a463a0017360a0746a93de3f283906ade95
ARG E2FSPROGS_OK_RELEASE=v1.47.0-3.ok2
ARG LIBEXT2FS2_OK_SHA256_AMD64=4eaf27e79a570ec89e15d8851f7df693b8b3ae78ccbb5b2a4a7d3eee845f1beb
ARG LIBSS2_OK_SHA256_AMD64=6dfc4612915d885607f36fcea2b851a947f9c73943494208ec48a0cbe9887427
ARG E2FSPROGS_OK_SHA256_AMD64=360adf4aa996cc80bf39002775d39900ae207b591c105d12a8ad2a833eb536f4
# 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
@@ -247,12 +279,23 @@ ARG KALEIDO_SHA256_AMD64=3eaa3efd41a00db05dd71add7f54ad4e9d4259552e3f7470ac4c1a8
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
COPY --from=binwalk-build /opt/unblob /opt/unblob
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 - \
&& mkdir /tmp/e2fsprogs-ok && cd /tmp/e2fsprogs-ok \
&& v="${E2FSPROGS_OK_RELEASE#v}" \
&& printf '%s %s\n' \
"${LIBEXT2FS2_OK_SHA256_AMD64}" "libext2fs2_${v}_${arch}.deb" \
"${LIBSS2_OK_SHA256_AMD64}" "libss2_${v}_${arch}.deb" \
"${E2FSPROGS_OK_SHA256_AMD64}" "e2fsprogs_${v}_${arch}.deb" > SHA256SUMS \
&& for f in $(awk '{print $2}' SHA256SUMS); do \
curl -fsSLO "https://github.com/onekey-sec/e2fsprogs/releases/download/${E2FSPROGS_OK_RELEASE}/$f"; \
done \
&& sha256sum -c SHA256SUMS && cd / \
&& 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 - \
@@ -260,16 +303,23 @@ RUN set -e \
&& rm /tmp/kaleido.zip \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
/tmp/sasquatch.deb \
/tmp/sasquatch.deb /tmp/e2fsprogs-ok/*.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 \
android-sdk-libsparse-utils erofs-utils lziprecover partclone unar \
upx-ucl libmagic1t64 xz-utils \
&& rm -rf /var/lib/apt/lists/* /tmp/sasquatch.deb /tmp/e2fsprogs-ok \
&& 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
&& printf '%s\n' '#!/bin/sh' 'PATH="/opt/unblob/bin:$PATH" exec /opt/unblob/bin/unblob "$@"' \
> /usr/local/bin/unblob \
&& chmod 755 /usr/local/bin/unblob \
&& binwalk --version && esptool version && unblob --version \
&& debugfs -V 2>&1 | grep -q 1.47.0 \
&& unblob --show-external-dependencies
# 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 \
@@ -312,7 +362,8 @@ RUN set -e; cd /tmp \
&& npm install --global --omit=optional --no-audit --no-fund "./rea-agents-${REA_VERSION}.tgz" \
&& rm -rf "rea-agents-${REA_VERSION}.tgz" /root/.npm \
&& [ "$(rea --version)" = "${REA_VERSION}" ]
ENV REA_BINWALK_COMMAND=/usr/local/bin/binwalk
ENV REA_BINWALK_COMMAND=/usr/local/bin/binwalk \
REA_UNBLOB_COMMAND=/usr/local/bin/unblob
# stdio-to-HTTP bridge (mcp-remote) that Claude Desktop starts to reach REA's
# HTTP endpoint in the other container. Installed from a committed lockfile.