Berkner Tech

Pulling Firmware Over a Bootloader Console

Dumping firmware over a U-Boot serial console without removing the flash chip

Reading flash with a chip clip is reliable, but sometimes the device hands you the firmware for free. If a bootloader console is reachable over the serial port, you can often dump memory without touching a soldering iron. Here is how that goes on a typical U-Boot device.

When the Device Hands You the Image

A hardware read is the dependable method, but it is also the slowest and the riskiest to the board. The bootloader console is the shortcut. If the vendor left the prompt reachable, the loader’s own commands will read flash and push it off the board, and you never lift a chip.

This is worth trying first on any device with an accessible serial port, because when it works it is faster than a clip and it leaves the hardware untouched. When it does not work, you have lost ten minutes and learned that the console is locked, which is itself a finding.

Find and Wire the UART First

The console needs a serial line, so the first job is locating UART on the board. Find a row of three or four pads near the SoC, identify ground with a multimeter, and watch for the pin that flickers at boot, which is TX. Wire a 3.3V adapter, then confirm the rate.

# if you do not know the baud, detect it against the noisy TX line
baudrate.py /dev/ttyUSB0     # or just try 115200 first
Example output
Trying 9600...   garbage
Trying 57600...  garbage
Trying 115200... readable: 'U-Boot 2021.04'
[+] baud rate is 115200

Most devices land at 115200, so trying that first usually saves time. Readable text is the signal that you have the rate and the wiring right, and that the console is at least printing.

Interrupt the Boot

Open a terminal and power the device while watching the console. Many bootloaders print a prompt and wait a beat for input. Catching that window drops you into the loader with a command set that can read and write memory.

picocom -b 115200 /dev/ttyUSB0
Example output
U-Boot 2021.04 (May 18 2023 - 11:42:07)
DRAM:  256 MiB
Hit any key to stop autoboot:  2
=> 

The => prompt means you are in. From here, help lists the commands the vendor compiled in, and printenv is almost always worth running before anything else.

Read the Environment First

The bootloader environment often holds boot arguments, network settings, and occasionally credentials or update URLs. Printing it is a quick win that maps the startup sequence before you dump a single byte.

=> printenv
Example output
baudrate=115200
bootargs=console=ttyS0,115200 root=/dev/mtdblock3
bootcmd=sf probe; sf read 0x82000000 0x100000 0x600000; bootm 0x82000000
ethaddr=00:11:22:33:44:55
ipaddr=192.168.1.10
serverip=192.168.1.2

That bootcmd is a recipe: it probes the SPI flash, reads a region into RAM at 0x82000000, and boots it. You can reuse those same addresses and the flash geometry to pull the image yourself.

Read Flash Into RAM

On a SPI-flash device, sf probe initializes the chip and reports its size, and sf read copies a region into RAM. Read the whole chip into a known RAM address so you can export it next.

=> sf probe
=> sf read 0x82000000 0x0 0x1000000
Example output
SF: Detected w25q128 with page size 256 Bytes, erase size 4 KiB, total 16 MiB
device 0 whole chip
SF: 16777216 bytes @ 0x0 Read: OK

Now the entire 16 MiB flash sits in RAM at 0x82000000. The only thing left is getting it off the board, and there are two ways depending on whether the loader has a working network.

Get It Off Over the Network

If the loader has Ethernet, TFTP is fastest. Point it at a TFTP server on your laptop and write RAM out in one shot.

=> setenv serverip 192.168.1.2
=> tftpput 0x82000000 0x1000000 dump.bin
Example output
Using ethernet@1e660000 device
TFTP to server 192.168.1.2; our IP is 192.168.1.10
Sending dump.bin
16777216 bytes sent in 41.2s (388 KiB/s)

Forty seconds for the whole chip, with nothing but the serial console and a network cable. That is hard to beat for speed and risk, and it is why I always check for working Ethernet at the prompt.

Or Dump It Over Serial

With no network, fall back to dumping RAM over the serial link. The md command prints memory as hex, and capturing the terminal log gives you the bytes to reassemble.

=> md 0x82000000 0x20
Example output
82000000: 27051956 0a1b3c4d 5e6f7081 92a3b4c5    '..V..<M^op......
82000010: deadbeef 00100000 80000000 1f8b0800    ................

Serial is slow for 16 MiB, so it is a last resort for a full image, but it needs nothing but the wire you already have attached. For a quick look at a header or a single partition, it is often all you need.

When the Storage Is NAND or eMMC

Not every device boots from SPI flash. On NAND, the read command is nand read with a device offset, and on eMMC the loader exposes the chip through mmc read. The principle is identical: copy a region into RAM, then export it.

# NAND variant
=> nand read 0x82000000 0x0 0x800000
# eMMC variant
=> mmc dev 0; mmc read 0x82000000 0x0 0x2000
Example output
NAND read: device 0 offset 0x0, size 0x800000
 8388608 bytes read: OK

Knowing which command the device’s storage needs is just a matter of reading the boot log, which names the flash type, or trying help and seeing whether sf, nand, or mmc is present.

Verify the Dump Before You Trust It

A dump pulled over a console can be truncated or corrupted by a flaky serial link. Run binwalk on the result before you build any analysis on it, the same way you would validate a hardware read.

binwalk dump.bin | head
Example output
DECIMAL    HEXADECIMAL   DESCRIPTION
-------------------------------------------------------
0          0x0           uImage header, U-Boot
65600      0x10040       LZMA compressed data
1638400    0x190000      Squashfs filesystem, little endian

A clean structure, a header, a kernel, and a filesystem at sensible offsets, means the dump is whole. A binwalk that finds nothing where you expect a filesystem usually means a dropped chunk, and the fix is to read it again, more slowly.

Scripting the Whole Dump

Doing this by hand is fine once. For a full chip over serial, or to repeat it reliably, script the prompt interaction so the read and the capture happen without babysitting. An expect script drives the console and logs everything to a file.

# expect script: interrupt boot, read flash to RAM, dump it over serial
expect -c 'spawn picocom -b 115200 /dev/ttyUSB0
  expect "stop autoboot"; send "\r"
  expect "=> "; send "sf probe\r"
  expect "=> "; send "sf read 0x82000000 0x0 0x1000000\r"
  expect "=> "; send "md.b 0x82000000 0x1000000\r"
  log_file dump.log; expect "=> "'
Example output
SF: Detected w25q128 ... total 16 MiB
# md.b output streaming to dump.log; reassemble with a short python parser

The captured hex log then reassembles into a binary with a few lines of Python. Scripting it removes the transcription errors that creep into a long manual session and gives you a repeatable procedure you can hand to someone else.

Reading Just the Interesting Partition

A full 16 MiB dump over serial is slow, and often you only need one partition. The boot arguments and the flash layout tell you where the filesystem or the environment lives, so you can read just that region and save most of the time.

# read only the 6 MiB rootfs region named in bootcmd, not the whole chip
=> sf read 0x82000000 0x100000 0x600000
=> tftpput 0x82000000 0x600000 rootfs.bin
Example output
SF: 6291456 bytes @ 0x100000 Read: OK
6291456 bytes sent in 16.1s

Targeting the partition you actually want turns a forty-second full read, or a much longer serial dump, into something quicker and cleaner. The layout from printenv is what makes that targeting possible.

When the Console Is Locked

Some vendors password-protect the prompt, disable interactive mode, or remove the autoboot delay so there is no window to interrupt. That is the right move, and when you hit it, the console route is closed and you go back to reading the flash chip directly with a clip and a programmer.

It is worth confirming the lock is real and not cosmetic. A prompt that asks for a password but accepts a known default, or a delay set to zero that still honors a specific keypress, is a lock in name only.

What the Console Reveals Beyond the Image

Even when you never dump the full image, a few minutes at the prompt is rarely wasted. The environment exposes boot arguments and network configuration, the version banner names the loader and its build date, and the command list hints at what the device trusts.

I often learn more from five minutes of printenv and help than from an hour of static analysis, because the bootloader is describing, in its own words, exactly how the device starts and what it checks on the way up.

Locking the Console Properly

An open bootloader console is a gift to an attacker and cheap to close. Password-protect the prompt, disable interactive mode in production builds, set the autoboot delay to zero, and keep the most powerful memory commands out of the shipping image.

None of that stops a hardware-level flash read, but it removes the easiest path in, which is exactly the path opportunistic attackers take first. Defense in depth means closing the cheap doors even when the expensive ones remain.

Where This Fits

Checking whether the boot console hands out the firmware is a standard step in a hardware penetration test. If you want your boot path reviewed, including whether the console lock actually holds, that is the kind of work we do at Berkner Tech.


References and Further Reading