- 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
103 lines
3.1 KiB
Markdown
103 lines
3.1 KiB
Markdown
# Accela Signal -- Solution
|
||
|
||
## Overview
|
||
A LoRa-like Chirp Spread Spectrum (CSS) IQ stream containing two types of frames:
|
||
beacon (cleartext) and data (XOR-encrypted flag). Players must implement CSS
|
||
demodulation from scratch to decode the frames.
|
||
|
||
## Steps
|
||
|
||
### 1. Capture IQ Stream
|
||
Connect to TCP port 9002. A text banner appears first, followed by raw IQ data.
|
||
```bash
|
||
nc HOST 9002 > capture.raw
|
||
# Or use the solve script
|
||
```
|
||
|
||
The banner tells you: `IQ baseband, 8000 sps, int16 LE interleaved`.
|
||
|
||
### 2. Analyze the Signal
|
||
Open the IQ data in a spectrogram tool (e.g., inspectrum, Python matplotlib, or
|
||
GNU Radio). You'll see:
|
||
- Characteristic **chirp** patterns: frequency sweeps from low to high
|
||
- Repeating preambles (identical chirps)
|
||
- Gaps of noise between transmissions
|
||
|
||
This is **Chirp Spread Spectrum (CSS)**, the modulation used by LoRa.
|
||
|
||
### 3. Determine Parameters
|
||
- Each chirp spans 128 samples → **N = 128**
|
||
- Since N = 2^SF → **SF = 7** (spreading factor)
|
||
- Bandwidth = sample rate = 8000 Hz (baseband at Nyquist)
|
||
- 7 bits per symbol
|
||
|
||
### 4. Implement Dechirping
|
||
The key to CSS demodulation is **dechirping**:
|
||
|
||
1. Generate the base upchirp (symbol 0):
|
||
```
|
||
x0[n] = exp(j * π * n²/N) for n = 0..127
|
||
```
|
||
|
||
2. To decode a received chirp, multiply by the **conjugate** of the base chirp:
|
||
```
|
||
dechirped[n] = received[n] * conj(x0[n])
|
||
```
|
||
|
||
3. Take the **DFT/FFT** of the dechirped signal. The peak bin = symbol value.
|
||
|
||
### 5. Detect Frames
|
||
Frame structure:
|
||
```
|
||
[Preamble: 8× symbol 0] [Sync: 2× downchirp] [Header: 1 symbol] [Payload: L symbols]
|
||
```
|
||
|
||
- **Preamble**: 8 consecutive chirps all decoding to symbol 0
|
||
- **Sync**: 2 downchirps (conjugate of upchirps)
|
||
- **Header**: 1 symbol = payload length in bytes (Gray-coded)
|
||
- **Payload**: L symbols encoding the data bytes
|
||
|
||
### 6. Gray Decoding
|
||
Symbol values are **Gray-coded** (like real LoRa). After finding the FFT peak
|
||
bin, apply inverse Gray code:
|
||
```python
|
||
def gray_decode(val):
|
||
mask = val
|
||
while mask:
|
||
mask >>= 1
|
||
val ^= mask
|
||
return val
|
||
```
|
||
|
||
### 7. Symbol-to-Byte Unpacking
|
||
Each symbol carries 7 bits (SF=7). Concatenate all bits from decoded symbols,
|
||
then group into 8-bit bytes.
|
||
|
||
### 8. Parse Frame Payload
|
||
Payload format: `[type:1] [data:L] [crc16:2]`
|
||
|
||
- Type 0x01 = beacon (ASCII text, for verification)
|
||
- Type 0x02 = data (XOR-encrypted flag)
|
||
- CRC-16 CCITT validates the payload
|
||
|
||
### 9. Decrypt Flag
|
||
The data frame's content is XOR'd with the repeating key `"L41N"` (4 bytes).
|
||
|
||
```python
|
||
xor_key = b"L41N"
|
||
flag = bytes(b ^ xor_key[i % 4] for i, b in enumerate(encrypted_data))
|
||
```
|
||
|
||
## Key Insights
|
||
- CSS/LoRa modulation encodes data as cyclic frequency shifts of a chirp signal
|
||
- The dechirp + FFT technique converts the frequency-domain problem into a simple peak detection
|
||
- Gray coding ensures that adjacent symbols (close FFT bins) differ by only 1 bit, reducing errors
|
||
- The 7-bit symbol → 8-bit byte packing is standard for non-byte-aligned symbol sizes
|
||
- The banner hints at CSS ("Chirp Spread Spectrum detected") to point players in the right direction
|
||
|
||
## Flag
|
||
`ESPILON{4cc3l4_ch1rp_spr34d_w1r3d}`
|
||
|
||
## Author
|
||
Eun0us
|