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.salFlag:
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"

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?
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:
$ 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.binthroughdigital-7.bin- raw signal data for each channel
Quick size check tells us everything:
$ 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 activityOnly 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:
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:
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:
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
# 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-interlacedOpen 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
.salfiles are ZIP archives containingmeta.json+ raw channel binaries- UART/Serial is the most basic hardware protocol - just voltage transitions on a wire, one bit at a time
- Logic analyzers (like Saleae) capture those transitions and decode them
- Always check
meta.jsonin.salfiles - it tells you the protocol settings (baud rate, etc.) iVBORw0KGgo= base64 PNG. If you see this, decode it immediately- Saleae Logic 2 has a Python automation API for scripted analysis - super useful for CTFs
Tools used
| Tool | Purpose |
|---|---|
unzip | Extract the .sal archive |
Saleae Logic 2 | Decode UART protocol from raw signals |
awk | Extract data column from CSV |
base64 | Decode base64 to binary |
file | Identify the decoded PNG |
GG, baby serial down. Now go touch some real hardware.