Challenge Information
- Category: Reverse / Audio Forensics
- Description:
Joyce and Murray intercepted a faint audio signal originating from a high-security Russian prison in Kamchatka. It sounds like someone is rhythmically banging on a heating pipe. Decode the message to find out who is trapped inside.
Files Provided
Analysis
The “rhythmically banging” description is a clear hint for Morse code. The WAV file contains sharp, impulsive tapping sounds separated by varying intervals.
1. Visualization
Generating a waveform plot confirms the rhythmic structure:
ffmpeg -i kamchatka_taps.wav -lavfi "showwavespic=s=2000x400:colors=red" waveform.png
2. Automated Decoding
A Python script was used to:
- Load the audio data and calculate the amplitude envelope.
- Apply smoothing (moving average) to merge the sharp transients into measurable pulses.
- Detect pulse durations and gaps.
- Categorize pulses into dots and dashes, and gaps into letter and word separators.
Solution
Solver:
import numpy as np
from scipy.io import wavfile
from scipy.ndimage import uniform_filter1d
# Load the wav file
sample_rate, data = wavfile.read("/home/eph/CTF/2026/Stranger's CTF - Techtrix '26/reverse/The Kamchatka Taps/kamchatka_taps.wav")
if data.dtype == np.int16:
data = data.astype(np.float32) / 32768.0
elif data.dtype == np.int32:
data = data.astype(np.float32) / 2147483648.0
# Amplitude envelope
amplitude = np.abs(data)
# SMOOTHING: Moving average to join impulsive taps
# A typical dot should be ~50-100ms.
# Window of 10ms (441 samples)
window_size = int(0.01 * sample_rate)
smoothed = uniform_filter1d(amplitude, size=window_size)
max_amp = np.max(smoothed)
threshold = 0.3 * max_amp
active = smoothed > threshold
# Find pulses
transitions = np.diff(active.astype(np.int8))
starts = np.where(transitions == 1)[0]
ends = np.where(transitions == -1)[0]
if len(active) > 0 and active[0]: starts = np.insert(starts, 0, 0)
if len(active) > 0 and active[-1]: ends = np.append(ends, len(active)-1)
min_len = min(len(starts), len(ends))
starts, ends = starts[:min_len], ends[:min_len]
pulses = list(zip(starts, ends))
pulse_lengths = [e - s for s, e in pulses]
print(f"Total smoothed pulses: {len(pulses)}")
if pulses:
# Meaningful pulses > 500 samples (~11ms)
filtered_indices = [i for i, l in enumerate(pulse_lengths) if l > 500]
meaningful_lengths = [pulse_lengths[i] for i in filtered_indices]
print(f"Meaningful pulses (>500 samples): {len(meaningful_lengths)}")
if len(meaningful_lengths) > 5:
sorted_m = sorted(meaningful_lengths)
dot_val = sorted_m[int(len(sorted_m)*0.3)]
dash_val = sorted_m[int(len(sorted_m)*0.8)]
print(f"Estimated dot: {dot_val}, dash: {dash_val}")
mid = (dot_val + dash_val) / 2
morse_string = ""
for idx in range(len(filtered_indices)):
i = filtered_indices[idx]
l = pulse_lengths[i]
morse_string += "." if l < mid else "-"
if idx < len(filtered_indices) - 1:
next_i = filtered_indices[idx+1]
gap = starts[next_i] - ends[i]
# Morse Timing: element=1u, letter=3u, word=7u
# Dot is approx 'dot_val'
if gap > 1.5 * dot_val:
if gap > 4 * dot_val:
morse_string += " "
else:
morse_string += " "
print(f"Morse: {morse_string}")
MORSE_CODE_DICT = { 'A':'.-', 'B':'-...', 'C':'-.-.', 'D':'-..', 'E':'.', 'F':'..-.', 'G':'--.', 'H':'....', 'I':'..', 'J':'.---', 'K':'-.-', 'L':'.-..', 'M':'--', 'N':'-.', 'O':'---', 'P':'.--.', 'Q':'--.-', 'R':'.-.', 'S':'...', 'T':'-', 'U':'..-', 'V':'...-', 'W':'.--', 'X':'-..-', 'Y':'-.--', 'Z':'--..', '1':'.----', '2':'..---', '3':'...--', '4':'....-', '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.', '0':'-----'}
words = []
for w in morse_string.split(" "):
chars = ""
for c in w.split(" "):
if not c: continue
found = False
for k, v in MORSE_CODE_DICT.items():
if v == c:
chars += k
found = True
break
if not found: chars += "?"
words.append(chars)
print(f"Decoded: {' '.join(words)}")
The processed signal revealed the following Morse sequence:
-.-. - ..-. - .... . .- -- . .-. .. -.-. .- -. .. ... .- .-.. .. ...- .
Decoded text:
CTF THE AMERICAN IS ALIVE
The message identifies the trapped prisoner as “THE AMERICAN” (Hopper). Following the challenge format:
Flag
CTF{THE AMERICAN IS ALIVE}