CTF Steganography
Quick Start — Try These First
Basic analysis
file image.png exiftool image.png # EXIF metadata (flags hide here!) strings image.png | grep -iE "flag|ctf" binwalk image.png # Embedded files xxd image.png | tail # Data appended after EOF
Steganography tools
steghide extract -sf image.jpg # JPEG stego (tries empty password) steghide extract -sf image.jpg -p "" # Explicit empty password zsteg image.png # PNG/BMP LSB analysis stegsolve # Visual bit-plane analysis (GUI)
Image Steganography
LSB (Least Significant Bit)
The most common image stego technique. Data hidden in the least significant bits of pixel values.
from PIL import Image
img = Image.open('image.png') pixels = list(img.getdata())
Extract LSB from each channel
bits = '' for pixel in pixels: for channel in pixel[:3]: # R, G, B bits += str(channel & 1)
Convert bits to bytes
flag = bytes(int(bits[i:i+8], 2) for i in range(0, len(bits), 8)) print(flag)
Tools:
-
zsteg — Automated LSB analysis for PNG/BMP (try zsteg -a image.png )
-
stegsolve — Visual analysis, toggle bit planes
-
Stegano — Python library: pip install stegano
Pixel Value Encoding
Values ARE the data (not hidden in LSB)
from PIL import Image img = Image.open('image.png') pixels = list(img.getdata())
Each pixel R value is an ASCII char
flag = ''.join(chr(p[0]) for p in pixels if 32 <= p[0] < 127)
Or pixel coordinates encode data
Or specific color pixels spell out a message
Image Format Tricks
PNG chunks:
pngcheck -v image.png # Validate and list chunks python3 -c " import struct with open('image.png', 'rb') as f: data = f.read()
Look for custom chunks (tEXt, zTXt, iTXt)
idx = data.find(b'tEXt') if idx > 0: print(data[idx:idx+100]) "
JPEG markers:
Data after JPEG EOF marker (FF D9)
python3 -c " with open('image.jpg', 'rb') as f: data = f.read() eof = data.find(b'\xff\xd9') if eof > 0 and eof + 2 < len(data): print(f'Data after EOF: {data[eof+2:eof+102]!r}') "
BMP:
BMP has a data offset field — gap between header and pixel data can hide data
xxd image.bmp | head -5
GIF:
GIF frames may contain hidden data
ffmpeg -i image.gif frame_%03d.png # Extract all frames identify -verbose image.gif # Frame details
Image Dimension Tricks
Wrong dimensions in header:
PNG: Fix height to reveal hidden rows
import struct, zlib
with open('image.png', 'rb') as f: data = bytearray(f.read())
PNG IHDR chunk starts at offset 16 (width at 16, height at 20)
Try increasing height
struct.pack_into('>I', data, 20, 1000) # Set height to 1000
with open('fixed.png', 'wb') as f: # Recalculate IHDR CRC ihdr_data = data[12:29] crc = zlib.crc32(ihdr_data) & 0xffffffff struct.pack_into('>I', data, 29, crc) f.write(data)
Steghide (JPEG/WAV/BMP/AU)
steghide extract -sf file.jpg -p "password" steghide info file.jpg # Check if data is embedded
Brute force password
stegcracker file.jpg wordlist.txt
Or use stegseek (much faster)
stegseek file.jpg wordlist.txt
Visual Steganography
-
Flags as tiny/low-contrast text in images
-
Black text on dark background, white on light
-
Check ALL corners and edges at full resolution
-
Profile pictures and avatars are common hiding spots
-
Zoom in on what looks like solid color areas
Audio Steganography
Spectrogram Analysis
Generate spectrogram image
sox audio.wav -n spectrogram -o spectrogram.png
Or use Audacity: View → Spectrogram
Look for text/images drawn in frequency domain
SSTV (Slow-Scan Television)
Decode SSTV signal from audio
qsstv # GUI decoder
Or sstv Python package
pip install sstv sstv -d audio.wav -o output.png
DTMF Tones
Phone keypad tones
multimon-ng -t wav -a DTMF audio.wav
Or via sox + multimon-ng:
sox audio.wav -t raw -r 22050 -e signed-integer -b 16 -c 1 - | multimon-ng -t raw -a DTMF -
Audio LSB
import wave import struct
wav = wave.open('audio.wav', 'rb') frames = wav.readframes(wav.getnframes()) samples = struct.unpack(f'<{len(frames)//2}h', frames)
Extract LSB from samples
bits = ''.join(str(s & 1) for s in samples) data = bytes(int(bits[i:i+8], 2) for i in range(0, len(bits), 8)) print(data[:100])
Morse Code
Visual: look at waveform for long/short patterns
Audio: listen for dots and dashes
Automated: use online morse decoder or:
pip install morse-audio-decoder
Text/Data Steganography
Whitespace Stego
Zero-width characters in text
python3 -c " with open('text.txt', 'rb') as f: data = f.read()
Zero-width space: U+200B, Zero-width joiner: U+200D
for b in data: if b in [0xe2]: # Start of multi-byte UTF-8 print(f'Found zero-width char at position') "
snow tool for whitespace stego
snow -C -p "password" stego.txt
Unicode Stego
-
Homoglyph substitution (Cyrillic а vs Latin a)
-
Invisible Unicode characters between visible text
-
Variation selectors and combining characters
File Concatenation / Polyglots
Multiple files concatenated
binwalk suspicious_file # Find embedded files foremost suspicious_file # Carve out files
ZIP at end of image
unzip image.png # Works if ZIP appended after image data
PDF + ZIP polyglot
File is valid as both PDF and ZIP
Network Steganography
PCAP Hidden Data
-
DNS queries encoding data in subdomain labels
-
ICMP payloads carrying hidden messages
-
TCP sequence numbers encoding data
-
HTTP headers with encoded data
-
TLS certificate fields
DNS Exfiltration
tshark -r capture.pcap -Y "dns.qry.name" -T fields -e dns.qry.name |
sed 's/.example.com//' | tr -d '\n' | base64 -d
Common Patterns
Clue Technique
"Look closer" / "More than meets the eye" LSB or visual stego
Image looks normal but file is huge Embedded/appended data
Audio with static/noise sections Spectrogram or SSTV
"Password protected" Steghide with password
PNG with wrong colors or glitches Bit plane analysis
Text file with trailing whitespace Whitespace stego
Challenge says "nothing to see here" Definitely stego