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,670 @@
1
+ # CTF Reverse - Competition-Specific Patterns (Part 1)
2
+
3
+ ## Table of Contents
4
+ - [Hidden Emulator Opcodes + LD_PRELOAD Key Extraction (0xFun 2026)](#hidden-emulator-opcodes--ld_preload-key-extraction-0xfun-2026)
5
+ - [Spectre-RSB SPN Cipher — Static Parameter Extraction (0xFun 2026)](#spectre-rsb-spn-cipher--static-parameter-extraction-0xfun-2026)
6
+ - [Image XOR Mask Recovery via Smoothness (VuwCTF 2025)](#image-xor-mask-recovery-via-smoothness-vuwctf-2025)
7
+ - [Shellcode in Data Section via mmap RWX (VuwCTF 2025)](#shellcode-in-data-section-via-mmap-rwx-vuwctf-2025)
8
+ - [Recursive execve Subtraction (VuwCTF 2025)](#recursive-execve-subtraction-vuwctf-2025)
9
+ - [Byte-at-a-Time Block Cipher Attack (UTCTF 2024)](#byte-at-a-time-block-cipher-attack-utctf-2024)
10
+ - [Mathematical Convergence Bitmap (EHAX 2026)](#mathematical-convergence-bitmap-ehax-2026)
11
+ - [Windows PE XOR Bitmap Extraction + OCR (srdnlenCTF 2026)](#windows-pe-xor-bitmap-extraction--ocr-srdnlenctf-2026)
12
+ - [Two-Stage Loader: RC4 Gate + VM Constraints (srdnlenCTF 2026)](#two-stage-loader-rc4-gate--vm-constraints-srdnlenctf-2026)
13
+ - [GBA ROM VM Hash Inversion via Meet-in-the-Middle (srdnlenCTF 2026)](#gba-rom-vm-hash-inversion-via-meet-in-the-middle-srdnlenctf-2026)
14
+ - [Sprague-Grundy Game Theory Binary (DiceCTF 2026)](#sprague-grundy-game-theory-binary-dicectf-2026)
15
+ - [Kernel Module Maze Solving (DiceCTF 2026)](#kernel-module-maze-solving-dicectf-2026)
16
+ - [Multi-Threaded VM with Channel Synchronization (DiceCTF 2026)](#multi-threaded-vm-with-channel-synchronization-dicectf-2026)
17
+ - [Backdoored Shared Library Detection via String Diffing (Hack.lu CTF 2012)](#backdoored-shared-library-detection-via-string-diffing-hacklu-ctf-2012)
18
+ - [Custom binfmt Kernel Module with RC4 Flat Binaries (BSidesSF 2026)](#custom-binfmt-kernel-module-with-rc4-flat-binaries-bsidessf-2026)
19
+ - [Hash-Resolved Imports / No-Import Ransomware (BSidesSF 2026)](#hash-resolved-imports--no-import-ransomware-bsidessf-2026)
20
+ - [ELF Section Header Corruption for Anti-Analysis (BSidesSF 2026)](#elf-section-header-corruption-for-anti-analysis-bsidessf-2026)
21
+ - [VM Trace Diffing Instead of Full Disassembly (CONFidence CTF 2019 Teaser)](#vm-trace-diffing-instead-of-full-disassembly-confidence-ctf-2019-teaser)
22
+
23
+ ---
24
+
25
+ ## Hidden Emulator Opcodes + LD_PRELOAD Key Extraction (0xFun 2026)
26
+
27
+ **Pattern (CHIP-8):** Non-standard opcode `FxFF` triggers hidden `superChipRendrer()` → AES-256-CBC decryption. Key derived from binary constants.
28
+
29
+ **Technique:**
30
+ 1. Check all instruction dispatch branches for non-standard opcodes
31
+ 2. Hidden opcode may trigger crypto functions (OpenSSL)
32
+ 3. Use `LD_PRELOAD` hook on `EVP_DecryptInit_ex` to capture AES key at runtime:
33
+
34
+ ```c
35
+ #include <openssl/evp.h>
36
+ int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
37
+ ENGINE *impl, const unsigned char *key,
38
+ const unsigned char *iv) {
39
+ // Log key
40
+ for (int i = 0; i < 32; i++) printf("%02x", key[i]);
41
+ printf("\n");
42
+ // Call original
43
+ return ((typeof(EVP_DecryptInit_ex)*)dlsym(RTLD_NEXT, "EVP_DecryptInit_ex"))
44
+ (ctx, type, impl, key, iv);
45
+ }
46
+ ```
47
+
48
+ ```bash
49
+ gcc -shared -fPIC -ldl -lssl hook.c -o hook.so
50
+ LD_PRELOAD=./hook.so ./emulator rom.ch8
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Spectre-RSB SPN Cipher — Static Parameter Extraction (0xFun 2026)
56
+
57
+ **Pattern:** Binary uses cache side channels to implement S-boxes, but ALL cipher parameters (round keys, S-box tables, permutation) are in the binary's data section.
58
+
59
+ **Key insight:** Don't try to run on special hardware. Extract parameters statically:
60
+ - 8 S-boxes × 8 output bits, 256 entries each
61
+ - Values `0x340` = bit 1, `0x100` = bit 0
62
+ - 64-byte permutation table, 8 round keys
63
+
64
+ ```python
65
+ # Extract from binary data section
66
+ import struct
67
+ sbox = [[0]*256 for _ in range(8)]
68
+ for i in range(8):
69
+ for j in range(256):
70
+ val = struct.unpack('<I', data[sbox_offset + (i*256+j)*4 : ...])[0]
71
+ sbox[i][j] = 1 if val == 0x340 else 0
72
+ ```
73
+
74
+ **Lesson:** Side-channel implementations embed lookup tables in memory. Extract statically.
75
+
76
+ ---
77
+
78
+ ## Image XOR Mask Recovery via Smoothness (VuwCTF 2025)
79
+
80
+ **Pattern (Trianglification):** Image divided into triangle regions, each XOR-encrypted with `key = (mask * x - y) & 0xFF` where mask is unknown (0-255).
81
+
82
+ **Recovery:** Natural images have smooth gradients. Brute-force mask (256 values per region), score by neighbor pixel differences:
83
+
84
+ ```python
85
+ import numpy as np
86
+ from PIL import Image
87
+
88
+ img = np.array(Image.open('encrypted.png'))
89
+
90
+ def score_smoothness(region_pixels, mask, positions):
91
+ decrypted = []
92
+ for (x, y), pixel in zip(positions, region_pixels):
93
+ key = (mask * x - y) & 0xFF
94
+ decrypted.append(pixel ^ key)
95
+ # Score: sum of absolute differences between adjacent pixels
96
+ return -sum(abs(decrypted[i] - decrypted[i+1]) for i in range(len(decrypted)-1))
97
+
98
+ for region in regions:
99
+ best_mask = max(range(256), key=lambda m: score_smoothness(region, m, positions))
100
+ ```
101
+
102
+ **Search space:** 256 candidates × N regions = trivial. Smoothness is a reliable scoring metric for natural images.
103
+
104
+ ---
105
+
106
+ ## Shellcode in Data Section via mmap RWX (VuwCTF 2025)
107
+
108
+ **Pattern (Missing Function):** Binary relocates data to RWX memory (mmap with PROT_READ|PROT_WRITE|PROT_EXEC) and jumps to it.
109
+
110
+ **Detection:** Look for `mmap` with PROT_EXEC flag. Embedded shellcode often uses XOR with rotating key.
111
+
112
+ **Analysis:** Extract data section, apply XOR key (try 3-byte rotating), disassemble result.
113
+
114
+ ---
115
+
116
+ ## Recursive execve Subtraction (VuwCTF 2025)
117
+
118
+ **Pattern (String Inspector):** Binary recursively calls itself via `execve`, subtracting constants each time.
119
+
120
+ **Solution:** Find base case and work backward. Often a mathematical relationship like `N * M + remainder`.
121
+
122
+ ---
123
+
124
+ ## Byte-at-a-Time Block Cipher Attack (UTCTF 2024)
125
+
126
+ **Pattern (PES-128):** First output byte depends only on first input byte (no diffusion).
127
+
128
+ **Attack:** For each position, try all 256 byte values, compare output byte with target ciphertext. One match per byte = full plaintext recovery without knowing the key.
129
+
130
+ **Detection:** Change one input byte → only corresponding output byte changes. This means zero cross-byte diffusion = trivially breakable.
131
+
132
+ ---
133
+
134
+ ## Mathematical Convergence Bitmap (EHAX 2026)
135
+
136
+ **Pattern (Compute It):** Binary classifies complex-plane coordinates by Newton's method convergence. The classification results, arranged as a grid, spell out the flag in ASCII art.
137
+
138
+ **Recognition:**
139
+ - Input file with coordinate pairs (x, y)
140
+ - Binary iterates a mathematical function (e.g., z^3 - 1 = 0) and outputs pass/fail
141
+ - Grid dimensions hinted by point count (e.g., 2600 = 130×20)
142
+ - 5-pixel-high ASCII art font common in CTFs
143
+
144
+ **Newton's method for z^3 - 1:**
145
+ ```python
146
+ def newton_converges_to_one(px, py, max_iter=50, target_count=12):
147
+ """Returns True if Newton's method converges to z=1 in exactly target_count steps."""
148
+ x, y = px, py
149
+ count = 0
150
+ for _ in range(max_iter):
151
+ f_real = x**3 - 3*x*y**2 - 1.0
152
+ f_imag = 3*x**2*y - y**3
153
+ J_rr = 3.0 * (x**2 - y**2)
154
+ J_ri = 6.0 * x * y
155
+ det = J_rr**2 + J_ri**2
156
+ if det < 1e-9:
157
+ break
158
+ x -= (f_real * J_rr + f_imag * J_ri) / det
159
+ y -= (f_imag * J_rr - f_real * J_ri) / det
160
+ count += 1
161
+ if abs(x - 1.0) < 1e-6 and abs(y) < 1e-6:
162
+ break
163
+ return count == target_count
164
+
165
+ # Read coordinates and render bitmap
166
+ points = [(float(x), float(y)) for x, y in ...]
167
+ bits = [1 if newton_converges_to_one(px, py) else 0 for px, py in points]
168
+ WIDTH = 130 # 2600 / 20 rows
169
+ for r in range(len(bits) // WIDTH):
170
+ print(''.join('#' if bits[r*WIDTH+c] else '.' for c in range(WIDTH)))
171
+ ```
172
+
173
+ **Key insight:** The binary is a mathematical classifier, not a flag checker. The flag is in the visual pattern of classifications, not in the binary's output. Reverse-engineer the math, apply to all coordinates, and visualize as bitmap.
174
+
175
+ ---
176
+
177
+ ## Windows PE XOR Bitmap Extraction + OCR (srdnlenCTF 2026)
178
+
179
+ **Pattern (Artistic Warmup):** Binary renders input text, compares rendered bitmap against expected pixel data stored XOR'd with constant in `.rdata`. No need to compute — extract expected pixels directly.
180
+
181
+ **Attack:**
182
+ 1. Reverse the core check function to identify rendering and comparison logic
183
+ 2. Find the expected pixel blob in `.rdata` (look for large data block referenced near comparison)
184
+ 3. XOR with constant (e.g., 0xAA) to recover expected rendered DIB
185
+ 4. Save as image and OCR to recover flag text
186
+
187
+ ```python
188
+ import numpy as np
189
+ from PIL import Image
190
+
191
+ with open("binary.exe", "rb") as f:
192
+ data = f.read()
193
+
194
+ # Extract from .rdata section (offsets from reversing)
195
+ blob_offset = 0xC3620 # .rdata offset to XOR'd blob
196
+ blob_size = 0x15F90 # 450 * 50 * 4 (BGRA)
197
+ blob = np.frombuffer(data[blob_offset:blob_offset + blob_size], dtype=np.uint8)
198
+ expected = blob ^ 0xAA # XOR with constant key
199
+
200
+ # Reshape as BGRA image (dimensions from reversing)
201
+ img = expected.reshape(50, 450, 4)
202
+ channel = img[:, :, 0] # Take one channel (grayscale text)
203
+ Image.fromarray(channel, "L").save("target.png")
204
+
205
+ # OCR with charset whitelist
206
+ import subprocess
207
+ result = subprocess.run(
208
+ ["tesseract", "target.png", "stdout", "-c",
209
+ "tessedit_char_whitelist=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_"],
210
+ capture_output=True, text=True)
211
+ print(result.stdout)
212
+ ```
213
+
214
+ **Key insight:** When a binary renders text and compares pixels, the expected pixel data is the flag rendered as an image. Extract it directly from the binary data section without needing to understand the rendering logic. OCR with charset whitelist improves accuracy for CTF flag characters.
215
+
216
+ ---
217
+
218
+ ## Two-Stage Loader: RC4 Gate + VM Constraints (srdnlenCTF 2026)
219
+
220
+ **Pattern (Cornflake v3.5):** Two-stage malware loader — stage 1 uses RC4 username gate, stage 2 downloaded from C2 contains VM-based password validation.
221
+
222
+ **Stage 1 — RC4 username recovery:**
223
+ ```python
224
+ def rc4(key, data):
225
+ s = list(range(256))
226
+ j = 0
227
+ for i in range(256):
228
+ j = (j + s[i] + key[i % len(key)]) & 0xFF
229
+ s[i], s[j] = s[j], s[i]
230
+ i = j = 0
231
+ out = bytearray()
232
+ for b in data:
233
+ i = (i + 1) & 0xFF
234
+ j = (j + s[i]) & 0xFF
235
+ s[i], s[j] = s[j], s[i]
236
+ out.append(b ^ s[(s[i] + s[j]) & 0xFF])
237
+ return bytes(out)
238
+
239
+ # Key from binary strings, ciphertext from stored hex
240
+ username = rc4(b"s3cr3t_k3y_v1", bytes.fromhex("46f5289437bc009c17817e997ae82bfbd065545d"))
241
+ ```
242
+
243
+ **Stage 2 — VM constraint extraction:**
244
+ 1. Download stage 2 from C2 endpoint (e.g., `/updates/check.php`)
245
+ 2. Reverse VM bytecode interpreter (typically 15-20 opcodes)
246
+ 3. Extract linear equality constraints over flag characters
247
+ 4. Solve constraint system (Z3 or manual)
248
+
249
+ **Key insight:** Multi-stage loaders often use simple crypto (RC4) for the first gate and more complex validation (custom VM) for the second. The VM memory may be uninitialized (all zeros), drastically simplifying constraint extraction since memory-dependent operations become constants.
250
+
251
+ ---
252
+
253
+ ## GBA ROM VM Hash Inversion via Meet-in-the-Middle (srdnlenCTF 2026)
254
+
255
+ **Pattern (Dante's Trial):** Game Boy Advance ROM implements a custom VM. Hash function uses FNV-1a variant with uninitialized memory (stays all zeros). Meet-in-the-middle attack splits the search space.
256
+
257
+ **Hash function structure:**
258
+ ```python
259
+ # FNV-1a variant with XOR/multiply
260
+ P = 0x100000001b3 # FNV prime
261
+ CUP = 0x9e3779b185ebca87 # Golden ratio constant
262
+ MASK64 = (1 << 64) - 1
263
+
264
+ def fmix64(h):
265
+ """Finalization mixer."""
266
+ h ^= h >> 33; h = (h * 0xff51afd7ed558ccd) & MASK64
267
+ h ^= h >> 33; h = (h * 0xc4ceb9fe1a85ec53) & MASK64
268
+ h ^= h >> 33
269
+ return h
270
+
271
+ def hash_input(chars, seed_lo=0x84222325, seed_hi=0xcbf29ce4):
272
+ hlo, hhi, ptr = seed_lo, seed_hi, 0
273
+ for c in chars:
274
+ # tri_mix(c, mem[ptr]) — mem is always 0
275
+ delta = ((ord(c) * CUP) ^ (0 * P)) & MASK64
276
+ hlo = ((hlo ^ (delta & 0xFFFFFFFF)) * (P & 0xFFFFFFFF)) & 0xFFFFFFFF
277
+ hhi = ((hhi ^ (delta >> 32)) * (P >> 32)) & 0xFFFFFFFF
278
+ ptr = (ptr + 1) & 0xFF
279
+ combined = ((hhi << 32) | (hlo ^ ptr)) & MASK64
280
+ return fmix64((combined * P) & MASK64)
281
+ ```
282
+
283
+ **Meet-in-the-middle attack:**
284
+ ```python
285
+ import string
286
+
287
+ TARGET = 0x73f3ebcbd9b4cd93
288
+ LENGTH = 6
289
+ SPLIT = 3
290
+ charset = [c for c in string.printable if 32 <= ord(c) < 127]
291
+
292
+ # Forward pass: enumerate first 3 characters from seed state
293
+ forward = {}
294
+ for c1 in charset:
295
+ for c2 in charset:
296
+ for c3 in charset:
297
+ state = hash_forward(seed, [c1, c2, c3])
298
+ forward[state] = c1 + c2 + c3
299
+
300
+ # Backward pass: invert fmix64 and final multiply, enumerate last 3 chars
301
+ inv_target = invert_fmix64(TARGET)
302
+ for c4 in charset:
303
+ for c5 in charset:
304
+ for c6 in charset:
305
+ state = hash_backward(inv_target, [c4, c5, c6])
306
+ if state in forward:
307
+ print(f"Found: {forward[state]}{c4}{c5}{c6}")
308
+ ```
309
+
310
+ **Key insight:** Meet-in-the-middle reduces search from `95^6 ≈ 7.4×10^11` to `2×95^3 ≈ 1.7×10^6` — a factor of ~430,000x speedup. Critical when the hash function is invertible from the output side (i.e., `fmix64` and the final multiply can be undone). Also: uninitialized VM memory that stays zero simplifies the hash function by removing a variable.
311
+
312
+ ---
313
+
314
+ ## Sprague-Grundy Game Theory Binary (DiceCTF 2026)
315
+
316
+ **Pattern (Bedtime):** Stripped Rust binary plays N rounds of bounded Nim. Each round has piles and max-move parameter k. Binary uses a PRNG for moves when in a losing position; user must respond optimally so the PRNG eventually generates an invalid move (returns 1). Sum of return values must equal a target.
317
+
318
+ **Game theory identification:**
319
+ - Bounded Nim: remove 1 to k items from any pile per turn
320
+ - **Grundy value** per pile: `pile_value % (k+1)`
321
+ - **XOR** of all Grundy values: non-zero = winning (N-position), zero = losing (P-position)
322
+ - N-positions: computer wins automatically (returns 0)
323
+ - P-positions: computer uses PRNG, may make invalid move (returns 1)
324
+
325
+ **PRNG state tracking through user feedback:**
326
+ ```python
327
+ MASK64 = (1 << 64) - 1
328
+
329
+ def prng_step(state, pile_count, k):
330
+ """Computer's PRNG move. Returns (pile_idx, amount, new_state)."""
331
+ r12 = state[2] ^ 0x28027f28b04ccfa7
332
+ rax = (state[1] + r12) & MASK64
333
+ s0_new = ROL64((state[0] ** 2 + rax) & MASK64, 32)
334
+ r12_upd = (r12 + rax) & MASK64
335
+ s0_final = ROL64((s0_new ** 2 + r12_upd) & MASK64, 32)
336
+
337
+ pile_idx = rax % pile_count
338
+ amount = (r12_upd % k) + 1
339
+ return pile_idx, amount, [s0_final, r12_upd, state[2]]
340
+
341
+ # Critical: state[2] updated ONLY by user moves (XOR of pile_idx, amount, new_value)
342
+ # PRNG moves do NOT affect state[2] — creates feedback loop
343
+ ```
344
+
345
+ **Solving approach:**
346
+ 1. Dump game data from GDB (all entries with pile values and parameters)
347
+ 2. Classify: count P-positions (return 1) vs N-positions (return 0)
348
+ 3. Simulate each P-position: PRNG moves → user responds optimally → track state[2]
349
+ 4. Encode user moves as input format (4-digit decimal pairs, reversed order)
350
+
351
+ **Key insight:** When a game binary's PRNG state depends on user input, you must simulate the full feedback loop — not just solve the game theory. Use GDB hardware watchpoints to discover which state variables are affected by user vs computer moves.
352
+
353
+ ---
354
+
355
+ ## Kernel Module Maze Solving (DiceCTF 2026)
356
+
357
+ **Pattern (Explorer):** Rust kernel module implements a 3D maze via `/dev/challenge` ioctls. Navigate the maze, avoid decoy exits (status=2), find the real exit (status=1), read the flag.
358
+
359
+ **Ioctl enumeration:**
360
+ | Command | Description |
361
+ |---------|-------------|
362
+ | `0x80046481-83` | Get maze dimensions (3 axes, 8-16 each) |
363
+ | `0x80046485` | Get status: 0=playing, 1=WIN, 2=decoy |
364
+ | `0x80046486` | Get wall bitfield (6 directions) |
365
+ | `0x80406487` | Get flag (64 bytes, only when status=1) |
366
+ | `0x40046488` | Move in direction (0-5) |
367
+ | `0x6489` | Reset position |
368
+
369
+ **DFS solver with decoy avoidance:**
370
+ ```c
371
+ // Minimal static binary using raw syscalls (no libc) for small upload size
372
+ // gcc -nostdlib -static -Os -fno-builtin -o solve solve.c -Wl,--gc-sections && strip solve
373
+
374
+ int visited[16][16][16];
375
+ int bad[16][16][16]; // decoy positions across resets
376
+
377
+ void dfs(int fd, int x, int y, int z) {
378
+ if (visited[x][y][z] || bad[x][y][z]) return;
379
+ visited[x][y][z] = 1;
380
+
381
+ int status = ioctl_get_status(fd);
382
+ if (status == 1) { read_flag(fd); exit(0); }
383
+ if (status == 2) { bad[x][y][z] = 1; return; } // decoy — mark bad
384
+
385
+ int walls = ioctl_get_walls(fd);
386
+ int dx[] = {1,-1,0,0,0,0}, dy[] = {0,0,1,-1,0,0}, dz[] = {0,0,0,0,1,-1};
387
+ int opp[] = {2,3,0,1,5,4}; // opposite directions for backtracking
388
+
389
+ for (int dir = 0; dir < 6; dir++) {
390
+ if (!(walls & (1 << dir))) continue; // wall present
391
+ ioctl_move(fd, dir);
392
+ dfs(fd, x+dx[dir], y+dy[dir], z+dz[dir]);
393
+ ioctl_move(fd, opp[dir]); // backtrack
394
+ }
395
+ }
396
+ // After decoy hit: reset via ioctl 0x6489, clear visited, re-run DFS
397
+ ```
398
+
399
+ **Remote deployment:** Upload binary via base64 chunks over netcat shell, decode, execute.
400
+
401
+ **Key insight:** For kernel module challenges, injecting test binaries into initramfs and probing ioctls dynamically is faster than static RE of stripped kernel modules. Keep solver binary minimal (raw syscalls, no libc) for fast upload.
402
+
403
+ ---
404
+
405
+ ## Multi-Threaded VM with Channel Synchronization (DiceCTF 2026)
406
+
407
+ **Pattern (locked-in):** Custom stack-based VM runs 16 concurrent threads verifying a 30-char flag. Threads communicate via futex-based channels. Pipeline: input → XOR scramble → transformation → base-4 state machine → final check.
408
+
409
+ **Analysis approach:**
410
+ 1. **Identify thread roles** by tracing channel read/write patterns in GDB
411
+ 2. **Extract constants** (XOR scramble values, lookup tables) via breakpoints on specific opcodes
412
+ 3. **Watch for inverted logic:** validity check returns 0 for valid, non-zero for blocked (opposite of intuition)
413
+ 4. **Detect futex quirks:** `unlock_pi` on unowned mutex returns EPERM=1, which can change all computations
414
+
415
+ **BFS state space search for constrained state machines:**
416
+ ```python
417
+ from collections import deque
418
+
419
+ def solve_flag(scramble_vals, lookup_table, initial_state, target_state):
420
+ """BFS through state machine to find valid flag bytes."""
421
+ flag = [None] * 30
422
+ # Known prefix/suffix from flag format
423
+ flag[0:5] = list(b'dice{')
424
+ flag[29] = ord('}')
425
+
426
+ # For each unknown position, try all printable ASCII
427
+ states = {initial_state}
428
+ for pos in range(28, 4, -1): # processed in reverse
429
+ next_states = {}
430
+ for state in states:
431
+ for ch in range(32, 127):
432
+ transformed = transform(ch, scramble_vals[pos])
433
+ digits = to_base4(transformed)
434
+ new_state = apply_digits(state, digits, lookup_table)
435
+ if new_state is not None: # valid path exists
436
+ next_states.setdefault(new_state, []).append((state, ch))
437
+ states = set(next_states.keys())
438
+
439
+ # Trace back from target_state to recover flag
440
+ ```
441
+
442
+ **Key insight:** Multi-threaded VMs require tracing data flow across thread boundaries. Channel-based communication creates a pipeline — identify each thread's role (input, transform, validate, output) by watching which channels it reads/writes. Constants that affect computation may come from unexpected sources (futex return values, thread IDs).
443
+
444
+ ---
445
+
446
+ ## Backdoored Shared Library Detection via String Diffing (Hack.lu CTF 2012)
447
+
448
+ **Pattern (Zombie Lockbox):** A setuid binary uses `strcmp` for password validation. The expected password is visible via `strings` and works under GDB (which drops suid), but fails when run normally. The binary links against a non-standard libc that patches function behavior based on suid status.
449
+
450
+ **Detection steps:**
451
+ 1. Check for non-standard library paths with `ldd`:
452
+ ```bash
453
+ ldd ./binary
454
+ # Suspicious: libc.so.6 => /lib/libc/libc.so.6 (non-standard path)
455
+ # Normal: libc.so.6 => /lib32/libc.so.6
456
+ ```
457
+
458
+ 2. Diff strings between the suspicious and system libc:
459
+ ```bash
460
+ strings /lib/libc/libc.so.6 > suspicious_strings
461
+ strings /lib32/libc-2.15.so > normal_strings
462
+ diff suspicious_strings normal_strings
463
+ ```
464
+
465
+ 3. Disassemble the patched function (e.g., `puts`) to find injected code:
466
+ ```bash
467
+ gdb /lib/libc/libc.so.6
468
+ (gdb) disas puts
469
+ # Look for unexpected calls or branches
470
+ # Injected code may check suid status (getuid/geteuid syscalls)
471
+ # and swap the expected password at runtime
472
+ ```
473
+
474
+ **Key insight:** When a binary behaves differently under GDB vs. normal execution, check `ldd` for non-standard library paths. Suid binaries drop privileges under debuggers, so a backdoored libc can detect this via `getuid`/`geteuid` syscalls and change program behavior accordingly. The `strings | diff` approach quickly reveals injected data without full disassembly.
475
+
476
+ ---
477
+
478
+ ---
479
+
480
+ ## Custom binfmt Kernel Module with RC4 Flat Binaries (BSidesSF 2026)
481
+
482
+ **Pattern (Private Binary):** A custom Linux kernel module (`.ko`) registers a `binfmt` handler for non-standard binary formats. When a file with a specific magic number is executed, the kernel module intercepts it, decrypts the contents in memory, and jumps to the entry point.
483
+
484
+ **Reverse engineering approach:**
485
+ 1. **Analyze the `.ko`:** Look for `register_binfmt()` call — it registers a `struct linux_binfmt` with a `load_binary` callback
486
+ 2. **Find the magic number:** The `load_binary` function checks the file's first bytes against a specific magic number to identify its format
487
+ 3. **Extract the encryption key:** Look for `movabs` instructions loading 8-byte constants — these are often RC4 key bytes
488
+ 4. **Identify the encryption scheme:** Common choices are RC4, XOR, or AES-ECB. RC4 is identifiable by the S-box initialization loop (256-byte array, swap pattern)
489
+ 5. **Decrypt the flat binary:** Apply the recovered key to the encrypted file contents, skipping any header
490
+
491
+ ```python
492
+ from Crypto.Cipher import ARC4
493
+
494
+ # Extract RC4 key from kernel module (found via movabs instructions)
495
+ key = bytes([0x41, 0x42, 0x43, ...]) # Key bytes from .ko disassembly
496
+
497
+ with open('encrypted.bin', 'rb') as f:
498
+ header = f.read(HEADER_SIZE) # Skip binfmt header
499
+ encrypted = f.read()
500
+
501
+ cipher = ARC4.new(key)
502
+ decrypted = cipher.decrypt(encrypted)
503
+
504
+ # The decrypted output is a flat binary (no ELF headers)
505
+ # Load at the fixed virtual address specified in the kernel module
506
+ # Disassemble with: objdump -b binary -m i386:x86-64 -D decrypted.bin
507
+ # Or in Ghidra: import as "Raw Binary", set base address from .ko
508
+ ```
509
+
510
+ **Detection in kernel module:**
511
+ - `register_binfmt` / `unregister_binfmt` calls
512
+ - `vm_mmap()` or `vm_brk()` for memory allocation at fixed addresses
513
+ - Direct jump to mapped memory (entry point execution)
514
+ - S-box initialization pattern (RC4): loop 0-255, swap `S[i]` with `S[j]`
515
+
516
+ **Key insight:** The flat binary has no ELF headers, so standard tools won't recognize it. You must extract the load address from the kernel module (look for the `vm_mmap` call's address argument) and import the decrypted blob at that address in your disassembler. RC4 keys in kernel modules are often stored as immediate values in `mov` or `movabs` instructions rather than in data sections.
517
+
518
+ **References:** BSidesSF 2026 "Private Binary"
519
+
520
+ ---
521
+
522
+ ## Hash-Resolved Imports / No-Import Ransomware (BSidesSF 2026)
523
+
524
+ **Pattern (Ran Somewhere):** Malware binary has zero visible imports — all API calls are resolved at runtime by hashing symbol names and comparing against pre-computed hash values. The binary uses `dlopen` + a custom hash table to find libc and libcrypto functions.
525
+
526
+ **Identification:**
527
+ - `readelf -d` shows no dynamic symbols or very few (just `dlopen`/`dlsym`)
528
+ - Strings reveal no standard API names
529
+ - Disassembly shows hash computation loops followed by indirect calls
530
+ - RC4-encrypted embedded strings (RSA public key, file paths, passphrases)
531
+
532
+ **Analysis shortcut — LD_PRELOAD key extraction:**
533
+
534
+ Rather than reversing the full hash resolution and key derivation, hook the crypto functions that the malware ultimately calls:
535
+
536
+ ```c
537
+ // hook_crypto.c — captures AES key used by the ransomware
538
+ #define _GNU_SOURCE
539
+ #include <dlfcn.h>
540
+ #include <openssl/evp.h>
541
+ #include <stdio.h>
542
+
543
+ int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type,
544
+ ENGINE *impl, const unsigned char *key,
545
+ const unsigned char *iv) {
546
+ if (key) {
547
+ FILE *f = fopen("/tmp/aes_key.bin", "wb");
548
+ fwrite(key, 1, 32, f); // AES-256
549
+ fclose(f);
550
+ fprintf(stderr, "[HOOK] AES key captured\n");
551
+ }
552
+ typedef int (*orig_t)(EVP_CIPHER_CTX*, const EVP_CIPHER*, ENGINE*,
553
+ const unsigned char*, const unsigned char*);
554
+ orig_t orig = (orig_t)dlsym(RTLD_NEXT, "EVP_CipherInit_ex");
555
+ return orig(ctx, type, impl, key, iv);
556
+ }
557
+ ```
558
+
559
+ ```bash
560
+ # Compile and run
561
+ gcc -shared -fPIC -o hook.so hook_crypto.c -ldl
562
+ # Run in Docker container (ransomware may be destructive!)
563
+ docker run --rm -v $(pwd):/work -w /work ubuntu:22.04 \
564
+ bash -c "LD_PRELOAD=./hook.so ./ransomware; xxd /tmp/aes_key.bin"
565
+ ```
566
+
567
+ **Hash resolution patterns:**
568
+ - **SipHash variant:** Two 64-bit seeds, iterative mixing with symbol name bytes
569
+ - **DJB2/FNV variants:** Simpler hash functions with recognizable constants (`5381`, `0xcbf29ce484222325`)
570
+ - **ROR13-based:** Windows malware favorite: `hash = (hash >> 13) | (hash << 19); hash += c`
571
+
572
+ **Decryption after key capture:**
573
+ ```python
574
+ from Crypto.Cipher import AES
575
+
576
+ key = open('/tmp/aes_key.bin', 'rb').read()
577
+ iv = open('/tmp/aes_iv.bin', 'rb').read() # Also hookable
578
+ cipher = AES.new(key, AES.MODE_CBC, iv)
579
+
580
+ with open('flag.txt.enc', 'rb') as f:
581
+ ct = f.read()
582
+ pt = cipher.decrypt(ct)
583
+ # Remove PKCS7 padding
584
+ pt = pt[:-pt[-1]]
585
+ print(pt.decode())
586
+ ```
587
+
588
+ **Key insight:** When a binary resolves all imports via hashing, don't waste time reversing the hash function and building a rainbow table. Instead, let the malware resolve everything itself by running it in a sandboxed environment with `LD_PRELOAD` hooks on the functions you care about (OpenSSL crypto functions, file I/O, network calls). The AES key is deterministic across runs — if it works once, it works always.
589
+
590
+ **Safety:** Always run suspected ransomware in a Docker container or VM. Mount only copies of the encrypted files, never originals.
591
+
592
+ **References:** BSidesSF 2026 "Ran Somewhere"
593
+
594
+ ---
595
+
596
+ ## ELF Section Header Corruption for Anti-Analysis (BSidesSF 2026)
597
+
598
+ **Pattern (stubborn-elf):** An ELF binary has deliberately corrupted section header table entries, causing standard analysis tools (`readelf`, `objdump`, IDA, Ghidra) to crash or produce errors. However, the **program headers** (which the OS loader uses) are intact, so the binary executes normally. The flag is appended after the corrupted sections, marked with magic bytes.
599
+
600
+ ```python
601
+ import sys
602
+
603
+ # Standard tools fail on corrupted section headers
604
+ # Manual parsing bypasses section headers entirely
605
+
606
+ with open("stubborn_elf", "rb") as f:
607
+ data = f.read()
608
+
609
+ # Search for magic marker appended after ELF sections
610
+ magic = b"\xDE\xAD\xBE\xEF\xCA\xFE\xBA\xBE"
611
+ idx = data.find(magic)
612
+ if idx >= 0:
613
+ # Data after magic is XOR-encrypted
614
+ encrypted = data[idx + len(magic):]
615
+ decrypted = bytes(b ^ 0x42 for b in encrypted)
616
+ print(decrypted.decode(errors='ignore'))
617
+ ```
618
+
619
+ **Key insight:** ELF execution requires **program headers** (PT_LOAD segments), NOT section headers. Section headers are metadata for debuggers and analysis tools — they're optional at runtime. Corrupting `e_shoff`, `e_shnum`, or `e_shstrndx` in the ELF header breaks tools but not execution. When tools fail, parse the binary manually or patch the ELF header to zero out section header references before loading in a disassembler.
620
+
621
+ **Recovery approach:**
622
+ ```bash
623
+ # Patch section header offset to 0 (removes section table)
624
+ printf '\x00\x00\x00\x00\x00\x00\x00\x00' | dd of=binary bs=1 seek=40 conv=notrunc
625
+ # Now Ghidra/IDA can load it using program headers only
626
+
627
+ # Or use readelf -l (program headers only, ignores sections)
628
+ readelf -l stubborn_elf
629
+ ```
630
+
631
+ **When to recognize:** `readelf -S` crashes or shows garbage. `file` command identifies it as ELF. `readelf -l` (lowercase L, program headers) works fine. The binary runs normally despite tool failures.
632
+
633
+ **References:** BSidesSF 2026 "stubborn-elf"
634
+
635
+ ---
636
+
637
+ ## VM Trace Diffing Instead of Full Disassembly (CONFidence CTF 2019 Teaser)
638
+
639
+ **Pattern (Go Machine):** Go binary runs a 15-handler stack-VM (`0123456789OEQLCI` dispatch string) whose opcode meanings are rotated by an LCG-driven `shuffle` handler after every tick. Rewriting the interpreter faithfully is painful; the VM actually computes a simple 32-bit hash over 4-character input groups.
640
+
641
+ Instead, attach a debugger-driven tracer to the dispatch routine and dump `(opcode, stack)` per step — then compare traces for two nearly identical inputs:
642
+
643
+ ```python
644
+ # Pseudo-code for an IDAPython / gdb conditional-breakpoint tracer
645
+ def on_dispatch():
646
+ op = read_byte(bytecode + pc)
647
+ top = stack[:sp+1]
648
+ print(f"{decode(op)}\t({'|'.join(hex(x) for x in top)})")
649
+
650
+ # Replay the dumped trace in plain Python; no bytecode parsing, no shuffle logic:
651
+ elif line.startswith('save at (0x51)'):
652
+ return stack[top] == expected_hash # calculated hash lands at mem[0x51]
653
+
654
+ # Diff trace("abcd") vs trace("dcba") -> the same mul/mod sequence shows up,
655
+ # revealing the real algorithm:
656
+ def calc_hash(x, mod):
657
+ for _ in range(8):
658
+ x = x * x % mod
659
+ return x * x_original % mod
660
+ ```
661
+
662
+ Dump the per-group moduli (`[0x88ca6b51, 0x8405b751, 0xbfa08c87, 0x82013f23, 0x4666751b, 0x5271083f]`) and expected hashes from the trace, then brute-force 4-character permutations of `string.printable` against `calc_hash`.
663
+
664
+ **Key insight:** Custom VMs with self-modifying dispatch (shuffle, rotor, LCG-keyed opcode table) are designed to punish naive reimplementation. Recording the executed instruction stream bypasses the trick entirely — the trace is deterministic for a given input, and diffing two traces with a single-bit difference localises the "real" algorithm hiding under the VM overhead.
665
+
666
+ **References:** CONFidence CTF 2019 Teaser — Go Machine, writeup 13947
667
+
668
+ ---
669
+
670
+ See also: [patterns-ctf-2.md](patterns-ctf-2.md) for Part 2 (multi-layer self-decrypting binary, embedded ZIP+XOR license, stack string deobfuscation, prefix hash brute-force, CVP/LLL lattice, decision tree obfuscation, GF(2^8) Gaussian elimination), [patterns-ctf-3.md](patterns-ctf-3.md) for Part 3 (Z3 boolean circuit, sliding window popcount, keyboard LED Morse code, C++ destructor-hidden validation, VM sequential key-chain brute-force, BWT inversion, OpenType font ligature exploitation, GLSL shader VM with self-modifying code).