Baby Serial

Eh4x CTFby smothy

Baby Serial - EH4X CTF 2026 | Forensics | 500 pts

Challenge: "Joe was trying to sniff the data over a serial communication. Was he successful?"

File: babyserial.sal

Flag: EH4X{baby_U4rt}


TL;DR

.sal file -> Saleae Logic capture -> decode UART (115200 8N1) -> base64 -> PNG image -> flag


Step 0: WTF is a .sal file?

So we get this file called babyserial.sal and the challenge says "serial communication". If you've never touched hardware hacking before, you might be like:

"a .sal file?? is this a spreadsheet or what"

confused

Turns out .sal is a Saleae Logic 2 capture file. Saleae Logic is a very popular logic analyzer used in hardware hacking / embedded security. It captures digital signals from wires and lets you decode protocols like UART, SPI, I2C, etc.

What is a Logic Analyzer?

Logic Analyzer Setup

It just records the HIGH/LOW voltage transitions on wires over time. Then software decodes those transitions into actual data.


Step 1: Crack open the .sal

A .sal file is secretly just a ZIP archive. Unzip it:

bash
$ file babyserial.sal
babyserial.sal: Zip archive data

$ unzip babyserial.sal -d extracted/
Archive:  babyserial.sal
  inflating: extracted/meta.json
  inflating: extracted/digital-0.bin
  inflating: extracted/digital-1.bin
  ... (digital-0 through digital-7)

Inside we find:

  • meta.json - capture config (analyzer settings, sample rates, etc.)
  • digital-0.bin through digital-7.bin - raw signal data for each channel

Quick size check tells us everything:

bash
$ ls -la extracted/
 63185  digital-0.bin   # <-- THIS is where the data lives
    81  digital-1.bin   # empty/flat
    81  digital-2.bin   # empty/flat
    ...                 # all others 81 bytes = no activity

Only Channel 0 has actual data. Makes sense - it's a single UART TX line.


Step 2: Read the meta.json (know your enemy)

The meta.json is a goldmine. It tells us exactly how the capture was configured:

Protocol: Async Serial (UART) Channel: 0 Baud Rate: 115200 Bits/Frame: 8 Stop Bits: 1 Parity: None Bit Order: LSB first Inversion: Non-inverted Sample Rate: 1 MHz (digital)

This is standard 115200 8N1 - the most common UART config ever. The "Hello World" of serial.

Quick UART refresher:

UART Frame

Each bit takes 1/115200 = ~8.68 microseconds. The Logic was sampling at 1 MHz, so we get roughly 8-9 samples per bit. Tight but totally workable.


Step 3: Decode the UART data

Option A: Use Saleae Logic 2 (the easy way)

If you have Logic 2 installed, just open the .sal file. It already has the Async Serial analyzer configured. Click on the data table and export.

You can even run it headless with the Automation API:

python
from saleae.automation import Manager

manager = Manager.connect(port=10430)
capture = manager.load_capture("/path/to/babyserial.sal")

analyzer = capture.add_analyzer('Async Serial', label='UART', settings={
    'Input Channel': 0,
    'Bit Rate (Bits/s)': 115200,
    'Bits per Frame': 8,
    'Stop Bits': '1 Stop Bit (Standard)',
    'Parity Bit': 'No Parity Bit (Standard)',
    'Significant Bit': 'Least Significant Bit Sent First (Standard)',
    'Signal inversion': 'Non Inverted (Standard)',
    'Mode': 'Normal',
})

capture.export_data_table(filepath="uart_data.csv", analyzers=[analyzer])

Option B: Parse the binary yourself (the pain way)

The digital-0.bin uses Saleae's internal binary format (version 2, type 100) which stores transition deltas using a custom variable-length encoding. It's undocumented and painful. Don't do this to yourself unless you enjoy suffering.

"i'll just parse the binary format myself, how hard can it be"

narrator: it was hard


Step 4: Look at the decoded data

The CSV export gives us 9630 decoded bytes, one per line:

csv
name,type,start_time,duration,"data"
"UART","data",0.814855,0.000082,"i"
"UART","data",0.814943,0.000082,"V"
"UART","data",0.815031,0.000082,"B"
"UART","data",0.81512,0.000082,"O"
"UART","data",0.815208,0.000082,"R"
"UART","data",0.815296,0.000082,"w"
"UART","data",0.815384,0.000082,"0"
"UART","data",0.815472,0.000082,"K"
"UART","data",0.81556,0.000082,"G"
"UART","data",0.815648,0.000082,"g"
"UART","data",0.815737,0.000082,"o"
...

Concatenate all data characters:

iVBORw0KGgoAAAANSUhEUgAAAqsAAAGACAMAAAC9RturAAACQFBMVEX/AID9AIH...

Wait... iVBORw0KGgo...

THAT'S BASE64 FOR A PNG FILE HEADER!!

If you've done enough CTFs, this string is burned into your retina. The bytes 89 50 4E 47 (PNG magic) base64-encode to iVBORw0.

raw PNG magic: 89 50 4E 47 0D 0A 1A 0A base64 of it: i V B O R w 0 K G g o

Step 5: Decode and get the flag

bash
# Extract just the data characters
$ awk -F',' '/^"UART","data"/{gsub(/"/, "", $5); printf "%s", $5}' \
    uart_data.csv > uart_raw.txt

# Strip any non-base64 chars and decode
$ cat uart_raw.txt | tr -cd 'A-Za-z0-9+/=' | base64 -d > flag.png

$ file flag.png
flag.png: PNG image data, 683 x 384, 8-bit colormap, non-interlaced

Open the image and we get the flag: blue text on pink background.

Flag: EH4X{baby_U4rt}

The flag is a play on "baby UART" with a leet-speak 4 instead of A. Classic.


What we learned

  1. .sal files are ZIP archives containing meta.json + raw channel binaries
  2. UART/Serial is the most basic hardware protocol - just voltage transitions on a wire, one bit at a time
  3. Logic analyzers (like Saleae) capture those transitions and decode them
  4. Always check meta.json in .sal files - it tells you the protocol settings (baud rate, etc.)
  5. iVBORw0KGgo = base64 PNG. If you see this, decode it immediately
  6. Saleae Logic 2 has a Python automation API for scripted analysis - super useful for CTFs

Tools used

ToolPurpose
unzipExtract the .sal archive
Saleae Logic 2Decode UART protocol from raw signals
awkExtract data column from CSV
base64Decode base64 to binary
fileIdentify the decoded PNG

GG, baby serial down. Now go touch some real hardware.