ESPILON-CTF-2026-Writeups/Misc/Accela_Signal/README.md

103 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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