Audio steganography: spectrograms, LSB, and signals that are not music
A layered workflow for audio challenges - what the waveform tells you, why the spectrogram is almost always the first move, decoding DTMF and SSTV and Morse, and the LSB and metadata tricks that hide in a WAV.
An audio challenge hands you a file that sounds like noise, static, a phone call, or a very short piece of music. As with image steganography, the productive approach is layered: check the container, then the structure, then the signal, then the samples. Each layer is cheap and rules out a whole family.
One difference from images is worth stating early. Listening is a real diagnostic step. A trained ear identifies DTMF, SSTV, Morse and modem tones instantly, and those four account for a large share of audio challenges. Play the file before you open anything.
Layer 1: the container
file chal.wav
ffprobe -hide_banner chal.wav # sample rate, channels, duration, codec
exiftool chal.wav # tags, comments, and anything appended
binwalk chal.wav # a file inside the file
xxd chal.wav | tail -20 # data after the last chunk- A WAV is a RIFF file - a header and a series of chunks. Anything after the declared
datachunk length is not audio, and players ignore it. That is the single most common hiding place in a WAV. - MP3 and FLAC carry tags - ID3, Vorbis comments - which hold arbitrary binary.
exiftoolandmetaflac --listshow them. - Duration mismatch is a tell. If
ffprobereports a longer duration than the file plays, or the file is far larger than the sample rate and duration imply, something else is in there. - An unusual sample rate - 8,000 Hz for something musical, or 192,000 Hz for speech - is a hint that the data was written by a script rather than recorded.
Layer 2: the spectrogram
A spectrogram plots frequency against time with intensity as brightness. It is the highest-yield single view in audio forensics because a huge share of challenges write the flag *as an image* into the frequency domain - text that is inaudible but plainly visible once you look at the right picture.
| What you see | What it is |
|---|---|
| Readable text drawn in the spectrum | The flag, written directly. Done. |
| Two alternating tone pairs | DTMF - telephone keypad digits. |
| Bands sweeping in a repeating pattern | SSTV - slow-scan television, an image transmitted as audio. |
| Short and long bursts at one frequency | Morse code. |
| A solid block of energy above the audible range | Data hidden where you cannot hear it. Filter and look closer. |
| Regular vertical stripes | A digital modulation - FSK, PSK, or a modem. Try minimodem or a GNU Radio flow. |
| Nothing but broadband noise | Move to layer 3. The signal is in the samples, not the spectrum. |
Layer 3: decoding a signal
DTMF
Each keypad digit is the sum of two sine tones, one from a low group and one from a high group. On a spectrogram it is unmistakable: two horizontal lines per digit, in short bursts. Decoding is a lookup once you have the frequency pairs.
| 1209 Hz | 1336 Hz | 1477 Hz | |
|---|---|---|---|
| 697 Hz | 1 | 2 | 3 |
| 770 Hz | 4 | 5 | 6 |
| 852 Hz | 7 | 8 | 9 |
| 941 Hz | * | 0 | # |
SSTV
Slow-scan television encodes an image line by line as an FM-modulated audio tone, with a sync pulse between lines. The spectrogram shows a distinctive repeating ramp. Decode with QSSTV, RX-SSTV, or sstv in Python - you usually do not need to identify the exact mode, since the decoders detect it from the VIS header.
Morse
A single tone switching on and off. Read it off the spectrogram if it is short, or use a decoder. The common trap is that the tone may not be audible - Morse written at 18 kHz is inaudible to most people and perfectly clear on a spectrogram.
Modems and digital modes
minimodem handles Bell 103/202 and arbitrary-baud FSK: minimodem -r --auto-carrier 1200 < chal.wav. multimon-ng covers POCSAG, AFSK, and a dozen amateur-radio modes and is worth running blind - it identifies what it can and costs one command. Anything more exotic is a GNU Radio problem and, in a CTF, usually a hint in the challenge description.
Layer 4: the samples themselves
When the spectrum is empty, the data is in the sample values. WAV audio is uncompressed PCM, so LSB steganography works exactly as it does in a bitmap - and it is inaudible, because flipping the bottom bit of a 16-bit sample changes the amplitude by one part in 32,768.
import wave
with wave.open("chal.wav") as w:
frames = w.readframes(w.getnframes())
print(w.getparams())
# 16-bit mono: take the low bit of every second byte (little-endian samples).
bits = "".join(str(frames[i] & 1) for i in range(0, len(frames), 2))
data = bytes(int(bits[i:i+8], 2) for i in range(0, len(bits) - 7, 8))
print(data[:200])- Channel differencing. If the two channels are nearly identical, subtract them. What remains is whatever was added to one side, and it is often the entire message.
- Reversed audio. Playing backwards is a real challenge trick and takes one command.
- Speed and pitch shifts. A voice slowed by 16x is unintelligible and sped back up is clear. Same for a signal recorded at the wrong rate: reinterpreting 8 kHz data as 44.1 kHz is one flag in
sox. - Phase encoding and echo hiding exist but are rare in CTF, because they need a decoder the challenge would have to supply.
- Raw sample data as an image. If the samples are not audio at all, reshape them into a bitmap and look. This is the audio version of the file-type confusion problem.
Tool-specific formats
Some audio stego is produced by a named tool and needs that tool to reverse. Worth trying blind, since each is one command:
- Steghide works on WAV and AU as well as JPEG. Try an empty passphrase first, then crack it with
stegseekagainst rockyou - the same approach as hash cracking. - DeepSound is a Windows tool that hides files in WAV and FLAC, with an optional password. Its container has a recognisable signature.
- Sonic Visualiser is not a stego tool but is the best free viewer for exploring a file interactively, with a spectrogram layer you can tune while the audio plays.
- Audacity remains useful for the manual operations: reverse, change speed, split stereo, and view the spectrogram of a selection.
The audio-forensics workflow
- Listen to it. Thirty seconds, and it identifies DTMF, Morse, SSTV or a modem outright.
file,exiftool,binwalk, and check for data past the declared chunk length.- Spectrogram, sweeping the FFT size and looking at the full frequency range including above 16 kHz.
- If there is a recognisable signal, decode it with the matching tool rather than by eye.
- If the spectrum is empty, go to the samples: LSB, per channel, both bit orders.
- Try channel differencing, reversal, and speed changes - each is one command.
- Try steghide with an empty password, then with a wordlist.
- If all of that is clean, reconsider whether it is audio at all. A
.wavheader on non-audio data is a container trick, not a stego one.
The reason to keep the order is that the layers cost wildly different amounts. Listening is free, a spectrogram is ten seconds, and a full LSB sweep across channels and bit orders is a few minutes of scripting. Working upwards means most challenges end in the first two steps.