# 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.

- Skill: `purpleailab/firmware-acquisition` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/firmware-acquisition`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/firmware-acquisition/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/firmware-acquisition

---


# 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

```bash
# 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)

```bash
# 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

```bash
# 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).
```

```bash
# 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

```bash
# 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

```bash
# 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)

```bash
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)

```bash
# 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

```bash
# 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)

```bash
# 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

```bash
# 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

```bash
# 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/`:

```bash
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/`

