ctf-agent 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +131 -0
- package/Dockerfile +192 -0
- package/README.md +522 -0
- package/agents/ctf-analyzer.md +58 -0
- package/agents/ctf-controller.md +151 -0
- package/agents/ctf-speedrun.md +74 -0
- package/bin/ctf-agent.js +119 -0
- package/docker-compose.yml +13 -0
- package/mcp_config.json +19 -0
- package/package.json +44 -0
- package/references/ctf-triage-ladder.md +68 -0
- package/references/exploit-databases.md +575 -0
- package/references/llm-safety-and-policy-compliance.md +173 -0
- package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
- package/references/security-events-and-intelligence.md +566 -0
- package/references/version-matrix.md +59 -0
- package/rules/ctf-execution-rules.md +44 -0
- package/rules/ctf-safety-framing-rules.md +140 -0
- package/rules/flag-validation-rules.md +25 -0
- package/scripts/__init__.py +1 -0
- package/scripts/ctf-tools.lock +205 -0
- package/scripts/ctf_init.py +1026 -0
- package/scripts/ctf_update.py +583 -0
- package/scripts/ctfd_client.py +81 -0
- package/scripts/cve_lookup.py +453 -0
- package/scripts/extract_flags.py +155 -0
- package/scripts/install_as_agent.py +278 -0
- package/scripts/install_ctf_tools.sh +603 -0
- package/scripts/parallel_triage.py +395 -0
- package/scripts/prompt_policy_sanitizer.py +283 -0
- package/scripts/scope_guard.py +444 -0
- package/scripts/skill_validator.py +504 -0
- package/scripts/workspace_cleaner.py +141 -0
- package/skills/ctf-ai-ml/SKILL.md +120 -0
- package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
- package/skills/ctf-ai-ml/llm-attacks.md +487 -0
- package/skills/ctf-ai-ml/model-attacks.md +422 -0
- package/skills/ctf-crypto/SKILL.md +320 -0
- package/skills/ctf-crypto/advanced-math.md +798 -0
- package/skills/ctf-crypto/classic-ciphers.md +651 -0
- package/skills/ctf-crypto/ecc-attacks.md +347 -0
- package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
- package/skills/ctf-crypto/exotic-crypto.md +528 -0
- package/skills/ctf-crypto/historical.md +113 -0
- package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
- package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
- package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
- package/skills/ctf-crypto/modern-ciphers.md +649 -0
- package/skills/ctf-crypto/prng-attacks.md +257 -0
- package/skills/ctf-crypto/prng.md +664 -0
- package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
- package/skills/ctf-crypto/rsa-attacks.md +487 -0
- package/skills/ctf-crypto/stream-ciphers.md +390 -0
- package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
- package/skills/ctf-forensics/3d-printing.md +121 -0
- package/skills/ctf-forensics/SKILL.md +379 -0
- package/skills/ctf-forensics/disk-advanced.md +497 -0
- package/skills/ctf-forensics/disk-and-memory.md +491 -0
- package/skills/ctf-forensics/disk-recovery.md +699 -0
- package/skills/ctf-forensics/linux-forensics.md +511 -0
- package/skills/ctf-forensics/network-advanced.md +583 -0
- package/skills/ctf-forensics/network.md +645 -0
- package/skills/ctf-forensics/peripheral-capture.md +287 -0
- package/skills/ctf-forensics/signals-and-hardware.md +713 -0
- package/skills/ctf-forensics/steganography.md +694 -0
- package/skills/ctf-forensics/stego-advanced-2.md +475 -0
- package/skills/ctf-forensics/stego-advanced.md +481 -0
- package/skills/ctf-forensics/stego-image.md +691 -0
- package/skills/ctf-forensics/windows.md +625 -0
- package/skills/ctf-malware/SKILL.md +181 -0
- package/skills/ctf-malware/c2-and-protocols.md +274 -0
- package/skills/ctf-malware/pe-and-dotnet.md +108 -0
- package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
- package/skills/ctf-misc/SKILL.md +498 -0
- package/skills/ctf-misc/bashjails.md +323 -0
- package/skills/ctf-misc/ctfd-navigation.md +465 -0
- package/skills/ctf-misc/dns.md +255 -0
- package/skills/ctf-misc/encodings-advanced.md +504 -0
- package/skills/ctf-misc/encodings.md +431 -0
- package/skills/ctf-misc/games-and-vms-2.md +254 -0
- package/skills/ctf-misc/games-and-vms-3.md +690 -0
- package/skills/ctf-misc/games-and-vms-4.md +229 -0
- package/skills/ctf-misc/games-and-vms.md +529 -0
- package/skills/ctf-misc/linux-privesc.md +333 -0
- package/skills/ctf-misc/pyjails.md +671 -0
- package/skills/ctf-misc/rf-sdr.md +91 -0
- package/skills/ctf-osint/SKILL.md +198 -0
- package/skills/ctf-osint/geolocation-and-media.md +464 -0
- package/skills/ctf-osint/social-media.md +312 -0
- package/skills/ctf-osint/web-and-dns.md +341 -0
- package/skills/ctf-pwn/SKILL.md +214 -0
- package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
- package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
- package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
- package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
- package/skills/ctf-pwn/advanced-exploits.md +773 -0
- package/skills/ctf-pwn/advanced.md +326 -0
- package/skills/ctf-pwn/field-notes.md +245 -0
- package/skills/ctf-pwn/format-string.md +694 -0
- package/skills/ctf-pwn/heap-fsop.md +285 -0
- package/skills/ctf-pwn/heap-techniques-2.md +333 -0
- package/skills/ctf-pwn/heap-techniques.md +513 -0
- package/skills/ctf-pwn/kernel-bypass.md +421 -0
- package/skills/ctf-pwn/kernel-techniques.md +366 -0
- package/skills/ctf-pwn/kernel.md +636 -0
- package/skills/ctf-pwn/overflow-basics.md +611 -0
- package/skills/ctf-pwn/rop-advanced.md +725 -0
- package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
- package/skills/ctf-pwn/sandbox-escape.md +313 -0
- package/skills/ctf-reverse/SKILL.md +163 -0
- package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
- package/skills/ctf-reverse/anti-analysis.md +693 -0
- package/skills/ctf-reverse/field-notes.md +376 -0
- package/skills/ctf-reverse/languages-compiled.md +666 -0
- package/skills/ctf-reverse/languages-platforms.md +592 -0
- package/skills/ctf-reverse/languages.md +553 -0
- package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
- package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
- package/skills/ctf-reverse/patterns-ctf.md +670 -0
- package/skills/ctf-reverse/patterns-runtime.md +274 -0
- package/skills/ctf-reverse/patterns.md +572 -0
- package/skills/ctf-reverse/platforms-hardware.md +387 -0
- package/skills/ctf-reverse/platforms.md +664 -0
- package/skills/ctf-reverse/tools-advanced-2.md +421 -0
- package/skills/ctf-reverse/tools-advanced.md +407 -0
- package/skills/ctf-reverse/tools-dynamic.md +679 -0
- package/skills/ctf-reverse/tools-emulation.md +319 -0
- package/skills/ctf-reverse/tools.md +573 -0
- package/skills/ctf-web/SKILL.md +153 -0
- package/skills/ctf-web/auth-and-access-2.md +82 -0
- package/skills/ctf-web/auth-and-access.md +783 -0
- package/skills/ctf-web/auth-infra.md +321 -0
- package/skills/ctf-web/auth-jwt.md +186 -0
- package/skills/ctf-web/client-side-advanced.md +739 -0
- package/skills/ctf-web/client-side.md +529 -0
- package/skills/ctf-web/cves.md +373 -0
- package/skills/ctf-web/field-notes.md +482 -0
- package/skills/ctf-web/node-and-prototype.md +200 -0
- package/skills/ctf-web/server-side-2.md +337 -0
- package/skills/ctf-web/server-side-advanced-2.md +559 -0
- package/skills/ctf-web/server-side-advanced-3.md +125 -0
- package/skills/ctf-web/server-side-advanced-4.md +480 -0
- package/skills/ctf-web/server-side-advanced.md +378 -0
- package/skills/ctf-web/server-side-deser.md +443 -0
- package/skills/ctf-web/server-side-exec-2.md +799 -0
- package/skills/ctf-web/server-side-exec.md +457 -0
- package/skills/ctf-web/server-side.md +629 -0
- package/skills/ctf-web/sql-injection.md +790 -0
- package/skills/ctf-web/web3.md +374 -0
- package/skills/ctf-writeup/SKILL.md +90 -0
- package/skills/solve-challenge/SKILL.md +269 -0
- package/skills.json +16 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
# CTF Forensics - Advanced Steganography
|
|
2
|
+
|
|
3
|
+
See also: [stego-advanced-2.md](stego-advanced-2.md) for video frame techniques, JPEG XL TOC permutation, Arnold's Cat Map, SSTV FM demodulation, MJPEG steganography, EXIF/Stegano pixel patterns, PDF xref covert channels, ANSI escape code stego, and ECB image recovery.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [FFT Frequency Domain Steganography (Pragyan 2026)](#fft-frequency-domain-steganography-pragyan-2026)
|
|
7
|
+
- [SSTV Red Herring + LSB Audio Stego (0xFun 2026)](#sstv-red-herring--lsb-audio-stego-0xfun-2026)
|
|
8
|
+
- [DotCode Barcode via SSTV (0xFun 2026)](#dotcode-barcode-via-sstv-0xfun-2026)
|
|
9
|
+
- [DTMF Audio Decoding](#dtmf-audio-decoding)
|
|
10
|
+
- [Custom Frequency DTMF / Dual-Tone Keypad Encoding (EHAX 2026)](#custom-frequency-dtmf--dual-tone-keypad-encoding-ehax-2026)
|
|
11
|
+
- [Multi-Track Audio Differential Subtraction (EHAX 2026)](#multi-track-audio-differential-subtraction-ehax-2026)
|
|
12
|
+
- [Cross-Channel Multi-Bit LSB Steganography (ApoorvCTF 2026)](#cross-channel-multi-bit-lsb-steganography-apoorvctf-2026)
|
|
13
|
+
- [Audio FFT Musical Note Identification (BYPASS CTF 2025)](#audio-fft-musical-note-identification-bypass-ctf-2025)
|
|
14
|
+
- [Audio Metadata Octal Encoding (BYPASS CTF 2025)](#audio-metadata-octal-encoding-bypass-ctf-2025)
|
|
15
|
+
- [Nested Tar Archive with Whitespace Encoding (UTCTF 2026)](#nested-tar-archive-with-whitespace-encoding-utctf-2026)
|
|
16
|
+
- [DeepSound Audio Steganography with Password Cracking (INShAck 2018)](#deepsound-audio-steganography-with-password-cracking-inshack-2018)
|
|
17
|
+
- [Audio Waveform Binary Encoding (BackdoorCTF 2013)](#audio-waveform-binary-encoding-backdoorctf-2013)
|
|
18
|
+
- [Audio Spectrogram Hidden QR Code (BaltCTF 2013)](#audio-spectrogram-hidden-qr-code-baltctf-2013)
|
|
19
|
+
- [Byte-Reversed .docx ZIP Bidirectional Archive (Security Fest CTF 2018)](#byte-reversed-docx-zip-bidirectional-archive-security-fest-ctf-2018)
|
|
20
|
+
- [MIDI Note-On/Note-Off Pitch Pair Encoding (X-MAS CTF 2018)](#midi-note-onnote-off-pitch-pair-encoding-x-mas-ctf-2018)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## FFT Frequency Domain Steganography (Pragyan 2026)
|
|
25
|
+
|
|
26
|
+
**Pattern (H@rDl4u6H):** Image encodes data in frequency domain via 2D FFT.
|
|
27
|
+
|
|
28
|
+
**Decoding workflow:**
|
|
29
|
+
```python
|
|
30
|
+
import numpy as np
|
|
31
|
+
from PIL import Image
|
|
32
|
+
|
|
33
|
+
img = np.array(Image.open("image.png")).astype(float)
|
|
34
|
+
F = np.fft.fftshift(np.fft.fft2(img))
|
|
35
|
+
mag = np.log(1 + np.abs(F))
|
|
36
|
+
|
|
37
|
+
# Look for patterns: concentric rings, dots at specific positions
|
|
38
|
+
# Bright peak = 0 bit, Dark (no peak) = 1 bit
|
|
39
|
+
cy, cx = mag.shape[0]//2, mag.shape[1]//2
|
|
40
|
+
radii = [100 + 69*i for i in range(21)] # Example spacing
|
|
41
|
+
angles = [0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5]
|
|
42
|
+
THRESHOLD = 13.0
|
|
43
|
+
|
|
44
|
+
bits = []
|
|
45
|
+
for r in radii:
|
|
46
|
+
byte_val = 0
|
|
47
|
+
for a in angles:
|
|
48
|
+
fx = cx + r * np.cos(np.radians(a))
|
|
49
|
+
fy = cy - r * np.sin(np.radians(a))
|
|
50
|
+
bit = 0 if mag[int(round(fy)), int(round(fx))] > THRESHOLD else 1
|
|
51
|
+
byte_val = (byte_val << 1) | bit
|
|
52
|
+
bits.append(byte_val)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Identification:** Challenge mentions "transform", poem about "frequency", or image looks blank/noisy. Try FFT visualization first.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## SSTV Red Herring + LSB Audio Stego (0xFun 2026)
|
|
60
|
+
|
|
61
|
+
**Pattern (Melodie):** WAV contains SSTV signal (Scottie 1) that decodes to "SEEMS LIKE A DEADEND". Real flag in 2-bit LSB of audio samples.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Decode SSTV (red herring)
|
|
65
|
+
qsstv # Will show decoy message
|
|
66
|
+
|
|
67
|
+
# Extract real flag from LSB
|
|
68
|
+
pip install stego-lsb
|
|
69
|
+
stegolsb wavsteg -r -i audio.wav -o out.bin -n 2 -b 1000
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Lesson:** Obvious signals may be decoys. Always check LSB even when another encoding is found.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## DotCode Barcode via SSTV (0xFun 2026)
|
|
77
|
+
|
|
78
|
+
**Pattern (Dots):** SSTV decoding produces dot pattern image. Not QR — it's DotCode format.
|
|
79
|
+
|
|
80
|
+
**Identification:** Dot pattern that isn't a standard QR code. DotCode is a 2D barcode optimized for high-speed printing.
|
|
81
|
+
|
|
82
|
+
**Tool:** Aspose online DotCode reader (free).
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## DTMF Audio Decoding
|
|
87
|
+
|
|
88
|
+
**Pattern (Phone Home):** Audio file contains phone dialing tones encoding data.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Decode DTMF tones
|
|
92
|
+
sox phonehome.wav -t raw -r 22050 -e signed-integer -b 16 -c 1 - | \
|
|
93
|
+
multimon-ng -t raw -a DTMF -
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Post-processing:** Phone number may contain octal-encoded ASCII after delimiter (#):
|
|
97
|
+
```python
|
|
98
|
+
# Convert octal groups to ASCII
|
|
99
|
+
octal_groups = ["115", "145", "164", "141"] # M, e, t, a
|
|
100
|
+
flag = ''.join(chr(int(g, 8)) for g in octal_groups)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Custom Frequency DTMF / Dual-Tone Keypad Encoding (EHAX 2026)
|
|
106
|
+
|
|
107
|
+
**Pattern (Quantum Message):** Audio with dual-tone sequences at non-standard frequencies, aligned at regular intervals (e.g., every 1 second). Hints about "harmonic oscillators" or physics point to custom frequency design.
|
|
108
|
+
|
|
109
|
+
**Identification:** Spectrogram shows two distinct frequency sets that don't match standard DTMF (697-1633 Hz). Look for evenly-spaced rows/columns of frequency tones.
|
|
110
|
+
|
|
111
|
+
**Decoding workflow:**
|
|
112
|
+
```python
|
|
113
|
+
import numpy as np
|
|
114
|
+
from scipy.io import wavfile
|
|
115
|
+
|
|
116
|
+
rate, audio = wavfile.read('challenge.wav')
|
|
117
|
+
|
|
118
|
+
# 1. Generate spectrogram to identify frequency grid
|
|
119
|
+
# Use ffmpeg: ffmpeg -i challenge.wav -lavfi showspectrumpic=s=1920x1080 spec.png
|
|
120
|
+
|
|
121
|
+
# 2. Map frequencies to keypad (custom grid, NOT standard DTMF)
|
|
122
|
+
# Example: rows = [301, 902, 1503, 2104] Hz, cols = [2705, 3306, 3907] Hz
|
|
123
|
+
# Forms 4x3 keypad -> digits 0-9 + symbols
|
|
124
|
+
|
|
125
|
+
# 3. Extract tone pairs per time window
|
|
126
|
+
window_size = rate # 1 second per symbol
|
|
127
|
+
for i in range(0, len(audio), window_size):
|
|
128
|
+
segment = audio[i:i+window_size]
|
|
129
|
+
freqs = np.fft.rfftfreq(len(segment), 1/rate)
|
|
130
|
+
magnitude = np.abs(np.fft.rfft(segment))
|
|
131
|
+
# Find two dominant peaks -> map to row/col -> digit
|
|
132
|
+
|
|
133
|
+
# 4. Convert digit sequence to ASCII
|
|
134
|
+
# Split digits into variable-length groups (ASCII range 32-126)
|
|
135
|
+
# E.g., "72101108108111" -> [72, 101, 108, 108, 111] -> "Hello"
|
|
136
|
+
def digits_to_ascii(digits):
|
|
137
|
+
result, i = [], 0
|
|
138
|
+
while i < len(digits):
|
|
139
|
+
for length in [2, 3]: # ASCII codes are 2-3 digits
|
|
140
|
+
if i + length <= len(digits):
|
|
141
|
+
val = int(digits[i:i+length])
|
|
142
|
+
if 32 <= val <= 126:
|
|
143
|
+
result.append(chr(val))
|
|
144
|
+
i += length
|
|
145
|
+
break
|
|
146
|
+
else:
|
|
147
|
+
i += 1
|
|
148
|
+
return ''.join(result)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Key insight:** When tones don't match standard DTMF frequencies, generate a spectrogram first to identify the custom frequency grid. The mapping is challenge-specific.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Multi-Track Audio Differential Subtraction (EHAX 2026)
|
|
156
|
+
|
|
157
|
+
**Pattern (Penguin):** MKV/video file with two nearly-identical audio tracks. Hidden data is embedded as a tiny difference between the tracks, invisible when listening to either individually.
|
|
158
|
+
|
|
159
|
+
**Identification:**
|
|
160
|
+
- `ffprobe` reveals multiple audio streams (e.g., two stereo FLAC tracks)
|
|
161
|
+
- Metadata may contain a decoy flag (e.g., in comments)
|
|
162
|
+
- Track labels may be misleading (e.g., stereo labeled as "5.1 surround")
|
|
163
|
+
- `sox --info` / `sox -n stat` shows nearly identical RMS, amplitude, and frequency statistics for both tracks
|
|
164
|
+
|
|
165
|
+
**Extraction workflow:**
|
|
166
|
+
```bash
|
|
167
|
+
# 1. Extract both audio tracks
|
|
168
|
+
ffmpeg -i challenge.mkv -map 0:a:0 -c copy track0.flac
|
|
169
|
+
ffmpeg -i challenge.mkv -map 0:a:1 -c copy track1.flac
|
|
170
|
+
|
|
171
|
+
# 2. Convert to WAV for processing
|
|
172
|
+
ffmpeg -i track0.flac track0.wav
|
|
173
|
+
ffmpeg -i track1.flac track1.wav
|
|
174
|
+
|
|
175
|
+
# 3. Subtract: invert one track and mix (cancels shared content)
|
|
176
|
+
sox -m track0.wav "|sox track1.wav -p vol -1" diff.wav
|
|
177
|
+
|
|
178
|
+
# 4. Normalize the difference signal
|
|
179
|
+
sox diff.wav diff_norm.wav gain -n -3
|
|
180
|
+
|
|
181
|
+
# 5. Generate spectrogram to read the flag
|
|
182
|
+
sox diff_norm.wav -n spectrogram -o spectrogram.png -X 2000 -Y 1000 -z 100 -h
|
|
183
|
+
|
|
184
|
+
# 6. Optional: filter to isolate flag frequency range
|
|
185
|
+
sox diff_norm.wav filtered.wav sinc 5000-12000
|
|
186
|
+
sox filtered.wav -n spectrogram -o filtered_spec.png -X 2000 -Y 1000 -z 100 -h
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Key insight:** When two audio tracks are nearly identical, subtracting one from the other (phase inversion + mix) cancels shared content and isolates hidden data. The flag is typically encoded as text in the spectrogram of the difference signal, visible in a specific frequency band (e.g., 5-12 kHz).
|
|
190
|
+
|
|
191
|
+
**Common traps:**
|
|
192
|
+
- Decoy flags in metadata/comments — always verify
|
|
193
|
+
- Mislabeled channel configurations (stereo as 5.1)
|
|
194
|
+
- Flag may only be visible in a narrow time window — use high-resolution spectrogram (`-X 2000+`)
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Cross-Channel Multi-Bit LSB Steganography (ApoorvCTF 2026)
|
|
199
|
+
|
|
200
|
+
**Pattern (Beneath the Armor):** Standard LSB tools (zsteg, stegsolve) fail because different bit positions are used per RGB channel: Red channel bit 0, Green channel bit 1, Blue channel bit 2.
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from PIL import Image
|
|
204
|
+
|
|
205
|
+
img = Image.open("challenge.png")
|
|
206
|
+
pixels = img.load()
|
|
207
|
+
bits = []
|
|
208
|
+
for y in range(img.height):
|
|
209
|
+
for x in range(img.width):
|
|
210
|
+
r, g, b = pixels[x, y][:3]
|
|
211
|
+
bits.append((r >> 0) & 1) # Red: bit 0
|
|
212
|
+
bits.append((g >> 1) & 1) # Green: bit 1
|
|
213
|
+
bits.append((b >> 2) & 1) # Blue: bit 2
|
|
214
|
+
|
|
215
|
+
# Pack 3 bits per pixel into bytes
|
|
216
|
+
data = bytearray()
|
|
217
|
+
for i in range(0, len(bits) - 7, 8):
|
|
218
|
+
byte = 0
|
|
219
|
+
for j in range(8):
|
|
220
|
+
byte = (byte << 1) | bits[i + j]
|
|
221
|
+
data.append(byte)
|
|
222
|
+
print(data.decode('ascii', errors='ignore'))
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Key insight:** When standard LSB tools find nothing, the data may use different bit positions per channel. The hint "cycles" or "modular" suggests cycling through bit positions (0→1→2) across channels. Always try non-standard bit combinations: R[0]G[1]B[2], R[1]G[2]B[0], R[2]G[0]B[1], etc.
|
|
226
|
+
|
|
227
|
+
**Detection:** Standard `zsteg -a` and `stegsolve` produce no results on an image that metadata hints contain hidden data.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Audio FFT Musical Note Identification (BYPASS CTF 2025)
|
|
232
|
+
|
|
233
|
+
**Pattern (Piano):** Identify dominant frequencies via FFT (Fast Fourier Transform), map to musical notes (A-G), then read the letter names as a word.
|
|
234
|
+
|
|
235
|
+
**Technique:** Perform FFT on audio, identify dominant frequencies, map to musical notes.
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
import numpy as np
|
|
239
|
+
from scipy.io import wavfile
|
|
240
|
+
|
|
241
|
+
rate, audio = wavfile.read('challenge.wav')
|
|
242
|
+
if audio.ndim > 1:
|
|
243
|
+
audio = audio[:, 0] # mono
|
|
244
|
+
|
|
245
|
+
# FFT to find dominant frequencies
|
|
246
|
+
freqs = np.fft.rfftfreq(len(audio), 1/rate)
|
|
247
|
+
magnitude = np.abs(np.fft.rfft(audio))
|
|
248
|
+
|
|
249
|
+
# Find top peaks
|
|
250
|
+
peak_indices = np.argsort(magnitude)[-20:]
|
|
251
|
+
peak_freqs = sorted(set(round(freqs[i]) for i in peak_indices if freqs[i] > 20))
|
|
252
|
+
|
|
253
|
+
# Musical note frequency mapping (A4 = 440 Hz)
|
|
254
|
+
NOTE_FREQS = {
|
|
255
|
+
'C4': 261.63, 'D4': 293.66, 'E4': 329.63, 'F4': 349.23,
|
|
256
|
+
'G4': 392.00, 'A4': 440.00, 'B4': 493.88,
|
|
257
|
+
'C5': 523.25, 'D5': 587.33, 'E5': 659.25, 'F5': 698.46,
|
|
258
|
+
'G5': 783.99, 'A5': 880.00, 'B5': 987.77,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
def freq_to_note(freq):
|
|
262
|
+
return min(NOTE_FREQS.items(), key=lambda x: abs(x[1] - freq))[0]
|
|
263
|
+
|
|
264
|
+
notes = [freq_to_note(f) for f in peak_freqs]
|
|
265
|
+
# Extract letter names: B, A, D, F, A, C, E → "BADFACE"
|
|
266
|
+
answer = ''.join(n[0] for n in notes)
|
|
267
|
+
print(f"Notes: {notes}")
|
|
268
|
+
print(f"Answer: {answer}")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Extract and examine audio metadata** using `exiftool audio.mp3` for encoded hints in comment fields (e.g., octal-separated values → base64 → decoded hint).
|
|
272
|
+
|
|
273
|
+
**Key insight:** Musical note names (A-G) can spell words. When a challenge involves music/piano, identify dominant frequencies via FFT and read the note letter names as text.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Audio Metadata Octal Encoding (BYPASS CTF 2025)
|
|
278
|
+
|
|
279
|
+
**Pattern (Piano metadata):** Audio file metadata (exiftool comment field) contains underscore-separated numbers representing octal-encoded ASCII values (digits 0-7 only).
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
# Extract and decode octal metadata
|
|
283
|
+
import subprocess, base64
|
|
284
|
+
|
|
285
|
+
# Get metadata comment
|
|
286
|
+
comment = "103_137_63_157_144_145_144_40_162_145_154_151_143"
|
|
287
|
+
octal_values = comment.split('_')
|
|
288
|
+
decoded = ''.join(chr(int(v, 8)) for v in octal_values)
|
|
289
|
+
|
|
290
|
+
# May decode to base64, requiring another layer
|
|
291
|
+
result = base64.b64decode(decoded).decode()
|
|
292
|
+
print(result)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Key insight:** When metadata contains underscore-separated numbers, try octal (digits 0-7 only), decimal, or hex interpretation. Multi-layer encoding (octal → base64 → plaintext) is common.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Nested Tar Archive with Whitespace Encoding (UTCTF 2026)
|
|
300
|
+
|
|
301
|
+
**Pattern (Silent Archive):** Deeply nested tar archives where data is encoded in whitespace characters (spaces, tabs, newlines) within file names or content.
|
|
302
|
+
|
|
303
|
+
**Detection:** Archive extracts to another archive (tar-in-tar chain). File content appears empty but contains invisible whitespace characters.
|
|
304
|
+
|
|
305
|
+
**Decoding workflow:**
|
|
306
|
+
```python
|
|
307
|
+
import tarfile
|
|
308
|
+
import os
|
|
309
|
+
|
|
310
|
+
# 1. Recursively extract nested tar archives
|
|
311
|
+
def extract_all(path, depth=0):
|
|
312
|
+
if depth > 100: # Guard against infinite nesting
|
|
313
|
+
return
|
|
314
|
+
if tarfile.is_tarfile(path):
|
|
315
|
+
with tarfile.open(path) as tf:
|
|
316
|
+
tf.extractall(f'layer_{depth}')
|
|
317
|
+
for member in tf.getmembers():
|
|
318
|
+
extract_all(f'layer_{depth}/{member.name}', depth + 1)
|
|
319
|
+
|
|
320
|
+
# 2. Collect whitespace from file names or content
|
|
321
|
+
whitespace_data = []
|
|
322
|
+
for root, dirs, files in os.walk('layer_0'):
|
|
323
|
+
for f in files:
|
|
324
|
+
path = os.path.join(root, f)
|
|
325
|
+
with open(path, 'rb') as fh:
|
|
326
|
+
content = fh.read()
|
|
327
|
+
# Check for whitespace-only content
|
|
328
|
+
if content.strip() == b'':
|
|
329
|
+
for byte in content:
|
|
330
|
+
if byte == 0x20: # space
|
|
331
|
+
whitespace_data.append('0')
|
|
332
|
+
elif byte == 0x09: # tab
|
|
333
|
+
whitespace_data.append('1')
|
|
334
|
+
|
|
335
|
+
# 3. Convert binary from whitespace
|
|
336
|
+
bits = ''.join(whitespace_data)
|
|
337
|
+
message = bytes(int(bits[i:i+8], 2) for i in range(0, len(bits)-7, 8))
|
|
338
|
+
print(message.decode(errors='replace'))
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Whitespace encoding variants:**
|
|
342
|
+
- Space = 0, Tab = 1 (binary encoding)
|
|
343
|
+
- Whitespace Steganography: trailing spaces/tabs at end of lines
|
|
344
|
+
- Zero-width characters (U+200B, U+200C, U+FEFF) in Unicode text
|
|
345
|
+
- Number of spaces between words encodes data
|
|
346
|
+
|
|
347
|
+
**Key insight:** "Silent" or "invisible" hints point to whitespace encoding. Use `xxd` or `cat -A` to reveal hidden whitespace characters. Deeply nested archives are misdirection — the data is in the whitespace, not the nesting depth.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## DeepSound Audio Steganography with Password Cracking (INShAck 2018)
|
|
352
|
+
|
|
353
|
+
**Pattern:** Two-phase audio steganography: part 1 visible in Audacity spectrogram, part 2 hidden with DeepSound tool (password-protected). Use `deepsound2john.py` to extract the hash, crack with John, then retrieve hidden files.
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# Phase 1: Check spectrogram for visible text
|
|
357
|
+
sox audio.wav -n spectrogram -o spec.png
|
|
358
|
+
|
|
359
|
+
# Phase 2: Extract DeepSound password hash
|
|
360
|
+
python3 deepsound2john.py audio.wav > hash.txt
|
|
361
|
+
|
|
362
|
+
# Crack password
|
|
363
|
+
john --wordlist=rockyou.txt hash.txt
|
|
364
|
+
|
|
365
|
+
# Extract hidden file with DeepSound GUI or CLI using cracked password
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**DeepSound detection:**
|
|
369
|
+
```python
|
|
370
|
+
# DeepSound embeds a signature in WAV files
|
|
371
|
+
# Check for DeepSound header pattern in audio data
|
|
372
|
+
with open('audio.wav', 'rb') as f:
|
|
373
|
+
data = f.read()
|
|
374
|
+
# DeepSound uses specific byte patterns in the audio data section
|
|
375
|
+
# deepsound2john.py from John the Ripper's bleeding-jumbo branch
|
|
376
|
+
# handles detection and hash extraction automatically
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Tool installation:**
|
|
380
|
+
```bash
|
|
381
|
+
# deepsound2john.py is part of John the Ripper bleeding-jumbo
|
|
382
|
+
git clone https://github.com/openwall/john.git
|
|
383
|
+
# Script located at: john/run/deepsound2john.py
|
|
384
|
+
|
|
385
|
+
# DeepSound GUI (Windows): http://jpinsoft.net/deepsound/
|
|
386
|
+
# For Linux: run under Wine or use the extracted hash + john approach
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Key insight:** DeepSound embeds files in WAV audio with optional AES encryption. The password hash is extractable with `deepsound2john.py` from John the Ripper's bleeding-jumbo branch. Always check both spectrogram (visual stego) and DeepSound (data stego) in audio challenges.
|
|
390
|
+
|
|
391
|
+
**Detection:** WAV file that seems normal but `deepsound2john.py` produces a hash. Challenge has two-part structure where first part is easy (spectrogram) and second part requires a tool. Challenge mentions "layers", "hidden", or "deep".
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Audio Waveform Binary Encoding (BackdoorCTF 2013)
|
|
396
|
+
|
|
397
|
+
**Pattern:** WAV file contains two distinct waveform shapes representing binary 0 and 1. Group 8 bits into bytes and decode as ASCII.
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
import wave, struct
|
|
401
|
+
wf = wave.open('audio.wav', 'rb')
|
|
402
|
+
frames = wf.readframes(wf.getnframes())
|
|
403
|
+
samples = struct.unpack(f'{len(frames)//2}h', frames)
|
|
404
|
+
|
|
405
|
+
# Identify two distinct wave patterns (e.g., positive peak vs flat)
|
|
406
|
+
# Segment audio into fixed-length windows, classify each as 0 or 1
|
|
407
|
+
bits = ''
|
|
408
|
+
window = len(samples) // num_bits
|
|
409
|
+
for i in range(num_bits):
|
|
410
|
+
segment = samples[i*window:(i+1)*window]
|
|
411
|
+
bits += '1' if max(segment) > threshold else '0'
|
|
412
|
+
|
|
413
|
+
# Decode binary to ASCII
|
|
414
|
+
flag = ''.join(chr(int(bits[i:i+8], 2)) for i in range(0, len(bits)-7, 8))
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Key insight:** Open in Audacity and zoom in — two visually distinct wave patterns alternate. Each pattern represents one bit. Count the patterns, group into 8-bit bytes, decode as ASCII.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Audio Spectrogram Hidden QR Code (BaltCTF 2013)
|
|
422
|
+
|
|
423
|
+
**Pattern:** Audio file contains visual data hidden in the frequency domain, visible only in a spectrogram view.
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# Generate spectrogram image
|
|
427
|
+
sox audio.mp3 -n spectrogram -o spec.png
|
|
428
|
+
# Or use Sonic Visualiser for interactive exploration
|
|
429
|
+
|
|
430
|
+
# Look for visual patterns in specific frequency bands (often 5-12 kHz)
|
|
431
|
+
# Extract/assemble QR code fragments from spectrogram
|
|
432
|
+
# Scan with: zbarimg assembled_qr.png
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Key insight:** Use Sonic Visualiser (Layer → Add Spectrogram) with adjustable window size and color mapping. QR codes or text often appear in the 2-15 kHz band. Multiple spectrogram fragments may need to be stitched together in an image editor before scanning.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Byte-Reversed .docx ZIP Bidirectional Archive (Security Fest CTF 2018)
|
|
440
|
+
|
|
441
|
+
**Pattern (Zion):** Distributed file is a valid `.docx` (ZIP archive). Extract it normally and you see only a decoy document. Reverse the entire file byte-for-byte and the result is *also* a valid ZIP archive — containing a second `word/media/*.png` whose contents are the flag.
|
|
442
|
+
|
|
443
|
+
**Extraction:**
|
|
444
|
+
```bash
|
|
445
|
+
# Verify the forward archive
|
|
446
|
+
unzip -l doc.docx
|
|
447
|
+
|
|
448
|
+
# Reverse the byte stream and unpack the mirror archive
|
|
449
|
+
python3 -c "import sys;sys.stdout.buffer.write(open('doc.docx','rb').read()[::-1])" > mirror.zip
|
|
450
|
+
unzip -l mirror.zip
|
|
451
|
+
unzip mirror.zip 'word/media/*' -d mirror/
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Why both directions succeed:** ZIP's central directory sits at the end of the archive and the local file headers are parsed only via offsets in that directory. By placing a second set of local headers at the *start* of the file, and a matching central directory at the very end after reversing, the file satisfies the ZIP specification in both reading orders. Python's `zipfile` and `unzip -l` read the central directory from the tail, so they happily open whichever end is presented first.
|
|
455
|
+
|
|
456
|
+
**Key insight:** Always test container files for byte-reversal, bit-reversal, and byte-interleaving when the forward extraction yields only a decoy. Run `binwalk` on both the forward and reversed copies to surface embedded archives hidden in either direction. The trick generalizes to any format whose parser tolerates trailing garbage (ZIP, RAR, PDF, tar).
|
|
457
|
+
|
|
458
|
+
**References:** Security Fest CTF 2018 — writeup 10204
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## MIDI Note-On/Note-Off Pitch Pair Encoding (X-MAS CTF 2018)
|
|
463
|
+
|
|
464
|
+
**Pattern:** MIDI file plays nothing recognisable but has a strict alternating Note-On/Note-Off pattern. The hidden message is split one byte per *pair*: `ord(char) = note_on_pitch + note_off_pitch`. Sometimes encoded as high/low nibble: `ord(char) = (on << 4) | off`.
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
import mido
|
|
468
|
+
mid = mido.MidiFile('hidden.mid')
|
|
469
|
+
ons, offs = [], []
|
|
470
|
+
for ev in mid.tracks[0]:
|
|
471
|
+
if ev.type == 'note_on': ons.append(ev.note)
|
|
472
|
+
elif ev.type == 'note_off': offs.append(ev.note)
|
|
473
|
+
flag = ''.join(chr(o + f) for o, f in zip(ons, offs))
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Key insight:** MIDI pitch values are 7-bit (0..127), so any byte can be split across two notes. When a MIDI sounds "atonal" but obeys strict note-pair alternation, test `on + off`, `(on<<4)|off`, `off - on`, and XOR combinations before assuming audio stego.
|
|
477
|
+
|
|
478
|
+
**References:** X-MAS CTF 2018 — A Christmas Carol, writeup 12667
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|