Flag:
MCTF25{1_t0O_can_533_50uNdwav35}
We’re given a single file:
output.wav – a short “looping message from deep space”.We’re told that flags are always in the format:
MCTF25{flag}
So the goal is to pull some kind of data out of the audio and recover the flag.
On the Ubuntu VM:
# Optional: install tools
sudo apt update
sudo apt install audacity -y
# Open the file in Audacity
audacity output.wav &
Switch Audacity to a spectrogram view:
Spectrogram → Spectrogram.You should now see:
This strongly suggests a multi-frequency digital encoding:
From the spectrogram:
So each symbol:
That gives us values from 0–127, i.e. 7-bit ASCII.
If you look at the very start of the file, you can see a clearly structured pattern:
So the plan:
Below is a simplified version of a Python script that does the decoding.
(You might tweak the exact frequencies & thresholds based on your measurements.)
import wave
import numpy as np
FILENAME = "output.wav"
# Parameters (measured from the spectrogram)
SAMPLE_RATE = 44100
SYMBOL_DURATION = 0.1 # seconds per symbol (time between clock ticks)
N_SAMPLES_PER_SYMBOL = int(SYMBOL_DURATION * SAMPLE_RATE)
# Frequencies (one clock + 7 data tones) – approximate values from the spectrogram
CLOCK_FREQ = 11250
DATA_FREQS = [
9000, 9300, 9600, 9900,
10200, 10500, 10800
]
def goertzel(samples, freq, sr):
"""Energy at a specific frequency using the Goertzel algorithm."""
n = len(samples)
k = int(0.5 + (n * freq / sr))
w = 2.0 * np.pi * k / n
cos_w = np.cos(w)
sin_w = np.sin(w)
coeff = 2.0 * cos_w
s_prev = 0
s_prev2 = 0
for x in samples:
s = x + coeff * s_prev - s_prev2
s_prev2 = s_prev
s_prev = s
power = s_prev2**2 + s_prev**2 - coeff * s_prev * s_prev2
return power
# --- Load WAV ---
with wave.open(FILENAME, "rb") as w:
assert w.getnchannels() == 1
assert w.getframerate() == SAMPLE_RATE
raw = w.readframes(w.getnframes())
# Convert 8-bit unsigned PCM to float centered at 0
data = np.frombuffer(raw, dtype=np.uint8).astype(np.float32) - 128.0
# --- Split into symbols and decode ---
symbols = len(data) // N_SAMPLES_PER_SYMBOL
values = []
for i in range(symbols):
start = i * N_SAMPLES_PER_SYMBOL
end = start + N_SAMPLES_PER_SYMBOL
chunk = data[start:end] * np.hanning(N_SAMPLES_PER_SYMBOL)
# (Optional) Check clock power to verify this is a valid symbol
clock_power = goertzel(chunk, CLOCK_FREQ, SAMPLE_RATE)
if clock_power < 1e6: # adjust threshold as needed
continue
bits = []
for f in DATA_FREQS:
p = goertzel(chunk, f, SAMPLE_RATE)
bits.append(1 if p > 1e6 else 0) # threshold tuned by trial
# Convert 7 bits (LSB first) to integer
val = 0
for idx, b in enumerate(bits):
val |= (b << idx)
values.append(val)
# First 128 symbols are training (0..127)
payload_vals = values[128:]
# Convert to text
text = "".join(chr(v) for v in payload_vals)
print(text)
Running this gives a long text starting with a warped Lorem ipsum-style paragraph.
Somewhere in the middle you’ll see:
... lorem ipsum bla bla MCTF25{1_t0O_can_533_50uNdwav35} ...
So the flag is clearly embedded as a normal ASCII substring.
Extracting the {...} part gives us the final answer:
MCTF25{1_t0O_can_533_50uNdwav35}