Numbers Station - SEC-T CTF 2026 Writeup
Description
We pulled this off an unattended transmitter. It has been running on a loop for weeks, and as far as anyone can tell it is only playing music.
It is not only playing music.
Paste it into strudel.cc and press play. Everything you can hear is a decoy.
Solution
The challenge provides some strudel.cc code.
// SEC-T CTF 2026 — intercepted broadcast, channel 7
//
// Paste into https://strudel.cc and press play.
//
// Recovered from a single unattended transmitter. The music is the music.
// The carrier is not.
setcpm(136 / 4)
// --- ground ---------------------------------------------------------------
let kick = s("bd:5")
.struct("x ~ x ~ x ~ x ~")
.bank("RolandTR909")
.shape(.35)
.gain(1.1)
let sub = note("<c1 c1 as0 gs0>")
.s("sine")
.struct("x ~ ~ ~ x ~ ~ ~")
.clip(.9)
.attack(.005).release(.18)
.gain(.9)
// --- pulse ----------------------------------------------------------------
let hats = s("hh")
.bank("RolandTR909")
.struct("x*16")
.gain(sine.range(.25, .55).fast(2))
.pan(sine.range(.35, .65).slow(3))
.hpf(7000)
let ohat = s("oh")
.bank("RolandTR909")
.struct("~ x ~ x")
.gain(.4)
.room(.3)
let clap = s("cp")
.bank("RolandTR909")
.struct("~ ~ x ~")
.room(.45).size(.7)
.gain(.7)
// --- the signal -----------------------------------------------------------
let arp = n("0 3 5 7 5 3 7 10".fast(2))
.scale("c3:phrygian")
.s("sawtooth")
.cutoff(perlin.range(400, 2400).slow(8))
.resonance(14)
.attack(.01).decay(.12).sustain(.1).release(.2)
.delay(.45).delaytime(.1875).delayfeedback(.55)
.room(.5)
.gain(.5)
let air = stack(
s("pink").gain(.16).lpf(perlin.range(600, 3000).slow(16)),
s("crackle").gain(.9).density(.6)
).room(.8)
// --- carrier --------------------------------------------------------------
// 169 steps in 4 cycles. Muted at the output stage: the
// transmitter keys it, the speaker never sees it. Whatever it is modulating,
// it is not the air.
let carrier = note("[10,13,14,15] [9,13,16] [9,13,16] [9,13,16] [9,12,16] [10,11,12,15] ~ [9,10,11,12,13,14,15,16] [9,13,16] [9,13,16] [9,13,16] [9,13,16] [9,13,16] [11,12,13,14] [10,15] [9,16] [9,16] [9,16] [10,15] [16] [16] [16] [9,10,11,12,13,14,15,16] [16] [16] [16] ~ [13] [13] [8,9,10,11,12,14,15,16,17] [8,17] [8,17] [17] [17] [10,11,12,13,14,15,16,17] [9] [9] ~ ~ ~ [10,11,12,13] [9,14] [9,14] [9,14] [10,11,12,13] ~ [10,11,12,13] [9,14] [9,14] [9,14] [10,11,12,13] ~ ~ [9,10,11,12,13,14,15,16,17] [12] [11,13] [10,14] [9] ~ [6] [6] [6] [6] [6] [6] [6] [10,11,14] [9,12,14] [9,12,14] [9,12,14] [9,10,11,12,13] ~ [14] [14] [9,10,11,12,13,14,15,16] [9,14] [9,14] ~ ~ [6] [6] [6] [6] [6] [6] [6,14] [14] [9,10,11,12,13,14,15,16] [9,14] [9,14] ~ ~ ~ [9,10,11,12,13,14,15,16,17] [13] [14] [14] [9,10,11,12,13] ~ [10,11,12,13] [9,12,14] [9,12,14] [9,12,14] [9,12,13] ~ [6] [6] [6] [6] [6] [6] [6] ~ [9,10,11,12,13,14] [14] [9,10,11,12,13,14] [14] [9,10,11,12,13,14] ~ [10,11,12,13,14] [9] [9] [9] [9,10,11,12,13,14] ~ ~ [9,12,13] [9,12,14] [9,12,14] [9,11,14] [10,11,14] ~ ~ [9,14] [9,14] [9,10,11,12,13,14,17] [9] [9] ~ [10,11,12,13] [9,10,13,14] [9,14] [9,14] [9,14] ~ ~ [8,17] [8,17] [8,9,10,11,12,14,15,16,17] [13] [13] ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~")
.slow(4)
.s("square")
.postgain(0)
// --- arrangement ----------------------------------------------------------
// The carrier runs through every section. It costs nothing to leave it in,
// since it makes no sound, and it means the transmission is on screen from the
// first bar rather than fourteen seconds in.
arrange(
[4, stack(air, sub, carrier)],
[4, stack(air, sub, kick, carrier)],
[8, stack(air, sub, kick, hats, arp.gain(.32), carrier)],
[8, stack(air, sub, kick, hats, ohat, clap, arp, carrier)],
[8, stack(air, sub, kick, hats, ohat, clap, arp, carrier)],
[4, stack(air, sub, kick, hats, carrier)],
[2, stack(air, sub, carrier)]
)
From the comments, we can see that the carrier is muted and looks very
suspicious. Since strudel.cc has multiple ways to visualize the sound, we can
use a function like pianoroll() to visualize the carrier and see if it
contains any hidden information. To get the visualization directly in the
strudel REPL, we use _pianoroll().
let carrier = note("[10,13,14,15] [9,13,16] [9,13,16] [9,13,16] [9,12,16] [10,11,12,15] ~ [9,10,11,12,13,14,15,16] [9,13,16] [9,13,16] [9,13,16] [9,13,16] [9,13,16] [11,12,13,14] [10,15] [9,16] [9,16] [9,16] [10,15] [16] [16] [16] [9,10,11,12,13,14,15,16] [16] [16] [16] ~ [13] [13] [8,9,10,11,12,14,15,16,17] [8,17] [8,17] [17] [17] [10,11,12,13,14,15,16,17] [9] [9] ~ ~ ~ [10,11,12,13] [9,14] [9,14] [9,14] [10,11,12,13] ~ [10,11,12,13] [9,14] [9,14] [9,14] [10,11,12,13] ~ ~ [9,10,11,12,13,14,15,16,17] [12] [11,13] [10,14] [9] ~ [6] [6] [6] [6] [6] [6] [6] [10,11,14] [9,12,14] [9,12,14] [9,12,14] [9,10,11,12,13] ~ [14] [14] [9,10,11,12,13,14,15,16] [9,14] [9,14] ~ ~ [6] [6] [6] [6] [6] [6] [6,14] [14] [9,10,11,12,13,14,15,16] [9,14] [9,14] ~ ~ ~ [9,10,11,12,13,14,15,16,17] [13] [14] [14] [9,10,11,12,13] ~ [10,11,12,13] [9,12,14] [9,12,14] [9,12,14] [9,12,13] ~ [6] [6] [6] [6] [6] [6] [6] ~ [9,10,11,12,13,14] [14] [9,10,11,12,13,14] [14] [9,10,11,12,13,14] ~ [10,11,12,13,14] [9] [9] [9] [9,10,11,12,13,14] ~ ~ [9,12,13] [9,12,14] [9,12,14] [9,11,14] [10,11,14] ~ ~ [9,14] [9,14] [9,10,11,12,13,14,17] [9] [9] ~ [10,11,12,13] [9,10,13,14] [9,14] [9,14] [9,14] ~ ~ [8,17] [8,17] [8,9,10,11,12,14,15,16,17] [13] [13] ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~")
.slow(4)
.s("square")
._pianoroll()
.postgain(0)
Playing this updated version, we get the flag as output from the visualization.

Flag: SECT{look_at_the_music}