- Remove undeployed challenges: Phantom_Byte, Cr4cK_w1f1, Lain_Br34kC0r3 V1, Lain_VS_Knights, Lets_All_Love_UART, AETHER_NET, Last_Train_451, Web3/ - Sync 24 solve/ files from main CTF-Espilon repo - Update all READMEs with real CTFd final scores at freeze - Add git-header.png banner - Rewrite README: scoreboard top 10, edition stats (1410 users, 264 boards, 1344 solves), correct freeze date March 26 2026 |
||
|---|---|---|
| .. | ||
| solve | ||
| README.md | ||
Accela Signal
| Field | Value |
|---|---|
| Category | Misc |
| Difficulty | Hard |
| Points | 536 |
| 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/<host>: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
nc <HOST> 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.
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.
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
import numpy as np
# Read raw int16 LE interleaved IQ
raw = np.fromfile("capture.raw", dtype="<i2").astype(float) / 32768.0
iq = raw[0::2] + 1j * raw[1::2]
N = 128
# Base upchirp (symbol 0): exp(j * pi * n^2 / N)
n = np.arange(N)
upchirp0 = np.exp(1j * np.pi * n**2 / N)
downchirp = np.conj(upchirp0)
def decode_symbol(chirp_samples):
"""Dechirp then find FFT peak = symbol value"""
dechirped = chirp_samples * np.conj(upchirp0)
spectrum = np.abs(np.fft.fft(dechirped))
return int(np.argmax(spectrum))
Step 5 — Detect frames
Frame structure:
[Preamble: 8x symbol 0] [Sync: 2x downchirp] [Header: 1 symbol = length] [Payload: L symbols]
Scan the IQ stream for runs of 8 consecutive symbols decoding to 0.
Step 6 — Gray decode and symbol-to-byte unpacking
def gray_decode(val):
mask = val
while mask:
mask >>= 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))
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)
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())
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}



