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
75%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
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.mdAcquire 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.
# 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># 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# 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"UART is the fastest hardware path: non-destructive, works while device is powered.
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# 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)"# 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.0openocd -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)# 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# Segger J-Flash CLI for known targets:
JFlash -openprj target.jflash -readchip /tmp/flash_full.bin -exitMost cost-effective hardware method for 8-pin SPI NOR chips.
# 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"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.# 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 dumpsLast resort: BGA desoldering, direct eMMC reader. Destructive to PCB.
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.# 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_p1Store 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.loghttps://flashrom.org/Supported_hardwarereferences/ch341a-soic-wiring.mdhttps://u-boot.readthedocs.io/en/latest/usage/index.html/usr/share/openocd/scripts/target/31e1c8e
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.