FM Hunting
FM Hunting
Platform: ASIS CTF | Category: Hardware | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2025-12-27 | Status: Solved Techniques: Baud rate detection from signal analysis, Envelope detection via Hilbert transform, FSK demodulation with bandpass filtering, XOR decryption with extracted key
Summary
Task: ASIS Hardware "FM Hunting" — radio hunting. Solution: FM radio signal analysis, frequency identification.
FM Hunting - ASIS CTF
Description
A wayward field node on 65.109.194.34:7356 spills raw baseband IQ at 48 kHz. It's a simple 2-FSK beacon. Follow the payload to get the flag!
Analysis
Step 1: Data Capture
Connected to the server and captured raw IQ data:
bash read-only • copy allowednc 65.109.194.34 7356 > raw_iq.bin
The data was 16-bit audio samples at 48 kHz sample rate.
Step 2: Signal Analysis
Analyzed the captured data using spectrogram and frequency analysis:
- Sample rate: 48 kHz
- Modulation: 2-FSK (Binary Frequency Shift Keying)
- Tone frequencies:
- Low tone: ~500 Hz (represents bit 0)
- High tone: ~23500 Hz (represents bit 1)
- Baud rate: 100 baud (480 samples per bit)
Step 3: FSK Demodulation
Used bandpass filters to isolate each tone and envelope detection to extract the binary signal:
python read-only • copy allowedimport numpy as np from scipy.signal import butter, filtfilt, hilbert from scipy.io import wavfile # Read audio data sample_rate, data = wavfile.read('audio_16bit.wav') # Bandpass filter parameters def bandpass_filter(data, lowcut, highcut, fs, order=5): nyq = 0.5 * fs low = lowcut / nyq high = highcut / nyq b, a = butter(order, [low, high], btype='band') return filtfilt(b, a, data) # Filter for each tone low_tone = bandpass_filter(data, 400, 600, sample_rate) # ~500 Hz high_tone = bandpass_filter(data, 23000, 24000, sample_rate) # ~23500 Hz # Envelope detection using Hilbert transform low_envelope = np.abs(hilbert(low_tone)) high_envelope = np.abs(hilbert(high_tone)) # Determine bits based on which envelope is stronger samples_per_bit = 480 # 48000 Hz / 100 baud bits = [] for i in range(0, len(data) - samples_per_bit, samples_per_bit): low_power = np.mean(low_envelope[i:i+samples_per_bit]) high_power = np.mean(high_envelope[i:i+samples_per_bit]) bits.append(1 if high_power > low_power else 0)
Step 4: Bit to Byte Conversion
Converted bits to bytes using LSB-first ordering:
python read-only • copy alloweddef bits_to_bytes_lsb(bits): result = [] for i in range(0, len(bits) - 7, 8): byte = 0 for j in range(8): byte |= (bits[i + j] << j) # LSB first result.append(byte) return bytes(result) decoded = bits_to_bytes_lsb(bits)
Step 5: Message Analysis
The decoded message contained:
- Text: "This file contains a message encrypted with XOR"
- KEY:{0lymp1c} at offset 347
- Encrypted FLAG at offset 659
Step 6: XOR Decryption
python read-only • copy allowedkey = b'0lymp1c' encrypted_flag = decoded[659:659+24] # FLAG: section def xor_decrypt(data, key): return bytes([data[i] ^ key[i % len(key)] for i in range(len(data))]) flag = xor_decrypt(encrypted_flag, key) print(flag.decode()) # ASIS{REDACTED}
Solution
Complete demodulation and decryption script:
python read-only • copy allowed#!/usr/bin/env python3 """ ASIS CTF - FM Hunting 2-FSK Demodulation and XOR Decryption """ import numpy as np from scipy.signal import butter, filtfilt, hilbert from scipy.io import wavfile def bandpass_filter(data, lowcut, highcut, fs, order=5): """Apply bandpass filter to isolate frequency band""" nyq = 0.5 * fs low = lowcut / nyq high = highcut / nyq b, a = butter(order, [low, high], btype='band') return filtfilt(b, a, data) def demodulate_fsk(audio_file, low_freq=500, high_freq=23500, baud_rate=100): """Demodulate 2-FSK signal""" sample_rate, data = wavfile.read(audio_file) # Normalize if stereo if len(data.shape) > 1: data = data[:, 0] # Bandpass filter for each tone low_tone = bandpass_filter(data, low_freq - 100, low_freq + 100, sample_rate) high_tone = bandpass_filter(data, high_freq - 500, high_freq + 500, sample_rate) # Envelope detection low_envelope = np.abs(hilbert(low_tone)) high_envelope = np.abs(hilbert(high_tone)) # Extract bits samples_per_bit = sample_rate // baud_rate bits = [] for i in range(0, len(data) - samples_per_bit, samples_per_bit): low_power = np.mean(low_envelope[i:i+samples_per_bit]) high_power = np.mean(high_envelope[i:i+samples_per_bit]) bits.append(1 if high_power > low_power else 0) return bits def bits_to_bytes_lsb(bits): """Convert bits to bytes (LSB first)""" result = [] for i in range(0, len(bits) - 7, 8): byte = 0 for j in range(8): byte |= (bits[i + j] << j) result.append(byte) return bytes(result) def xor_decrypt(data, key): """XOR decrypt with repeating key""" return bytes([data[i] ^ key[i % len(key)] for i in range(len(data))]) def main(): # Demodulate FSK signal bits = demodulate_fsk('audio_16bit.wav') decoded = bits_to_bytes_lsb(bits) # Save decoded data with open('decoded.bin', 'wb') as f: f.write(decoded) # Find and extract key key_marker = b'KEY:{' key_start = decoded.find(key_marker) if key_start != -1: key_end = decoded.find(b'}', key_start) key = decoded[key_start + len(key_marker):key_end] print(f"Found key: {key.decode()}") # Find and decrypt flag flag_marker = b'FLAG:' flag_start = decoded.find(flag_marker) if flag_start != -1: # Extract encrypted flag bytes after marker encrypted = decoded[flag_start + len(flag_marker):flag_start + len(flag_marker) + 50] # Remove any padding/null bytes encrypted = encrypted.split(b'\x00')[0] decrypted = xor_decrypt(encrypted, key) print(f"Decrypted flag: {decrypted.decode()}") if __name__ == '__main__': main()
Notes
- Flag meaning: "XOR ALL THE THINGS" in leet-speak (X0R = XOR, A11 = ALL, 7H3 = THE, 7H1NG5 = THINGS)
- 2-FSK: Binary Frequency Shift Keying uses two frequencies to represent 0 and 1
- Hilbert transform: Used for envelope detection to extract amplitude of filtered signals
- LSB-first: Least Significant Bit first ordering for byte reconstruction
- Baud rate detection: 48000 Hz / 480 samples per bit = 100 baud
References
- FSK Demodulation
- Hilbert Transform for Envelope Detection
- Bandpass Filter Design