name: binwalk-extract description: Firmware image extraction with binwalk and firmware-mod-kit — recursive archive carving, squashfs/jffs2/ubifs mounting, entropy analysis to detect packed/encrypted regions, and nested container handling. Entry point for all static filesystem analysis after a raw binary image is acquired. allowed-tools: Bash Read Write metadata: subdomain: iot when_to_use: binwalk, firmware extract, squashfs mount, jffs2 mount, ubifs, firmware-mod-kit, entropy analysis, nested archive, rootfs extract, firmware analysis tags: iot, firmware, binwalk, squashfs, jffs2, ubifs, extraction, entropy, static-analysis, embedded mitre_attack: T1601, T1592.002, T1083
Firmware Extraction with binwalk
Turn a raw binary image into a navigable filesystem tree. binwalk handles most common containers; firmware-mod-kit covers special-case squashfs variants; manual mounting handles the rest.
Prerequisites
- Raw firmware binary (from
firmware-acquisitionskill). - Tools:
binwalk(≥ 2.3),sasquatch(non-standard squashfs decompressor),jefferson(JFFS2 extractor),ubireader(UBI/UBIFS),mtd-utils,firmware-mod-kit(FMK),7-zip,lzma,xz-utils.
# Kali / Debian install
sudo apt-get install -y binwalk firmware-mod-kit mtd-utils
pip3 install jefferson ubireader
# sasquatch (handles non-standard squashfs compression: LZMA, XZ, ZLIB with vendor patches)
git clone https://github.com/devttys0/sasquatch && cd sasquatch
./build.sh && sudo cp sasquatch /usr/local/bin/
Step 1 — Identify + Entropy Survey
FW=/workspace/evidence/iot/<target>/firmware/flash_full.bin
# Quick filetype + offset scan
binwalk "$FW"
# Entropy analysis: flat line near 1.0 = encrypted/compressed; structured = filesystem
binwalk -E "$FW"
# Output plot to PNG for report
binwalk -E --save "$FW" # saves <fw>.png beside the binary
# High entropy region with no signature = encrypted blob — note offset + size
# Low-to-medium entropy with known FS signature = squashfs/jffs2/cramfs — extract
Reading the entropy graph
| Entropy range | Interpretation |
|---|---|
| 0.0–0.3 | Mostly zero-fill / padding — skip |
| 0.5–0.8 | Structured data (FS headers, ELF) — extract |
| 0.8–0.95 | Compressed data (gzip, lzma, zlib) — normal |
| 0.95–1.0 flat | AES/RSA encrypted or already-compressed blob — flag for key hunt |
Step 2 — Recursive Extraction
OUTDIR=/workspace/evidence/iot/<target>/extracted
# Recursive extraction (-M), follow symlinks (-r), output to dedicated dir (-C)
binwalk -eM -C "$OUTDIR" "$FW"
# Inspect what was carved
find "$OUTDIR" -maxdepth 4 -type f | head -60
ls -lah "$OUTDIR"/_*
Common extraction outputs
_flash_full.bin.extracted/
squashfs-root/ ← mounted squashfs rootfs
40 ← raw uImage kernel (strip 64-byte header for vmlinuz)
40.7z ← carved archive at offset 0x40
A00000 ← raw block at offset 0xA00000
Step 3 — Squashfs (standard + vendor variants)
SQFS=$(find "$OUTDIR" -name "*.squashfs" -o -name "squashfs-root.img" 2>/dev/null | head -1)
# Standard unsquashfs
unsquashfs -d /tmp/squashfs_root "$SQFS"
# Non-standard (Broadcom LZMA, TP-Link XZ, vendor-patched):
sasquatch -d /tmp/squashfs_root "$SQFS"
# If both fail, force a specific compression type:
sasquatch -p 1 -le -d /tmp/squashfs_root "$SQFS" # little-endian
sasquatch -p 1 -be -d /tmp/squashfs_root "$SQFS" # big-endian
# Verify extraction
ls /tmp/squashfs_root/{bin,etc,lib,usr,var} 2>/dev/null
Step 4 — JFFS2
JFFS2_IMG=$(find "$OUTDIR" -name "*.jffs2" 2>/dev/null | head -1)
# Method A: jefferson (Python, handles most variants)
jefferson "$JFFS2_IMG" -d /tmp/jffs2_root
# Method B: kernel loop mount (requires modprobe jffs2 + mtdram)
sudo modprobe mtdram total_size=65536 erase_size=256
sudo modprobe mtdblock
sudo dd if="$JFFS2_IMG" of=/dev/mtd0
sudo mount -t jffs2 /dev/mtdblock0 /mnt/jffs2
Step 5 — UBIFS (NAND-based devices)
UBI_IMG=$(find "$OUTDIR" -name "*.ubi" -o -name "*.ubifs" 2>/dev/null | head -1)
# ubireader_extract_files: most direct path
ubireader_extract_files -o /tmp/ubifs_root "$UBI_IMG"
# For raw UBI volume images, ubiextract:
sudo modprobe ubi
sudo ubiattach -m 0 -d 0 /dev/ubi_ctrl
sudo mount -t ubifs /dev/ubi0_0 /mnt/ubifs
Step 6 — Manual Carving (when binwalk misses)
# Find filesystem magic bytes manually
python3 -c "
import sys
data = open('$FW','rb').read()
sigs = {b'hsqs': 'SquashFS LE', b'sqsh': 'SquashFS BE',
b'\\x19\\x85': 'JFFS2', b'UBI#': 'UBI', b'\\x27\\x05\\x19\\x56': 'uImage'}
for sig, name in sigs.items():
off = 0
while True:
idx = data.find(sig, off)
if idx == -1: break
print(f' {name} @ 0x{idx:08x}')
off = idx + 1"
# Carve a specific region for separate analysis
dd if="$FW" bs=1 skip=$((0xA00000)) count=$((0x600000)) of=/tmp/carved_rootfs.bin
# Run binwalk on the carved piece
binwalk -eM -C /tmp/carved_extract /tmp/carved_rootfs.bin
Step 7 — Nested Archive Handling
# Device firmware often wraps: .zip/.tar → signed header → lzma → squashfs
# firmware-mod-kit handles multi-layer TP-Link / Netgear / Asus formats:
cd /opt/firmware-mod-kit
./extract-firmware.sh "$FW"
ls /tmp/fmk/
# D-Link WRGG (proprietary container):
binwalk --dd='.*' "$FW" # dump ALL matched signatures
file _*.extracted/*
# Lzma-raw regions binwalk missed (entropy ~0.9, no gzip magic):
lzma -d < /tmp/region.lzma > /tmp/region.decompressed
xz -d < /tmp/region.xz > /tmp/region.decompressed
Step 8 — Post-Extraction Triage
ROOT=/tmp/squashfs_root # adjust to wherever rootfs landed
# Architecture + OS identification
file "$ROOT/bin/busybox"
readelf -h "$ROOT/bin/busybox" | grep -E "Machine|Class|Data"
# Enumerate interesting paths
ls "$ROOT/etc/"
ls "$ROOT/usr/bin/" | head -40
find "$ROOT" -name "*.conf" -o -name "*.ini" -o -name "*.cfg" | head -30
# SUID / SGID binaries (potential priv-esc on device)
find "$ROOT" -perm -u=s -type f 2>/dev/null
find "$ROOT" -perm -g=s -type f 2>/dev/null
# World-writable directories (writeable by web/telnet processes)
find "$ROOT" -perm -o=w -type d 2>/dev/null | grep -v proc
# Symlinks that escape the rootfs (path traversal potential)
find "$ROOT" -type l | while read l; do
target=$(readlink "$l")
echo "$l -> $target"
done | grep '^\.\.'
Evidence
EVDIR=/workspace/evidence/iot/<target>/extracted
mkdir -p "$EVDIR"
# Save extraction tree summary
find /tmp/squashfs_root -type f > "$EVDIR/file_tree.txt"
# Save entropy plot
cp "$FW.png" "$EVDIR/entropy_plot.png" 2>/dev/null || true
# Note encrypted regions for follow-up
echo "Encrypted blob @ 0xXXXXXX, length 0xYYY — likely AES-CBC, key TBD" \
>> "$EVDIR/notes.txt"
OPSEC Notes
binwalk -eMcan write gigabytes if the firmware contains recursive containers; run on a dedicated partition or tmpfs with sufficient space.- Some vendor firmware images are signed; extraction still works (signature is just
a header — binwalk skips it). Repacking for re-flash requires bypassing signature
verification (see
bootloader-ubootskill). - Encrypted regions at entropy ≈ 1.0 with no detectable IV/tag structure may indicate XTS-AES or ChaCha20 stream — note the offset; search the OTA update binary or companion app for the key material.
References
- binwalk docs:
https://github.com/ReFirmLabs/binwalk/wiki - sasquatch:
https://github.com/devttys0/sasquatch - jefferson (JFFS2):
https://github.com/sviehb/jefferson - ubireader:
https://github.com/jrspruitt/ubi_reader - firmware-mod-kit:
https://github.com/rampageX/firmware-mod-kit