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,664 @@
1
+ # CTF Crypto - PRNG & Key Recovery
2
+
3
+ Foundational PRNG state/seed recovery techniques. For CTF-era advanced attacks (MT19937 constraint propagation, Rule 86 cellular automaton, Java LCG MITM, LFSR bit-fold, Z3 timing oracle, randcrack DSA, NTP-poisoned UUID), see [prng-attacks.md](prng-attacks.md).
4
+
5
+ ## Table of Contents
6
+ - [Mersenne Twister (MT19937) State Recovery](#mersenne-twister-mt19937-state-recovery)
7
+ - [MT State Recovery from random.random() Floats via GF(2) Matrix (PHD CTF Quals 2012)](#mt-state-recovery-from-randomrandom-floats-via-gf2-matrix-phd-ctf-quals-2012)
8
+ - [Time-Based Seed Attacks](#time-based-seed-attacks)
9
+ - [C srand/rand Synchronization via Python ctypes](#c-srandrand-synchronization-via-python-ctypes)
10
+ - [Layered Encryption Recovery](#layered-encryption-recovery)
11
+ - [LCG Parameter Recovery Attack](#lcg-parameter-recovery-attack)
12
+ - [ChaCha20 Key Recovery](#chacha20-key-recovery)
13
+ - [GF(2) Matrix PRNG Seed Recovery (0xFun 2026)](#gf2-matrix-prng-seed-recovery-0xfun-2026)
14
+ - [Middle-Square PRNG Brute Force (UTCTF 2024)](#middle-square-prng-brute-force-utctf-2024)
15
+ - [Deterministic RNG from Flag Bytes + Hill Climbing (VuwCTF 2025)](#deterministic-rng-from-flag-bytes--hill-climbing-vuwctf-2025)
16
+ - [Byte-by-Byte Oracle with Random Mode Matching (VuwCTF 2025)](#byte-by-byte-oracle-with-random-mode-matching-vuwctf-2025)
17
+ - [RSA Key Reuse / Replay (UTCTF 2024)](#rsa-key-reuse--replay-utctf-2024)
18
+ - [Password Cracking Strategy](#password-cracking-strategy)
19
+ - [Logistic Map / Chaotic PRNG Seed Recovery (BYPASS CTF 2025)](#logistic-map--chaotic-prng-seed-recovery-bypass-ctf-2025)
20
+ - [V8 XorShift128+ State Recovery (Math.random Prediction)](#v8-xorshift128-state-recovery-mathrandom-prediction)
21
+
22
+ See [prng-attacks.md](prng-attacks.md) for CTF-era advanced attacks (2017+).
23
+
24
+ ---
25
+
26
+ ## Mersenne Twister (MT19937) State Recovery
27
+
28
+ Python's `random` module uses Mersenne Twister. If you can observe outputs, you can recover the state and predict future values.
29
+
30
+ **Key properties:**
31
+ - 624 × 32-bit internal state
32
+ - Each output is tempered from state
33
+ - After 624 outputs, state is twisted (regenerated)
34
+
35
+ **Basic untemper (reverse single output):**
36
+ ```python
37
+ def untemper(y):
38
+ y ^= y >> 18
39
+ y ^= (y << 15) & 0xefc60000
40
+ for _ in range(7):
41
+ y ^= (y << 7) & 0x9d2c5680
42
+ y ^= y >> 11
43
+ y ^= y >> 22
44
+ return y
45
+
46
+ # Given 624 consecutive outputs, recover state
47
+ state = [untemper(output) for output in outputs]
48
+ ```
49
+
50
+ **Python's randrange(maxsize) on 64-bit:**
51
+ - `maxsize = 2^63 - 1`, so `getrandbits(63)` is used
52
+ - Each 63-bit output uses 2 MT outputs: `(mt1 << 31) | (mt2 >> 1)`
53
+ - One bit lost per output → need symbolic solving
54
+
55
+ **Symbolic approach with z3:**
56
+ ```python
57
+ from z3 import *
58
+
59
+ def symbolic_temper(y):
60
+ y = y ^ (LShR(y, 11))
61
+ y = y ^ ((y << 7) & 0x9d2c5680)
62
+ y = y ^ ((y << 15) & 0xefc60000)
63
+ y = y ^ (LShR(y, 18))
64
+ return y
65
+
66
+ # Create symbolic MT state
67
+ mt = [BitVec(f'mt_{i}', 32) for i in range(624)]
68
+ solver = Solver()
69
+
70
+ # For each observed 63-bit output
71
+ for i, out63 in enumerate(outputs):
72
+ if 2*i + 1 >= 624: break
73
+ y1 = symbolic_temper(mt[2*i])
74
+ y2 = symbolic_temper(mt[2*i + 1])
75
+ combined = Concat(Extract(31, 0, y1), Extract(31, 1, y2))
76
+ solver.add(combined == out63)
77
+
78
+ if solver.check() == sat:
79
+ state = [solver.model()[mt[i]].as_long() for i in range(624)]
80
+ ```
81
+
82
+ **Applications:**
83
+ - MIME boundary prediction (email libraries)
84
+ - Session token prediction
85
+ - CAPTCHA bypass (predictable codes)
86
+ - Game RNG exploitation
87
+
88
+ ## MT State Recovery from random.random() Floats via GF(2) Matrix (PHD CTF Quals 2012)
89
+
90
+ **Pattern:** Server exposes `random.random()` float outputs (e.g., via an API endpoint). Standard MT untemper requires 624 × 32-bit integer outputs, but `random.random()` produces 53-bit floats — truncating each to 8 usable bits per observation. A precomputed GF(2) magic matrix maps observed byte values back to the 624-word MT state.
91
+
92
+ **Key insight:** `random.random()` returns `(a*2^27+b)/2^53` where `a` = 27 bits from one MT output and `b` = 26 bits from the next. Truncating `int(float * 256)` yields only 8 bits per float, so 3360+ observations are needed (vs. 624 for integer outputs). The `not_random` library precomputes the GF(2) relationship between observed bits and state bits.
93
+
94
+ ```python
95
+ import random, gzip, hashlib
96
+
97
+ # Load precomputed GF(2) magic matrix (from github.com/fx5/not_random)
98
+ f = gzip.GzipFile("magic_data", "r")
99
+ magic = eval(f.read())
100
+ f.close()
101
+
102
+ def rebuild_from_floats(floats):
103
+ """Convert float observations to byte values, then recover MT state."""
104
+ vals = [int(f * 256) for f in floats] # truncate to 8-bit
105
+ return rebuild_random(vals)
106
+
107
+ def rebuild_random(vals):
108
+ """Recover MT19937 state from 3360+ byte observations using GF(2) matrix."""
109
+ def getbit(bit):
110
+ assert bit >= 0
111
+ return (vals[bit // 8] >> (7 - bit % 8)) & 1
112
+ state = []
113
+ for i in range(624):
114
+ val = 0
115
+ data = magic[i % 2]
116
+ for bit in data:
117
+ val <<= 1
118
+ for b in bit:
119
+ val ^= getbit(b + (i // 2) * 8 - 8)
120
+ state.append(val)
121
+ state.append(0)
122
+ ran = random.Random()
123
+ ran.setstate((3, tuple(state), None))
124
+ # Advance past consumed outputs
125
+ for i in range(len(vals) - 3201 + 394):
126
+ ran.randint(0, 255)
127
+ return ran
128
+
129
+ # Collect 3360+ random.random() floats from the target
130
+ floats = [...] # observed values from server API
131
+
132
+ # Recover state and predict future outputs
133
+ my_random = rebuild_from_floats(floats[:3360])
134
+
135
+ # Verify predictions match remaining observations
136
+ for observed, predicted in zip(floats[3360:], [my_random.random() for _ in range(40)]):
137
+ assert '%.16f' % observed == '%.16f' % predicted
138
+
139
+ # Forge password reset token (same hash the server computes)
140
+ token = hashlib.md5(('%.16f' % my_random.random()).encode()).hexdigest()
141
+ reset_url = f'http://target/reset/{user_id}-{token}/'
142
+ ```
143
+
144
+ **Attack flow (password reset token prediction):**
145
+ 1. Request 3360+ random float values from an API endpoint that exposes them (e.g., `/?count=3360`)
146
+ 2. Simultaneously trigger a password reset (the reset token is `md5(random.random())`)
147
+ 3. Recover the MT state from the observed floats
148
+ 4. Predict the `random.random()` call used for the reset token
149
+ 5. Construct the reset URL with the predicted token
150
+
151
+ **When to use:** Server uses Python's `random.random()` for security-sensitive tokens (session IDs, password resets, CSRF tokens) and also exposes random values through another endpoint. The `not_random` library handles the bit-level math — focus on collecting enough float observations and synchronizing timing with the target operation.
152
+
153
+ ---
154
+
155
+ ## Time-Based Seed Attacks
156
+
157
+ When encryption uses time-based PRNG seed:
158
+ ```python
159
+ seed = f"{username}_{timestamp}_{random_bits}"
160
+ ```
161
+
162
+ **Attack approach:**
163
+ 1. **Username:** Extract from metadata, email headers, challenge context
164
+ 2. **Timestamp:** Get from file metadata (ZIP, exiftool)
165
+ 3. **Random bits:** Check for hardcoded seed in binary, or bruteforce if small range
166
+
167
+ **Timestamp extraction:**
168
+ ```bash
169
+ # Set timezone to match target
170
+ TZ=Pacific/Galapagos exiftool file.enc
171
+ # Look for File Modification Date/Time
172
+ ```
173
+
174
+ **Bruteforce milliseconds:**
175
+ ```python
176
+ from datetime import datetime
177
+ import random
178
+
179
+ for ms in range(1000):
180
+ ts = f"2021-02-09!07:23:54.{ms:03d}"
181
+ seed = f"{username}_{ts}_{rdata}"
182
+ rng = random.Random()
183
+ rng.seed(seed)
184
+ key = bytes(rng.getrandbits(8) for _ in range(32))
185
+ if try_decrypt(ciphertext, key):
186
+ print(f"Found seed: {seed}")
187
+ break
188
+ ```
189
+
190
+ ## C srand/rand Synchronization via Python ctypes
191
+
192
+ **Pattern:** Binary seeds C's PRNG with `srand(time(NULL))` at startup and uses `rand()` for encryption keys, random challenges, or XOR masks. Python's `random` module uses Mersenne Twister (different algorithm), so calling `random.seed(t)` produces wrong outputs. Use `ctypes` to load the same libc and call C's `srand()`/`rand()` directly.
193
+
194
+ **Basic synchronization (L3akCTF 2024, MireaCTF):**
195
+ ```python
196
+ from ctypes import CDLL
197
+ from time import time
198
+
199
+ # Load the SAME libc used by the target binary
200
+ libc = CDLL('./libc.so.6') # or CDLL('libc.so.6') for system libc
201
+
202
+ # Seed at the same second as the binary starts
203
+ libc.srand(int(time()))
204
+
205
+ # Generate the same sequence as the binary's rand() calls
206
+ for i in range(16):
207
+ value = libc.rand() & 0xff # match binary's truncation (e.g., & 0xff for byte)
208
+ print(value)
209
+ ```
210
+
211
+ **Decrypting XOR-encrypted data (L3akCTF 2024 chonccfile):**
212
+ ```python
213
+ from ctypes import CDLL
214
+ from time import time
215
+ from pwn import u32, p32
216
+
217
+ libc_imp = CDLL('./libc.so.6')
218
+ libc_imp.srand(int(time()))
219
+
220
+ # Binary XORs each 4-byte block with rand() output
221
+ encrypted_data = b'...' # read from heap/memory
222
+ result = b''
223
+ for i in range(0, len(encrypted_data), 4):
224
+ block = u32(encrypted_data[i:i+4])
225
+ libc_imp.rand() # skip delay-related rand() call if binary does extra calls
226
+ key = libc_imp.rand()
227
+ block ^= key
228
+ result += p32(block)
229
+ ```
230
+
231
+ **Timing considerations:**
232
+ - `time(NULL)` has 1-second granularity — start the exploit within the same second as the binary
233
+ - Remote targets may have startup delay — try offsets of `+1` or `+2` seconds
234
+ - Account for any `rand()` calls between `srand()` and the target usage (e.g., random delays)
235
+ - Not 100% reliable on first try — retry with adjacent seeds if needed
236
+
237
+ **Key insight:** Python's `random` and C's `rand()` are completely different PRNGs. When a C binary uses `srand(time(NULL))`, the only way to reproduce the sequence from Python is `ctypes.CDLL` calling the same libc's `srand`/`rand`. Load the challenge's provided `libc.so.6` for exact compatibility. This works for any C PRNG output prediction — XOR keys, random challenges, token generation, or encrypted heap data.
238
+
239
+ **Alternative — custom shared library (MireaCTF):**
240
+ ```c
241
+ // random_lib.c — compile with: gcc -shared -o random_lib.so random_lib.c
242
+ #include <stdlib.h>
243
+ void setseed(int seed) { srand(seed); }
244
+ int generate() { return rand() & 0xff; }
245
+ ```
246
+ ```python
247
+ from ctypes import CDLL
248
+ lib = CDLL('./random_lib.so')
249
+ lib.setseed(int(time()) + 1) # +1 for remote delay
250
+ numbers = [lib.generate() for _ in range(16)]
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Layered Encryption Recovery
256
+
257
+ When binary uses multiple encryption layers:
258
+ 1. Identify encryption order (e.g., Serpent → TEA)
259
+ 2. Find seed derivation (e.g., sum of flag chars)
260
+ 3. Keys often derived from `srand()` sequence
261
+ 4. Bruteforce seed range (sum of printable ASCII is limited)
262
+
263
+ ## LCG Parameter Recovery Attack
264
+
265
+ Linear Congruential Generators are weak PRNGs. Given consecutive outputs, recover parameters:
266
+
267
+ **LCG formula:** `x_{n+1} = (a * x_n + c) mod m`
268
+
269
+ **Recovery from output sequence (SageMath):**
270
+ ```python
271
+ # Given sequence: [s0, s1, s2, s3, ...]
272
+ # crypto-attacks library: github.com/jvdsn/crypto-attacks
273
+ from attacks.lcg import parameter_recovery
274
+
275
+ sequence = [
276
+ 72967016216206426977511399018380411256993151454761051136963936354667101207529,
277
+ 49670218548812619526153633222605091541916798863041459174610474909967699929824,
278
+ # ... more outputs
279
+ ]
280
+
281
+ m, a, c = parameter_recovery.attack(sequence)
282
+ print(f"Modulus m: {m}")
283
+ print(f"Multiplier a: {a}")
284
+ print(f"Increment c: {c}")
285
+ ```
286
+
287
+ **Weak RSA from LCG primes:**
288
+ - If RSA primes are generated from LCG, recover LCG params first
289
+ - Use known plaintext XOR ciphertext to extract LCG outputs
290
+ - Regenerate same prime sequence to factor N
291
+
292
+ ```python
293
+ # Recover XOR key (which is LCG output)
294
+ def recover_lcg_output(plaintext, ciphertext, timestamp):
295
+ pt_bytes = plaintext.encode('utf-8').ljust(32, b'\0')
296
+ ct_int = int.from_bytes(bytes.fromhex(ciphertext), 'big')
297
+ return timestamp ^ int.from_bytes(pt_bytes, 'big') ^ ct_int
298
+
299
+ # After recovering LCG params, generate RSA primes
300
+ lcg = LCG(a, c, m, seed)
301
+ primes = []
302
+ while len(primes) < 8:
303
+ candidate = lcg.next()
304
+ if is_prime(candidate) and candidate.bit_length() == 256:
305
+ primes.append(candidate)
306
+
307
+ n = prod(primes)
308
+ phi = prod(p - 1 for p in primes)
309
+ d = pow(65537, -1, phi)
310
+ ```
311
+
312
+ ## ChaCha20 Key Recovery
313
+
314
+ When ChaCha20 key is derived from recoverable data:
315
+
316
+ ```python
317
+ from Crypto.Cipher import ChaCha20
318
+
319
+ # If key derived from predictable source (timestamp, PID, etc.)
320
+ for candidate_key in generate_candidates():
321
+ cipher = ChaCha20.new(key=candidate_key, nonce=nonce)
322
+ plaintext = cipher.decrypt(ciphertext)
323
+ if is_valid(plaintext): # Check for expected format
324
+ print(f"Key found: {candidate_key.hex()}")
325
+ break
326
+ ```
327
+
328
+ **Ghidra emulator for key extraction:**
329
+ When key is computed by complex function, emulate it rather than reimplementing.
330
+
331
+ ## GF(2) Matrix PRNG Seed Recovery (0xFun 2026)
332
+
333
+ **Pattern (BitStorm):** PRNG using only XOR, shifts, and rotations is linear over GF(2).
334
+
335
+ **Key insight:** Express entire PRNG as matrix multiplication: `output_bits = M * seed_bits (mod 2)`. With enough outputs, Gaussian elimination recovers the seed.
336
+
337
+ ```python
338
+ import numpy as np
339
+
340
+ def build_prng_matrix(prng_func, seed_bits=2048, output_bits=2048):
341
+ """Build GF(2) matrix by running PRNG on unit vectors."""
342
+ M = np.zeros((output_bits, seed_bits), dtype=np.uint8)
343
+ for i in range(seed_bits):
344
+ # Set bit i of seed
345
+ seed = 1 << (seed_bits - 1 - i)
346
+ output = prng_func(seed)
347
+ for j in range(output_bits):
348
+ M[j, i] = (output >> (output_bits - 1 - j)) & 1
349
+ return M
350
+
351
+ # Given output, solve: M * seed = output (mod 2)
352
+ # Use GF(2) Gaussian elimination (see modern-ciphers.md solve_gf2)
353
+ seed = solve_gf2(M, output_bits_array)
354
+ ```
355
+
356
+ **Identification:** Any PRNG using only `^`, `<<`, `>>`, bitwise rotate. DON'T try iterative state recovery — go straight to the matrix.
357
+
358
+ ---
359
+
360
+ ## Middle-Square PRNG Brute Force (UTCTF 2024)
361
+
362
+ **Pattern (numbers go brrr):** Middle-square method with small seed space.
363
+
364
+ ```python
365
+ # PRNG: seed = int(str(seed * seed).zfill(12)[3:9]) — 6-digit seed
366
+ # Seed source: int(time.time() * 1000) % (10**6) — only 1M possibilities
367
+ # AES key: 8 rounds of PRNG, each produces seed % 2^16 as 2-byte fragment
368
+
369
+ def middle_square_keygen(seed):
370
+ key = b''
371
+ for _ in range(8):
372
+ seed = int(str(seed * seed).zfill(12)[3:9])
373
+ key += (seed % (2**16)).to_bytes(2, 'big')
374
+ return key
375
+
376
+ # Brute-force: encrypt known plaintext, compare
377
+ for seed in range(10**6):
378
+ key = middle_square_keygen(seed)
379
+ if try_decrypt(ciphertext, key):
380
+ print(f"Seed: {seed}")
381
+ break
382
+ ```
383
+
384
+ **Even with time-limited interactions:** 1 known-plaintext pair suffices for offline brute force.
385
+
386
+ ---
387
+
388
+ ## Deterministic RNG from Flag Bytes + Hill Climbing (VuwCTF 2025)
389
+
390
+ **Pattern (Totally Random Art):** Flag bytes seed Python `random.Random()`. First N bytes of flag are known format, remaining bytes produce deterministic output.
391
+
392
+ **Attack:** When PRNG seed is known/derivable from flag format, hill-climb unknown characters:
393
+ ```python
394
+ import random
395
+
396
+ def render(flag_bytes):
397
+ rng = random.Random()
398
+ rng.seed(flag_bytes)
399
+ grid = [[0]*10 for _ in range(5)]
400
+ for b in flag_bytes:
401
+ steps, stroke = divmod(b, 16)
402
+ x, y = 0, 0
403
+ for _ in range(steps):
404
+ dx, dy = rng.choice([(0,1),(0,-1),(1,0),(-1,0)])
405
+ x = (x + dx) % 10
406
+ y = (y + dy) % 5
407
+ grid[y][x] = (grid[y][x] + stroke) % 16
408
+ return grid
409
+
410
+ # Hill climb: try each byte value, keep the one that maximizes grid match
411
+ target = parse_target_art()
412
+ flag = list(b'VuwCTF{')
413
+ for pos in range(7, 17):
414
+ best_score, best_char = -1, 0
415
+ for c in range(32, 127):
416
+ candidate = bytes(flag + [c])
417
+ score = sum(1 for y in range(5) for x in range(10)
418
+ if render(candidate)[y][x] == target[y][x])
419
+ if score > best_score:
420
+ best_score, best_char = score, c
421
+ flag.append(best_char)
422
+ ```
423
+
424
+ ---
425
+
426
+ ## Byte-by-Byte Oracle with Random Mode Matching (VuwCTF 2025)
427
+
428
+ **Pattern (Unorthodox IV):** Server encrypts with one of N random modes/IVs per encryption. Can submit own plaintexts.
429
+
430
+ **Attack strategy:**
431
+ 1. Connect, get encrypted flag
432
+ 2. Probe with known prefix to check if connection can "reach" the flag's mode (same mode = same ciphertext prefix). ~50 probes, if no match, reconnect.
433
+ 3. Once reachable, test candidate characters. Mode match AND next byte match = correct char. Mode match but byte mismatch = eliminate candidate permanently.
434
+ 4. Elimination persists across reconnections.
435
+
436
+ **Key insight:** Probe for mode reachability first to avoid wasting attempts. Elimination-based search is more efficient than confirmation-based when modes are randomized.
437
+
438
+ ---
439
+
440
+ ## RSA Key Reuse / Replay (UTCTF 2024)
441
+
442
+ **Pattern (simple signature):** RSA keys reused across rounds with alternating inputs.
443
+
444
+ **Attack:** Submit previously captured encrypted output back to the server. If keys are static across interactions, replay attacks are trivial. Always check if crypto keys change between rounds.
445
+
446
+ ---
447
+
448
+ ## Logistic Map / Chaotic PRNG Seed Recovery (BYPASS CTF 2025)
449
+
450
+ **Pattern (Chaotic Trust):** Stream cipher using the logistic map `x_{n+1} = r * x * (1 - x)` as PRNG. Keystream generated by packing iterated float values.
451
+
452
+ **Key insight:** Logistic map with `r ≈ 4.0` is chaotic but deterministic — recovering the seed (initial x value) enables full keystream reconstruction. Seed is usually a decimal between 0 and 1, such as 0.123456789.
453
+
454
+ ```python
455
+ import struct
456
+
457
+ def logistic_map(x, r=3.99):
458
+ return r * x * (1 - x)
459
+
460
+ def decrypt_logistic(cipher_hex, seed):
461
+ cipher = bytes.fromhex(cipher_hex)
462
+ x = seed
463
+ stream = b""
464
+
465
+ while len(stream) < len(cipher):
466
+ x = logistic_map(x)
467
+ # Pack float as bytes for keystream (check endianness)
468
+ stream += struct.pack("<f", x)[-2:] # or full 4 bytes
469
+
470
+ stream = stream[:len(cipher)]
471
+ return bytes(a ^ b for a, b in zip(cipher, stream))
472
+
473
+ # Brute-force seed precision
474
+ for precision in range(6, 12):
475
+ for base in [123456, 234567, 314159, 271828]:
476
+ seed = base / (10 ** precision)
477
+ result = decrypt_logistic(cipher_hex, seed)
478
+ if b"FLAG" in result or b"CTF" in result:
479
+ print(f"Seed: {seed}, Flag: {result}")
480
+ ```
481
+
482
+ **Variations:**
483
+ - **r parameter:** Usually `r = 3.99` or `r = 4.0` (full chaos regime)
484
+ - **Packing:** `struct.pack("<f", x)` (4 bytes), `struct.pack("<d", x)` (8 bytes), or `[-2:]` for 2-byte fragments
485
+ - **Seed range:** Often a recognizable decimal like `0.123456789` or derived from challenge hints
486
+
487
+ **Identification:** Challenge mentions "chaos", "logistic", "butterfly effect", or provides `r` parameter. Look for source code containing `x = r * x * (1 - x)` iteration.
488
+
489
+ ---
490
+
491
+ ## V8 XorShift128+ State Recovery (Math.random Prediction)
492
+
493
+ **Pattern:** Web challenge uses `Math.floor(CONST * Math.random())` to generate tokens, codes, or game values. V8's `Math.random()` uses XorShift128+ (xs128p) PRNG. Given consecutive floored outputs, recover the internal state (state0, state1) with Z3, then predict future values.
494
+
495
+ **V8 internals:**
496
+ 1. xs128p produces 64-bit state; V8 uses `state0 >> 12 | 0x3FF0000000000000` to create a double in [1.0, 2.0), then subtracts 1.0
497
+ 2. `Math.random()` reads from a **64-value LIFO cache**. When the cache is empty, `RefillCache()` generates 64 new values. Values are consumed in reverse order from the cache
498
+ 3. Only `state0` is used for the output (not `state1`)
499
+
500
+ **xs128p algorithm:**
501
+ ```python
502
+ def xs128p(state0, state1):
503
+ s1 = state0 & 0xFFFFFFFFFFFFFFFF
504
+ s0 = state1 & 0xFFFFFFFFFFFFFFFF
505
+ s1 ^= (s1 << 23) & 0xFFFFFFFFFFFFFFFF
506
+ s1 ^= (s1 >> 17) & 0xFFFFFFFFFFFFFFFF
507
+ s1 ^= s0 & 0xFFFFFFFFFFFFFFFF
508
+ s1 ^= (s0 >> 26) & 0xFFFFFFFFFFFFFFFF
509
+ state0 = state1 & 0xFFFFFFFFFFFFFFFF
510
+ state1 = s1 & 0xFFFFFFFFFFFFFFFF
511
+ return state0, state1, state0 # output is new state0
512
+ ```
513
+
514
+ **Z3 solver for `Math.floor(CONST * Math.random())`:**
515
+ ```python
516
+ from z3 import *
517
+ from decimal import Decimal
518
+ import struct
519
+
520
+ def to_double(value):
521
+ double_bits = (value >> 12) | 0x3FF0000000000000
522
+ return struct.unpack('d', struct.pack('<Q', double_bits))[0] - 1
523
+
524
+ def from_double(dbl):
525
+ return struct.unpack('<Q', struct.pack('d', dbl + 1))[0] & 0x7FFFFFFFFFFFFFFF
526
+
527
+ def sym_xs128p(s0, s1):
528
+ s1_ = s0
529
+ s0_ = s1
530
+ s1_ ^= (s1_ << 23)
531
+ s1_ ^= LShR(s1_, 17)
532
+ s1_ ^= s0_
533
+ s1_ ^= LShR(s0_, 26)
534
+ return s1, s1_ # new state0, state1
535
+
536
+ def solve_v8_random(observed_values, multiple):
537
+ """Recover xs128p state from consecutive Math.floor(multiple * Math.random()) outputs.
538
+ observed_values must be in REVERSE order (oldest first after tac)."""
539
+ ostate0, ostate1 = BitVecs('ostate0 ostate1', 64)
540
+ sym_s0, sym_s1 = ostate0, ostate1
541
+ slvr = SolverFor("QF_BV")
542
+
543
+ for val in observed_values:
544
+ sym_s0, sym_s1 = sym_xs128p(sym_s0, sym_s1)
545
+ calc = LShR(sym_s0, 12) # V8's ToDouble mantissa bits
546
+ # Constrain: floor(multiple * to_double(state0)) == val
547
+ lower = from_double(Decimal(val) / Decimal(multiple))
548
+ upper = from_double(Decimal(val + 1) / Decimal(multiple))
549
+ lower_m = lower & 0x000FFFFFFFFFFFFF
550
+ upper_m = upper & 0x000FFFFFFFFFFFFF
551
+ upper_e = (upper >> 52) & 0x7FF
552
+ slvr.add(And(lower_m <= calc, Or(upper_m >= calc, upper_e == 1024)))
553
+
554
+ if slvr.check() == sat:
555
+ m = slvr.model()
556
+ return m[ostate0].as_long(), m[ostate1].as_long()
557
+ return None, None
558
+
559
+ # Predict next values after state recovery
560
+ def predict_next(state0, state1, multiple, count):
561
+ results = []
562
+ for _ in range(count):
563
+ state0, state1, output = xs128p(state0, state1)
564
+ import math
565
+ results.append(math.floor(multiple * to_double(output)))
566
+ return results
567
+ ```
568
+
569
+ **Usage (tool: d0nutptr/v8_rand_buster):**
570
+ ```bash
571
+ # Collect observed values, reverse them (LIFO cache order), pipe to solver
572
+ cat observed_codes.txt | tac | python3 xs128p.py --multiple 100000
573
+
574
+ # Generate predictions from recovered state
575
+ python3 xs128p.py --multiple 100000 --gen <state0>,<state1>,<count>
576
+ ```
577
+
578
+ **Key insight:** The LIFO cache means observed values are in reverse generation order — reverse them with `tac` before solving. The Z3 `QF_BV` (quantifier-free bitvector) theory efficiently handles the bitwise operations. Typically 5-10 consecutive outputs suffice for a unique solution.
579
+
580
+ **Common pitfalls:**
581
+ - Forgetting to reverse the observation order (cache is LIFO)
582
+ - Multiple browser tabs or web workers may have separate PRNG states
583
+ - Cache boundary (every 64 calls) can introduce discontinuities if observations span a refill
584
+
585
+ **Inverse xorshift128+ (backward prediction):** After recovering state, step the PRNG backward to predict values generated *before* the observed sequence. Essential when the target value was generated earlier than observations (e.g., predicting another user's 2FA code). (Midnight Flag 2026)
586
+
587
+ ```python
588
+ def undo_rshift_xor(val, shift):
589
+ """Invert val ^= (val >> shift)"""
590
+ result = val
591
+ for _ in range(3): # 3 iterations sufficient for 64-bit
592
+ result = val ^ (result >> shift)
593
+ return result & 0xFFFFFFFFFFFFFFFF
594
+
595
+ def undo_lshift_xor(val, shift):
596
+ """Invert val ^= (val << shift)"""
597
+ result = val
598
+ for _ in range(3):
599
+ result = val ^ ((result << shift) & 0xFFFFFFFFFFFFFFFF)
600
+ return result & 0xFFFFFFFFFFFFFFFF
601
+
602
+ def reverse_step(s0, s1):
603
+ """Run xs128p one step backward: (s0, s1) → (old_s0, old_s1)"""
604
+ old_s1 = s0
605
+ known = (s1 ^ s0 ^ ((s0 >> 26) & 0xFFFFFFFFFFFFFFFF)) & 0xFFFFFFFFFFFFFFFF
606
+ x = undo_rshift_xor(known, 17)
607
+ old_s0 = undo_lshift_xor(x, 23)
608
+ return old_s0, old_s1
609
+
610
+ # Usage: step backward N times from recovered state
611
+ for _ in range(N):
612
+ state0, state1 = reverse_step(state0, state1)
613
+ predicted = math.floor(CONST * to_double(state0))
614
+ ```
615
+
616
+ **When to use:** Web challenge where JavaScript generates predictable-looking random values (tokens, verification codes, game rolls) using `Math.random()`. Look for patterns like `Math.floor(N * Math.random())` or `Math.random().toString(36).substr(2)` in client-side or server-side Node.js code.
617
+
618
+ ---
619
+
620
+ ## Password Cracking Strategy
621
+
622
+ **Attack order for unknown passwords:**
623
+ 1. Common wordlists: `rockyou.txt`, `10k-common.txt`
624
+ 2. Theme-based wordlist (usernames, challenge keywords)
625
+ 3. Rules attack: wordlist + `best66.rule`, `dive.rule`
626
+ 4. Hybrid: `word + ?d?d?d?d` (word + 4 digits)
627
+ 5. Brute force: start at 4 chars, increase
628
+
629
+ **SHA256 with hex salt (VuwCTF 2025, Delicious Cooking):** Format `hash$hex_salt`. Salt must be hex-decoded before `SHA256(password + salt_bytes)`. Password often derivable from security questions (e.g., "fav movie + PIN" = "ratatouille0000"-"ratatouille9999").
630
+
631
+ **CTF password patterns:**
632
+ ```text
633
+ base_password + year → actnowonclimatechange2026
634
+ username + digits → nemo123, admin2026
635
+ theme + numbers → flag2026, ctf2025
636
+ leet speak → p@ssw0rd, s3cr3t
637
+ ```
638
+
639
+ **Hashcat modes reference:**
640
+ ```bash
641
+ # Common modes
642
+ -m 0 # MD5
643
+ -m 1000 # NTLM
644
+ -m 5600 # NTLMv2
645
+ -m 13600 # WinZip AES
646
+ -m 13000 # RAR5
647
+ -m 11600 # 7-Zip
648
+
649
+ # Attack modes
650
+ -a 0 # Dictionary
651
+ -a 3 # Brute force mask
652
+ -a 6 # Hybrid (word + mask)
653
+ -a 7 # Hybrid (mask + word)
654
+ ```
655
+
656
+ **When password relates to another in challenge:**
657
+ - Try variations: `password + year`, `password + 123`
658
+ - Try reversed: `drowssap`
659
+ - Try with common suffixes: `!`, `@`, `#`, `1`, `123`
660
+ - If SMB/FTP password known, ZIP password often related
661
+
662
+ ---
663
+
664
+ See [prng-attacks.md](prng-attacks.md) for CTF-era advanced attacks (2017+).