# Accela Signal | Field | Value | |-------|-------| | Category | Misc | | Difficulty | Hard | | Points | 500 | | Author | Eun0us | | CTF | Espilon 2026 | --- ## Description During the KIDS experiment, Tachibana Labs deployed a covert LoRa-like mesh network throughout the hospital infrastructure. Code-named "Accela Signal", these transmissions use Chirp Spread Spectrum modulation to carry data fragments below the detection threshold of conventional receivers. Your NAVI has intercepted the baseband IQ stream from one of these nodes. The signal contains chirp-modulated frames with encoded data. Analyze the modulation, demodulate the chirps, decode the protocol, and extract the hidden message. Hint: The stream banner tells you the basics. The rest is signal processing. - IQ Stream: `tcp/:9002` Format: **ESPILON{flag}** --- ## TL;DR Capture an 8 kSps int16 LE IQ stream. Identify Chirp Spread Spectrum (LoRa-like) modulation (N=128, SF=7). Implement dechirp + FFT demodulation. Detect frames by preamble (8x symbol-0 chirps) and sync (2x downchirps). Gray-decode symbols. Unpack 7-bit symbols to bytes. Find the data frame (type=0x02), XOR with key `L41N`, decode the flag. --- ## Tools | Tool | Purpose | |------|---------| | `nc` | Capture IQ stream | | Python 3 + numpy + scipy | CSS demodulation pipeline | | `inspectrum` / matplotlib | Visual spectrogram analysis | --- ## Solution ### Step 1 — Capture and read the banner ```bash nc 9002 > capture.raw ``` First bytes are a text banner before the binary IQ data: ``` IQ baseband, 8000 sps, int16 LE interleaved Chirp Spread Spectrum detected. N=128. ``` ![nc output showing the IQ stream banner](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/accela_nc.png) ### Step 2 — Analyze the spectrogram Load the IQ data in inspectrum or plot with matplotlib. You see: - Characteristic **chirps**: frequency sweeps from low to high - Repeating groups of identical chirps (preamble) - Occasional downchirps (sync) This is **Chirp Spread Spectrum (CSS)**, identical in principle to LoRa. ![spectrogram showing upchirp preamble and downchirp sync patterns](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/accela_spectrogram.png) ### Step 3 — Determine parameters - Each chirp spans 128 samples → **N = 128** - `N = 2^SF` → **SF = 7** (spreading factor) - Each symbol encodes 7 bits → 128 possible symbols - Baseband sample rate = 8000 Hz ### Step 4 — Implement dechirping ```python import numpy as np # Read raw int16 LE interleaved IQ raw = np.fromfile("capture.raw", dtype=">= 1 val ^= mask return val def symbols_to_bytes(symbols): """Pack 7-bit symbols (SF=7) into 8-bit bytes""" bits = "" for s in symbols: bits += f"{s:07b}" return bytes(int(bits[i:i+8], 2) for i in range(0, len(bits) - 7, 8)) ``` ![Python decoder output showing decoded symbols and CRC16 validation pass](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/accela_decoder.png) ### Step 7 — Parse frame payload and decrypt Payload format: `[type:1] [data:L] [crc16:2]` - Type `0x01` = beacon (cleartext ASCII, for verification) - Type `0x02` = data frame (XOR-encrypted flag) ```python import crcmod crc16_fn = crcmod.predefined.mkCrcFun('crc-ccitt-false') for frame_type, data, crc in decoded_frames: if crc16_fn(bytes([frame_type]) + data) != crc: continue # invalid frame if frame_type == 0x02: key = b"L41N" flag = bytes(b ^ key[i % 4] for i, b in enumerate(data)) print(flag.rstrip(b'\x00').decode()) ``` ![script printing the decrypted flag from the data frame](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/accela_flag.png) ### Key insights - CSS encodes data as a cyclic frequency shift of a chirp signal - The dechirp + FFT converts frequency offset into a bin index (symbol value) - Gray coding ensures adjacent symbols differ by 1 bit, reducing BER on noisy channels - The beacon frame (type 0x01) provides a known-plaintext verification step - The stream banner hint `"N=128"` directly gives the spreading factor --- ## Flag `ESPILON{4cc3l4_ch1rp_spr34d_w1r3d}`