CtrlK
BlogDocsLog inGet started
Tessl Logo

firmware-acquisition

Systematic firmware extraction from IoT and embedded targets — vendor portals, OTA interception, SPI flash dumping with flashrom/CH341A, eMMC chip-off, and UART/JTAG console dumps. Covers the full acquisition chain from zero hardware access to a raw binary ready for static analysis.

62

Quality

75%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/iot/firmware-acquisition/SKILL.md
SKILL.md
Quality
Evals
Security

Firmware Acquisition

Acquire a binary image of the target's firmware before any static analysis. Work through the acquisition tiers in order — cheapest and least invasive first. Each tier degrades hardware less and preserves the device's operational state.

Prerequisites

  • Device PCB photographed (top + bottom). Mark: SoC part number, flash chip marking, debug pad pattern (UART = 4-pin row; JTAG = TAP header or test points).
  • Identify flash type from silkscreen or datasheet: SPI NOR (SOIC-8/WSON), SPI NAND, eMMC (BGA-153/169), parallel NOR/NAND.
  • Bench tools staged: CH341A programmer + SOIC-8 clip, BusPirate / J-Link / SEGGER J-Trace, USB-UART adapter (CP2102 / FT232), multimeter for UART baud sniffing.

Tier 0 — Vendor Portal / OTA Proxy (no hardware needed)

0a. Vendor download portal

# Check common firmware CDN patterns
curl -sI "https://firmware.vendor.com/latest/<model>.bin"
curl -sI "https://downloads.vendor.com/firmware/<model>/<version>.tar.gz"

# Scrape firmware release page for direct links
wget -r -l1 -nd -A "*.bin,*.tar.gz,*.zip,*.img" \
    "https://www.vendor.com/support/firmware/<model>"

# Checksum-verify before unpacking
sha256sum <fw.bin>

0b. OTA interception (MITM the device's update check)

# 1. ARP-spoof the device to route its traffic through your machine
sudo arpspoof -i eth0 -t <device_ip> <gateway_ip> &
sudo arpspoof -i eth0 -t <gateway_ip> <device_ip> &

# 2. Forward all traffic except OTA traffic; intercept OTA with mitmproxy
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8080
mitmproxy --mode transparent --set ssl_insecure=true -w /tmp/ota_capture.mitm

# 3. Trigger an OTA check from the device (power cycle or companion app)
# 4. Inspect intercepted traffic; export firmware binary
mitmdump -r /tmp/ota_capture.mitm -w /tmp/ota_fw.bin \
    --set flow_detail=3 '~t application/octet-stream or ~t application/x-gzip'

# 5. Decrypt if AES-encrypted OTA (key often in companion app resources)
openssl enc -d -aes-128-cbc -K <hex_key> -iv <hex_iv> -in /tmp/ota_fw.bin -out /tmp/fw_plain.bin

0c. Companion-app embedded firmware

# APK often bundles the firmware binary or partial images
apktool d companion.apk -o companion_decompiled/
find companion_decompiled/ -name "*.bin" -o -name "*.fw" -o -name "*.img" | xargs file
grep -r "firmware\|update\|download\|cdn" companion_decompiled/assets/ --include="*.js"

Tier 1 — UART Console Dump

UART is the fastest hardware path: non-destructive, works while device is powered.

1a. Identify UART pins

Typical 4-pin UART header: VCC | GND | TX | RX
Probe each with multimeter (DC, 3.3 V idle = TX candidate).
Baud detect: minicom -D /dev/ttyUSB0 -b 115200 (try 9600, 57600, 115200, 460800).
# Baud-rate autoprobe with sigrok / baudrate.py
python3 baudrate.py /dev/ttyUSB0    # https://github.com/devttys0/baudrate
# Or with screen – step through common rates
for baud in 9600 19200 38400 57600 115200 230400 460800 921600; do
    echo "Testing $baud..."; screen /dev/ttyUSB0 $baud; done

1b. Interrupt boot and dump from U-Boot

# Connect: GND → GND, RX(USB) → TX(device), TX(USB) → RX(device)
screen /dev/ttyUSB0 115200
# Power on device; press any key within 1-3s to break into U-Boot

# At U-Boot prompt: locate kernel + rootfs partition offsets
=> printenv
=> mtdparts      # shows flash map  (e.g. 0x00000000 kernel 0x200000, rootfs 0x600000)

# Dump via XMODEM to host
=> loady 0x80000000          # load address in RAM
=> md.b 0x80000000 0x200000  # memory-display: prints hex to terminal
# Capture terminal output → convert hex dump → binary:
python3 -c "
import sys, re
data = open('uart_hexdump.txt').read()
chunks = re.findall(r':\s+((?:[0-9a-fA-F]{8} ){1,4})', data)
raw = bytes.fromhex(''.join(''.join(c.split()) for c in chunks))
open('fw_from_uart.bin','wb').write(raw)"

1c. Full flash dump via UART + tftp

# If U-Boot has tftp + nand/sf commands:
=> setenv ipaddr 192.168.1.100
=> setenv serverip 192.168.1.10
=> sf probe; sf read 0x80000000 0x0 0x1000000    # read 16MB SPI NOR into RAM
=> tftp 0x80000000 flash_full.bin                 # upload to your tftp server
# On your host: python3 -m py3tftp -p 69 --ip 0.0.0.0

Tier 2 — JTAG / SWD Debug Interface

2a. OpenOCD + J-Link SWD dump (ARM Cortex-M)

openocd -f interface/jlink.cfg -f target/stm32f4x.cfg \
    -c "init; halt; dump_image /tmp/flash.bin 0x08000000 0x100000; shutdown"
# Adjust start address and size per datasheet (STM32F4: 1 MB @ 0x08000000)

2b. JTAG boundary-scan enumeration (MIPS/ARM application processors)

# UrJTAG or JTAGulator to identify IR length + IDCODE
jtag> cable jtagkey
jtag> detect
jtag> print chain
# Identify MIPS/ARM AP; attach GDB via OpenOCD
gdb-multiarch vmlinux
(gdb) target remote :3333
(gdb) monitor halt
(gdb) dump binary memory /tmp/mem.bin 0x80000000 0x90000000   # DRAM range

2c. JTAG boundary-scan via eBBoot / Segger

# Segger J-Flash CLI for known targets:
JFlash -openprj target.jflash -readchip /tmp/flash_full.bin -exit

Tier 3 — SPI NOR Flash Dump (CH341A + flashrom)

Most cost-effective hardware method for 8-pin SPI NOR chips.

3a. In-circuit dump (device powered off, clip attached)

# Connect SOIC-8 clip to CH341A programmer
# Identify chip: run flashrom probe first
sudo flashrom -p ch341a_spi --verbose 2>&1 | grep -E "Found|Matched|chip"

# Dump (use the exact chip name from probe output)
sudo flashrom -p ch341a_spi -c "MX25L12835F" -r /tmp/flash_dump.bin
sudo flashrom -p ch341a_spi -c "MX25L12835F" -r /tmp/flash_dump2.bin

# Verify both reads match (disk corruption / clip contact issue otherwise)
sha256sum /tmp/flash_dump.bin /tmp/flash_dump2.bin
cmp /tmp/flash_dump.bin /tmp/flash_dump2.bin && echo "MATCH" || echo "MISMATCH — re-seat clip"

3b. Troubleshooting in-circuit reads

Symptom: flashrom sees 0x00 or 0xFF → chip held in reset by SoC pull-downs.
Fix: power-cycle with clip attached BEFORE the SoC powers up (race the SoC).
Or: locate HOLD# / WP# pins, tie to VCC; CS# must go low only from CH341A.
If SoC fights the bus: desolder for Tier 4.

3c. SPI NAND / eMMC alternative: python-flashrom / serprog

# For SPI NAND (Winbond W25Nxxxx), use nandwrite flow:
# 1. Use flashrom with serprog (Raspberry Pi as SPI master):
sudo flashrom -p serprog:dev=/dev/ttyUSB0:4000000 -c "W25N01GV" -r /tmp/nand.bin

# 2. Extract with nanddump equivalent for raw bin:
binwalk -e /tmp/nand.bin     # handles OOB stripping for most NAND dumps

Tier 4 — eMMC Chip-Off

Last resort: BGA desoldering, direct eMMC reader. Destructive to PCB.

4a. Chip-off procedure

1. Hot-air rework (350 °C, 60 L/min): heat BGA from below; lift with vacuum.
2. Clean pads with flux + wick.
3. Reflow eMMC onto BGA breakout board (eMMC adapter):
   - SD-to-eMMC: Allwinner eMMC reader, Emuelec adapter, or custom PCB.
4. Insert into card reader that supports eMMC protocol.

4b. Dump with dd / usbimager

# Identify device node (dmesg after plugging in)
dmesg | tail -20 | grep sd
lsblk -d /dev/sdb    # confirm size matches eMMC spec

# Full raw dump
sudo dd if=/dev/sdb of=/tmp/emmc_full.img bs=512 status=progress conv=noerror,sync
sha256sum /tmp/emmc_full.img > /tmp/emmc_full.img.sha256

# Partition inspection
fdisk -l /tmp/emmc_full.img
file /tmp/emmc_full.img

# Mount individual partition (offset in sectors from fdisk output)
sudo mount -o loop,offset=$((512*2048)) /tmp/emmc_full.img /mnt/emmc_p1

Evidence

Store all artifacts under /workspace/evidence/iot/<target>/firmware/:

mkdir -p /workspace/evidence/iot/<target>/firmware
cp /tmp/flash_dump.bin /workspace/evidence/iot/<target>/firmware/flash_full.bin
sha256sum /workspace/evidence/iot/<target>/firmware/flash_full.bin \
    > /workspace/evidence/iot/<target>/firmware/flash_full.bin.sha256
# Log acquisition method and hardware used
echo "Acquired via: CH341A + SOIC-8 clip; flashrom 1.4.0; chip: MX25L12835F" \
    > /workspace/evidence/iot/<target>/firmware/acquisition.log

OPSEC Notes

  • In-circuit clips can corrupt firmware if contact is intermittent — always compare two reads before treating the image as canonical.
  • OTA MITM is logged server-side; only perform on isolated test-lab networks unless RoE explicitly permits production intercept.
  • JTAG debug may trigger internal tamper fuses on hardened targets (e.g., Qualcomm secure boot with JTAG-disable eFuse). Read the SoC TRM before probing.
  • eMMC RPMB partition requires authentication key to read — skip on initial triage; flag for key extraction if bootloader secrets are needed.

References

  • flashrom supported hardware: https://flashrom.org/Supported_hardware
  • CH341A pinout + SOIC clip wiring: references/ch341a-soic-wiring.md
  • U-Boot command reference: https://u-boot.readthedocs.io/en/latest/usage/index.html
  • OpenOCD target configs: /usr/share/openocd/scripts/target/
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.