CTF stego methodology
0. CRITICAL — never Read an unvalidated image
Per CLAUDE.md: reading a corrupt image poisons the conversation. Validate first:
run_tool("file", "/path/to/image")
run_tool("identify", "/path/to/image") # ImageMagick
# or run_script:
# from PIL import Image; img = Image.open(path); print(img.size, img.mode)
Only Read the image after both checks pass.
1. Triage every input
file <file>
xxd <file> | head -30
exiftool <file> # metadata — flag often hides here
strings <file> | grep -i "ctf\|flag\|key\|pass" | head
binwalk <file> # multi-file polyglots
Always check metadata first. ~30% of intro stego is just exiftool.
2. Image (PNG / JPG / BMP / GIF)
| Technique |
Tool / approach |
| LSB in pixels |
zsteg (PNG/BMP), stegsolve |
| EOF data after IEND/EOI |
binwalk -e, manual hex |
| JPG specific |
steghide extract -sf <file> (often with empty pass), stegseek for brute |
| PNG color planes |
stegsolve (visual layer toggle) |
| LSB matching, custom |
zsteg -a, then write custom Python with PIL |
| Polyglot (file with valid headers for multiple formats) |
binwalk, foremost |
| File appended after IEND chunk (PNG) |
pngcheck -v, manual carve |
| Modified/extra chunks |
pngcheck -v, pngcsum |
| Width/height tampering (PNG) |
edit IHDR width/height, recalc CRC — tweakpng style |
# zsteg covers most LSB cases for PNG/BMP
zsteg -a image.png
# steghide brute (jpg/wav/bmp)
stegseek image.jpg rockyou.txt
# Multi-tool sweep
stegsolve image.png # GUI, manual layer toggle
3. Audio (WAV / MP3 / FLAC / OGG)
| Technique |
Tool |
| LSB in samples |
wavsteg, custom python |
| Steghide payload |
steghide, stegseek |
| Spectrogram (visual hidden in freq domain) |
audacity view spectrogram, sonic-visualiser |
| DTMF tones |
multimon-ng -a DTMF, manual tone analysis |
| Morse |
listen by ear, then morse-decoder or manual |
| SSTV (slow-scan TV in audio) |
qsstv, slowrx |
# Spectrogram via sox
sox audio.wav -n spectrogram -o spec.png
# Then validate spec.png before reading (see step 0)
# DTMF
multimon-ng -a DTMF audio.wav
4. Video / GIF
# Extract frames
ffmpeg -i video.mp4 -vf fps=1 frames/frame_%04d.png
# Per-frame stego
for f in frames/*.png; do zsteg -a "$f" 2>/dev/null | grep -i "flag\|ctf"; done
# Audio track
ffmpeg -i video.mp4 -vn audio.wav
# Then audio analysis
5. Text / Unicode
| Technique |
Tool |
| Zero-width characters |
zwsp-steg, manual unicode inspect |
| Whitespace stego |
snow, stegsnow |
| Homoglyph substitution |
unicode normalize + diff |
| Base-N nested encodings |
ciphey, manual |
| Bacon / null cipher |
manual + dcode.fr |
# Show every character including invisibles
python3 -c "import sys; [print(repr(c), hex(ord(c))) for c in open(sys.argv[1]).read()]" file.txt
6. Less common
- PDF:
pdf-parser, peepdf, qpdf --qdf — check streams, annotations, JS, attachments
- Office docs:
oletools (olevba, oleid), oledump, unzip the .docx/.xlsx
- QR / barcode:
zbarimg, zxing — also try partial QR reconstruction
- DNA/protein sequences: ROT/encoding tricks, decode A/C/G/T as base-4
7. Default workflow
exiftool + strings + binwalk -e always first
- If image →
zsteg -a (PNG/BMP) OR stegseek (JPG)
- If audio → spectrogram view
- If nothing obvious →
stegsolve GUI for visual analysis
- If still nothing → metadata might encode the answer (look at GPS, comment, software field)
8. Don't waste time on
- LSB scripts when
zsteg exists
- Manually viewing spectrograms in matplotlib when audacity/sonic-visualiser exist
- Reading the image with
Read before validation (see step 0)
After solve
Use the writeup-template skill.
1---2name: ctf-stego3description: Use when solving steganography CTF challenges — hidden data in images (PNG/JPG/BMP), audio (WAV/MP3), video, or text. Triggers on "ctf stego", "steganography", "hidden in image", "audio stego", "lsb".4---56# CTF stego methodology78## 0. CRITICAL — never `Read` an unvalidated image910Per CLAUDE.md: reading a corrupt image poisons the conversation. Validate first:1112```bash13run_tool("file", "/path/to/image")14run_tool("identify", "/path/to/image") # ImageMagick15# or run_script:16# from PIL import Image; img = Image.open(path); print(img.size, img.mode)17```1819Only `Read` the image after both checks pass.2021## 1. Triage every input2223```bash24file <file>25xxd <file> | head -3026exiftool <file> # metadata — flag often hides here27strings <file> | grep -i "ctf\|flag\|key\|pass" | head28binwalk <file> # multi-file polyglots29```3031Always check metadata first. ~30% of intro stego is just `exiftool`.3233## 2. Image (PNG / JPG / BMP / GIF)3435| Technique | Tool / approach |36| --- | --- |37| LSB in pixels | `zsteg` (PNG/BMP), `stegsolve` |38| EOF data after IEND/EOI | `binwalk -e`, manual hex |39| JPG specific | `steghide extract -sf <file>` (often with empty pass), `stegseek` for brute |40| PNG color planes | `stegsolve` (visual layer toggle) |41| LSB matching, custom | `zsteg -a`, then write custom Python with PIL |42| Polyglot (file with valid headers for multiple formats) | `binwalk`, `foremost` |43| File appended after IEND chunk (PNG) | `pngcheck -v`, manual carve |44| Modified/extra chunks | `pngcheck -v`, `pngcsum` |45| Width/height tampering (PNG) | edit IHDR width/height, recalc CRC — `tweakpng` style |4647```bash48# zsteg covers most LSB cases for PNG/BMP49zsteg -a image.png5051# steghide brute (jpg/wav/bmp)52stegseek image.jpg rockyou.txt5354# Multi-tool sweep55stegsolve image.png # GUI, manual layer toggle56```5758## 3. Audio (WAV / MP3 / FLAC / OGG)5960| Technique | Tool |61| --- | --- |62| LSB in samples | `wavsteg`, custom python |63| Steghide payload | `steghide`, `stegseek` |64| Spectrogram (visual hidden in freq domain) | `audacity` view spectrogram, `sonic-visualiser` |65| DTMF tones | `multimon-ng -a DTMF`, manual tone analysis |66| Morse | listen by ear, then `morse-decoder` or manual |67| SSTV (slow-scan TV in audio) | `qsstv`, `slowrx` |6869```bash70# Spectrogram via sox71sox audio.wav -n spectrogram -o spec.png72# Then validate spec.png before reading (see step 0)7374# DTMF75multimon-ng -a DTMF audio.wav76```7778## 4. Video / GIF7980```bash81# Extract frames82ffmpeg -i video.mp4 -vf fps=1 frames/frame_%04d.png8384# Per-frame stego85for f in frames/*.png; do zsteg -a "$f" 2>/dev/null | grep -i "flag\|ctf"; done8687# Audio track88ffmpeg -i video.mp4 -vn audio.wav89# Then audio analysis90```9192## 5. Text / Unicode9394| Technique | Tool |95| --- | --- |96| Zero-width characters | `zwsp-steg`, manual unicode inspect |97| Whitespace stego | `snow`, `stegsnow` |98| Homoglyph substitution | unicode normalize + diff |99| Base-N nested encodings | `ciphey`, manual |100| Bacon / null cipher | manual + dcode.fr |101102```bash103# Show every character including invisibles104python3 -c "import sys; [print(repr(c), hex(ord(c))) for c in open(sys.argv[1]).read()]" file.txt105```106107## 6. Less common108109- **PDF:** `pdf-parser`, `peepdf`, `qpdf --qdf` — check streams, annotations, JS, attachments110- **Office docs:** `oletools` (`olevba`, `oleid`), `oledump`, unzip the .docx/.xlsx111- **QR / barcode:** `zbarimg`, `zxing` — also try partial QR reconstruction112- **DNA/protein sequences:** ROT/encoding tricks, decode A/C/G/T as base-4113114## 7. Default workflow1151161. `exiftool` + `strings` + `binwalk -e` always first1172. If image → `zsteg -a` (PNG/BMP) OR `stegseek` (JPG)1183. If audio → spectrogram view1194. If nothing obvious → `stegsolve` GUI for visual analysis1205. If still nothing → metadata might encode the answer (look at GPS, comment, software field)121122## 8. Don't waste time on123124- LSB scripts when `zsteg` exists125- Manually viewing spectrograms in matplotlib when audacity/sonic-visualiser exist126- Reading the image with `Read` before validation (see step 0)127128## After solve129130Use the `writeup-template` skill.