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,563 @@
1
+ # CTF Crypto - Modern Cipher Attacks (Continued)
2
+
3
+ Hash-based attacks, protocol-level exploits, ECB oracles, Rabin/RSA parity attacks, and specialized cipher weaknesses. For core AES/CBC/padding oracle techniques, see [modern-ciphers.md](modern-ciphers.md). For stream cipher attacks (LFSR, RC4, XOR), see [stream-ciphers.md](stream-ciphers.md).
4
+
5
+ ## Table of Contents
6
+ - [Blum-Goldwasser Bit-Extension Oracle (PlaidCTF 2013)](#blum-goldwasser-bit-extension-oracle-plaidctf-2013)
7
+ - [Hash Length Extension Attack (PlaidCTF 2014)](#hash-length-extension-attack-plaidctf-2014)
8
+ - [Compression Oracle / CRIME-Style Attack (BCTF 2015)](#compression-oracle--crime-style-attack-bctf-2015)
9
+ - [Hash Function Time Reversal via Cycle Detection (BSidesSF 2025)](#hash-function-time-reversal-via-cycle-detection-bsidessf-2025)
10
+ - [OFB Mode with Invertible RNG Backward Decryption (BSidesSF 2026)](#ofb-mode-with-invertible-rng-backward-decryption-bsidessf-2026)
11
+ - [Weak Key Derivation via Public Key Hash XOR (BSidesSF 2026)](#weak-key-derivation-via-public-key-hash-xor-bsidessf-2026)
12
+ - [HMAC-CRC Linearity Attack (Boston Key Party 2016)](#hmac-crc-linearity-attack-boston-key-party-2016)
13
+ - [DES Weak Keys in OFB Mode (Boston Key Party 2016)](#des-weak-keys-in-ofb-mode-boston-key-party-2016)
14
+ - [SRP (Secure Remote Password) Protocol Bypass via Modular Arithmetic (ASIS CTF Finals 2016)](#srp-secure-remote-password-protocol-bypass-via-modular-arithmetic-asis-ctf-finals-2016)
15
+ - [Modified AES S-Box Brute-Force Recovery (H4ckIT CTF 2016)](#modified-aes-s-box-brute-force-recovery-h4ckit-ctf-2016)
16
+ - [Square Attack on Reduced-Round AES (0CTF 2016)](#square-attack-on-reduced-round-aes-0ctf-2016)
17
+ - [AES-ECB Byte-at-a-Time Chosen Plaintext (ABCTF 2016)](#aes-ecb-byte-at-a-time-chosen-plaintext-abctf-2016)
18
+ - [AES-ECB Cut-and-Paste Block Manipulation (NDH Quals 2016)](#aes-ecb-cut-and-paste-block-manipulation-ndh-quals-2016)
19
+ - [AES-CBC IV Bit-Flip Authentication Bypass (Google CTF 2016)](#aes-cbc-iv-bit-flip-authentication-bypass-google-ctf-2016)
20
+ - [Rabin Cryptosystem LSB Parity Oracle (PlaidCTF 2016)](#rabin-cryptosystem-lsb-parity-oracle-plaidctf-2016)
21
+ - [PBKDF2 Pre-Hash Bypass for Long Passwords (BackdoorCTF 2016)](#pbkdf2-pre-hash-bypass-for-long-passwords-backdoorctf-2016)
22
+ - [MD5 Multi-Collision via Fastcol (BackdoorCTF 2016)](#md5-multi-collision-via-fastcol-backdoorctf-2016)
23
+ - [GHASH Key Recovery over Prime Modulus (nullcon HackIM 2019)](#ghash-key-recovery-over-prime-modulus-nullcon-hackim-2019)
24
+ - [SHA-1 Length Extension Plus AES-CBC Cookie Forgery (BSidesSF 2019)](#sha-1-length-extension-plus-aes-cbc-cookie-forgery-bsidessf-2019)
25
+
26
+ See [modern-ciphers-3.md](modern-ciphers-3.md) for custom hash reversal, CRC32 brute-force, noisy RSA oracle, sponge collisions, CBC IV forgery, padding oracle bit-flip, SPN S-box intersection, AES-CFB IV recovery, three-round XOR, Unicode side channel, SHA-256 basis attack, and HMAC key recovery.
27
+
28
+ ---
29
+
30
+ ## Blum-Goldwasser Bit-Extension Oracle (PlaidCTF 2013)
31
+
32
+ **Pattern:** Exploit a decryption oracle for Blum-Goldwasser-style encryption by extending ciphertext length by one bit per query to leak plaintext via parity.
33
+
34
+ **Key insight:** Extend ciphertext by one bit (L+1), shift ciphertext left (`c << 1`), and submit a modified `y` value. The oracle reveals the LSB (parity) of each decrypted chunk. The squaring sequence `y = pow(y, 2, N)` can be manipulated to produce valid extended ciphertexts the server hasn't seen.
35
+
36
+ ```python
37
+ # Iterative plaintext recovery via bit-extension
38
+ for i in range(msg_length):
39
+ extended_c = original_c << 1 # Shift ciphertext left by 1
40
+ new_y = pow(original_y, 2, N) # Advance squaring sequence
41
+ response = oracle(extended_c, new_y, msg_length + 1)
42
+ leaked_bit = response & 1 # LSB reveals one plaintext bit
43
+ plaintext_bits.append(leaked_bit)
44
+ original_y = new_y
45
+ ```
46
+
47
+ **When to use:** Blum-Goldwasser or BBS-based (Blum Blum Shub) encryption with a decryption oracle that accepts variable-length ciphertexts. The parity leak accumulates one bit per query.
48
+
49
+ ---
50
+
51
+ ## Hash Length Extension Attack (PlaidCTF 2014)
52
+
53
+ **Pattern:** Server computes `hash(SECRET || user_data)` using MD5, SHA-1, or SHA-256 (Merkle-Damgard constructions). Given a valid hash and the original data, extend it with arbitrary appended data and compute a valid hash — without knowing the secret.
54
+
55
+ ```bash
56
+ # Using HashPump (install: apt install hashpump)
57
+ hashpump --keylength 8 \
58
+ --signature 'ef16c2bffbcf0b7567217f292f9c2a9a50885e01e002fa34db34c0bb916ed5c3' \
59
+ --data 'original_data' \
60
+ --additional ';admin=true'
61
+ # Outputs: new_signature and new_data (with padding bytes)
62
+ ```
63
+
64
+ ```python
65
+ # Python: hashpumpy
66
+ import hashpumpy
67
+ new_hash, new_data = hashpumpy.hashpump(
68
+ original_hash, original_data, append_data, secret_length
69
+ )
70
+ ```
71
+
72
+ **Key insight:** Merkle-Damgard hashes (MD5, SHA-1, SHA-256) process data in blocks, and the hash output IS the internal state. Given `H(secret || msg)`, you can compute `H(secret || msg || padding || extension)` without knowing `secret` — just initialize the hash state from the known output and continue hashing. Only HMAC (`H(K XOR opad || H(K XOR ipad || msg))`) is immune. If the secret length is unknown, try lengths 1-32.
73
+
74
+ *See also [ctf-web/auth-infra.md — Hash Length Extension Attack (ASIS CTF 2017)](../ctf-web/auth-infra.md#hash-length-extension-attack-asis-ctf-2017) for the same primitive applied to a web auth token bypass.*
75
+
76
+ ---
77
+
78
+ ## Compression Oracle / CRIME-Style Attack (BCTF 2015)
79
+
80
+ **Pattern:** Server compresses plaintext (LZW, zlib, etc.) before encrypting. By observing ciphertext length changes with chosen plaintexts, leak the unknown plaintext character-by-character.
81
+
82
+ ```python
83
+ import base64
84
+
85
+ def oracle(plaintext):
86
+ """Send chosen plaintext, get ciphertext length."""
87
+ resp = send_to_server(plaintext)
88
+ return len(base64.b64decode(resp))
89
+
90
+ # Baseline: empty input
91
+ base_len = oracle("")
92
+
93
+ # Recover secret byte-by-byte
94
+ known = ""
95
+ for pos in range(secret_length):
96
+ for c in string.printable:
97
+ candidate = known + c
98
+ length = oracle(candidate)
99
+ if length <= base_len + len(known): # Compressed = match
100
+ known += c
101
+ break
102
+ ```
103
+
104
+ **Key insight:** Compression algorithms (LZW, DEFLATE, zlib) replace repeated sequences with back-references. If `SALT + user_input` is compressed before encryption, sending input that matches part of the salt produces shorter ciphertext (the match compresses). This is the same class as CRIME (TLS), BREACH (HTTP), and HEIST attacks. The oracle is ciphertext length.
105
+
106
+ ---
107
+
108
+ ## Hash Function Time Reversal via Cycle Detection (BSidesSF 2025)
109
+
110
+ When a system uses iterated hashing as a "time" function (`state_t = H(state_{t-1})`), reverse time by exploiting the finite cycle structure:
111
+
112
+ 1. **Detect cycle:** Use Floyd's tortoise-and-hare or Brent's algorithm to find cycle length L
113
+ 2. **Compute backward steps:** To go from time T to earlier time T_goal: iterate forward `(L - (T - T_goal)) % L` steps
114
+
115
+ ```python
116
+ import hashlib
117
+
118
+ def hash_step(state):
119
+ return hashlib.md5(state).digest()[:8] # Truncated hash
120
+
121
+ def find_cycle(start):
122
+ """Brent's cycle detection: returns (cycle_length, start_of_cycle)"""
123
+ power = lam = 1
124
+ tortoise = start
125
+ hare = hash_step(start)
126
+ while tortoise != hare:
127
+ if power == lam:
128
+ tortoise = hare
129
+ power *= 2
130
+ lam = 0
131
+ hare = hash_step(hare)
132
+ lam += 1
133
+ # lam = cycle length; find cycle start
134
+ tortoise = hare = start
135
+ for _ in range(lam):
136
+ hare = hash_step(hare)
137
+ mu = 0
138
+ while tortoise != hare:
139
+ tortoise = hash_step(tortoise)
140
+ hare = hash_step(hare)
141
+ mu += 1
142
+ return lam, mu # cycle_length, cycle_start_offset
143
+
144
+ # Reverse from T_known to T_goal
145
+ cycle_len, _ = find_cycle(known_state)
146
+ forward_steps = (cycle_len - (t_known - t_goal)) % cycle_len
147
+ state = known_state
148
+ for _ in range(forward_steps):
149
+ state = hash_step(state)
150
+ # state is now the value at t_goal
151
+ ```
152
+
153
+ **Key insight:** For truncated hashes (e.g., MD5 -> 64 bits), the expected cycle length is ~2^32, making cycle detection feasible. Going "backward" N steps is equivalent to going forward (cycle_length - N) steps. Assumes the target state is within the main cycle, not on a tail.
154
+
155
+ ---
156
+
157
+ ## OFB Mode with Invertible RNG Backward Decryption (BSidesSF 2026)
158
+
159
+ **Pattern (randcrypt):** A custom block cipher uses OFB (Output Feedback) mode with a homemade RNG as the keystream generator. The last plaintext block is known (zero padding), leaking one RNG state. If the RNG's state transition function is invertible (bijective), all previous states can be recovered by running the RNG backwards, decrypting the entire ciphertext from the end to the beginning.
160
+
161
+ ```python
162
+ def rng_forward(state):
163
+ """Custom RNG state transition (from challenge)."""
164
+ # Example: linear congruential or reversible mixing
165
+ return (state * A + B) % M
166
+
167
+ def rng_inverse(state):
168
+ """Inverted RNG — recover previous state."""
169
+ return ((state - B) * pow(A, -1, M)) % M
170
+
171
+ # Last block is zero-padded → ciphertext XOR 0 = keystream = RNG state
172
+ leaked_state = int.from_bytes(ciphertext_blocks[-2], 'big')
173
+
174
+ # Decrypt backwards
175
+ state = leaked_state
176
+ plaintext_blocks = []
177
+ for i in range(len(ciphertext_blocks) - 3, -1, -1):
178
+ state = rng_inverse(state)
179
+ pt = xor_bytes(ciphertext_blocks[i], state.to_bytes(block_size, 'big'))
180
+ plaintext_blocks.insert(0, pt)
181
+ ```
182
+
183
+ **Key insight:** OFB mode decouples encryption from the plaintext — the keystream is deterministic from the initial state. If ANY block's plaintext is known (padding, headers, magic bytes), the corresponding RNG state is leaked. An invertible RNG then reveals ALL states. Always check if the RNG transition function has a mathematical inverse.
184
+
185
+ **When to recognize:** Custom OFB/CTR mode with a non-standard PRNG. Look for: (1) XOR-based encryption, (2) a state-update function that's bijective (no information loss), (3) predictable plaintext in any block position. Files with known padding (PKCS#7 zero-fill, null-terminated strings) are ideal leak points.
186
+
187
+ ---
188
+
189
+ ## Weak Key Derivation via Public Key Hash XOR (BSidesSF 2026)
190
+
191
+ **Pattern (ran-somewhere):** Hybrid RSA+AES encryption where the AES key is derived as `SHA256(DER_encoded_public_key) XOR seed`, with the seed hardcoded or predictable. Since the public key is public, the AES key is fully recoverable without the RSA private key.
192
+
193
+ ```python
194
+ from Crypto.PublicKey import RSA
195
+ from Crypto.Cipher import AES
196
+ from hashlib import sha256
197
+
198
+ # Public key is available
199
+ pubkey = RSA.import_key(open("public.pem").read())
200
+ der_bytes = pubkey.export_key("DER")
201
+
202
+ # Seed from challenge (hardcoded/predictable)
203
+ seed = b'BSidesSFCTF2026!'
204
+
205
+ # Derive AES key the same way the encryptor did
206
+ key_hash = sha256(der_bytes).digest()
207
+ aes_key = bytes(a ^ b for a, b in zip(key_hash, seed.ljust(32, b'\x00')))
208
+
209
+ # Decrypt
210
+ ct = open("flag.enc", "rb").read()
211
+ iv, ct_body = ct[:16], ct[16:]
212
+ cipher = AES.new(aes_key, AES.MODE_CBC, iv)
213
+ plaintext = cipher.decrypt(ct_body)
214
+ ```
215
+
216
+ **Key insight:** Key derivation that incorporates only public information (public keys, known constants) provides zero security regardless of the hash function used. The "hybrid" design creates a false sense of security — RSA protects nothing if the AES key doesn't depend on the RSA private key.
217
+
218
+ **When to recognize:** Challenge provides both a public key AND an encrypted file, but no private key or ciphertext for RSA. Look for key derivation code that hashes the public key, uses the public key's modulus/exponent as seed material, or XORs with a constant.
219
+
220
+ ---
221
+
222
+ ## HMAC-CRC Linearity Attack (Boston Key Party 2016)
223
+
224
+ **Pattern:** HMAC constructed with CRC as the hash function is completely broken because CRC is linear over GF(2). The key is directly recoverable from a single message-MAC pair via polynomial arithmetic over GF(2^64).
225
+
226
+ ```python
227
+ # CRC is linear: CRC(a XOR b) = CRC(a) XOR CRC(b)
228
+ # HMAC-CRC(key, msg) = CRC(key_opad || CRC(key_ipad || msg))
229
+ # Rewrite as polynomial in GF(2): K = known_terms * inverse(x^(128+M) + x^128) mod CRC_POLY
230
+ ```
231
+
232
+ **Key insight:** CRC's linearity over GF(2) means HMAC-CRC provides zero security. Always verify the underlying hash function is non-linear before trusting HMAC.
233
+
234
+ ---
235
+
236
+ ## DES Weak Keys in OFB Mode (Boston Key Party 2016)
237
+
238
+ **Pattern:** DES has 4 weak keys where `E(E(P,K),K) = P` (encryption is self-inverse). In OFB (Output Feedback) mode this causes the keystream to cycle with period 2: even blocks XOR with IV, odd blocks with E(IV,K). Reduces to a 16-byte repeating XOR key.
239
+
240
+ ```python
241
+ # DES weak keys: 0x0000000000000000, 0xFFFFFFFFFFFFFFFF,
242
+ # 0xE1E1E1E1F0F0F0F0, 0x1E1E1E1E0F0F0F0F
243
+ # OFB with weak key: keystream = [IV, E(IV,K), IV, E(IV,K), ...]
244
+ # Recovery: try all 4 weak keys; or treat as 16-byte repeating XOR
245
+ ```
246
+
247
+ **Key insight:** DES weak keys cause OFB keystream to cycle with period 2. When you see DES+OFB, always try the 4 weak keys first.
248
+
249
+ ---
250
+
251
+ ## Square Attack on Reduced-Round AES (0CTF 2016)
252
+
253
+ **Pattern:** 4-round AES is vulnerable to the square (integral) attack. Choose 256 plaintexts differing in one byte (a "lambda set"). After 3 rounds, the XOR sum at any byte position equals 0. Guess one byte of the last round key and partially decrypt -- if XOR sum is 0, the guess is correct.
254
+
255
+ ```python
256
+ # For each byte position in the last round key:
257
+ for candidate in range(256):
258
+ xor_sum = 0
259
+ for ct in ciphertexts:
260
+ xor_sum ^= inv_sub_bytes(ct[pos] ^ candidate)
261
+ if xor_sum == 0:
262
+ key_byte = candidate # correct guess
263
+ # Reduces 2^128 key recovery to ~16 * 256 = 4096 operations
264
+ ```
265
+
266
+ **Key insight:** Integral cryptanalysis exploits the "balanced" property (XOR-sum = 0) that propagates through AES rounds. Effective against 4-round AES; 5+ rounds require more sophisticated variants.
267
+
268
+ ---
269
+
270
+ ## SRP (Secure Remote Password) Protocol Bypass via Modular Arithmetic (ASIS CTF Finals 2016)
271
+
272
+ SRP implementations that only check `A != 0` and `A != N` can be bypassed by sending `A = 2*N`, causing the server to compute a zero session key.
273
+
274
+ ```python
275
+ from hashlib import sha256
276
+ import hmac
277
+
278
+ # SRP protocol: server computes session key from A (client's public value)
279
+ # S = (A * v^u) ^ b mod N
280
+ # If A = 2*N: S = (2*N * v^u) ^ b mod N = 0 (since 2*N mod N = 0)
281
+
282
+ N = server_modulus
283
+ # Send A = 2*N (bypasses checks for A != 0 and A != N)
284
+ A_malicious = 2 * N
285
+
286
+ # Server computes S = 0, so session key K = SHA256(0)
287
+ K = sha256(b'\x00').digest()
288
+
289
+ # Now compute valid HMAC proof with known K
290
+ proof = hmac.new(K, salt, sha256).hexdigest()
291
+ ```
292
+
293
+ **Key insight:** SRP implementations must validate `A % N != 0`, not just `A != 0` and `A != N`. Sending `A = k*N` for any integer k forces the shared secret to zero, allowing authentication without knowing the password.
294
+
295
+ ---
296
+
297
+ ## Modified AES S-Box Brute-Force Recovery (H4ckIT CTF 2016)
298
+
299
+ AES implementation with a custom S-Box created by swapping 3 elements of the standard S-Box. Brute-force all C(256,3) * 2 = 5,527,040 possible permutations.
300
+
301
+ ```cpp
302
+ // Three elements swapped from standard AES S-Box
303
+ // Total permutations: C(256,3) * 2 = ~5.5 million (feasible to brute-force)
304
+ #include <openssl/aes.h>
305
+
306
+ void bruteforce_sbox(uint8_t ciphertext[], uint8_t key[], int ct_len) {
307
+ uint8_t standard_sbox[256]; // standard AES S-Box
308
+ // Try all 3-element swaps
309
+ for (int i = 0; i < 256; i++)
310
+ for (int j = i+1; j < 256; j++)
311
+ for (int k = j+1; k < 256; k++) {
312
+ // Swap pairs: (i,j), (i,k), (j,k)
313
+ uint8_t sbox[256];
314
+ memcpy(sbox, standard_sbox, 256);
315
+ swap(sbox[i], sbox[j]); // try each 2-element swap from the triple
316
+ // Decrypt and check for valid plaintext
317
+ if (try_decrypt_with_sbox(sbox, ciphertext, key, ct_len))
318
+ return; // found it
319
+ }
320
+ }
321
+ ```
322
+
323
+ **Key insight:** When a custom AES S-Box differs from standard by only a few element swaps, the search space is small enough to brute-force. For 3 swapped elements: C(256,3) permutation groups times the swap combinations within each group.
324
+
325
+ ---
326
+
327
+ ## AES-ECB Byte-at-a-Time Chosen Plaintext (ABCTF 2016)
328
+
329
+ **Pattern (Encryption Service):** Server encrypts `user_input || secret_suffix` under AES-ECB. Recover the secret suffix one byte at a time by controlling the input length.
330
+
331
+ 1. Send inputs of decreasing length to push one unknown byte into a known block position
332
+ 2. For each position, try all 256 byte values and compare the encrypted block:
333
+
334
+ ```python
335
+ from pwn import *
336
+ import cryptanalib as ca # FeatherDuster's cryptanalib
337
+
338
+ def oracle(pt):
339
+ """Send plaintext, receive ECB-encrypted ciphertext."""
340
+ r = remote('target', 7765)
341
+ r.recvuntil('Send me some hex-encoded data to encrypt:\n')
342
+ r.sendline(pt.hex())
343
+ r.recvuntil('Here you go:')
344
+ ct = bytes.fromhex(r.recvline().strip().decode())
345
+ r.close()
346
+ return ct
347
+
348
+ # Automated byte-at-a-time recovery
349
+ flag = ca.ecb_cpa_decrypt(oracle, block_size=16, verbose=True)
350
+ print(flag)
351
+ ```
352
+
353
+ **Manual approach without library:**
354
+ ```python
355
+ block_size = 16
356
+ known = b''
357
+
358
+ for i in range(len(secret)):
359
+ # Pad so next unknown byte is at end of a block
360
+ pad_len = block_size - 1 - (len(known) % block_size)
361
+ pad = b'A' * pad_len
362
+
363
+ # Get target block
364
+ target_ct = oracle(pad)
365
+ target_block_idx = (pad_len + len(known)) // block_size
366
+ target_block = target_ct[target_block_idx*16:(target_block_idx+1)*16]
367
+
368
+ # Try all 256 byte values
369
+ for byte_val in range(256):
370
+ test = pad + known + bytes([byte_val])
371
+ test_ct = oracle(test)
372
+ if test_ct[target_block_idx*16:(target_block_idx+1)*16] == target_block:
373
+ known += bytes([byte_val])
374
+ break
375
+ ```
376
+
377
+ **Key insight:** ECB mode encrypts identical plaintext blocks to identical ciphertext blocks. By controlling the prefix length, the attacker shifts one unknown byte at a time to a position where it completes a known block prefix. Comparing the target ciphertext block against all 256 possibilities recovers each byte in at most 256 queries. Total queries: ~256 * secret_length. Tool: FeatherDuster's `cryptanalib.ecb_cpa_decrypt()` automates this completely.
378
+
379
+ ---
380
+
381
+ ## AES-ECB Cut-and-Paste Block Manipulation (NDH Quals 2016)
382
+
383
+ **Pattern (Toil33t):** Server encrypts JSON session data in AES-ECB mode. Fields like `is_admin: false` span predictable block boundaries. Construct chosen plaintext blocks via registration, then splice ciphertext blocks to change `false` to `true`.
384
+
385
+ 1. Detect ECB mode: register with repeating username (e.g., 'A' * 64), look for identical ciphertext blocks
386
+ 2. Map block boundaries by varying username length until block count changes
387
+ 3. Determine field ordering by independently varying username and email lengths
388
+ 4. Craft target block containing `true` by aligning it at a block boundary via padding:
389
+
390
+ ```python
391
+ # Align "true" at start of a block using space padding (JSON ignores whitespace)
392
+ # Original: {"username": "AA", "is_admin": false, "email": ""}
393
+ # Target: {"username": "AA", "is_admin": true, "email": ""}
394
+ # ^-- 16-byte block boundary
395
+
396
+ # Get the " true" block from:
397
+ username = "AAA" + " " * 12 + "true"
398
+ # Extract block 2 of the resulting ciphertext
399
+
400
+ # Get prefix blocks from a short username
401
+ # Get suffix block from a padded username
402
+ # Concatenate: prefix_blocks + true_block + suffix_block
403
+ ```
404
+
405
+ **Key insight:** AES-ECB encrypts each 16-byte block independently with no chaining. Identical plaintext blocks produce identical ciphertext blocks, allowing block-level cut-and-paste. JSON's tolerance for extra whitespace enables block alignment without breaking parsing. The attack requires: (a) detecting ECB via repeated blocks, (b) mapping field layout via length probing, (c) crafting and splicing blocks.
406
+
407
+ ---
408
+
409
+ ## AES-CBC IV Bit-Flip Authentication Bypass (Google CTF 2016)
410
+
411
+ **Pattern (Eucalypt Forest):** Server encrypts JSON session blob under AES-CBC and returns both IV and ciphertext as a cookie. No integrity check (no MAC/HMAC). Flip bits in the IV to change the first plaintext block.
412
+
413
+ 1. Register with username one bit away from target (e.g., `` `dmin `` instead of `admin` — flip LSB of 'a')
414
+ 2. Identify the IV byte position corresponding to the target character in the first block
415
+ 3. Flip the same bit in the IV byte — XOR propagates directly to the plaintext:
416
+
417
+ ```python
418
+ import binascii
419
+ cookie = binascii.unhexlify(auth_cookie)
420
+ iv = bytearray(cookie[:16])
421
+ ciphertext = cookie[16:]
422
+
423
+ # Flip LSB of byte at position where 'a'/'`' appears in first block
424
+ # Position depends on JSON structure: {"username":"`dmin"}
425
+ # 'a' (0x61) vs '`' (0x60) differ only in bit 0
426
+ target_pos = 13 # position of first char of username in block
427
+ iv[target_pos] ^= 0x01
428
+
429
+ forged = binascii.hexlify(bytes(iv) + ciphertext)
430
+ ```
431
+
432
+ **Key insight:** AES-CBC decryption XORs the previous ciphertext block (or IV for block 0) with the AES-decrypted block. Flipping bit `i` in the IV flips bit `i` in the first plaintext block with no other side effects. This only works when the server performs no integrity verification (no HMAC, AEAD, or authenticated encryption).
433
+
434
+ ---
435
+
436
+ ## Rabin Cryptosystem LSB Parity Oracle (PlaidCTF 2016)
437
+
438
+ **Pattern (rabit):** Server encrypts flag with the Rabin cryptosystem (`c = m^2 mod n`) and provides an LSB oracle — for any ciphertext, it returns the least significant bit of the decrypted plaintext. Binary search recovers the full plaintext in `log2(n)` queries.
439
+
440
+ ```python
441
+ from Crypto.Util.number import long_to_bytes
442
+
443
+ def lsb_oracle_attack(enc_flag, N, oracle_fn):
444
+ """Recover plaintext from Rabin/RSA LSB oracle via binary search."""
445
+ lower = 0
446
+ upper = N
447
+ C = enc_flag
448
+ # Rabin: encrypt(2,N) = 4; multiplying ciphertext by 4 doubles plaintext
449
+ e2 = pow(2, 2, N) # For Rabin; use pow(2, e, N) for RSA
450
+
451
+ for i in range(N.bit_length()):
452
+ C = (e2 * C) % N # Multiply plaintext by 2
453
+ lsb = oracle_fn(C)
454
+ if lsb == 1:
455
+ # 2*m > N (odd remainder after mod), increase lower bound
456
+ lower = (upper + lower) // 2
457
+ else:
458
+ # 2*m < N (even remainder), decrease upper bound
459
+ upper = (upper + lower) // 2
460
+ # Progressive decryption visible:
461
+ print(long_to_bytes(upper))
462
+ return upper
463
+ ```
464
+
465
+ **Key insight:** Rabin (and textbook RSA) are multiplicatively homomorphic: multiplying ciphertext by `2^e mod N` doubles the plaintext mod N. Since N is odd, doubling causes a modular wraparound iff the plaintext exceeds `N/2`, which changes the LSB parity. This creates a binary search: each oracle query halves the candidate range, recovering the full plaintext in exactly `log2(N)` queries (~1024 for RSA-1024).
466
+
467
+ ---
468
+
469
+ ## PBKDF2 Pre-Hash Bypass for Long Passwords (BackdoorCTF 2016)
470
+
471
+ **Pattern (Mindblown):** PBKDF2 (and HMAC generally) pre-hashes passwords longer than the hash block size (64 bytes for SHA-1/SHA-256). If the target password exceeds 64 bytes, `PBKDF2(password)` equals `PBKDF2(SHA1(password))`, enabling authentication with the hash instead of the original password.
472
+
473
+ ```python
474
+ import hashlib
475
+
476
+ original_password = "complexPasswordWhichContainsManyCharactersWithRandomSuffixeghjrjg"
477
+ # len > 64, so HMAC pre-hashes it
478
+ equivalent = hashlib.sha1(original_password.encode()).digest()
479
+ # Login with equivalent — PBKDF2 produces the same derived key
480
+ ```
481
+
482
+ **Key insight:** HMAC's inner construction is `H((K XOR ipad) || message)`. When the key (password) exceeds the hash block size, HMAC first reduces it via `K = H(password)`. This means `HMAC(long_password, ...)` equals `HMAC(H(long_password), ...)`. Any system using PBKDF2/HMAC with a `!==` identity check after hash comparison is vulnerable when passwords exceed 64 bytes. This is a HMAC specification behavior, not an implementation bug.
483
+
484
+ ---
485
+
486
+ ## MD5 Multi-Collision via Fastcol (BackdoorCTF 2016)
487
+
488
+ **Pattern (Forge):** Generate 2^k files with identical MD5 hashes by chaining `fastcol` (Marc Stevens' tool). Each run produces two suffixes (A, B) that when appended yield the same MD5. Chain 3 runs to produce 8 collisions:
489
+
490
+ ```text
491
+ [prefix][suffix1A][suffix2A][suffix3A] \
492
+ [prefix][suffix1A][suffix2A][suffix3B] |
493
+ [prefix][suffix1A][suffix2B][suffix3A] |-- all have same MD5
494
+ [prefix][suffix1A][suffix2B][suffix3B] |
495
+ [prefix][suffix1B][suffix2A][suffix3A] |
496
+ [prefix][suffix1B][suffix2B][suffix3B] /
497
+ ```
498
+
499
+ ```bash
500
+ # Install: git clone https://github.com/cr-marcstevens/hashclash
501
+ # Generate one collision pair (~minutes on modern CPU):
502
+ ./fastcol -o suffix1A.bin suffix1B.bin < prefix.bin
503
+ # Chain: append suffix1A to prefix, run fastcol again for suffix2A/2B, etc.
504
+ ```
505
+
506
+ **Key insight:** MD5 collision generation is practical with `fastcol` (~minutes per pair). Because MD5 uses Merkle-Damgard construction, collisions compose: if `H(A||X) == H(A||Y)`, then `H(A||X||Z) == H(A||Y||Z)` for any suffix Z. Chaining k collision pairs produces 2^k files with identical MD5. For CRC32 collisions, append bytes after PNG IEND chunk (parsers ignore trailing data) and brute-force the 4-byte CRC adjustment.
507
+
508
+ ---
509
+
510
+ ## GHASH Key Recovery over Prime Modulus (nullcon HackIM 2019)
511
+
512
+ **Pattern (GenuineCounterMode):** A custom GCM-like scheme computes `tag = c + sum(b_i * H^(i+1)) mod n` where `n` is a 128-bit prime (not `GF(2^128)`). The 12-byte nonce has 10 bytes fixed from the session ID plus 2 random bytes, so nonce collisions arrive in roughly 256 encryption queries (birthday bound). With two colliding nonces, the equations for `tag1` and `tag2` share the same `c = E_K(nonce || counter)`, so subtracting eliminates `c` and leaves a linear equation in `H` modulo prime `n` — solvable by a single modular inverse, not `GF(2^128)` polynomial factoring.
513
+
514
+ ```python
515
+ from Crypto.Util.number import bytes_to_long, long_to_bytes, inverse
516
+
517
+ n = 327989969870981036659934487747327553919 # prime modulus (not GF(2^128))
518
+
519
+ # 1. Request encryptions with single-block messages until two share a nonce
520
+ # 2. With colliding (nonce, ct1, tag1) and (nonce, ct2, tag2):
521
+ m1 = bytes_to_long(ct1) # single 16-byte block
522
+ m2 = bytes_to_long(ct2)
523
+ t1 = bytes_to_long(tag1)
524
+ t2 = bytes_to_long(tag2)
525
+ H = ((t1 - t2) * inverse(m1 - m2, n)) % n
526
+
527
+ # 3. Forge: encrypt "may i please have the galf", flip CTR bytes to 'flag'
528
+ # then recompute tag using recovered H and c = tag - sum(b_i * H^(i+1))
529
+ c0 = (t1 - sum(bytes_to_long(b) * pow(H, i + 1, n) for i, b in enumerate(blocks1))) % n
530
+ forged_tag = (c0 + sum(bytes_to_long(b) * pow(H, i + 1, n) for i, b in enumerate(forged_blocks))) % n
531
+ ```
532
+
533
+ **Key insight:** GCM's security rests on `GHASH` operating over `GF(2^128)` where inversion is hard without the key. Swapping the modulus to a plain prime `n` collapses the authentication to textbook linear algebra mod `n` — two nonce-colliding tags give one linear equation per unknown, solved with `inverse(m1 - m2, n)`. Short-nonce (2 random bytes) designs guarantee birthday collisions in ~256 queries. Contrast with the `GF(2^128)` AES-GCM forbidden attack in [modern-ciphers.md](modern-ciphers.md#aes-gcm-nonce-reuse--forbidden-attack), which needs polynomial factoring over binary fields.
534
+
535
+ ---
536
+
537
+ ## SHA-1 Length Extension Plus AES-CBC Cookie Forgery (BSidesSF 2019)
538
+
539
+ **Pattern (decrypto):** Cookie stores `user = iv || AES-CBC(key, plaintext)` plus a separate `signature = SHA1(secret || decrypt(ct))` tag. The session cookie leaks the AES key (e.g. trailing 32 bytes of a base64 session blob). To forge `UID 0`, length-extend the signature with `\nUID 0\n`, decrypt the current ciphertext to learn the plaintext, append hashpump's padding + extension, re-encrypt with the known key, and send both updated `user` and `signature`.
540
+
541
+ ```python
542
+ import hashpumpy, binascii, base64, urllib
543
+ from Crypto.Cipher import AES
544
+
545
+ # Extract AES key from leaked rack.session cookie
546
+ key = base64.b64decode(urllib.unquote(cookies['rack.session'].split('--')[0]))[-32:]
547
+ user = binascii.unhexlify(cookies['user'])
548
+ iv, ct = user[:16], user[16:]
549
+
550
+ def decrypt(c): return AES.new(key, AES.MODE_CBC, iv).decrypt(c).rstrip(b'\x10\x0f\x0e...')
551
+ def encrypt(p): pad = 16 - len(p) % 16; return AES.new(key, AES.MODE_CBC, iv).encrypt(p + bytes([pad])*pad)
552
+
553
+ # Length-extend signature (secret length guessed = 8)
554
+ new_sig, new_plain = hashpumpy.hashpump(cookies['signature'], decrypt(ct), b'\nUID 0\n', 8)
555
+ cookies['signature'] = new_sig
556
+ cookies['user'] = binascii.hexlify(iv + encrypt(new_plain))
557
+ ```
558
+
559
+ **Key insight:** Hash length extension applies whenever the MAC is `H(secret || data)` over a Merkle-Damgard hash (MD5/SHA-1/SHA-256). If the *same* `data` also lives inside a *separately* keyed cipher whose key is recoverable, you can combine primitives: hashpumpy produces the extended plaintext and new tag, then you re-encrypt with the leaked AES key so the server's CBC decryption matches the extended string the signature now covers. Parse-order quirks (later fields overriding earlier ones) let the appended `\nUID 0\n` win.
560
+
561
+ ---
562
+
563
+ See [modern-ciphers-3.md](modern-ciphers-3.md) for custom hash reversal, CRC32 brute-force, noisy RSA LSB oracle, sponge collisions, CBC IV forgery + block truncation, padding oracle + bit-flip command injection, SPN S-box intersection, AES-CFB IV recovery, three-round XOR, Unicode decode side channel, SHA-256 basis attack, MAC forgery via XOR block cancellation, and bit-by-bit HMAC key recovery.