Freakquency
Challenge
Content preserved from the original writeup source. Minimal normalization was applied to fit platform format.
Solution
Original Writeup Content (Preserved)
Freakquency (UDCTF) Writeup
Initial Challenge Prompt
- Name: Freakquency
- Points: 100
- Difficulty: Medium-Hard
- Prompt: You have the audacity to match my freak?
- Given file: hidden_message.wav
Challenge Summary
This challenge looks like digital audio at first glance (close tones around ~1.5 kHz can suggest FSK/phase-coded content), but the actual solve path is visual steganography in the spectrogram. The hidden payload appears as readable text in a higher frequency band.
Files
- hidden_message.wav
- spec_full.png (plus zoom/helper spectrogram images)
Solve Path
- Inspect audio metadata.
- Try decoder hypotheses (optional): low-shift FSK/clocked demod can be a false lead.
- Inspect full-band spectrogram with higher contrast.
- Read embedded Base64 string.
- Decode Base64 to recover the flag.
1) Inspect Audio
Commands:
file hidden_message.wav soxi hidden_message.wav
Relevant metadata:
- Mono
- 44100 Hz sample rate
- 32-bit floating PCM
- ~27 s duration
2) Spectrogram Forensics
Using the provided full spectrogram image (or generating one yourself), the hidden text is visible around the 4-6 kHz region.
Spectrogram Used and Why
We used an STFT spectrogram with these settings:
- Fs: 44100 (original sample rate, no resampling)
- NFFT: 2048
- noverlap: 1024 (50% overlap)
- cmap: gray (high contrast for bright text strokes)
- y-limit: 0 to 22050 Hz (full Nyquist band)
Why these settings work well:
- NFFT 2048 gives a useful balance between frequency detail and time sharpness. It is enough to make letter shapes readable in the 4-6 kHz band.
- 50% overlap reduces blockiness and makes character edges smoother across time.
- Full-band view avoids tunnel vision around ~1.5 kHz and exposes hidden content at higher frequencies.
- Grayscale mapping improves legibility of faint strokes versus many rainbow colormaps.
Focused code for the spectrogram step:
#!/usr/bin/env python3 import matplotlib.pyplot as plt from scipy.io import wavfile
sr, data = wavfile.read("hidden_message.wav") if getattr(data, "ndim", 1) > 1: data = data[:, 0]
plt.figure(figsize=(16, 6)) plt.specgram(data, Fs=sr, NFFT=2048, noverlap=1024, cmap="gray") plt.ylim(0, 22050) plt.xlabel("Time (s)") plt.ylabel("Hz") plt.colorbar(label="dB") plt.tight_layout() plt.savefig("spec_fullband.png", dpi=200) print("saved spec_fullband.png")
Extracted text:
VURDVEZ7dzB3X3kwdV9jNG5faDM0cl80X2YxbDM/fQ==
Note on OCR pitfalls:
- The tail is .../fQ== (slash then fQ), not .../v fQ== with a space.
- Correct decode starts with UDCTF, not URCTF.
3) Decode Base64
One-liner:
echo 'VURDVEZ7dzB3X3kwdV9jNG5faDM0cl80X2YxbDM/fQ==' | base64 -d
Output:
UDCTF{w0w_y0u_c4n_h34r_4_f1l3?}
Full Solve Code (Reproducible)
#!/usr/bin/env python3
import base64
import argparse
import matplotlib.pyplot as plt
from scipy.io import wavfile
def make_spectrogram(path_in: str, path_out: str = "spec_out.png") -> None:
sr, data = wavfile.read(path_in)
if getattr(data, "ndim", 1) > 1:
data = data[:, 0]
plt.figure(figsize=(16, 6))
plt.specgram(data, Fs=sr, NFFT=2048, noverlap=1024, cmap="gray")
plt.ylim(0, 22050)
plt.xlabel("Time (s)")
plt.ylabel("Hz")
plt.colorbar(label="dB")
plt.tight_layout()
plt.savefig(path_out, dpi=200)
print(f"Saved spectrogram to {path_out}")
def decode_payload() -> str:
b64_payload = "VURDVEZ7dzB3X3kwdV9jNG5faDM0cl80X2YxbDM/fQ=="
return base64.b64decode(b64_payload).decode()
def main() -> None:
parser = argparse.ArgumentParser(description="Freakquency solver helper")
parser.add_argument("wav", nargs="?", default="hidden_message.wav", help="Input WAV file")
parser.add_argument("--spec-out", default="spec_out.png", help="Output spectrogram image")
args = parser.parse_args()
make_spectrogram(args.wav, args.spec_out)
print("Decoded flag:", decode_payload())
if __name__ == "__main__":
main()
Run it:
python3 solve_freakquency.py hidden_message.wav --spec-out spec_out.png
Final Flag
UDCTF{w0w_y0u_c4n_h34r_4_f1l3?}