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,699 @@
1
+ # CTF Forensics - Disk Recovery and Extraction Patterns
2
+
3
+ ## Table of Contents
4
+ - [LUKS Master Key Recovery from Memory Dump (Hack.lu 2015)](#luks-master-key-recovery-from-memory-dump-hacklu-2015)
5
+ - [PRNG Timestamp Seed Brute-Force for Encryption Key Recovery (CSAW 2015)](#prng-timestamp-seed-brute-force-for-encryption-key-recovery-csaw-2015)
6
+ - [VBA Macro Encoded Binary Recovery (Sharif CTF 2016)](#vba-macro-encoded-binary-recovery-sharif-ctf-2016)
7
+ - [FemtoZip Shared Dictionary Decompression (Sharif CTF 2016)](#femtozip-shared-dictionary-decompression-sharif-ctf-2016)
8
+ - [XFS Filesystem Reconstruction from Corrupted Metadata (BSidesSF 2025)](#xfs-filesystem-reconstruction-from-corrupted-metadata-bsidessf-2025)
9
+ - [Tar Archive Duplicate Entry Extraction (BSidesSF 2025)](#tar-archive-duplicate-entry-extraction-bsidessf-2025)
10
+ - [Nested Matryoshka Filesystem Extraction (BSidesSF 2025)](#nested-matryoshka-filesystem-extraction-bsidessf-2025)
11
+ - [Anti-Carving via Null Byte Interleaving (BSidesSF 2024)](#anti-carving-via-null-byte-interleaving-bsidessf-2024)
12
+ - [BTRFS Subvolume/Snapshot Recovery (BSidesSF 2026)](#btrfs-subvolumesnapshot-recovery-bsidessf-2026)
13
+ - [FAT16 Free Space Data Recovery (BSidesSF 2026)](#fat16-free-space-data-recovery-bsidessf-2026)
14
+ - [FAT16 Deleted File Recovery via Sleuth Kit (MetaCTF Flash 2026)](#fat16-deleted-file-recovery-via-sleuth-kit-metactf-flash-2026)
15
+ - [Ext2 Orphaned Inode Recovery via fsck (BSidesSF 2026)](#ext2-orphaned-inode-recovery-via-fsck-bsidessf-2026)
16
+ - [Corrupted ZIP Repair via Header Field Manipulation (PlaidCTF 2017)](#corrupted-zip-repair-via-header-field-manipulation-plaidctf-2017)
17
+ - [Recovering Deleted .git Repository from FAT Image (Square CTF 2017)](#recovering-deleted-git-repository-from-fat-image-square-ctf-2017)
18
+ - [DNSSEC Key Recovery from Git Commit History (Hack.lu 2017)](#dnssec-key-recovery-from-git-commit-history-hacklu-2017)
19
+ - [XZ Stream Header Repair via CRC32 Reconstruction (Hackover 2018)](#xz-stream-header-repair-via-crc32-reconstruction-hackover-2018)
20
+ - [ZipCrypto Known-Plaintext Cracking via bkcrack (Codegate 2019)](#zipcrypto-known-plaintext-cracking-via-bkcrack-codegate-2019)
21
+ - [SQLite Serial-Type Byte Forensics (RITSEC 2018)](#sqlite-serial-type-byte-forensics-ritsec-2018)
22
+ - [Recursive Binwalk Chain PNG->PDF->DOCX->PNG->Base64 (TAMUctf 2019)](#recursive-binwalk-chain-png-pdf-docx-png-base64-tamuctf-2019)
23
+ - [Regex-Password Nested Zip Chain with exrex (UTCTF 2019)](#regex-password-nested-zip-chain-with-exrex-utctf-2019)
24
+ - [See Also](#see-also)
25
+
26
+ ---
27
+
28
+ ## LUKS Master Key Recovery from Memory Dump (Hack.lu 2015)
29
+
30
+ Recover LUKS encryption keys from VM memory dumps using AES key schedule detection:
31
+
32
+ 1. **Extract memory:** Obtain memory dump from VM snapshot (.elf, .vmem, .raw)
33
+ 2. **Find AES keys:** Use `aeskeyfind` to detect AES key schedules in memory
34
+
35
+ ```bash
36
+ aeskeyfind memory.elf
37
+ # Output: candidate AES-256 keys (64 hex chars each)
38
+ ```
39
+
40
+ 3. **Write key to file:** Convert hex key to binary
41
+
42
+ ```bash
43
+ echo "deadbeef..." | xxd -r -p > master.key
44
+ ```
45
+
46
+ 4. **Add new LUKS passphrase using master key:**
47
+
48
+ ```bash
49
+ cryptsetup luksAddKey --master-key-file master.key /dev/mapper/volume
50
+ # Enter new passphrase when prompted
51
+ cryptsetup luksOpen /dev/mapper/volume decrypted
52
+ mount /dev/mapper/decrypted /mnt
53
+ ```
54
+
55
+ **Key insight:** AES key schedules have a distinctive mathematical structure that `aeskeyfind` detects regardless of where they appear in memory. Works for LUKS, dm-crypt, FileVault, and BitLocker volumes.
56
+
57
+ Companion tools: `rsakeyfind` (RSA keys), `aesfix` (corrupted key recovery).
58
+
59
+ ---
60
+
61
+ ## PRNG Timestamp Seed Brute-Force for Encryption Key Recovery (CSAW 2015)
62
+
63
+ When encryption keys are generated from PRNG seeded with timestamps, brute-force the seed:
64
+
65
+ 1. **Identify seed source:** Look for `Time.now.to_i`, `time(NULL)`, `System.currentTimeMillis()` used as PRNG seed
66
+ 2. **Determine time window:** Use file metadata (creation/modification timestamps) to bound the search
67
+ 3. **Brute-force seeds:** Try each second in a +/-24 hour window around the file timestamp
68
+
69
+ ```python
70
+ import struct
71
+ from Crypto.Cipher import AES
72
+
73
+ # Ruby-compatible Random implementation (or use ctypes for C rand)
74
+ for seed in range(timestamp - 86400, timestamp + 86400):
75
+ rng = RandomWithSeed(seed)
76
+ key = bytes([rng.rand(256) for _ in range(32)]) # AES-256
77
+ iv = bytes([rng.rand(256) for _ in range(16)])
78
+
79
+ cipher = AES.new(key, AES.MODE_CBC, iv)
80
+ plaintext = cipher.decrypt(ciphertext)
81
+
82
+ # Validate: check for known file signatures
83
+ if plaintext[:4] == b'\x89PNG' or plaintext[:2] == b'\xff\xd8':
84
+ print(f"Found key with seed: {seed}")
85
+ break
86
+ ```
87
+
88
+ **Key insight:** Expand the time window beyond the obvious timestamp -- clock skew, timezone differences, and filesystem granularity can shift the effective seed by hours.
89
+
90
+ ---
91
+
92
+ ## VBA Macro Encoded Binary Recovery (Sharif CTF 2016)
93
+
94
+ Excel/Word macros may encode binary data in cell values. Extract and decode:
95
+
96
+ 1. **Extract macro:** Use `olevba` or open in LibreOffice to inspect VBA code
97
+ 2. **Identify encoding:** Look for cell iteration patterns like `Cells(i, j).Value`
98
+ 3. **Reverse the encoding formula:**
99
+
100
+ ```python
101
+ # If macro encodes as: cell_value = byte_value * 3 + 78
102
+ # Reverse: byte_value = (cell_value - 78) // 3
103
+
104
+ import openpyxl
105
+ wb = openpyxl.load_workbook('challenge.xlsx')
106
+ ws = wb.active
107
+
108
+ binary_data = bytearray()
109
+ for row in ws.iter_rows():
110
+ for cell in row:
111
+ if cell.value is not None:
112
+ binary_data.append((int(cell.value) - 78) // 3)
113
+
114
+ with open('recovered.elf', 'wb') as f:
115
+ f.write(binary_data)
116
+ ```
117
+
118
+ **Key insight:** Check the recovered file with `file` command -- common outputs are ELF binaries, PE executables, or images containing the flag.
119
+
120
+ ---
121
+
122
+ ## FemtoZip Shared Dictionary Decompression (Sharif CTF 2016)
123
+
124
+ FemtoZip uses a shared dictionary model for compressing corpora of similar documents. When given a `.model` file and compressed data:
125
+
126
+ ```bash
127
+ # Install femtozip
128
+ git clone https://github.com/gtoubassi/femtozip
129
+ cd femtozip && make
130
+
131
+ # Decompress using provided model
132
+ ./fzip --model fashion.model --decompress compressed_dir/ --output decompressed_dir/
133
+ ```
134
+
135
+ After decompression, search through potentially thousands of files:
136
+
137
+ ```bash
138
+ # Filter by metadata fields
139
+ grep -r "category.*forensic" decompressed_dir/ | grep "year.*2016"
140
+ ```
141
+
142
+ **Key insight:** FemtoZip is rare in CTFs. Identify it by the `.model` file and the presence of many small compressed files that share common structure (JSON, XML templates).
143
+
144
+ ---
145
+
146
+ ## XFS Filesystem Reconstruction from Corrupted Metadata (BSidesSF 2025)
147
+
148
+ When XFS superblock or allocation group metadata is corrupted but inodes are intact:
149
+
150
+ 1. **Parse inode directly:** XFS inodes contain extent lists with `[startoff, startblock, blockcount]` tuples
151
+ 2. **Calculate block offsets:** Multiply startblock by filesystem block size (typically 4K)
152
+ 3. **Extract file data:** Copy blocks directly from the raw disk image
153
+
154
+ ```bash
155
+ # Extract file from known inode extent
156
+ # startblock=104333, blockcount=256, block_size=4096
157
+ dd if=disk.img bs=4096 skip=104333 count=256 of=recovered.jpg
158
+
159
+ # Parse XFS inode structure (at known offset)
160
+ python3 -c "
161
+ import struct
162
+ with open('disk.img', 'rb') as f:
163
+ f.seek(inode_offset)
164
+ magic = f.read(2) # 'IN' = 0x494e
165
+ # Parse di_core (96 bytes): mode, uid, gid, nlink, size, etc.
166
+ # Parse extent list: each extent = 16 bytes
167
+ # startoff (54 bits) | startblock (52 bits) | blockcount (21 bits)
168
+ "
169
+ ```
170
+
171
+ **Key insight:** XFS stores extent maps inline in the inode (up to ~4 extents). For files with more extents, follow the B+tree root in the inode. Use `xfs_db` if available: `xfs_db -r disk.img` → `inode <num>` → `print`.
172
+
173
+ ---
174
+
175
+ ## Tar Archive Duplicate Entry Extraction (BSidesSF 2025)
176
+
177
+ Tar format allows multiple entries with the same filename. Standard extraction overwrites earlier entries, but specific occurrences can be targeted:
178
+
179
+ ```bash
180
+ # List all entries (shows duplicates)
181
+ tar -tvf archive.tar.xz | grep -c '^\.'
182
+
183
+ # Extract specific occurrence (1-indexed)
184
+ tar -Jxvf archive.tar.xz '.' --occurrence=2 -O > second_entry.bin
185
+
186
+ # Extract all occurrences via file carving
187
+ binwalk -e archive.tar
188
+ # Or iterate programmatically
189
+ python3 -c "
190
+ import tarfile
191
+ with tarfile.open('archive.tar.xz') as tf:
192
+ for i, member in enumerate(tf.getmembers()):
193
+ if member.name == '.':
194
+ data = tf.extractfile(member).read()
195
+ with open(f'entry_{i}.bin', 'wb') as f:
196
+ f.write(data)
197
+ "
198
+ ```
199
+
200
+ **Key insight:** The `--occurrence=N` flag in GNU tar selects the Nth entry with a matching name. Without it, only the last entry survives extraction. Challenges may hide flags in middle entries that normal extraction skips.
201
+
202
+ ---
203
+
204
+ ## Nested Matryoshka Filesystem Extraction (BSidesSF 2025)
205
+
206
+ Disk images containing nested compressed filesystem layers (potentially 10-20+ levels deep):
207
+
208
+ ```bash
209
+ #!/bin/bash
210
+ # Automated layer extraction
211
+ IMG="disk.img"
212
+ for i in $(seq 1 20); do
213
+ echo "=== Layer $i ==="
214
+ file "$IMG"
215
+
216
+ # Detect and decompress
217
+ case "$(file -b "$IMG")" in
218
+ *XZ*) xz -d "$IMG"; IMG="${IMG%.xz}" ;;
219
+ *gzip*) gunzip "$IMG"; IMG="${IMG%.gz}" ;;
220
+ *ext4*)
221
+ mkdir -p "layer_$i"
222
+ sudo mount -o ro,loop "$IMG" "layer_$i"
223
+ IMG=$(find "layer_$i" -type f -name "*.img" -o -name "*.xz" | head -1)
224
+ ;;
225
+ *ISO*|*HFS*|*XFS*|*AmigaDOS*)
226
+ mkdir -p "layer_$i"
227
+ sudo mount -o ro,loop "$IMG" "layer_$i" 2>/dev/null || \
228
+ sudo mount -t affs -o ro,loop "$IMG" "layer_$i" 2>/dev/null
229
+ IMG=$(find "layer_$i" -type f | head -1)
230
+ ;;
231
+ esac
232
+ done
233
+ ```
234
+
235
+ Filesystem types encountered: ext4, XFS, HFS/HFS+, AFFS (AmigaDOS), FAT. Use `losetup` with `--offset` for partitioned images. Final layer typically contains an image or text file with the flag.
236
+
237
+ **Key insight:** Install uncommon filesystem drivers (`hfsplus`, `affs`) beforehand. Some layers require manual sector offset calculation when partition tables are absent.
238
+
239
+ ---
240
+
241
+ ## Anti-Carving via Null Byte Interleaving (BSidesSF 2024)
242
+
243
+ Files stored with null bytes inserted at every other position defeat magic-byte-based file carving tools (binwalk, foremost, scalpel):
244
+
245
+ 1. **Identify anti-carving:** File carving finds nothing, but `xfs_db` or filesystem-level tools show the file exists with correct size
246
+ 2. **Extract raw blocks:** Use filesystem extent information to locate file data
247
+
248
+ ```bash
249
+ # XFS: find file extents
250
+ xfs_db -r disk.img -c 'inode <inum>' -c 'print'
251
+ # Extract extent data
252
+ dd if=disk.img bs=4096 skip=<startblock> count=<blockcount> of=raw.bin
253
+ ```
254
+
255
+ 3. **Remove interleaved null bytes:** Keep only even-positioned (or odd-positioned) bytes
256
+
257
+ ```python
258
+ with open('raw.bin', 'rb') as f:
259
+ data = f.read()
260
+ # Remove null bytes at odd positions
261
+ cleaned = bytes(data[i] for i in range(0, len(data), 2))
262
+ with open('recovered.png', 'wb') as f:
263
+ f.write(cleaned)
264
+ ```
265
+
266
+ ```perl
267
+ # Perl one-liner equivalent
268
+ perl -0777 -pe 's/(.)./\1/gs' raw.bin > recovered.png
269
+ ```
270
+
271
+ **Key insight:** When file carving fails but the filesystem metadata is intact, extract via block-level access and look for byte-level obfuscation patterns. Null byte interleaving doubles the file size — compare actual size vs expected size as a detection heuristic.
272
+
273
+ ---
274
+
275
+ ---
276
+
277
+ ## BTRFS Subvolume/Snapshot Recovery (BSidesSF 2026)
278
+
279
+ **Pattern (turn-back-the-clock):** Deleted files on a BTRFS filesystem may persist in snapshots or alternate subvolumes. The default mount shows only the active subvolume, but backup snapshots contain historical file states.
280
+
281
+ **Recovery workflow:**
282
+ ```bash
283
+ # 1. Set up loop device
284
+ sudo losetup /dev/loop0 challenge.img
285
+
286
+ # 2. List available subvolumes
287
+ sudo btrfs subvolume list /dev/loop0
288
+ # Output: ID 256 gen 7 top level 5 path @
289
+ # ID 257 gen 5 top level 5 path @backup
290
+
291
+ # 3. Mount the default subvolume (may show deleted files as missing)
292
+ sudo mount /dev/loop0 /mnt/default
293
+ ls /mnt/default/ # Flag file missing
294
+
295
+ # 4. Mount the backup subvolume
296
+ sudo mount -o subvol=@backup /dev/loop0 /mnt/backup
297
+ ls /mnt/backup/ # Flag file present!
298
+ cat /mnt/backup/flag.txt
299
+
300
+ # 5. Alternative: mount by subvolume ID
301
+ sudo mount -o subvolid=257 /dev/loop0 /mnt/backup
302
+ ```
303
+
304
+ **Key BTRFS commands for forensics:**
305
+ ```bash
306
+ # Show filesystem info
307
+ btrfs filesystem show /dev/loop0
308
+
309
+ # List all subvolumes (including snapshots)
310
+ btrfs subvolume list -a /mnt
311
+
312
+ # Show snapshot details
313
+ btrfs subvolume show /mnt/@backup
314
+
315
+ # Find deleted subvolumes (orphaned)
316
+ btrfs-find-root /dev/loop0
317
+ ```
318
+
319
+ **BTRFS snapshot types:**
320
+ - **Writable subvolumes:** `@`, `@home` — standard Ubuntu layout
321
+ - **Read-only snapshots:** Created by `btrfs subvolume snapshot -r` — immutable copies
322
+ - **Backup subvolumes:** `@backup`, `@snap-YYYYMMDD` — naming varies by tool (Timeshift, snapper)
323
+
324
+ **Key insight:** BTRFS is copy-on-write. Deleting a file from the active subvolume doesn't erase the data if a snapshot or alternate subvolume still references those blocks. Always enumerate all subvolumes with `btrfs subvolume list`. The `-o subvol=` mount option is the key to accessing non-default subvolumes.
325
+
326
+ **Detection:** `file disk.img` shows "BTRFS Filesystem". Challenge mentions "snapshots", "time travel", "turn back", or "recovery".
327
+
328
+ **References:** BSidesSF 2026 "turn-back-the-clock"
329
+
330
+ ---
331
+
332
+ ## FAT16 Free Space Data Recovery (BSidesSF 2026)
333
+
334
+ **Pattern (freeflag):** Data is hidden in the free (unallocated) clusters of a FAT16 filesystem. The mounted filesystem shows no suspicious files, but free clusters contain recoverable data.
335
+
336
+ ```python
337
+ import struct
338
+
339
+ with open("disk.img", "rb") as f:
340
+ # Read FAT16 boot sector
341
+ f.seek(0)
342
+ boot = f.read(512)
343
+ bytes_per_sector = struct.unpack_from("<H", boot, 11)[0]
344
+ sectors_per_cluster = boot[13]
345
+ reserved_sectors = struct.unpack_from("<H", boot, 14)[0]
346
+ num_fats = boot[16]
347
+ sectors_per_fat = struct.unpack_from("<H", boot, 22)[0]
348
+ root_entries = struct.unpack_from("<H", boot, 17)[0]
349
+
350
+ cluster_size = bytes_per_sector * sectors_per_cluster
351
+ fat_start = reserved_sectors * bytes_per_sector
352
+ root_dir_start = fat_start + (num_fats * sectors_per_fat * bytes_per_sector)
353
+ data_start = root_dir_start + (root_entries * 32)
354
+
355
+ # Read FAT table
356
+ f.seek(fat_start)
357
+ fat = f.read(sectors_per_fat * bytes_per_sector)
358
+
359
+ # Find free clusters (FAT entry == 0x0000)
360
+ free_data = b""
361
+ for cluster in range(2, len(fat) // 2):
362
+ entry = struct.unpack_from("<H", fat, cluster * 2)[0]
363
+ if entry == 0x0000: # Free cluster
364
+ offset = data_start + (cluster - 2) * cluster_size
365
+ f.seek(offset)
366
+ free_data += f.read(cluster_size)
367
+
368
+ # Search for flag in free space
369
+ if b"CTF{" in free_data:
370
+ idx = free_data.index(b"CTF{")
371
+ print(free_data[idx:idx+100])
372
+ ```
373
+
374
+ **Key insight:** FAT16/FAT32 mark deleted file clusters as "free" (entry = 0x0000) but don't zero the data. Enumerating free clusters and reading their contents recovers deleted or hidden data. Tools like `foremost`, `scalpel`, or manual FAT parsing extract this data. Check the volume label for hints (e.g., "FREESPACE").
375
+
376
+ **When to recognize:** Challenge provides a filesystem image. Mounting shows nothing useful, but `file` identifies it as FAT16/FAT32. Volume label or challenge description hints at "free space", "deleted", or "hidden in plain sight".
377
+
378
+ **References:** BSidesSF 2026 "freeflag"
379
+
380
+ ---
381
+
382
+ ## FAT16 Deleted File Recovery via Sleuth Kit (MetaCTF Flash 2026)
383
+
384
+ **Pattern (rm -rf flag.png):** A file has been deleted from a FAT16 filesystem image. The file's data and cluster chain remain intact, but the directory entry's first byte is replaced with `0xE5` (the FAT deletion marker). Sleuth Kit's `fls` and `icat` recover the file by inode.
385
+
386
+ ```bash
387
+ # Step 1: Identify the filesystem
388
+ file flash.img
389
+ # flash.img: DOS/MBR boot sector, code offset 0x3e+2, ... FAT (16 bit) ...
390
+
391
+ # Step 2: List all files including deleted ones (-d = deleted only, -r = recursive)
392
+ fls -r -d flash.img
393
+ # r/r * 4: _lag.png (first char replaced by FAT deletion marker)
394
+
395
+ # Step 3: Recover the deleted file by its inode number
396
+ icat flash.img 4 > recovered_flag.png
397
+
398
+ # Step 4: Verify recovery
399
+ file recovered_flag.png
400
+ # recovered_flag.png: PNG image data, 800 x 600, 8-bit/color RGBA
401
+ ```
402
+
403
+ **Key insight:** FAT16/FAT32 deletion only marks the directory entry's first byte as `0xE5` and marks clusters as free in the FAT table, but the actual file data remains on disk until overwritten. The filename appears scrambled (e.g., `flag.png` becomes `_lag.png`), but `fls -d` lists deleted entries and `icat` extracts the full file by following the original cluster chain. This is more targeted than free space carving because it preserves the original file boundaries.
404
+
405
+ **When to recognize:** Challenge provides a FAT filesystem image with a deleted file. The challenge name or description hints at deletion (`rm`, `deleted`, `removed`). Mount shows the file is missing, but `fls` reveals the deleted directory entry.
406
+
407
+ **Alternative approaches:**
408
+ - `foremost` / `scalpel` for carving without filesystem awareness
409
+ - `fatcat` for low-level FAT manipulation
410
+ - Manual hex editing: search for `0xE5` entries in directory clusters
411
+
412
+ **References:** MetaCTF Flash CTF 2026 "rm -rf flag.png"
413
+
414
+ ---
415
+
416
+ ## Ext2 Orphaned Inode Recovery via fsck (BSidesSF 2026)
417
+
418
+ **Pattern (orphan):** A file has been deleted from an ext2 filesystem, leaving an orphaned inode. The file doesn't appear in any directory listing, but `fsck` detects the unattached inode and can reconnect it to `/lost+found`.
419
+
420
+ ```bash
421
+ # Mount the image — no flag visible
422
+ sudo mount -o loop disk.img /mnt
423
+ ls /mnt # Nothing useful
424
+
425
+ # Run fsck to detect orphaned inodes
426
+ sudo umount /mnt
427
+ e2fsck -y disk.img
428
+ # Output: "Unattached inode 13"
429
+ # Output: "Connect to /lost+found? yes"
430
+
431
+ # Re-mount and check lost+found
432
+ sudo mount -o loop disk.img /mnt
433
+ ls /mnt/lost+found/
434
+ # Found: #13
435
+ file /mnt/lost+found/\#13 # Identify file type (e.g., PNG)
436
+ cp /mnt/lost+found/\#13 recovered_flag.png
437
+ ```
438
+
439
+ **Key insight:** Ext2/ext3/ext4 deletion removes directory entries but the inode and data blocks may persist until overwritten. `e2fsck` (with `-y` for auto-fix) detects these orphaned inodes and reconnects them to `/lost+found` with numeric names. For ext2 specifically (no journaling), recovery is more reliable because blocks aren't zeroed on deletion.
440
+
441
+ **When to recognize:** Challenge provides an ext2/ext3/ext4 filesystem image. Normal mounting shows nothing. Challenge hints at "deleted", "orphan", "lost", or "recovery". Always run `fsck` on forensics filesystem images.
442
+
443
+ **Alternative tools:**
444
+ - `debugfs` — interactive ext2 exploration: `debugfs disk.img` then `lsdel` to list deleted inodes
445
+ - `extundelete` — automated ext3/ext4 recovery
446
+ - `icat` (Sleuth Kit) — extract file by inode number: `icat disk.img 13 > recovered`
447
+
448
+ **References:** BSidesSF 2026 "orphan"
449
+
450
+ ---
451
+
452
+ ## Corrupted ZIP Repair via Header Field Manipulation (PlaidCTF 2017)
453
+
454
+ ZIP archives with corrupted filename length fields can be repaired by hex-editing both the Local File Header and Central Directory Entry.
455
+
456
+ ```python
457
+ # ZIP Local File Header format (at offset 0x04 from PK\x03\x04):
458
+ # Offset 26: filename length (2 bytes, little-endian)
459
+ # ZIP Central Directory Entry (at PK\x01\x02):
460
+ # Offset 28: filename length (2 bytes, little-endian)
461
+
462
+ # Fix: set both filename lengths to actual filename size
463
+ import struct
464
+ with open('broken.zip', 'rb') as f:
465
+ data = bytearray(f.read())
466
+
467
+ # Find and fix Local File Header filename length
468
+ lfh = data.index(b'PK\x03\x04')
469
+ struct.pack_into('<H', data, lfh + 26, 8) # set to 8 bytes
470
+
471
+ # Find and fix Central Directory filename length
472
+ cde = data.index(b'PK\x01\x02')
473
+ struct.pack_into('<H', data, cde + 28, 8) # must match
474
+
475
+ # Write fixed bytes as filename
476
+ data[lfh+30:lfh+38] = b'flag.txt'
477
+
478
+ with open('fixed.zip', 'wb') as f:
479
+ f.write(data)
480
+
481
+ # Alternative: brute-force deflate at candidate offsets
482
+ import zlib
483
+ with open('broken.zip', 'rb') as f:
484
+ raw = f.read()
485
+ for offset in range(0x1E, 0x100):
486
+ try:
487
+ result = zlib.decompress(raw[offset:], -15)
488
+ print(f"Offset {offset:#x}: {result}")
489
+ break
490
+ except zlib.error:
491
+ continue
492
+ ```
493
+
494
+ **Key insight:** ZIP filename length fields appear in both the Local File Header (offset 26) and Central Directory (offset 28). Both must match and reflect the actual filename. When these are corrupted to absurd values (e.g., 9001), the archive appears empty. As a fallback, brute-force raw deflate decompression at candidate data offsets.
495
+
496
+ **Detection:** ZIP file that `unzip -l` reports as empty or produces errors about invalid filename lengths. `hexdump` shows valid `PK\x03\x04` and `PK\x01\x02` signatures but unreasonable values in length fields.
497
+
498
+ ---
499
+
500
+ ## Recovering Deleted .git Repository from FAT Image (Square CTF 2017)
501
+
502
+ A FAT filesystem image with a deleted `.git` directory. Use TSK `fls -r` to list all files including deleted ones (marked with `*`). Extract deleted inodes with `icat`. Reconstruct the git object directory structure from the extracted files, then use `git fsck` and `git log` to recover commit history and flag.
503
+
504
+ ```bash
505
+ # Step 1: List all files including deleted ones (* prefix = deleted)
506
+ fls -r disk.img | grep '\*'
507
+ # Example output:
508
+ # r/r * 5: .git/HEAD
509
+ # r/r * 6: .git/config
510
+ # r/r * 7: .git/objects/ab/cdef1234...
511
+
512
+ # Step 2: Extract deleted files by inode number
513
+ icat disk.img 5 > HEAD
514
+ icat disk.img 6 > config
515
+ # Repeat for all git object inodes
516
+
517
+ # Step 3: Rebuild .git directory structure
518
+ mkdir -p recovered/.git/objects/ab/
519
+ # Place each extracted object at its correct path
520
+
521
+ # Step 4: Recover commit history
522
+ cd recovered
523
+ git fsck --full # Check object integrity, find dangling commits
524
+ git log --all # Show all commits including unreferenced ones
525
+ git show <commit_hash> # Inspect specific commit for flag
526
+ ```
527
+
528
+ **Key insight:** FAT marks deleted files by changing the first byte of the directory entry to `0xE5` but keeps cluster data intact until reused. TSK's `fls`/`icat` extracts deleted files by inode, making deletion forensically reversible. Git objects are content-addressed — once extracted, `git fsck` finds all reachable commits even without a valid HEAD reference.
529
+
530
+ ---
531
+
532
+ ## DNSSEC Key Recovery from Git Commit History (Hack.lu 2017)
533
+
534
+ DNSSEC private signing keys committed to a git repository and later deleted remain permanently in the commit history. Recover the keys to set up a local BIND instance and forge DNSSEC-signed DNS responses.
535
+
536
+ ```bash
537
+ # Step 1: Find commits that deleted key files
538
+ git log --all --diff-filter=D -- '*.private' '*.key' 'Kexample.*.+*.+*.key'
539
+
540
+ # Step 2: Recover the deleted key files from the commit before deletion
541
+ git show <commit_hash>^:<path/to/Kzone.+005+12345.private> > recovered.private
542
+ git show <commit_hash>^:<path/to/Kzone.+005+12345.key> > recovered.key
543
+
544
+ # Alternative: search all commits for key material
545
+ git log --all -p -- '*.private' | grep -A 20 'Private-key-format'
546
+
547
+ # Step 3: Verify key contents
548
+ cat recovered.private
549
+ # Private-key-format: v1.3
550
+ # Algorithm: 5 (RSASHA1)
551
+ # ...
552
+
553
+ # Step 4: Use recovered keys to forge DNSSEC-signed responses
554
+ # Configure BIND with the recovered signing keys and sign the zone
555
+ dnssec-signzone -K /path/to/keys -o example.com zone.db
556
+ ```
557
+
558
+ **Key insight:** Sensitive cryptographic key material in git history is permanently recoverable — `git log --diff-filter=D` finds all commits that deleted files, and `git show <commit>^:<path>` retrieves the file's state just before deletion. DNSSEC private keys enable forging any DNS record for the zone, allowing DNS cache poisoning or redirecting traffic to attacker-controlled servers.
559
+
560
+ ---
561
+
562
+ ## XZ Stream Header Repair via CRC32 Reconstruction (Hackover 2018)
563
+
564
+ **Pattern:** The file has a valid XZ stream footer but the stream header has been overwritten (commonly with `PK\x03\x04` to make it look like a ZIP). Rebuild the 12-byte XZ header from the format spec: magic `FD 37 7A 58 5A 00`, two bytes of stream flags, and a 4-byte little-endian CRC32 of those flags. Prepend the reconstructed header to the rest of the file and `xz -d` decompresses cleanly.
565
+
566
+ ```bash
567
+ # 1. Confirm the footer — XZ stream footer magic is "YZ" at the end.
568
+ xxd broken.xz | tail -1
569
+ # 00002ff0: 00 00 01 59 5A ...YZ
570
+
571
+ # 2. Read stream_flags from the footer (byte at offset -6 from EOF)
572
+ STREAM_FLAGS=$(xxd -p -s -6 -l 2 broken.xz)
573
+ # e.g. 00 04 → CHECK_CRC64
574
+
575
+ # 3. Compute CRC32 of the 2 flag bytes (little-endian output)
576
+ CRC=$(python3 -c "import binascii; print(binascii.crc32(bytes.fromhex('$STREAM_FLAGS')).to_bytes(4,'little').hex())")
577
+
578
+ # 4. Rebuild the header and replace the first 12 bytes
579
+ printf '\xFD7zXZ\x00' > newhdr.bin
580
+ printf '%s' "$STREAM_FLAGS" | xxd -r -p >> newhdr.bin
581
+ printf '%s' "$CRC" | xxd -r -p >> newhdr.bin
582
+ dd if=newhdr.bin of=broken.xz bs=1 count=12 conv=notrunc
583
+
584
+ # 5. Decompress
585
+ xz -d broken.xz
586
+ ```
587
+
588
+ **Key insight:** XZ streams are defined by a fixed 12-byte header and a 12-byte footer that both include the same `stream_flags` byte — when the header is damaged you can copy the flags out of the still-intact footer and recompute the header CRC32 locally. The same header-reconstruction trick works for any format where the checksum input is small enough to brute-force or derive from the footer: GZIP (trailing `isize`/`crc32`), ZIP (central directory before the local file header), and zstd (frame header with skip-frames). When the challenge hands you a blob whose magic bytes belong to the wrong format, check the **last few bytes** for the real footer signature before trying to salvage the header.
589
+
590
+ **References:** Hackover CTF 2018 — UnbreakMyStart, writeup 11508
591
+
592
+ ---
593
+
594
+ ## ZipCrypto Known-Plaintext Cracking via bkcrack (Codegate 2019)
595
+
596
+ **Pattern:** ZipCrypto (the legacy PKZIP stream cipher, not AES-256) falls to known-plaintext attacks when you have at least 12 bytes of known plaintext for an encrypted file. `pkcrack` is the classic tool but often fails on modern archives; `bkcrack` (https://github.com/kimci86/bkcrack) handles edge cases with partial headers.
597
+
598
+ ```bash
599
+ # Extract any unencrypted neighbour and its encrypted version
600
+ unzip secret.zip unencrypted_known.txt
601
+ bkcrack -C secret.zip -c target.txt -p unencrypted_known.txt -P known.zip
602
+ # Decrypt the whole archive with the recovered internal state
603
+ bkcrack -C secret.zip -k <k0> <k1> <k2> -d target_decrypted.bin
604
+ ```
605
+
606
+ **Key insight:** ZIP headers often include well-known constants (PNG/JPEG magic, empty `README.txt`, `.gitignore`). Any encrypted ZIP that also ships an unencrypted reference file — or where you can guess 12+ bytes of header — falls immediately to `bkcrack`. Swap to it when `pkcrack` throws.
607
+
608
+ **References:** Codegate CTF 2019 — Rich Project, writeup 12907
609
+
610
+ ---
611
+
612
+ ## SQLite Serial-Type Byte Forensics (RITSEC 2018)
613
+
614
+ **Pattern:** Two near-identical SQLite files differ only in selected bytes. SQLite records encode each column with a "serial type" varint that both describes the type and carries the length (types ≥13 mean strings, length `(type - 13) / 2`). Walk the records, locate the changed serial-type bytes between versions, and read the adjacent text payload to recover hidden characters.
615
+
616
+ ```python
617
+ def extract_hidden(path):
618
+ with open(path, 'rb') as f: db = f.read()
619
+ offsets = [0x892, 0xBA5, 0xE13] # diff the two files first
620
+ return bytes(db[off] for off in offsets)
621
+ ```
622
+
623
+ **Key insight:** SQLite's varint serial-type scheme stores metadata *inline* with the payload, so an attacker who can flip one varint changes the interpretation of the next N bytes. Diff two versions byte-by-byte, cluster the diffs by record, and decode each varint to locate hidden text fields.
624
+
625
+ **References:** RITSEC CTF 2018 — Lite Forensics, writeup 12223
626
+
627
+ ---
628
+
629
+ ## Recursive Binwalk Chain PNG->PDF->DOCX->PNG->Base64 (TAMUctf 2019)
630
+
631
+ **Pattern:** One carrier file hides a chain of embedded documents — PNG with a PDF appended, the PDF embeds a DOCX (which is a ZIP), the DOCX embeds another PNG, and that PNG has Base64 appended after the IEND/EOF. Each layer changes container format to evade naive string searches.
632
+
633
+ ```bash
634
+ # Layer 1-2: carve everything out of the outer PNG (pulls PDF, ZIP streams, etc.)
635
+ binwalk --dd=".*" art.png
636
+ cd _art.png.extracted
637
+ file * # identify the Microsoft Word 2007+ blob
638
+
639
+ # Layer 3: DOCX is a ZIP archive
640
+ unzip 34591D -d docx/ # hex offset from binwalk becomes the filename
641
+ ls docx/word/media/ # image1.png is the next-layer carrier
642
+
643
+ # Layer 4: recurse binwalk into the inner PNG to pull an embedded PDF
644
+ binwalk --dd=".*" docx/word/media/image1.png
645
+
646
+ # Layer 5: check for data appended after %%EOF of the inner PDF
647
+ strings _image1.png.extracted/*.pdf | tail -n 10
648
+ # -> ZmxhZ3tQMGxZdEByX0QwX3kwdV9HM3RfSXRfTjB3P30K
649
+ echo 'ZmxhZ3tQMGxZdEByX0QwX3kwdV9HM3RfSXRfTjB3P30K' | base64 -d
650
+ ```
651
+
652
+ **Key insight:** When `grep flag` on the outermost file fails, assume each extracted file is itself a carrier. DOCX/XLSX/PPTX/APK/JAR are all ZIPs, so `unzip` works directly. PDFs commonly carry data *after* the final `%%EOF`, so always `strings | tail` or seek past the trailer. `binwalk --dd=".*"` writes every signature hit to disk so you can recurse with minimal typing.
653
+
654
+ **References:** TAMUctf 2019 — I Heard You Like Files, writeups 13412 and 13587
655
+
656
+ ---
657
+
658
+ ## Regex-Password Nested Zip Chain with exrex (UTCTF 2019)
659
+
660
+ **Pattern:** Outer zip contains a `hint.txt` (regex) and `archive.zip`; the regex enumerates the password set for the inner zip. Each extracted zip produces the next regex hint. Chain is deep (1000+ layers) so it must be scripted. `exrex.generate(regex)` materialises every string matching a regex, which is perfect for constrained password spaces.
661
+
662
+ ```python
663
+ import exrex, zipfile, os
664
+
665
+ hint = r'^ 7 y RU[A-Z]KKx2 R4\d[a-z]B N$'
666
+ archive = 'RegularZips.zip'
667
+
668
+ for i in range(10000):
669
+ candidates = list(exrex.generate(hint))
670
+ out_dir = f'layer{i}'
671
+ os.makedirs(out_dir, exist_ok=True)
672
+ with zipfile.ZipFile(archive) as zf:
673
+ for pw in candidates:
674
+ try:
675
+ zf.extractall(out_dir, pwd=pw.encode())
676
+ print(f'[{i}] pw={pw}')
677
+ break
678
+ except Exception:
679
+ continue
680
+ else:
681
+ raise RuntimeError(f'no password matched regex at layer {i}')
682
+ with open(os.path.join(out_dir, 'hint.txt')) as f:
683
+ hint = f.read().strip()
684
+ archive = os.path.join(out_dir, 'archive.zip')
685
+ if not os.path.exists(archive):
686
+ print('FLAG IN', out_dir)
687
+ break
688
+ ```
689
+
690
+ **Key insight:** When a zip's password is described by a regex, don't brute ASCII — use `exrex` to enumerate only matching strings (often just a handful of candidates per layer). Automate the extract-read-hint-repeat cycle; 1000 layers finish in seconds because the search space per layer is tiny.
691
+
692
+ **References:** UTCTF 2019 — Regular Zips, writeups 13951 and 13861
693
+
694
+ ---
695
+
696
+ ## See Also
697
+
698
+ - [disk-and-memory.md](disk-and-memory.md) - Core disk/memory forensics (Volatility, disk image analysis, VM/OVA/VMDK, VMware snapshots, coredumps, KAPE triage, PowerShell ransomware, Android/Docker/cloud forensics, BSON reconstruction, TrueCrypt/VeraCrypt mounting)
699
+ - [disk-advanced.md](disk-advanced.md) - Advanced disk and memory techniques (deleted partitions, ZFS forensics, GPT GUID encoding, VMDK sparse parsing, memory dump string carving, ransomware key recovery, WordPerfect macro XOR, minidump ISO 9660 recovery, APFS snapshots, RAID 5 XOR recovery)