Skip to content
All posts
stegorevised July 31, 20267 min read

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 data chunk 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. exiftool and metaflac --list show them.
  • Duration mismatch is a tell. If ffprobe reports 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 seeWhat it is
Readable text drawn in the spectrumThe flag, written directly. Done.
Two alternating tone pairsDTMF - telephone keypad digits.
Bands sweeping in a repeating patternSSTV - slow-scan television, an image transmitted as audio.
Short and long bursts at one frequencyMorse code.
A solid block of energy above the audible rangeData hidden where you cannot hear it. Filter and look closer.
Regular vertical stripesA digital modulation - FSK, PSK, or a modem. Try minimodem or a GNU Radio flow.
Nothing but broadband noiseMove 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 Hz1336 Hz1477 Hz
697 Hz123
770 Hz456
852 Hz789
941 Hz*0#
The 1633 Hz column (A-D) exists and almost never appears in a challenge.

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])
Try the other orderings too: MSB-first bit packing, the high byte instead of the low, and one channel at a time on a stereo file. Stereo is a frequent trick - the payload is in the right channel only, and reading both interleaved gives noise.
  • 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 stegseek against 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

  1. Listen to it. Thirty seconds, and it identifies DTMF, Morse, SSTV or a modem outright.
  2. file, exiftool, binwalk, and check for data past the declared chunk length.
  3. Spectrogram, sweeping the FFT size and looking at the full frequency range including above 16 kHz.
  4. If there is a recognisable signal, decode it with the matching tool rather than by eye.
  5. If the spectrum is empty, go to the samples: LSB, per channel, both bit orders.
  6. Try channel differencing, reversal, and speed changes - each is one command.
  7. Try steghide with an empty password, then with a wordlist.
  8. If all of that is clean, reconsider whether it is audio at all. A .wav header 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.