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.
Files changed (152) hide show
  1. package/AGENTS.md +131 -0
  2. package/Dockerfile +192 -0
  3. package/README.md +522 -0
  4. package/agents/ctf-analyzer.md +58 -0
  5. package/agents/ctf-controller.md +151 -0
  6. package/agents/ctf-speedrun.md +74 -0
  7. package/bin/ctf-agent.js +119 -0
  8. package/docker-compose.yml +13 -0
  9. package/mcp_config.json +19 -0
  10. package/package.json +44 -0
  11. package/references/ctf-triage-ladder.md +68 -0
  12. package/references/exploit-databases.md +575 -0
  13. package/references/llm-safety-and-policy-compliance.md +173 -0
  14. package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
  15. package/references/security-events-and-intelligence.md +566 -0
  16. package/references/version-matrix.md +59 -0
  17. package/rules/ctf-execution-rules.md +44 -0
  18. package/rules/ctf-safety-framing-rules.md +140 -0
  19. package/rules/flag-validation-rules.md +25 -0
  20. package/scripts/__init__.py +1 -0
  21. package/scripts/ctf-tools.lock +205 -0
  22. package/scripts/ctf_init.py +1026 -0
  23. package/scripts/ctf_update.py +583 -0
  24. package/scripts/ctfd_client.py +81 -0
  25. package/scripts/cve_lookup.py +453 -0
  26. package/scripts/extract_flags.py +155 -0
  27. package/scripts/install_as_agent.py +278 -0
  28. package/scripts/install_ctf_tools.sh +603 -0
  29. package/scripts/parallel_triage.py +395 -0
  30. package/scripts/prompt_policy_sanitizer.py +283 -0
  31. package/scripts/scope_guard.py +444 -0
  32. package/scripts/skill_validator.py +504 -0
  33. package/scripts/workspace_cleaner.py +141 -0
  34. package/skills/ctf-ai-ml/SKILL.md +120 -0
  35. package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
  36. package/skills/ctf-ai-ml/llm-attacks.md +487 -0
  37. package/skills/ctf-ai-ml/model-attacks.md +422 -0
  38. package/skills/ctf-crypto/SKILL.md +320 -0
  39. package/skills/ctf-crypto/advanced-math.md +798 -0
  40. package/skills/ctf-crypto/classic-ciphers.md +651 -0
  41. package/skills/ctf-crypto/ecc-attacks.md +347 -0
  42. package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
  43. package/skills/ctf-crypto/exotic-crypto.md +528 -0
  44. package/skills/ctf-crypto/historical.md +113 -0
  45. package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
  46. package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
  47. package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
  48. package/skills/ctf-crypto/modern-ciphers.md +649 -0
  49. package/skills/ctf-crypto/prng-attacks.md +257 -0
  50. package/skills/ctf-crypto/prng.md +664 -0
  51. package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
  52. package/skills/ctf-crypto/rsa-attacks.md +487 -0
  53. package/skills/ctf-crypto/stream-ciphers.md +390 -0
  54. package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
  55. package/skills/ctf-forensics/3d-printing.md +121 -0
  56. package/skills/ctf-forensics/SKILL.md +379 -0
  57. package/skills/ctf-forensics/disk-advanced.md +497 -0
  58. package/skills/ctf-forensics/disk-and-memory.md +491 -0
  59. package/skills/ctf-forensics/disk-recovery.md +699 -0
  60. package/skills/ctf-forensics/linux-forensics.md +511 -0
  61. package/skills/ctf-forensics/network-advanced.md +583 -0
  62. package/skills/ctf-forensics/network.md +645 -0
  63. package/skills/ctf-forensics/peripheral-capture.md +287 -0
  64. package/skills/ctf-forensics/signals-and-hardware.md +713 -0
  65. package/skills/ctf-forensics/steganography.md +694 -0
  66. package/skills/ctf-forensics/stego-advanced-2.md +475 -0
  67. package/skills/ctf-forensics/stego-advanced.md +481 -0
  68. package/skills/ctf-forensics/stego-image.md +691 -0
  69. package/skills/ctf-forensics/windows.md +625 -0
  70. package/skills/ctf-malware/SKILL.md +181 -0
  71. package/skills/ctf-malware/c2-and-protocols.md +274 -0
  72. package/skills/ctf-malware/pe-and-dotnet.md +108 -0
  73. package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
  74. package/skills/ctf-misc/SKILL.md +498 -0
  75. package/skills/ctf-misc/bashjails.md +323 -0
  76. package/skills/ctf-misc/ctfd-navigation.md +465 -0
  77. package/skills/ctf-misc/dns.md +255 -0
  78. package/skills/ctf-misc/encodings-advanced.md +504 -0
  79. package/skills/ctf-misc/encodings.md +431 -0
  80. package/skills/ctf-misc/games-and-vms-2.md +254 -0
  81. package/skills/ctf-misc/games-and-vms-3.md +690 -0
  82. package/skills/ctf-misc/games-and-vms-4.md +229 -0
  83. package/skills/ctf-misc/games-and-vms.md +529 -0
  84. package/skills/ctf-misc/linux-privesc.md +333 -0
  85. package/skills/ctf-misc/pyjails.md +671 -0
  86. package/skills/ctf-misc/rf-sdr.md +91 -0
  87. package/skills/ctf-osint/SKILL.md +198 -0
  88. package/skills/ctf-osint/geolocation-and-media.md +464 -0
  89. package/skills/ctf-osint/social-media.md +312 -0
  90. package/skills/ctf-osint/web-and-dns.md +341 -0
  91. package/skills/ctf-pwn/SKILL.md +214 -0
  92. package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
  93. package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
  94. package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
  95. package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
  96. package/skills/ctf-pwn/advanced-exploits.md +773 -0
  97. package/skills/ctf-pwn/advanced.md +326 -0
  98. package/skills/ctf-pwn/field-notes.md +245 -0
  99. package/skills/ctf-pwn/format-string.md +694 -0
  100. package/skills/ctf-pwn/heap-fsop.md +285 -0
  101. package/skills/ctf-pwn/heap-techniques-2.md +333 -0
  102. package/skills/ctf-pwn/heap-techniques.md +513 -0
  103. package/skills/ctf-pwn/kernel-bypass.md +421 -0
  104. package/skills/ctf-pwn/kernel-techniques.md +366 -0
  105. package/skills/ctf-pwn/kernel.md +636 -0
  106. package/skills/ctf-pwn/overflow-basics.md +611 -0
  107. package/skills/ctf-pwn/rop-advanced.md +725 -0
  108. package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
  109. package/skills/ctf-pwn/sandbox-escape.md +313 -0
  110. package/skills/ctf-reverse/SKILL.md +163 -0
  111. package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
  112. package/skills/ctf-reverse/anti-analysis.md +693 -0
  113. package/skills/ctf-reverse/field-notes.md +376 -0
  114. package/skills/ctf-reverse/languages-compiled.md +666 -0
  115. package/skills/ctf-reverse/languages-platforms.md +592 -0
  116. package/skills/ctf-reverse/languages.md +553 -0
  117. package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
  118. package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
  119. package/skills/ctf-reverse/patterns-ctf.md +670 -0
  120. package/skills/ctf-reverse/patterns-runtime.md +274 -0
  121. package/skills/ctf-reverse/patterns.md +572 -0
  122. package/skills/ctf-reverse/platforms-hardware.md +387 -0
  123. package/skills/ctf-reverse/platforms.md +664 -0
  124. package/skills/ctf-reverse/tools-advanced-2.md +421 -0
  125. package/skills/ctf-reverse/tools-advanced.md +407 -0
  126. package/skills/ctf-reverse/tools-dynamic.md +679 -0
  127. package/skills/ctf-reverse/tools-emulation.md +319 -0
  128. package/skills/ctf-reverse/tools.md +573 -0
  129. package/skills/ctf-web/SKILL.md +153 -0
  130. package/skills/ctf-web/auth-and-access-2.md +82 -0
  131. package/skills/ctf-web/auth-and-access.md +783 -0
  132. package/skills/ctf-web/auth-infra.md +321 -0
  133. package/skills/ctf-web/auth-jwt.md +186 -0
  134. package/skills/ctf-web/client-side-advanced.md +739 -0
  135. package/skills/ctf-web/client-side.md +529 -0
  136. package/skills/ctf-web/cves.md +373 -0
  137. package/skills/ctf-web/field-notes.md +482 -0
  138. package/skills/ctf-web/node-and-prototype.md +200 -0
  139. package/skills/ctf-web/server-side-2.md +337 -0
  140. package/skills/ctf-web/server-side-advanced-2.md +559 -0
  141. package/skills/ctf-web/server-side-advanced-3.md +125 -0
  142. package/skills/ctf-web/server-side-advanced-4.md +480 -0
  143. package/skills/ctf-web/server-side-advanced.md +378 -0
  144. package/skills/ctf-web/server-side-deser.md +443 -0
  145. package/skills/ctf-web/server-side-exec-2.md +799 -0
  146. package/skills/ctf-web/server-side-exec.md +457 -0
  147. package/skills/ctf-web/server-side.md +629 -0
  148. package/skills/ctf-web/sql-injection.md +790 -0
  149. package/skills/ctf-web/web3.md +374 -0
  150. package/skills/ctf-writeup/SKILL.md +90 -0
  151. package/skills/solve-challenge/SKILL.md +269 -0
  152. package/skills.json +16 -0
@@ -0,0 +1,694 @@
1
+ # CTF Forensics - Steganography
2
+
3
+ Non-image steganography techniques (PDF, SVG, terminal, text, compression, spreadsheet) and general-purpose image stego patterns (PNG structure, file overlays, GIF, autostereograms, interleaving). For image-specific steganography (JPEG DQT/F5/slack, BMP bitplane, PNG palette, pixel permutation, edge matching), see [stego-image.md](stego-image.md). For advanced techniques (FFT, SSTV, audio, video, JPEG XL), see [stego-advanced.md](stego-advanced.md) and [stego-advanced-2.md](stego-advanced-2.md).
4
+
5
+ ## Table of Contents
6
+ - [Quick Tools](#quick-tools)
7
+ - [Binary Border Steganography](#binary-border-steganography)
8
+ - [Multi-Layer PDF Steganography (Pragyan 2026)](#multi-layer-pdf-steganography-pragyan-2026)
9
+ - [Advanced PDF Steganography (Nullcon 2026 rdctd series)](#advanced-pdf-steganography-nullcon-2026-rdctd-series)
10
+ - [SVG Animation Keyframe Steganography (UTCTF 2024)](#svg-animation-keyframe-steganography-utctf-2024)
11
+ - [PNG Chunk Reordering (0xFun 2026)](#png-chunk-reordering-0xfun-2026)
12
+ - [File Format Overlays (0xFun 2026)](#file-format-overlays-0xfun-2026)
13
+ - [Nested PNG with Iterating XOR Keys (VuwCTF 2025)](#nested-png-with-iterating-xor-keys-vuwctf-2025)
14
+ - [GIF Frame Differential + Morse Code (BaltCTF 2013)](#gif-frame-differential--morse-code-baltctf-2013)
15
+ - [GZSteg + Spammimic Text Steganography (VolgaCTF 2013)](#gzsteg--spammimic-text-steganography-volgactf-2013)
16
+ - [Spreadsheet Frequency Analysis Binary Recovery (Sharif CTF 2016)](#spreadsheet-frequency-analysis-binary-recovery-sharif-ctf-2016)
17
+ - [Kitty Terminal Graphics Protocol Decoding (BSidesSF 2026)](#kitty-terminal-graphics-protocol-decoding-bsidessf-2026)
18
+ - [ANSI Escape Sequence Steganography in Terminal Art (BSidesSF 2026)](#ansi-escape-sequence-steganography-in-terminal-art-bsidessf-2026)
19
+ - [Autostereogram / Magic Eye Solving (BSidesSF 2026)](#autostereogram--magic-eye-solving-bsidessf-2026)
20
+ - [Two-Layer Byte+Line Interleaving (BSidesSF 2026)](#two-layer-byteline-interleaving-bsidessf-2026)
21
+ - [Progressive PNG Layered XOR Decryption (OpenCTF 2016)](#progressive-png-layered-xor-decryption-openctf-2016)
22
+ - [Multi-Stream Video Container Steganography (BSidesSF 2026)](#multi-stream-video-container-steganography-bsidessf-2026)
23
+ - [APNG (Animated PNG) Frame Extraction (IceCTF 2016)](#apng-animated-png-frame-extraction-icectf-2016)
24
+ - [PNG Height/CRC Manipulation for Hidden Content (H4ckIT CTF 2016)](#png-heightcrc-manipulation-for-hidden-content-h4ckit-ctf-2016)
25
+ - [QR Code Reconstruction from Curved Glass Reflection in Video (PlaidCTF 2018)](#qr-code-reconstruction-from-curved-glass-reflection-in-video-plaidctf-2018)
26
+ - [GIF Palette Manipulation for QR Code Reconstruction (3DSCTF 2017)](#gif-palette-manipulation-for-qr-code-reconstruction-3dsctf-2017)
27
+ - [Angecryption: AES-CBC Encrypting One Valid File into Another (34C3 CTF 2017)](#angecryption-aes-cbc-encrypting-one-valid-file-into-another-34c3-ctf-2017)
28
+ - [SVG Micro-Coordinate Steganography (SharifCTF 8)](#svg-micro-coordinate-steganography-sharifctf-8)
29
+
30
+ ---
31
+
32
+ ## Quick Tools
33
+
34
+ ```bash
35
+ steghide extract -sf image.jpg
36
+ zsteg image.png # PNG/BMP analysis
37
+ stegsolve # Visual analysis
38
+
39
+ # Steghide brute-force (0xFun 2026)
40
+ stegseek image.jpg rockyou.txt # Faster than stegcracker
41
+ # Common weak passphrases: "simple", "password", "123456"
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Binary Border Steganography
47
+
48
+ **Pattern (Framer, PascalCTF 2026):** Message encoded as black/white pixels in 1-pixel border around image.
49
+
50
+ ```python
51
+ from PIL import Image
52
+
53
+ img = Image.open('output.jpg')
54
+ w, h = img.size
55
+ bits = []
56
+
57
+ # Read border clockwise: top → right → bottom (reversed) → left (reversed)
58
+ for x in range(w): bits.append(0 if sum(img.getpixel((x, 0))[:3]) < 384 else 1)
59
+ for y in range(1, h): bits.append(0 if sum(img.getpixel((w-1, y))[:3]) < 384 else 1)
60
+ for x in range(w-2, -1, -1): bits.append(0 if sum(img.getpixel((x, h-1))[:3]) < 384 else 1)
61
+ for y in range(h-2, 0, -1): bits.append(0 if sum(img.getpixel((0, y))[:3]) < 384 else 1)
62
+
63
+ # Convert bits to ASCII
64
+ msg = ''.join(chr(int(''.join(map(str, bits[i:i+8])), 2)) for i in range(0, len(bits)-7, 8))
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Multi-Layer PDF Steganography (Pragyan 2026)
70
+
71
+ **Pattern (epstein files):** Flag hidden across multiple layers in a PDF.
72
+
73
+ **Layer checklist:**
74
+ 1. `strings file.pdf | grep -i hidden` -- hidden comments in PDF objects
75
+ 2. Extract hex strings, try XOR with theme-related keywords
76
+ 3. Check bytes **after `%%EOF`** marker -- may contain GPG/encrypted data
77
+ 4. Try ROT18 (ROT13 on letters + ROT5 on digits) as final decode layer
78
+
79
+ ```bash
80
+ # Extract post-EOF data
81
+ python3 -c "
82
+ data = open('file.pdf','rb').read()
83
+ eof = data.rfind(b'%%EOF')
84
+ print(data[eof+5:].hex())
85
+ "
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Advanced PDF Steganography (Nullcon 2026 rdctd series)
91
+
92
+ Six distinct hiding techniques in a single PDF:
93
+
94
+ **1. Invisible text separators:** Underscores rendered as invisible line segments. Extract with `pdftotext -layout` and normalize whitespace to underscores.
95
+
96
+ **2. URI annotations with escaped braces:** Link annotations contain flag in URI with `\{` and `\}` escapes:
97
+ ```python
98
+ import pikepdf
99
+ pdf = pikepdf.Pdf.open(pdf_path)
100
+ for page in pdf.pages:
101
+ for annot in (page.get("/Annots") or []):
102
+ obj = annot.get_object()
103
+ if obj.get("/Subtype") == pikepdf.Name("/Link"):
104
+ uri = str(obj.get("/A").get("/URI")).replace(r"\{", "{").replace(r"\}", "}")
105
+ # Check for flag pattern
106
+ ```
107
+
108
+ **3. Blurred/redacted image with Wiener deconvolution:**
109
+ ```python
110
+ from skimage.restoration import wiener
111
+ import numpy as np
112
+
113
+ def gaussian_psf(sigma):
114
+ k = int(sigma * 6 + 1) | 1
115
+ ax = np.arange(-(k//2), k//2 + 1, dtype=np.float32)
116
+ xx, yy = np.meshgrid(ax, ax)
117
+ psf = np.exp(-(xx**2 + yy**2) / (2 * sigma * sigma))
118
+ return psf / psf.sum()
119
+
120
+ img_arr = np.asarray(img.convert("L")).astype(np.float32) / 255.0
121
+ deconv = wiener(img_arr, gaussian_psf(3.0), balance=0.003, clip=False)
122
+ ```
123
+
124
+ **4. Vector rectangle QR code:** Hundreds of tiny filled rectangles (e.g., 1.718x1.718 units) forming a QR code. Parse PDF content stream for `re` operators, extract centers, render as grid, decode with `zbarimg`.
125
+
126
+ **5. Compressed object streams:** Use `mutool clean -d -c -m input.pdf output.pdf` to decompress all streams, then `strings` to search.
127
+
128
+ **6. Document metadata:** Check Producer, Author, Keywords fields: `pdfinfo doc.pdf` or `exiftool doc.pdf`.
129
+
130
+ **Official writeup details (Nullcon 2026 rdctd 1-6):**
131
+ - **rdctd 1:** Flag is visible in plain text (Section 3.4)
132
+ - **rdctd 2:** Flag in hyperlink URI with escaped braces (`\{`, `\}`)
133
+ - **rdctd 3:** LSB stego in Blue channel, **bit plane 5** (not bit 0!). Use `zsteg` with all planes: `zsteg -a extracted.ppm | grep ENO`
134
+ - **rdctd 4:** QR code hidden under black redaction box. Use Master PDF Editor to remove the box, scan QR
135
+ - **rdctd 5:** Flag in FlateDecode compressed stream (not visible with `strings`):
136
+ ```python
137
+ import re, zlib
138
+ pdf = open('file.pdf', 'rb').read()
139
+ for s in re.findall(b'stream[\r\n]+(.*?)[\r\n]+endstream', pdf, re.S):
140
+ try:
141
+ dec = zlib.decompress(s)
142
+ if b'ENO{' in dec: print(dec)
143
+ except: pass
144
+ ```
145
+ - **rdctd 6:** Flag in `/Producer` metadata field
146
+
147
+ **Comprehensive PDF flag hunt checklist:**
148
+ 1. `strings -a file.pdf | grep -o 'FLAG_FORMAT{[^}]*}'`
149
+ 2. `exiftool file.pdf` (all metadata fields)
150
+ 3. `pdfimages -all file.pdf img` + `zsteg -a img-*.ppm`
151
+ 4. Open in PDF editor, check for overlay/redaction boxes hiding content
152
+ 5. Decompress FlateDecode streams and search
153
+ 6. Parse link annotations for URIs with escaped characters
154
+ 7. `mutool clean -d file.pdf clean.pdf && strings clean.pdf`
155
+
156
+ ---
157
+
158
+ ## SVG Animation Keyframe Steganography (UTCTF 2024)
159
+
160
+ **Pattern (Insanity Check):** SVG favicon contains animation keyframes with alternating fill colors.
161
+
162
+ **Encoding:** `#FFFF` = 1, `#FFF6` = 0. Timing intervals (~0.314s or 3x0.314s) encode Morse code dots/dashes.
163
+
164
+ **Detection:** SVG files with `<animate>` tags, `keyTimes`/`values` attributes. Check favicon.svg and other vector assets. Two-value alternation patterns encode binary or Morse.
165
+
166
+ ---
167
+
168
+ ## APNG (Animated PNG) Frame Extraction (IceCTF 2016)
169
+
170
+ APNG files contain multiple frames within a standard PNG container. Tools like `tweakpng` or `apngdis` extract individual frames that may contain hidden data.
171
+
172
+ ```bash
173
+ # Check if PNG is actually APNG (contains acTL chunk)
174
+ python3 -c "
175
+ import struct
176
+ with open('image.png', 'rb') as f:
177
+ data = f.read()
178
+ if b'acTL' in data:
179
+ print('APNG detected!')
180
+ idx = data.index(b'acTL')
181
+ num_frames = struct.unpack('>I', data[idx+4:idx+8])[0]
182
+ print(f'Number of frames: {num_frames}')
183
+ "
184
+
185
+ # Extract frames using apngdis
186
+ apngdis image.apng # produces frame_01.png, frame_02.png, ...
187
+
188
+ # Alternative: use PHP or Python libraries
189
+ # pip install apng
190
+ python3 -c "
191
+ from apng import APNG
192
+ im = APNG.open('image.apng')
193
+ for i, (png, control) in enumerate(im.frames):
194
+ png.save(f'frame_{i:02d}.png')
195
+ "
196
+ ```
197
+
198
+ **Key insight:** Regular PNG viewers display only the first frame of an APNG. Hidden data can be in any subsequent frame. The `acTL` chunk signals APNG format; `fcTL`/`fdAT` chunks contain additional frame data.
199
+
200
+ ---
201
+
202
+ ## PNG Height/CRC Manipulation for Hidden Content (H4ckIT CTF 2016)
203
+
204
+ PNG images with incorrect IHDR dimensions hide content below the visible area. Brute-force the correct height by matching the IHDR CRC.
205
+
206
+ ```python
207
+ import struct, zlib
208
+
209
+ def fix_png_height(filename):
210
+ with open(filename, 'rb') as f:
211
+ data = bytearray(f.read())
212
+
213
+ # IHDR chunk starts at offset 8 (after 8-byte PNG signature)
214
+ # IHDR layout: width(4) height(4) bitdepth(1) colortype(1) ...
215
+ ihdr_start = 8 + 4 # skip signature + chunk length
216
+ ihdr_data = data[ihdr_start:ihdr_start + 17] # "IHDR" + 13 bytes
217
+ stored_crc = struct.unpack('>I', data[ihdr_start + 17:ihdr_start + 21])[0]
218
+
219
+ width = struct.unpack('>I', ihdr_data[4:8])[0]
220
+
221
+ # Brute-force correct height
222
+ for h in range(1, 4096):
223
+ test_ihdr = ihdr_data[:8] + struct.pack('>I', h) + ihdr_data[12:]
224
+ if zlib.crc32(test_ihdr) & 0xffffffff == stored_crc:
225
+ print(f"Correct height: {h} (was: {struct.unpack('>I', ihdr_data[8:12])[0]})")
226
+ data[ihdr_start + 8:ihdr_start + 12] = struct.pack('>I', h)
227
+ with open('fixed_' + filename, 'wb') as f:
228
+ f.write(data)
229
+ return h
230
+
231
+ # If no CRC match, the CRC itself may need fixing after setting height
232
+ # Manual approach: set height larger, fix CRC
233
+ return None
234
+ ```
235
+
236
+ **Key insight:** PNG stores image dimensions in the IHDR chunk with a CRC. If the height is reduced, data below the visible area is hidden but still present in IDAT chunks. Brute-forcing the height against the stored CRC reveals the correct dimensions. If the CRC was also modified, try increasing the height and recalculating the CRC.
237
+
238
+ ---
239
+
240
+ ## PNG Chunk Reordering (0xFun 2026)
241
+
242
+ **Pattern (Spectrum):** Invalid PNG has chunks out of order.
243
+
244
+ **Fix:** Reorder to: `signature + IHDR + (ancillary chunks) + (all IDAT in order) + IEND`.
245
+
246
+ ```python
247
+ import struct
248
+
249
+ with open('broken.png', 'rb') as f:
250
+ data = f.read()
251
+
252
+ sig = data[:8]
253
+ chunks = []
254
+ pos = 8
255
+ while pos < len(data):
256
+ length = struct.unpack('>I', data[pos:pos+4])[0]
257
+ chunk_type = data[pos+4:pos+8]
258
+ chunk_data = data[pos+8:pos+8+length]
259
+ crc = data[pos+8+length:pos+12+length]
260
+ chunks.append((chunk_type, length, chunk_data, crc))
261
+ pos += 12 + length
262
+
263
+ # Sort: IHDR first, IEND last, IDATs in original order
264
+ ihdr = [c for c in chunks if c[0] == b'IHDR']
265
+ idat = [c for c in chunks if c[0] == b'IDAT']
266
+ iend = [c for c in chunks if c[0] == b'IEND']
267
+ other = [c for c in chunks if c[0] not in (b'IHDR', b'IDAT', b'IEND')]
268
+
269
+ with open('fixed.png', 'wb') as f:
270
+ f.write(sig)
271
+ for typ, length, data, crc in ihdr + other + idat + iend:
272
+ f.write(struct.pack('>I', length) + typ + data + crc)
273
+ ```
274
+
275
+ ---
276
+
277
+ ## File Format Overlays (0xFun 2026)
278
+
279
+ **Pattern (Pixel Rehab):** Archive appended after PNG IEND, but magic bytes overwritten with PNG signature.
280
+
281
+ **Detection:** Check bytes after IEND for appended data. Compare magic bytes against known formats.
282
+
283
+ ```python
284
+ # Find IEND, check what follows
285
+ data = open('image.png', 'rb').read()
286
+ iend_pos = data.find(b'IEND') + 8 # After IEND + CRC
287
+ trailer = data[iend_pos:]
288
+ # Replace first 6 bytes with 7z magic if they match PNG sig
289
+ if trailer[:4] == b'\x89PNG':
290
+ trailer = b'\x37\x7a\xbc\xaf\x27\x1c' + trailer[6:]
291
+ open('hidden.7z', 'wb').write(trailer)
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Nested PNG with Iterating XOR Keys (VuwCTF 2025)
297
+
298
+ **Pattern (Matroiska):** Each PNG layer XOR-encrypted with incrementing keys ("layer2", "layer3", etc.).
299
+
300
+ **Identification:** Matryoshka/nested hints. Try incrementing key patterns for recursive extraction.
301
+
302
+ ---
303
+
304
+ ## GIF Frame Differential + Morse Code (BaltCTF 2013)
305
+
306
+ **Pattern:** Animated GIF contains hidden dots visible only when comparing frames against originals. Dots encode Morse code.
307
+
308
+ ```bash
309
+ # Extract frames from animated GIF
310
+ convert animated.gif frame_%03d.gif
311
+
312
+ # Compare each frame against its base using ImageMagick
313
+ for i in $(seq 1 100); do
314
+ compare -fuzz 10% -compose src stego_$i.gif original_$i.gif diff_$i.gif
315
+ done
316
+
317
+ # Inspect diff images — dots appear at specific positions
318
+ # Map dot patterns to Morse: small dot = dit, large dot = dah
319
+ ```
320
+
321
+ **Key insight:** `compare -fuzz 10%` reveals subtle single-pixel modifications invisible to the eye. The diff images show isolated dots whose timing/spacing encodes Morse code. Decode dots → dashes/dots → letters → flag.
322
+
323
+ ---
324
+
325
+ ## GZSteg + Spammimic Text Steganography (VolgaCTF 2013)
326
+
327
+ **Pattern:** Data hidden within gzip compression metadata, decoded through spammimic.com.
328
+
329
+ 1. Apply GZSteg patches to gzip 1.2.4 source, compile, extract with `gzip --s` flag
330
+ 2. Extracted text resembles spam email — submit to [spammimic.com](https://www.spammimic.com/) decoder
331
+ 3. Decoded output is the flag
332
+
333
+ **Key insight:** GZSteg exploits redundancy in the gzip DEFLATE compression format to embed covert data. The extracted payload often uses a second steganographic layer (spammimic encodes data as innocuous-looking spam text). Look for `.gz` files larger than expected for their content.
334
+
335
+ ---
336
+
337
+ ## Spreadsheet Frequency Analysis Binary Recovery (Sharif CTF 2016)
338
+
339
+ When spreadsheet cells contain numbers with varying frequencies, the frequency rank may encode binary data:
340
+
341
+ 1. **Count occurrences** of each unique value
342
+ 2. **Sort by frequency** to create a mapping: value -> frequency rank (0-255)
343
+ 3. **Replace each cell** with its frequency rank to recover raw bytes
344
+
345
+ ```python
346
+ from collections import Counter
347
+
348
+ # Count frequency of each value
349
+ freq = Counter(all_cell_values)
350
+
351
+ # Create mapping: value -> index in frequency-sorted list
352
+ sorted_vals = sorted(freq.keys(), key=lambda x: freq[x])
353
+ mapping = {v: i for i, v in enumerate(sorted_vals)}
354
+
355
+ # Apply mapping to recover binary
356
+ binary = bytes(mapping[v] for v in all_cell_values)
357
+ # Result is typically an ELF binary or image
358
+ ```
359
+
360
+ **Key insight:** 256 unique values suggest byte-level encoding. The frequency distribution of the mapped output should resemble typical binary file statistics.
361
+
362
+ ---
363
+
364
+ ## Kitty Terminal Graphics Protocol Decoding (BSidesSF 2026)
365
+
366
+ **Pattern (kitty):** A file contains Kitty terminal graphics protocol escape sequences (`ESC_G`) that embed zlib-compressed RGB image data in base64-encoded chunks.
367
+
368
+ **Protocol format:**
369
+ ```text
370
+ \x1b_Ga=T,q=2,f=24,o=z,m=1,s=WIDTH,v=HEIGHT;BASE64DATA\x1b\\
371
+ ```
372
+
373
+ **Header fields:**
374
+ - `a=T` — action: transmit
375
+ - `q=2` — quiet mode (suppress responses)
376
+ - `f=24` — format: 24-bit RGB
377
+ - `o=z` — compression: zlib
378
+ - `m=1` — more chunks follow; `m=0` — final chunk
379
+ - `s=WIDTH,v=HEIGHT` — image dimensions (present in first chunk only)
380
+
381
+ **Decoding workflow:**
382
+ ```python
383
+ import re
384
+ import base64
385
+ import zlib
386
+ from PIL import Image
387
+
388
+ # Read the raw file
389
+ data = open('kitty_output.bin', 'rb').read()
390
+
391
+ # Extract all base64 payloads from escape sequences
392
+ # Pattern: \x1b_G...;BASE64\x1b\\
393
+ chunks = re.findall(rb'\x1b_G([^;]*);([^\x1b]*)\x1b\\\\', data)
394
+
395
+ # Parse dimensions from first chunk's header
396
+ first_header = chunks[0][0].decode()
397
+ width = int(re.search(r's=(\d+)', first_header).group(1))
398
+ height = int(re.search(r'v=(\d+)', first_header).group(1))
399
+
400
+ # Concatenate all base64 payloads
401
+ b64_data = b''.join(chunk[1] for chunk in chunks)
402
+ compressed = base64.b64decode(b64_data)
403
+ raw_rgb = zlib.decompress(compressed)
404
+
405
+ # Reconstruct image
406
+ img = Image.frombytes('RGB', (width, height), raw_rgb)
407
+ img.save('recovered.png')
408
+ ```
409
+
410
+ **Key insight:** Kitty graphics protocol is a modern terminal image display mechanism. The data is invisible when viewed in non-Kitty terminals but can be decoded from the raw escape sequences. Multi-chunk messages (`m=1` followed by continuation chunks) must be concatenated before base64 decoding.
411
+
412
+ **Detection:** Binary file containing `\x1b_G` sequences. `strings` output shows base64-like data interspersed with escape codes. Challenge mentions "kitty", "terminal graphics", or "meow".
413
+
414
+ **References:** BSidesSF 2026 "kitty"
415
+
416
+ ---
417
+
418
+ ## ANSI Escape Sequence Steganography in Terminal Art (BSidesSF 2026)
419
+
420
+ **Pattern (roar):** Flag text is interleaved between ANSI color escape codes and Unicode braille characters in terminal art. When rendered in a terminal, the art displays normally while the flag characters are invisible (zero-width or same-color-as-background). However, the flag is extractable by stripping all escape sequences and non-ASCII characters.
421
+
422
+ **Extraction:**
423
+ ```python
424
+ import re
425
+
426
+ data = open('art.txt', 'rb').read().decode('utf-8', errors='replace')
427
+
428
+ # Strip ANSI escape sequences
429
+ clean = re.sub(r'\x1b\[[0-9;]*[a-zA-Z]', '', data)
430
+
431
+ # Extract only printable ASCII (flag characters)
432
+ flag_chars = [c for c in clean if 32 <= ord(c) <= 126 and c not in ' \t\n']
433
+
434
+ # Or: filter out braille unicode block (U+2800-U+28FF) and other non-ASCII
435
+ flag_chars = [c for c in clean if ord(c) < 128 and c.isprintable() and c != ' ']
436
+
437
+ print(''.join(flag_chars))
438
+ ```
439
+
440
+ **Alternative approach — diff against rendered output:**
441
+ ```bash
442
+ # Render with ANSI codes, capture visible text
443
+ cat art.txt | col -b > rendered.txt
444
+ # Compare raw vs rendered to find hidden characters
445
+ ```
446
+
447
+ **Key insight:** ANSI escape sequences control terminal colors, cursor position, and text attributes. Flag characters inserted between escape codes are technically present in the file but invisible when rendered because they're either: (a) the same color as the background, (b) followed by a cursor-move-back sequence, or (c) overwritten by subsequent characters. Raw byte extraction bypasses all rendering tricks.
448
+
449
+ **Detection:** File with many `\x1b[` sequences (ANSI codes), Unicode braille characters (U+2800-U+28FF), and unexpectedly large file size for the visible content. Challenge mentions "terminal", "art", "ANSI", or shows ASCII/Unicode art.
450
+
451
+ **References:** BSidesSF 2026 "roar"
452
+
453
+ ---
454
+
455
+ ### Autostereogram / Magic Eye Solving (BSidesSF 2026)
456
+
457
+ **Pattern (stereotype):** Challenge image is an autostereogram (Magic Eye). The hidden 3D content (flag text) is revealed by viewing with crossed/divergent eyes or programmatically via layer difference.
458
+
459
+ **Programmatic solve (GIMP or Python):**
460
+ 1. Duplicate the image as a second layer
461
+ 2. Set the top layer's blending mode to "Difference"
462
+ 3. Slide the top layer horizontally by the repeat width (~100 pixels)
463
+ 4. The hidden depth pattern appears as bright lines on a dark background
464
+
465
+ ```python
466
+ from PIL import Image
467
+ import numpy as np
468
+
469
+ img = np.array(Image.open('stereogram.png'))
470
+ shift = 100 # Repeat width — try values 80-120
471
+ diff = np.abs(img[:, shift:].astype(int) - img[:, :-shift].astype(int))
472
+ Image.fromarray(diff.astype(np.uint8)).save('revealed.png')
473
+ ```
474
+
475
+ **Finding the shift value:** The repeat width is the horizontal distance between identical vertical strips. Autocorrelate a single row: `np.correlate(row, row, mode='full')` — the first peak after center is the shift.
476
+
477
+ **Key insight:** Autostereograms encode depth via horizontal pixel displacement relative to a repeating pattern. Subtracting the image from a shifted copy of itself cancels the repeating background and reveals the depth variation as the flag text.
478
+
479
+ **When to recognize:** Image has a repeating texture/pattern, challenge mentions "eyes", "seeing", "3D", "magic", or "stereogram".
480
+
481
+ **References:** BSidesSF 2026 "stereotype"
482
+
483
+ ---
484
+
485
+ ### Two-Layer Byte+Line Interleaving (BSidesSF 2026)
486
+
487
+ **Pattern (seeing-double):** Two PNG files are interleaved at the byte level into a single file. After byte-level deinterlacing, the resulting images have their scanlines interleaved, requiring a second round of line-level deinterlacing.
488
+
489
+ **Step 1 — Byte deinterleave:**
490
+ ```python
491
+ data = open('interleaved.ppnngg', 'rb').read()
492
+ file_a = bytes(data[i] for i in range(0, len(data), 2)) # Even bytes
493
+ file_b = bytes(data[i] for i in range(1, len(data), 2)) # Odd bytes
494
+ # file_a and file_b are valid PNGs
495
+ ```
496
+
497
+ **Step 2 — Line deinterleave (if needed):**
498
+ ```python
499
+ from PIL import Image
500
+ import numpy as np
501
+
502
+ img = np.array(Image.open('file_a.png'))
503
+ # Even lines form one sub-image, odd lines form another
504
+ sub1 = img[0::2] # Lines 0, 2, 4, ...
505
+ sub2 = img[1::2] # Lines 1, 3, 5, ...
506
+ Image.fromarray(sub1).save('final_a.png')
507
+ Image.fromarray(sub2).save('final_b.png')
508
+ ```
509
+
510
+ **Key insight:** The two-layer interleaving (first bytes, then scanlines) means simple deinterleaving at one level produces garbled results. Recognize multi-layer interleaving by: (1) deinterleaved file is a valid image but content looks "striped" or has alternating line artifacts, (2) file extension hints (`.ppnngg` = two PNGs interleaved).
511
+
512
+ **Detection:** File has double-extension or unusual extension. `file` command may identify it as data or as one format. Even/odd byte extraction produces valid file headers (e.g., both halves start with PNG magic `89 50 4E 47`).
513
+
514
+ **References:** BSidesSF 2026 "seeing-double"
515
+
516
+ ---
517
+
518
+ ### Multi-Stream Video Container Steganography (BSidesSF 2026)
519
+
520
+ **Pattern (ads):** An MP4 video container holds multiple video streams. The default (stream 0:0) plays normally, but a second stream (0:1) contains the flag. Most video players only show the first/default stream. The secondary stream uses AV1 codec which has poor support in many tools, adding friction.
521
+
522
+ ```bash
523
+ # Detect multiple streams
524
+ ffprobe -hide_banner flag.mp4
525
+ # Look for Stream #0:1 — a second video stream
526
+
527
+ # Extract second stream to its own file
528
+ ffmpeg -i flag.mp4 -map 0:1 -c copy second_stream.mp4
529
+
530
+ # Or extract just the first frame from stream 1
531
+ ffmpeg -i flag.mp4 -map 0:1 -frames:v 1 flag.jpg
532
+ ```
533
+
534
+ **Key insight:** MP4/MKV containers can hold multiple video, audio, and subtitle tracks. Most players default to stream 0:0. Always run `ffprobe` or `mediainfo` to enumerate ALL streams. The `-map 0:N` flag in ffmpeg selects specific streams. VLC can also switch tracks via Video → Video Track menu.
535
+
536
+ **When to recognize:** Challenge provides a video file where the visible content seems irrelevant or is a red herring. `ffprobe` shows multiple `Stream` entries. Check metadata fields like `handler_name` for hints (e.g., "CTF Trickery").
537
+
538
+ **Detection checklist:**
539
+ 1. `ffprobe -hide_banner file.mp4` — count Stream lines
540
+ 2. `mediainfo file.mp4` — check track count
541
+ 3. VLC → Video → Video Track → try all tracks
542
+
543
+ **References:** BSidesSF 2026 "ads"
544
+
545
+ ---
546
+
547
+ ## Progressive PNG Layered XOR Decryption (OpenCTF 2016)
548
+
549
+ **Pattern (Progressive Encryption):** PNG contains standard `IDAT` chunk (coarse first scan) plus custom `scRT` chunks. Each `scRT` chunk is XOR-encrypted with a multi-byte key. Decrypting reveals another `IDAT` chunk plus another `scRT`, forming nested layers.
550
+
551
+ 1. Extract the custom `scRT` chunk data from the PNG
552
+ 2. Use xortool to guess the XOR key (expected most frequent byte: `\xFF` for image data):
553
+ ```bash
554
+ # Extract scRT chunk contents
555
+ python3 -c "
556
+ import struct
557
+ with open('image.png', 'rb') as f:
558
+ data = f.read()
559
+ # Parse PNG chunks, find scRT
560
+ pos = 8 # skip PNG signature
561
+ while pos < len(data):
562
+ length = struct.unpack('>I', data[pos:pos+4])[0]
563
+ chunk_type = data[pos+4:pos+8]
564
+ if chunk_type == b'scRT':
565
+ with open('layer.bin', 'wb') as out:
566
+ out.write(data[pos+8:pos+8+length])
567
+ pos += 12 + length
568
+ "
569
+
570
+ # Guess XOR key
571
+ xortool -c ff layer.bin
572
+ # Output: key = 'nacho'
573
+ ```
574
+
575
+ 3. Decrypt and split: the decrypted data contains a valid `IDAT` chunk followed by another `scRT`
576
+ 4. Repeat for each layer until all `scRT` chunks are decrypted
577
+ 5. Reassemble: concatenate PNG header + all decrypted `IDAT` chunks + `IEND`
578
+
579
+ **Layer keys in this challenge:** `nacho`, `savages`, `president`, `kilobits`, `monkey`, `butler`
580
+
581
+ **Shortcut:** Open the raw PNG bytes as a raw image in GraphBitStreamer (32 bpp, width matching original). Weak XOR encryption preserves visual patterns (like ECB-encrypted images), making the flag readable without full decryption.
582
+
583
+ **Key insight:** Custom PNG chunks (non-standard 4-letter types) often contain hidden data. The PNG spec allows arbitrary ancillary chunks — parsers ignore unknown types. When multiple layers use different XOR keys, each must be cracked independently using frequency analysis. The shortcut works because XOR with a short repeating key preserves large-scale pixel patterns, similar to ECB mode's visual leakage.
584
+
585
+ ---
586
+
587
+ ### QR Code Reconstruction from Curved Glass Reflection in Video (PlaidCTF 2018)
588
+
589
+ **Pattern:** QR code visible only as a curved reflection on a glass sphere in surveillance footage (~100px wide). Manual reconstruction required flipping, de-warping, identifying as Version 2 (25x25), decoding format string for ECC level, and pixel-by-pixel reconstruction using known flag prefix to fix initial data bytes.
590
+
591
+ **Steps:**
592
+ 1. Extract best frame from video, crop the reflection
593
+ 2. Flip horizontally (mirror reflection) and apply de-warping
594
+ 3. Identify QR version (25x25 = Version 2) and decode format bits for ECC level and mask pattern
595
+ 4. Begin manual pixel transcription of the 25x25 grid
596
+ 5. When initial decode fails, use known plaintext (flag prefix "PCTF{") to fix first data bytes
597
+ 6. High ECC level (Q = 25% recovery) corrects remaining pixel errors
598
+
599
+ ```python
600
+ from PIL import Image
601
+ import numpy as np
602
+
603
+ # Step 1: Extract and flip the reflection
604
+ frame = Image.open('best_frame.png')
605
+ reflection = frame.crop((x1, y1, x2, y2))
606
+ flipped = reflection.transpose(Image.FLIP_LEFT_RIGHT)
607
+
608
+ # Step 2: Scale up for manual transcription
609
+ scaled = flipped.resize((500, 500), Image.NEAREST)
610
+ scaled.save('reflection_scaled.png')
611
+
612
+ # Step 3: Manual 25x25 grid transcription (Version 2 QR)
613
+ # After manual pixel identification, create the QR matrix
614
+ qr_matrix = np.zeros((25, 25), dtype=np.uint8)
615
+ # Fill in identified modules from visual inspection...
616
+ # qr_matrix[row][col] = 1 # dark module
617
+ # qr_matrix[row][col] = 0 # light module
618
+
619
+ # Step 4: Render as clean QR image for scanning
620
+ cell_size = 20
621
+ qr_img = Image.new('L', (25 * cell_size, 25 * cell_size), 255)
622
+ for r in range(25):
623
+ for c in range(25):
624
+ if qr_matrix[r][c]:
625
+ for dy in range(cell_size):
626
+ for dx in range(cell_size):
627
+ qr_img.putpixel((c * cell_size + dx, r * cell_size + dy), 0)
628
+ qr_img.save('reconstructed_qr.png')
629
+
630
+ # Step 5: Scan with zbarimg or use known prefix to fix errors
631
+ # zbarimg reconstructed_qr.png
632
+ ```
633
+
634
+ **Key insight:** QR codes with high ECC levels (Q or H) can tolerate significant reconstruction errors. When a QR code is partially visible (reflection, damage, low resolution), manually reconstruct what you can, use known plaintext to fix early data modules, and let ECC correct the rest.
635
+
636
+ ---
637
+
638
+ ### GIF Palette Manipulation for QR Code Reconstruction (3DSCTF 2017)
639
+
640
+ GIF with 108,900 single-pixel frames. Each frame has identical pixel data but different palette entries. Map palette color to black/white to reconstruct a 330x330 QR code:
641
+
642
+ ```python
643
+ from PIL import Image
644
+ gif = Image.open('challenge.gif')
645
+ width = int(gif.n_frames ** 0.5) # sqrt(108900) = 330
646
+ pixels = []
647
+ for i in range(gif.n_frames):
648
+ gif.seek(i)
649
+ palette = gif.getpalette()
650
+ # First palette entry: yellow=(255,255,0) or green=(0,255,0)
651
+ pixels.append(0 if palette[0] > 128 else 255) # black or white
652
+
653
+ out = Image.new('L', (width, width))
654
+ out.putdata(pixels)
655
+ out.save('qr.png')
656
+ # zbarimg qr.png
657
+ ```
658
+
659
+ **Key insight:** GIF frames with identical pixel data but different color palettes encode binary data through palette manipulation. The number of frames is a perfect square, giving the side length of the hidden image. Each frame represents one pixel; the palette's first entry determines its color. When a GIF has an unusually large number of frames whose count is a perfect square, check for palette-based encoding.
660
+
661
+ ---
662
+
663
+ ### Angecryption: AES-CBC Encrypting One Valid File into Another (34C3 CTF 2017)
664
+
665
+ Based on Ange Albertini's technique: a crafted AES-CBC key and IV can encrypt one valid image file into another valid image file:
666
+
667
+ ```python
668
+ from Crypto.Cipher import AES
669
+ key = bytes.fromhex('...') # provided or recovered
670
+ iv = bytes.fromhex('...')
671
+ aes = AES.new(key, AES.MODE_CBC, iv)
672
+ encrypted = aes.encrypt(open('flag.png', 'rb').read())
673
+ # encrypted is ALSO a valid PNG (a mask image)
674
+ # Overlay the mask on the original to reveal hidden content
675
+ ```
676
+
677
+ **Key insight:** Angecryption exploits the fact that file format headers have enough degrees of freedom to survive AES-CBC encryption with chosen key/IV. The technique crafts the IV so that decrypting the "mask" file header produces a valid "flag" file header. When you find two valid image files and an AES key/IV in a challenge, try encrypting one — the result may be the other, and visual comparison reveals the flag.
678
+
679
+ ---
680
+
681
+ ### SVG Micro-Coordinate Steganography (SharifCTF 8)
682
+
683
+ SVG contains a visible graphic plus a second `<g>` element with extremely small coordinate values (e.g., 450.xxxxx, 835.xxxxx). Apply SVG transform to zoom in:
684
+
685
+ ```xml
686
+ <svg viewBox="448.75 834.69 2 2" width="2000" height="2000">
687
+ <!-- or apply transform: -->
688
+ <g transform="scale(200, 200) translate(-448.75, -834.69)">
689
+ <!-- hidden content becomes visible -->
690
+ </g>
691
+ </svg>
692
+ ```
693
+
694
+ **Key insight:** SVG coordinates with many decimal places hide micro-scale drawings invisible at normal zoom. Check for `<g>` elements with coordinate values that cluster in a tiny range. The fractional parts of the coordinates define the hidden image. Scale up by 100-1000x and translate to the cluster center to reveal. When SVG file size is unexpectedly large for the visible content, inspect coordinate precision in `<path>`, `<line>`, or `<g>` elements.