diff --git a/IoT/Wired_Airwave_013/README.md b/IoT/Wired_Airwave_013/README.md index b5540a4..8a3a489 100644 --- a/IoT/Wired_Airwave_013/README.md +++ b/IoT/Wired_Airwave_013/README.md @@ -1,60 +1,154 @@ -# Wired Airwave 013 -- Solution +# Wired Airwave 013 -## Overview +| Field | Value | +|-------|-------| +| Category | IoT | +| Difficulty | Medium | +| Points | 500 | +| Author | Eun0us | +| CTF | Espilon 2026 | -The challenge exposes: +--- -- `tcp/9001`: raw interleaved int8 IQ stream (2-FSK bursts) -- `tcp/31337`: maintenance console +## Description -Goal: +Clinique Sainte-Mika uses a wireless maintenance channel for Room 013 monitors. +The RF backend exposes raw baseband IQ over TCP. -1. Demodulate valid RF frames from IQ. -2. Recover the maintenance token hidden in maintenance frames. -3. Submit it with `unlock ` on the console. +Your objective: +1. Decode the FSK bursts from the IQ stream. +2. Recover the maintenance token hidden in service frames. +3. Submit the token on the control console. -## Packet format +- IQ Stream: `tcp/:9001` +- Maintenance Console: `tcp/:31337` -After preamble and sync, each frame carries 20 obfuscated bytes: +Format: **ESPILON{flag}** -- `type` (1 byte) -- `counter` (1 byte) -- `data` (16 bytes, text) -- `crc16-ccitt` (2 bytes, big endian) +--- -The 20-byte payload is XOR-obfuscated with repeating key `WIREDMED13`. +## TL;DR -## Decode path +Capture the raw int8 IQ stream (interleaved I/Q). Implement differential FSK demodulation. +Locate frames using preamble + sync markers. XOR-deobfuscate with key `WIREDMED13`. +Verify CRC16-CCITT. Reassemble maintenance frame parts (`P1:0BS3RV3` + `P2:-L41N-868`) +into token `0BS3RV3-L41N-868`. Submit to the console. -1. Convert stream to complex IQ (`int8` interleaved). -2. Differential FSK demod: - - sign of `imag(s[n] * conj(s[n-1]))` -3. Symbol slicing with `40` samples/symbol. -4. Find `preamble + sync` marker. -5. Parse payload, XOR-deobfuscate, verify CRC16. +--- -## Maintenance token +## Tools -Valid decoded maintenance frames include: +| Tool | Purpose | +|------|---------| +| `nc` | Capture IQ stream and connect to console | +| Python 3 + numpy | FSK demodulation and frame parsing | +| CRC16-CCITT library | Frame validation | -- `P1:0BS3RV3` -- `P2:-L41N-868` +--- -Token is: +## Solution -`0BS3RV3-L41N-868` +### Step 1 — Capture the IQ stream -## Unlock +```bash +nc 9001 > capture.raw +# Wait a few seconds, then Ctrl+C +``` + +The stream begins with a text banner: + +``` +IQ stream — int8 interleaved, samplerate=200000, encoding=2-FSK +``` + +After the banner, raw binary IQ data follows. Save after the newline. + +> 📸 `[screenshot: nc output showing the IQ stream banner before binary data]` + +### Step 2 — Demodulate the 2-FSK signal + +```python +import numpy as np + +with open("capture.raw", "rb") as f: + raw = np.frombuffer(f.read(), dtype=np.int8).astype(float) + +# Reconstruct complex samples from interleaved I/Q +samples = raw[0::2] + 1j * raw[1::2] + +# Differential FSK demodulation: sign of imag(s[n] * conj(s[n-1])) +diff = samples[1:] * np.conj(samples[:-1]) +bits_raw = (np.imag(diff) > 0).astype(int) + +# Symbol slicing at 40 samples per symbol +SAMPLES_PER_SYMBOL = 40 +symbols = [] +for i in range(0, len(bits_raw) - SAMPLES_PER_SYMBOL, SAMPLES_PER_SYMBOL): + chunk = bits_raw[i:i+SAMPLES_PER_SYMBOL] + symbols.append(int(np.mean(chunk) > 0.5)) +``` + +### Step 3 — Find and parse frames + +Look for the preamble pattern (eight `1`s then a sync marker). +Once found, read the 20-byte obfuscated payload. + +> 📸 `[screenshot: spectrogram of IQ data showing FSK burst patterns]` + +### Step 4 — XOR-deobfuscate and verify CRC + +```python +import crcmod + +crc16 = crcmod.predefined.mkCrcFun('crc-ccitt-false') + +KEY = b"WIREDMED13" + +for frame in detected_frames: + payload = bytes(frame[:20]) + deobf = bytes(b ^ KEY[i % len(KEY)] for i, b in enumerate(payload)) + + frame_type = deobf[0] + counter = deobf[1] + data = deobf[2:18] + crc = (deobf[18] << 8) | deobf[19] + + calculated = crc16(deobf[:18]) + if calculated == crc: + print(f"type={frame_type:02x} data={data}") +``` + +### Step 5 — Collect maintenance frame parts + +Valid decoded maintenance frames produce: + +``` +type=0x10 data=P1:0BS3RV3 +type=0x10 data=P2:-L41N-868 +``` + +Telemetry frames (type=0x01) are noise for this challenge. + +Token = `0BS3RV3-L41N-868` + +> 📸 `[screenshot: decoded frame output showing the two token parts]` + +### Step 6 — Submit to the console ```bash nc 31337 +``` + +```text unlock 0BS3RV3-L41N-868 ``` -Server returns the flag. +The server returns the flag. -## Automated solver +> 📸 `[screenshot: maintenance console returning the flag after unlock]` -```bash -python3 solve.py --host -``` +--- + +## Flag + +`ESPILON{sdr_fsk_w1r3d_m3d_013}`