ctf-stego

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CTF Steganography

CTF隐写术

Quick Start — Try These First

快速开始——先尝试这些操作

bash
undefined
bash
undefined

Basic analysis

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
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

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)
undefined
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)
undefined

Image Steganography

图片隐写术

LSB (Least Significant Bit)

LSB(最低有效位)

The most common image stego technique. Data hidden in the least significant bits of pixel values.
python
from PIL import Image

img = Image.open('image.png')
pixels = list(img.getdata())
这是最常见的图片隐写技术,数据隐藏在像素值的最低有效位中。
python
from PIL import Image

img = Image.open('image.png')
pixels = list(img.getdata())

Extract LSB from each channel

Extract LSB from each channel

bits = '' for pixel in pixels: for channel in pixel[:3]: # R, G, B bits += str(channel & 1)
bits = '' for pixel in pixels: for channel in pixel[:3]: # R, G, B bits += str(channel & 1)

Convert bits to bytes

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`
flag = bytes(int(bits[i:i+8], 2) for i in range(0, len(bits), 8)) print(flag)

**工具:**
- `zsteg` — 针对PNG/BMP的自动化LSB分析(可尝试`zsteg -a image.png`)
- `stegsolve` — 可视化位平面分析(GUI工具)
- `Stegano` — Python库:`pip install stegano`

Pixel Value Encoding

像素值编码

python
undefined
python
undefined

Values ARE the data (not hidden in LSB)

Values ARE the data (not hidden in LSB)

from PIL import Image img = Image.open('image.png') pixels = list(img.getdata())
from PIL import Image img = Image.open('image.png') pixels = list(img.getdata())

Each pixel R value is an ASCII char

Each pixel R value is an ASCII char

flag = ''.join(chr(p[0]) for p in pixels if 32 <= p[0] < 127)
flag = ''.join(chr(p[0]) for p in pixels if 32 <= p[0] < 127)

Or pixel coordinates encode data

Or pixel coordinates encode data

Or specific color pixels spell out a message

Or specific color pixels spell out a message

undefined
undefined

Image Format Tricks

图片格式技巧

PNG chunks:
bash
pngcheck -v image.png        # Validate and list chunks
python3 -c "
import struct
with open('image.png', 'rb') as f:
    data = f.read()
PNG块:
bash
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)

Look for custom chunks (tEXt, zTXt, iTXt)

idx = data.find(b'tEXt') if idx > 0: print(data[idx:idx+100]) "

**JPEG markers:**
```bash
idx = data.find(b'tEXt') if idx > 0: print(data[idx:idx+100]) "

**JPEG标记:**
```bash

Data after JPEG EOF marker (FF D9)

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:**
```bash
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:**
```bash

BMP has a data offset field — gap between header and pixel data can hide data

BMP has a data offset field — gap between header and pixel data can hide data

xxd image.bmp | head -5

**GIF:**
```bash
xxd image.bmp | head -5

**GIF:**
```bash

GIF frames may contain hidden data

GIF frames may contain hidden data

ffmpeg -i image.gif frame_%03d.png # Extract all frames identify -verbose image.gif # Frame details
undefined
ffmpeg -i image.gif frame_%03d.png # Extract all frames identify -verbose image.gif # Frame details
undefined

Image Dimension Tricks

图片尺寸技巧

Wrong dimensions in header:
python
undefined
头部中的错误尺寸:
python
undefined

PNG: Fix height to reveal hidden rows

PNG: Fix height to reveal hidden rows

import struct, zlib
with open('image.png', 'rb') as f: data = bytearray(f.read())
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)

PNG IHDR chunk starts at offset 16 (width at 16, height at 20)

Try increasing height

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)
undefined
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)
undefined

Steghide (JPEG/WAV/BMP/AU)

Steghide(支持JPEG/WAV/BMP/AU)

bash
steghide extract -sf file.jpg -p "password"
steghide info file.jpg                      # Check if data is embedded
bash
steghide extract -sf file.jpg -p "password"
steghide info file.jpg                      # Check if data is embedded

Brute force password

Brute force password

stegcracker file.jpg wordlist.txt
stegcracker file.jpg wordlist.txt

Or use stegseek (much faster)

Or use stegseek (much faster)

stegseek file.jpg wordlist.txt
undefined
stegseek file.jpg wordlist.txt
undefined

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

频谱图分析

bash
undefined
bash
undefined

Generate spectrogram image

Generate spectrogram image

sox audio.wav -n spectrogram -o spectrogram.png
sox audio.wav -n spectrogram -o spectrogram.png

Or use Audacity: View → Spectrogram

Or use Audacity: View → Spectrogram

Look for text/images drawn in frequency domain

Look for text/images drawn in frequency domain

undefined
undefined

SSTV (Slow-Scan Television)

SSTV(慢扫描电视)

bash
undefined
bash
undefined

Decode SSTV signal from audio

Decode SSTV signal from audio

qsstv # GUI decoder
qsstv # GUI decoder

Or sstv Python package

Or sstv Python package

pip install sstv sstv -d audio.wav -o output.png
undefined
pip install sstv sstv -d audio.wav -o output.png
undefined

DTMF Tones

DTMF音调

bash
undefined
bash
undefined

Phone keypad tones

Phone keypad tones

multimon-ng -t wav -a DTMF audio.wav
multimon-ng -t wav -a DTMF audio.wav

Or via sox + multimon-ng:

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 -
undefined
sox audio.wav -t raw -r 22050 -e signed-integer -b 16 -c 1 - | multimon-ng -t raw -a DTMF -
undefined

Audio LSB

音频LSB

python
import wave
import struct

wav = wave.open('audio.wav', 'rb')
frames = wav.readframes(wav.getnframes())
samples = struct.unpack(f'<{len(frames)//2}h', frames)
python
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

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])
undefined
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])
undefined

Morse Code

摩尔斯电码

bash
undefined
bash
undefined

Visual: look at waveform for long/short patterns

Visual: look at waveform for long/short patterns

Audio: listen for dots and dashes

Audio: listen for dots and dashes

Automated: use online morse decoder or:

Automated: use online morse decoder or:

pip install morse-audio-decoder
undefined
pip install morse-audio-decoder
undefined

Text/Data Steganography

文本/数据隐写术

Whitespace Stego

空白字符隐写

bash
undefined
bash
undefined

Zero-width characters in text

Zero-width characters in text

python3 -c " with open('text.txt', 'rb') as f: data = f.read()
python3 -c " with open('text.txt', 'rb') as f: data = f.read()

Zero-width space: U+200B, Zero-width joiner: U+200D

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') "
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 tool for whitespace stego

snow -C -p "password" stego.txt
undefined
snow -C -p "password" stego.txt
undefined

Unicode Stego

Unicode隐写

  • Homoglyph substitution (Cyrillic а vs Latin a)
  • Invisible Unicode characters between visible text
  • Variation selectors and combining characters
  • 同形字符替换(西里尔字母а vs 拉丁字母a)
  • 可见文本之间的不可见Unicode字符
  • 变体选择符和组合字符

File Concatenation / Polyglots

文件拼接/多格式文件

bash
undefined
bash
undefined

Multiple files concatenated

Multiple files concatenated

binwalk suspicious_file # Find embedded files foremost suspicious_file # Carve out files
binwalk suspicious_file # Find embedded files foremost suspicious_file # Carve out files

ZIP at end of image

ZIP at end of image

unzip image.png # Works if ZIP appended after image data
unzip image.png # Works if ZIP appended after image data

PDF + ZIP polyglot

PDF + ZIP polyglot

File is valid as both PDF and ZIP

File is valid as both PDF and ZIP

undefined
undefined

Network Steganography

网络隐写术

PCAP Hidden Data

PCAP隐藏数据

  • 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查询在子域名标签中编码数据
  • ICMP载荷携带隐藏消息
  • TCP序列号编码数据
  • HTTP头中包含编码数据
  • TLS证书字段

DNS Exfiltration

DNS数据泄露

bash
tshark -r capture.pcap -Y "dns.qry.name" -T fields -e dns.qry.name | \
    sed 's/\.example\.com//' | tr -d '\n' | base64 -d
bash
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

常见模式

ClueTechnique
"Look closer" / "More than meets the eye"LSB or visual stego
Image looks normal but file is hugeEmbedded/appended data
Audio with static/noise sectionsSpectrogram or SSTV
"Password protected"Steghide with password
PNG with wrong colors or glitchesBit plane analysis
Text file with trailing whitespaceWhitespace stego
Challenge says "nothing to see here"Definitely stego
线索技术手段
“仔细看” / “不止表面所见”LSB或视觉隐写
图片看起来正常但文件体积很大嵌入/附加的数据
包含静态/噪音段的音频频谱图或SSTV
“需要密码”带密码的Steghide
颜色异常或有故障的PNG位平面分析
带有尾随空白的文本文件空白字符隐写
挑战提示“这里没什么可看的”肯定存在隐写