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,649 @@
1
+ # CTF Crypto - Modern Cipher Attacks
2
+
3
+ Block cipher attacks, MAC forgery, padding oracles, and authenticated encryption. For hash/signature attacks (hash extension, PBKDF2, MD5 collision, Rabin, ECB oracles), see [modern-ciphers-2.md](modern-ciphers-2.md). For stream cipher attacks (LFSR, RC4, XOR), see [stream-ciphers.md](stream-ciphers.md).
4
+
5
+ ## Table of Contents
6
+ - [AES-CFB-8 Static IV State Forging](#aes-cfb-8-static-iv-state-forging)
7
+ - [ECB Pattern Leakage on Images](#ecb-pattern-leakage-on-images)
8
+ - [Padding Oracle Attack](#padding-oracle-attack)
9
+ - [CBC-MAC vs OFB-MAC Vulnerability](#cbc-mac-vs-ofb-mac-vulnerability)
10
+ - [Non-Permutation S-box Collision Attack](#non-permutation-s-box-collision-attack)
11
+ - [LCG Partial Output Recovery (0xFun 2026)](#lcg-partial-output-recovery-0xfun-2026)
12
+ - [Weak Hash Functions / GF(2) Gaussian Elimination](#weak-hash-functions--gf2-gaussian-elimination)
13
+ - [Affine Cipher over Composite Modulus (Nullcon 2026)](#affine-cipher-over-composite-modulus-nullcon-2026)
14
+ - [AES-GCM with Derived Keys (EHAX 2026)](#aes-gcm-with-derived-keys-ehax-2026)
15
+ - [AES-GCM Nonce Reuse / Forbidden Attack](#aes-gcm-nonce-reuse--forbidden-attack)
16
+ - [Ascon-like Reduced-Round Differential Cryptanalysis (srdnlenCTF 2026)](#ascon-like-reduced-round-differential-cryptanalysis-srdnlenctf-2026)
17
+ - [Custom Linear MAC Forgery (Nullcon 2026)](#custom-linear-mac-forgery-nullcon-2026)
18
+ - [CBC Padding Oracle Attack](#cbc-padding-oracle-attack)
19
+ - [Bleichenbacher / PKCS#1 v1.5 RSA Padding Oracle](#bleichenbacher--pkcs1-v15-rsa-padding-oracle)
20
+ - [Birthday Attack / Meet-in-the-Middle](#birthday-attack--meet-in-the-middle)
21
+ - [CRC32 Collision-Based Signature Forgery (iCTF 2013)](#crc32-collision-based-signature-forgery-ictf-2013)
22
+ - [AES Key Recovery via Byte-by-Byte Zeroing Oracle (CONFidence CTF 2017)](#aes-key-recovery-via-byte-by-byte-zeroing-oracle-confidence-ctf-2017)
23
+ - [AES-CTR Constant Counter / Repeating Keystream (SHA2017)](#aes-ctr-constant-counter--repeating-keystream-sha2017)
24
+ - [Custom SPN Column-Wise XOR Brute-Force (Hack Dat Kiwi 2017)](#custom-spn-column-wise-xor-brute-force-hack-dat-kiwi-2017)
25
+ - [AES-CTR Bitflip + CRC Linearity Signature Forgery (hxp CTF 2017)](#aes-ctr-bitflip--crc-linearity-signature-forgery-hxp-ctf-2017)
26
+ - [AES-CBC Ciphertext Forging via Error-Message Decryption Oracle (Nuit du Hack CTF 2018)](#aes-cbc-ciphertext-forging-via-error-message-decryption-oracle-nuit-du-hack-ctf-2018)
27
+ - [SHA-1 Chosen-Prefix Collision for PDF Signature Forgery (DEF CON Quals 2018)](#sha-1-chosen-prefix-collision-for-pdf-signature-forgery-def-con-quals-2018)
28
+ - [Hash Chain Preimage Authentication Bypass (picoCTF 2017)](#hash-chain-preimage-authentication-bypass-picoctf-2017)
29
+ - [AES-CBC Nonce Strip via Block Boundary Alignment (Trend Micro 2018)](#aes-cbc-nonce-strip-via-block-boundary-alignment-trend-micro-2018)
30
+
31
+ See also [modern-ciphers-2.md](modern-ciphers-2.md) for CRC32 forgery, Blum-Goldwasser, hash length extension, compression oracle, hash time reversal, OFB invertible RNG, weak key derivation, HMAC-CRC, DES weak keys, SRP bypass, modified AES S-Box, square attack, AES-ECB byte-at-a-time, AES-ECB cut-and-paste, AES-CBC IV bit-flip, Rabin LSB parity oracle, PBKDF2 pre-hash bypass, MD5 multi-collision, custom hash state reversal, and CRC32 brute-force.
32
+
33
+ ---
34
+
35
+ ## AES-CFB-8 Static IV State Forging
36
+
37
+ **Pattern (Cleverly Forging Breaks):** AES-CFB with 8-bit feedback and reused IV allows state reconstruction.
38
+
39
+ **Key insight:** After encrypting 16 known bytes, the AES internal shift register state is fully determined by those ciphertext bytes. Forge new ciphertexts by continuing encryption from known state.
40
+
41
+ ---
42
+
43
+ ## ECB Pattern Leakage on Images
44
+
45
+ **Pattern (Electronic Christmas Book):** AES-ECB on BMP/image data preserves visual patterns.
46
+
47
+ **Exploitation:** Identical plaintext blocks produce identical ciphertext blocks, revealing image structure even when encrypted. Rearrange or identify patterns visually.
48
+
49
+ ---
50
+
51
+ ## Padding Oracle Attack
52
+
53
+ **Pattern (The Seer):** Server reveals whether decrypted padding is valid.
54
+
55
+ **Byte-by-byte decryption:**
56
+ ```python
57
+ def decrypt_byte(block, prev_block, position, oracle, known):
58
+ """known = bytearray(16) tracking recovered intermediate bytes for this block."""
59
+ for guess in range(256):
60
+ modified = bytearray(prev_block)
61
+ # Set known bytes to produce valid padding
62
+ pad_value = 16 - position
63
+ for j in range(position + 1, 16):
64
+ modified[j] = known[j] ^ pad_value
65
+ modified[position] = guess
66
+ if oracle(bytes(modified) + block):
67
+ return guess ^ pad_value
68
+ ```
69
+
70
+ ---
71
+
72
+ ## CBC-MAC vs OFB-MAC Vulnerability
73
+
74
+ OFB mode creates a keystream that can be XORed for signature forgery.
75
+
76
+ **Attack:** If you have signature for known plaintext P1, forge for P2:
77
+ ```text
78
+ new_sig = known_sig XOR block2_of_P1 XOR block2_of_P2
79
+ ```
80
+
81
+ **Important:** Don't forget PKCS#7 padding in calculations! Small bruteforce space? Just try all combinations (e.g., 100 for 2 unknown digits).
82
+
83
+ **Key insight:** OFB-MAC generates a keystream independent of the plaintext, so knowing one (message, MAC) pair lets you forge MACs for arbitrary messages by XORing the known plaintext blocks out and XORing the new ones in. CBC-MAC does not have this weakness because each block's encryption depends on the previous ciphertext block.
84
+
85
+ ---
86
+
87
+ ## Non-Permutation S-box Collision Attack
88
+
89
+ **Pattern (Tetraes, Nullcon 2026):** Custom AES-like cipher with S-box collisions.
90
+
91
+ **Detection:** `len(set(sbox)) < 256` means collisions exist. Find collision pairs and their XOR delta.
92
+
93
+ **Attack:** For each key byte, try 256 plaintexts differing by delta. When `ct1 == ct2`, S-box input was in collision set. 2-way ambiguity per byte, 2^16 brute-force. Total: 4,097 oracle queries.
94
+
95
+ See [advanced-math.md](advanced-math.md) for full S-box collision analysis code.
96
+
97
+ ---
98
+
99
+ ## LCG Partial Output Recovery (0xFun 2026)
100
+
101
+ **Known parameters:** If LCG (Linear Congruential Generator) constants (M, A, C) are known and output is `state mod N`, iterate by N through modulus to find state:
102
+ ```python
103
+ # output = state % N, state = (A * prev + C) % M
104
+ for candidate in range(output, M, N):
105
+ # Check if candidate is consistent with next output
106
+ next_state = (A * candidate + C) % M
107
+ if next_state % N == next_output:
108
+ print(f"State: {candidate}")
109
+ ```
110
+
111
+ **Upper bits only (e.g., upper 32 of 64):** Brute-force lower 32 bits:
112
+ ```python
113
+ for low in range(2**32):
114
+ state = (observed_upper << 32) | low
115
+ next_state = (A * state + C) % M
116
+ if (next_state >> 32) == next_observed_upper:
117
+ print(f"Full state: {state}")
118
+ ```
119
+
120
+ **Key insight:** LCG output truncation (modulo or upper bits only) hides part of the state, but consecutive outputs constrain it. When output is `state mod N`, iterate candidates by N through the modulus. When only upper bits are visible, brute-force the hidden lower bits and validate against the next output.
121
+
122
+ ---
123
+
124
+ ## Weak Hash Functions / GF(2) Gaussian Elimination
125
+
126
+ Linear permutations (only XOR, rotations) are algebraically attackable. Build transformation matrix and solve over GF(2).
127
+
128
+ ```python
129
+ import numpy as np
130
+
131
+ def solve_gf2(A, b):
132
+ """Solve Ax = b over GF(2)."""
133
+ m, n = A.shape
134
+ Aug = np.hstack([A, b.reshape(-1, 1)]) % 2
135
+ pivot_cols, row = [], 0
136
+ for col in range(n):
137
+ pivot = next((r for r in range(row, m) if Aug[r, col]), None)
138
+ if pivot is None: continue
139
+ Aug[[row, pivot]] = Aug[[pivot, row]]
140
+ for r in range(m):
141
+ if r != row and Aug[r, col]: Aug[r] = (Aug[r] + Aug[row]) % 2
142
+ pivot_cols.append((row, col)); row += 1
143
+ if any(Aug[r, -1] for r in range(row, m)): return None
144
+ x = np.zeros(n, dtype=np.uint8)
145
+ for r, c in reversed(pivot_cols):
146
+ x[c] = Aug[r, -1] ^ sum(Aug[r, c2] * x[c2] for c2 in range(c+1, n)) % 2
147
+ return x
148
+ ```
149
+
150
+ **Key insight:** Hash functions built from only XOR and rotations (no S-boxes or modular addition) are linear over GF(2). Build the transformation as a binary matrix, then invert it with Gaussian elimination to recover the preimage directly. This breaks any "custom hash" that avoids non-linear operations.
151
+
152
+ ---
153
+
154
+ ## Affine Cipher over Composite Modulus (Nullcon 2026)
155
+
156
+ Affine encryption `c = A*x + b (mod M)` with composite M: split into prime factor fields, invert independently, CRT recombine. See [advanced-math.md](advanced-math.md#affine-cipher-over-non-prime-modulus-nullcon-2026) for full chosen-plaintext key recovery and implementation.
157
+
158
+ ---
159
+
160
+ ## AES-GCM with Derived Keys (EHAX 2026)
161
+
162
+ **Pattern:** Final decryption step after recovering a secret (e.g., from LWE, key exchange). Session nonce and AES key derived via SHA-256 hashing of the recovered secret.
163
+
164
+ ```python
165
+ import hashlib
166
+ from Cryptodome.Cipher import AES
167
+
168
+ # Common key derivation chain:
169
+ # 1. Recover secret bytes (s_bytes) from crypto challenge
170
+ # 2. Unwrap session nonce: nonce = wrapped_nonce XOR SHA256(s_bytes)[:nonce_len]
171
+ # 3. Derive AES key: key = SHA256(s_bytes + session_nonce)
172
+ # 4. Decrypt AES-GCM
173
+
174
+ def decrypt_with_derived_key(s_bytes, wrapped_nonce, ciphertext, aes_nonce, tag, nonce_len=16):
175
+ secret_hash = hashlib.sha256(s_bytes).digest()
176
+ session_nonce = bytes(a ^ b for a, b in zip(wrapped_nonce, secret_hash[:nonce_len]))
177
+ aes_key = hashlib.sha256(s_bytes + session_nonce).digest()
178
+ cipher = AES.new(aes_key, AES.MODE_GCM, nonce=aes_nonce)
179
+ return cipher.decrypt_and_verify(ciphertext, tag)
180
+ ```
181
+
182
+ **Key insight:** When AES-GCM authentication fails (`ValueError: MAC check failed`), the derived key is wrong — usually means the upstream secret recovery was incorrect or endianness is swapped.
183
+
184
+ ---
185
+
186
+ ## AES-GCM Nonce Reuse / Forbidden Attack
187
+
188
+ AES-GCM (Galois/Counter Mode) combines AES-CTR encryption with a GHASH polynomial authentication tag. Reusing a nonce with the same key is catastrophic -- it enables both plaintext recovery AND authentication key recovery.
189
+
190
+ **CTR keystream reuse:** Same nonce = same keystream. XOR two ciphertexts to cancel the keystream: `C1 XOR C2 = P1 XOR P2`. With known plaintext in one message, recover the other.
191
+
192
+ **GHASH authentication key recovery:** The authentication tag is a polynomial evaluation over GF(2^128). Two messages with the same nonce produce two equations in the same authentication key H. XOR the tag polynomials and factor over GF(2^128) to recover H. With H, forge valid tags for arbitrary messages.
193
+
194
+ ```python
195
+ from Crypto.Cipher import AES
196
+ from sage.all import GF, PolynomialRing
197
+
198
+ # Given: two (ciphertext, tag, nonce) pairs with same nonce
199
+ # Step 1: Recover plaintext via CTR keystream reuse
200
+ keystream = xor(known_plaintext, ciphertext1)
201
+ plaintext2 = xor(keystream, ciphertext2)
202
+
203
+ # Step 2: Recover GHASH auth key H
204
+ # Construct tag difference polynomial in GF(2^128)
205
+ F = GF(2**128, 'x', modulus=...) # GCM polynomial
206
+ # T1 XOR T2 = P(H) where P is polynomial from ciphertext difference
207
+ # Factor P(H) = 0 to find H candidates
208
+ # Verify H against known tags
209
+
210
+ # Step 3: Forge tags for arbitrary messages
211
+ # GHASH(H, aad, ciphertext) computed with recovered H
212
+ ```
213
+
214
+ **Tool:** [nonce-disrespect](https://github.com/nonce-disrespect/nonce-disrespect) automates GHASH key recovery and tag forgery from nonce-reused GCM ciphertexts.
215
+
216
+ **Short nonce brute-force:** When GCM uses a short nonce (1-4 bytes), brute-force all nonce values if the key is known. AES-GCM with 1-byte nonce = only 256 candidates.
217
+
218
+ **Key insight:** AES-GCM is a "one-time nonce" scheme -- a single nonce reuse breaks both confidentiality (CTR keystream reuse) AND authenticity (GHASH key recovery). Always check for repeated nonces in GCM challenge traffic.
219
+
220
+ ---
221
+
222
+ ## Ascon-like Reduced-Round Differential Cryptanalysis (srdnlenCTF 2026)
223
+
224
+ **Pattern (Lightweight):** 4-round Ascon-like permutation with reduced diffusion. Key-dependent biases in output-bit differentials allow key recovery via chosen input differences.
225
+
226
+ **Attack:**
227
+ 1. Reproduce the permutation exactly (critical: post-S-box x4 assignment order matters)
228
+ 2. Invert the linear layer of x0 using a precomputed 64×64 GF(2) inverse matrix
229
+ 3. For each bit position i, query with `diff = (1<<i, 1<<i)` across multiple samples
230
+ 4. Measure empirical biases at output bits `j1 = (i+1) mod 64` and `j2 = (i+14) mod 64`
231
+ 5. Classify key bits `(k0[i], k1[i])` via centroid-based clustering with sign-pattern mask
232
+ 6. Verify candidate key in-session; refine low-margin bits with additional samples
233
+
234
+ **GF(2) linear layer inversion:**
235
+ ```python
236
+ def build_inverse(shifts=(19, 28)):
237
+ """Construct GF(2) inverse matrix for Ascon-like linear layer: x ^= rot(x,19) ^ rot(x,28)."""
238
+ # Build 64x64 matrix over GF(2)
239
+ M = [[0]*64 for _ in range(64)]
240
+ for out_bit in range(64):
241
+ M[out_bit][out_bit] = 1
242
+ for shift in shifts:
243
+ M[out_bit][(out_bit + shift) % 64] ^= 1
244
+ # Gaussian elimination to find inverse
245
+ aug = [row + [1 if i == j else 0 for j in range(64)] for i, row in enumerate(M)]
246
+ for col in range(64):
247
+ pivot = next(r for r in range(col, 64) if aug[r][col])
248
+ aug[col], aug[pivot] = aug[pivot], aug[col]
249
+ for r in range(64):
250
+ if r != col and aug[r][col]:
251
+ aug[r] = [a ^ b for a, b in zip(aug[r], aug[col])]
252
+ return [row[64:] for row in aug]
253
+ ```
254
+
255
+ **Centroid clustering for key classification:**
256
+ ```python
257
+ # For each bit position, measure bias at two output positions
258
+ # 4 possible (k0[i], k1[i]) pairs → 4 centroid patterns
259
+ # Uses sign-pattern mask CMASK=0x73 to account for bit-position-dependent behavior
260
+ # Classify by minimum Euclidean distance in 2D bias space
261
+ CMASK = 0x73
262
+ for i in range(64):
263
+ bias_j1, bias_j2 = measure_biases(i, samples)
264
+ mask_bit = (CMASK >> (i % 8)) & 1
265
+ centroids = centroid_table[mask_bit] # Precomputed per-position centroids
266
+ k0_bit, k1_bit = min(range(4), key=lambda c: euclidean_dist(
267
+ (bias_j1, bias_j2), centroids[c]))
268
+ ```
269
+
270
+ **Key insight:** Reduced-round lightweight ciphers (Ascon, GIFT, etc.) have exploitable biases when the number of rounds is insufficient for full diffusion. The linear layer's inverse can be computed algebraically, and differential biases measured across chosen-plaintext queries reveal individual key bits. This is practical even with noisy measurements if you collect enough samples.
271
+
272
+ ---
273
+
274
+ ## Custom Linear MAC Forgery (Nullcon 2026)
275
+
276
+ **Pattern (Pasty):** Server signs paste IDs with a custom SHA-256-based construction. The signature is linear in three 8-byte secret blocks derived from the key.
277
+
278
+ **Structure:** For each 8-byte output block `i`:
279
+ - `selector = SHA256(id)[i*8] % 3` → chooses which secret block to use
280
+ - `out[i] = hash_block[i] XOR secret[selector] XOR chain[i-1]`
281
+
282
+ **Recovery:** Create ~10 pastes to collect `(id, sig)` pairs. Each pair reveals `secret[selector]` for 4 selectors. With ~4-5 pairs, all 3 secret blocks are recovered. Then forge for target ID.
283
+
284
+ **Key insight:** Linearity in custom crypto constructions (XOR-based signing) makes them trivially forgeable. Always check if the MAC has the property: knowing the secret components lets you compute valid signatures for arbitrary inputs.
285
+
286
+ ---
287
+
288
+ ## CBC Padding Oracle Attack
289
+
290
+ **Pattern:** Server reveals whether CBC-mode ciphertext has valid PKCS#7 padding (via error messages, timing, or status codes). Decrypt any ciphertext block-by-block without the key.
291
+
292
+ ```python
293
+ from pwn import *
294
+
295
+ def padding_oracle(iv, ct):
296
+ """Returns True if server accepts padding."""
297
+ resp = requests.post(URL, data={'iv': iv.hex(), 'ct': ct.hex()})
298
+ return 'padding' not in resp.text.lower() # or check status code
299
+
300
+ def decrypt_block(prev_block, target_block):
301
+ """Decrypt one 16-byte block using padding oracle."""
302
+ intermediate = bytearray(16)
303
+ plaintext = bytearray(16)
304
+
305
+ for byte_pos in range(15, -1, -1):
306
+ pad_val = 16 - byte_pos
307
+ # Set already-known bytes to produce correct padding
308
+ crafted = bytearray(16)
309
+ for k in range(byte_pos + 1, 16):
310
+ crafted[k] = intermediate[k] ^ pad_val
311
+
312
+ for guess in range(256):
313
+ crafted[byte_pos] = guess
314
+ if padding_oracle(bytes(crafted), target_block):
315
+ intermediate[byte_pos] = guess ^ pad_val
316
+ plaintext[byte_pos] = intermediate[byte_pos] ^ prev_block[byte_pos]
317
+ break
318
+
319
+ return bytes(plaintext)
320
+ ```
321
+
322
+ **Tools:**
323
+ ```bash
324
+ # PadBuster — automated padding oracle exploitation
325
+ padbuster http://target/decrypt.php ENCRYPTED_B64 16 \
326
+ -encoding 0 -error "Invalid padding"
327
+
328
+ # Python: pip install padding-oracle
329
+ from padding_oracle import PaddingOracle
330
+ oracle = PaddingOracle(block_size=16, oracle_fn=check_padding)
331
+ plaintext = oracle.decrypt(ciphertext, iv=iv)
332
+ ```
333
+
334
+ **Key insight:** The oracle only needs to distinguish "valid padding" from "invalid padding." This can be a different HTTP status code, error message, response time, or even whether the application processes the request further. A single bit of information per query is sufficient. Decryption requires at most 256 x 16 = 4096 queries per block.
335
+
336
+ **Detection:** CBC mode encryption + any distinguishable behavior difference on padding errors. Common in cookie encryption, token systems, and encrypted API parameters.
337
+
338
+ ---
339
+
340
+ ## Bleichenbacher / PKCS#1 v1.5 RSA Padding Oracle
341
+
342
+ **Pattern:** RSA encryption with PKCS#1 v1.5 padding where the server reveals whether decrypted plaintext has valid `0x00 0x02` prefix. Adaptive chosen-ciphertext attack recovers the plaintext.
343
+
344
+ ```python
345
+ import gmpy2
346
+
347
+ def bleichenbacher_oracle(c, n, e):
348
+ """Returns True if RSA decryption has valid PKCS#1 v1.5 padding (0x00 0x02 prefix)."""
349
+ resp = send_to_server(c)
350
+ return resp.status_code != 400 # Server returns 400 on bad padding
351
+
352
+ def bleichenbacher_attack(c0, n, e, oracle, k):
353
+ """
354
+ c0: target ciphertext (integer)
355
+ k: byte length of modulus (e.g., 256 for RSA-2048)
356
+ """
357
+ B = pow(2, 8 * (k - 2))
358
+
359
+ # Step 1: Start with s1 = ceil(n / 3B)
360
+ s = (n + 3 * B - 1) // (3 * B)
361
+
362
+ # Step 2: Search for s where oracle(c0 * s^e mod n) is True
363
+ while True:
364
+ c_prime = (c0 * pow(s, e, n)) % n
365
+ if oracle(c_prime, n, e):
366
+ break
367
+ s += 1
368
+
369
+ # Step 3: Narrow interval [a, b] using s values
370
+ # Repeat: find new s, narrow interval, until a == b
371
+ # When interval collapses, plaintext = a * modinv(s, n) % n
372
+ # (Full implementation requires interval tracking — use existing tools)
373
+ ```
374
+
375
+ **Tools:**
376
+ ```bash
377
+ # ROBOT attack scanner (modern Bleichenbacher variant)
378
+ python3 robot-detect.py -H target.com
379
+
380
+ # TLS-Attacker framework
381
+ java -jar TLS-Attacker.jar -connect target:443 -workflow_type BLEICHENBACHER
382
+ ```
383
+
384
+ **Key insight:** The attack is adaptive — each oracle response narrows the range of possible plaintexts. Typically requires ~10,000 oracle queries for RSA-2048. The ROBOT attack (Return Of Bleichenbacher's Oracle Threat) showed this affects modern TLS implementations through subtle timing differences. Any server that distinguishes "bad padding" from "bad content" is vulnerable.
385
+
386
+ ---
387
+
388
+ ## Birthday Attack / Meet-in-the-Middle
389
+
390
+ **Pattern:** Find collisions in hash functions or MACs using the birthday paradox. With an n-bit hash, expect a collision after ~2^(n/2) random inputs.
391
+
392
+ ```python
393
+ import hashlib, os
394
+
395
+ def birthday_collision(hash_fn, output_bits, prefix=b''):
396
+ """Find two inputs with the same truncated hash."""
397
+ target_bytes = output_bits // 8
398
+ seen = {}
399
+
400
+ while True:
401
+ msg = prefix + os.urandom(16)
402
+ h = hash_fn(msg).digest()[:target_bytes]
403
+ if h in seen:
404
+ return seen[h], msg # Collision found!
405
+ seen[h] = msg
406
+
407
+ # Example: find collision on first 4 bytes of SHA-256 (~65536 attempts)
408
+ msg1, msg2 = birthday_collision(hashlib.sha256, 32)
409
+ ```
410
+
411
+ **Meet-in-the-Middle (2DES, double encryption):**
412
+ ```python
413
+ def meet_in_the_middle(encrypt_fn, decrypt_fn, plaintext, ciphertext, keyspace):
414
+ """Break double encryption E(k2, E(k1, pt)) = ct."""
415
+ # Forward: encrypt plaintext with all possible k1
416
+ forward = {}
417
+ for k1 in keyspace:
418
+ intermediate = encrypt_fn(k1, plaintext)
419
+ forward[intermediate] = k1
420
+
421
+ # Backward: decrypt ciphertext with all possible k2
422
+ for k2 in keyspace:
423
+ intermediate = decrypt_fn(k2, ciphertext)
424
+ if intermediate in forward:
425
+ return forward[intermediate], k2 # Found k1, k2!
426
+ ```
427
+
428
+ **Key insight:** Birthday attack: n-bit hash needs ~2^(n/2) queries for 50% collision probability. 32-bit hash -> ~65K, 64-bit -> ~4 billion. Meet-in-the-middle reduces double encryption from O(2^(2k)) to O(2^k) time + O(2^k) space — this is why 2DES provides only 1 extra bit of security over DES.
429
+
430
+ ---
431
+
432
+ ## CRC32 Collision-Based Signature Forgery (iCTF 2013)
433
+
434
+ **Pattern:** CRC32 is linear — appending 4 carefully chosen bytes to any message produces a target CRC32 value, enabling signature forgery without knowing the secret key.
435
+
436
+ **Key insight:** `CRC32(msg || secret)` is not a secure MAC. Given any signed response `(msg, sig)`, compute 4 suffix bytes that force `CRC32(forged_msg || suffix || secret) == target_sig`. The linearity of CRC32 means the suffix computation is deterministic and instant.
437
+
438
+ ```python
439
+ import struct, binascii
440
+
441
+ def crc32_forge(data, target_crc):
442
+ """Append 4 bytes to data so CRC32(data + suffix) == target_crc"""
443
+ current = binascii.crc32(data) & 0xFFFFFFFF
444
+ # CRC32 polynomial table lookup to find suffix bytes
445
+ # that transform current CRC into target_crc
446
+ suffix = b''
447
+ crc = target_crc ^ 0xFFFFFFFF
448
+ for _ in range(4):
449
+ byte = (crc & 0xFF)
450
+ crc = (crc >> 8)
451
+ suffix = bytes([byte]) + suffix
452
+ return data + suffix # Simplified — full implementation requires polynomial division
453
+ ```
454
+
455
+ **When to use:** Any protocol using CRC32 as a message authentication code (MAC). CRC32 is a checksum, not a cryptographic hash — it provides no integrity guarantees against adversarial modification.
456
+
457
+ ---
458
+
459
+ ## AES Key Recovery via Byte-by-Byte Zeroing Oracle (CONFidence CTF 2017)
460
+
461
+ **Pattern:** When a service allows selective zeroing of key bytes (e.g., via integer overflow in key slot indexing), recover the full AES key by testing one byte at a time.
462
+
463
+ ```python
464
+ # Service has key slots and a "regenerate" function with integer overflow
465
+ # offset = index * ENTRY_SIZE wraps around, allowing arbitrary byte zeroing
466
+
467
+ # Strategy: zero bytes progressively, brute-force each unknown byte
468
+ for byte_pos in range(16):
469
+ # Zero all bytes EXCEPT byte_pos (by overflowing index calculation)
470
+ zero_index = (target_offset * modinv(ENTRY_SIZE, 2**32)) % 2**32
471
+ regenerate(zero_index)
472
+
473
+ # Key is now: [0,0,...,key[byte_pos],...,0,0]
474
+ # Brute-force the single non-zero byte (256 possibilities)
475
+ known_ct = encrypt(known_pt)
476
+ for guess in range(256):
477
+ test_key = bytes([0]*byte_pos + [guess] + [0]*(15-byte_pos))
478
+ if AES.new(test_key, AES.MODE_ECB).encrypt(known_pt) == known_ct:
479
+ recovered_key[byte_pos] = guess
480
+ break
481
+ ```
482
+
483
+ **Key insight:** Integer overflow in `index * ENTRY_SIZE` calculations can target arbitrary memory offsets. By selectively zeroing all-but-one key bytes, the key becomes trivially brute-forceable one byte at a time (256 attempts per byte, 4096 total vs 2^128 for the full key).
484
+
485
+ **References:** CONFidence CTF 2017
486
+
487
+ ---
488
+
489
+ ## AES-CTR Constant Counter / Repeating Keystream (SHA2017)
490
+
491
+ **Pattern:** When an AES-CTR implementation uses `counter=lambda: secret` (a constant function), the counter never increments. AES-CTR with a fixed counter produces the same 16-byte block on every call — equivalent to Vigenere cipher at the byte level with a 16-byte repeating key.
492
+
493
+ ```python
494
+ # Constant counter makes CTR equivalent to repeating-key XOR
495
+ key_byte = ciphertext_byte ^ known_plaintext_byte
496
+ # Apply recovered key bytes across all 16-byte-aligned blocks
497
+ for i, ct_byte in enumerate(ciphertext):
498
+ plaintext_byte = ct_byte ^ keystream[i % 16]
499
+ ```
500
+
501
+ **Exploit using file format headers:**
502
+ 1. Identify the file format from context (e.g., `%PDF-1.` for PDF files)
503
+ 2. XOR the known header bytes against the ciphertext to recover `keystream[0:len(header)]`
504
+ 3. Iteratively extend: use recovered plaintext to guess the next structural keyword (`endobj`, `/Page`, `stream`, etc.), verify XOR produces consistent ASCII, and extend the keystream further
505
+ 4. Tool: `otp_pwn` supports interactive block-aligned crib-dragging for this workflow
506
+
507
+ **Key insight:** Constant AES-CTR counter = repeating 16-byte Vigenere key. Known file format magic bytes bootstrap iterative key recovery via crib-dragging. Any known-plaintext at block-aligned positions reveals the full keystream byte at that position.
508
+
509
+ **References:** SHA2017
510
+
511
+ ---
512
+
513
+ ## Custom SPN Column-Wise XOR Brute-Force (Hack Dat Kiwi 2017)
514
+
515
+ **Pattern:** SPN (Substitution-Permutation Network) cipher with a seed-based sbox/pbox and a final XOR key layer. If the XOR key is applied column-wise (each key byte affects one column position independently), each key byte can be brute-forced separately using printable-text consistency as an oracle.
516
+
517
+ **Attack:**
518
+ 1. Collect multiple ciphertext blocks (same key, different plaintexts)
519
+ 2. For each column position `c` (0-15), try all 256 candidate key bytes `k`
520
+ 3. Apply the inverse pbox and sbox to undo the SPN rounds, then XOR with candidate `k`
521
+ 4. Keep only candidates where ALL blocks produce printable ASCII at position `c`
522
+ 5. The intersection of valid candidates across blocks recovers each key byte
523
+
524
+ **Multi-round variant:** Peel one round at a time. After recovering the outermost XOR key, apply the inverse pbox/sbox for that round using the recovered bytes, then repeat for the next inner round.
525
+
526
+ **Seed-based permutation dependency:** When sbox and pbox are generated from a shared seed, recovering partial key bytes constrains the seed (and thus the remaining permutation entries). Use this to propagate partial solutions across columns with cross-column dependencies.
527
+
528
+ **Key insight:** Column-aligned XOR layers in SPN ciphers allow independent per-byte brute-force using printable-text consistency as an oracle. Cross-column key reuse from seed-based permutations propagates partial solutions.
529
+
530
+ **References:** Hack Dat Kiwi 2017
531
+
532
+ ---
533
+
534
+ ## AES-CTR Bitflip + CRC Linearity Signature Forgery (hxp CTF 2017)
535
+
536
+ **Pattern:** AES-CTR allows targeted plaintext modification via XOR. CRC is linear w.r.t. XOR: `CRC(A ^ B) = CRC(A) ^ CRC(B) ^ CRC(zeros)`. Flip `{admin: 0}` to `{admin: 1}` in ciphertext and fix the encrypted CRC:
537
+
538
+ ```python
539
+ import binascii
540
+ # X = desired_plaintext XOR original_plaintext (flip bit)
541
+ X = b'\x00' * offset + b'\x01' + b'\x00' * remaining
542
+ crc_diff = binascii.crc32(X) ^ binascii.crc32(b'\x00' * len(X))
543
+ # New ciphertext = old_ciphertext XOR X (for data portion)
544
+ # New CRC ciphertext = old_CRC_ciphertext XOR pack(crc_diff)
545
+ ```
546
+
547
+ **Key insight:** CRC is GF(2)-linear -- XOR-based modifications to plaintext produce predictable CRC changes without knowing the key. When a system uses AES-CTR for confidentiality + CRC for integrity (instead of a proper MAC like HMAC or GCM), you can flip arbitrary plaintext bits and fix the CRC simultaneously. This is a fundamental failure of using CRC as a MAC: CRC detects random errors but provides zero protection against adversarial modification under stream ciphers.
548
+
549
+ **References:** hxp CTF 2017
550
+
551
+ ---
552
+
553
+ ### AES-CBC Ciphertext Forging via Error-Message Decryption Oracle (Nuit du Hack CTF 2018)
554
+
555
+ **Pattern:** Server decrypts AES-CBC cookie and displays decrypted value in error messages. Send zero blocks, read decrypted intermediates from error, XOR with desired plaintext to forge ciphertext block-by-block. Use forged ciphertext to deliver blind SQLi payloads through encrypted cookies. (Nuit du Hack CTF 2018)
556
+
557
+ ```python
558
+ # Forge ciphertext for arbitrary plaintext
559
+ for i in range(blocks):
560
+ payload = b'\x00' * 16 * (blocks - 1) + last_forged_block
561
+ response = send_payload(payload)
562
+ decrypted = parse_error_message(response) # server leaks decrypted bytes
563
+ intermediate = decrypted[-16:]
564
+ new_block = xor(target_plaintext_block, intermediate)
565
+ forged_blocks.append(new_block)
566
+ ```
567
+
568
+ **Key insight:** When the server reveals decrypted ciphertext in error messages, you can forge arbitrary plaintext without knowing the key. Send zero IV blocks to learn the intermediate state, then XOR with desired plaintext to produce the correct ciphertext. Build block-by-block from last to first.
569
+
570
+ ---
571
+
572
+ ## SHA-1 Chosen-Prefix Collision for PDF Signature Forgery (DEF CON Quals 2018)
573
+
574
+ **Pattern (EmojiVote):** Server extracts commands from an uploaded PDF via OCR, then signs the OCR'd byte-string as `sha1(data)` and attaches the signature. Use a shattered-style SHA-1 chosen-prefix collision to produce two PDFs that OCR to different commands but share the same SHA-1 digest.
575
+
576
+ **Exploit workflow:**
577
+ 1. Build PDF A that OCR's to a benign command (no `EXECUTE`) and PDF B that OCR's to `EXECUTE <attacker command>`.
578
+ 2. Pad both with shattered-style suffix data so `sha1(A) == sha1(B)`.
579
+ 3. Submit A to obtain a valid signature for the shared digest.
580
+ 4. Replay that signature on B — the server verifies the SHA-1 matches and executes the attacker command.
581
+
582
+ ```bash
583
+ # Build the two colliding PDFs (cpc = chosen-prefix collision tool)
584
+ ./cpc prefix_A prefix_B collision_A.pdf collision_B.pdf
585
+ sha1sum collision_A.pdf collision_B.pdf # identical
586
+ # Upload A, capture signature, replay on B
587
+ ```
588
+
589
+ **Key insight:** When a protocol signs a message as `sign(sha1(M))` instead of `sign(M)` directly, any SHA-1 collision becomes a signature forgery. Chosen-prefix collisions are practical (cpc/shattered toolkit) — the signer only inspects the digest, never the second preimage.
590
+
591
+ **References:** DEF CON CTF Qualifier 2018 — writeup 10075
592
+
593
+ ---
594
+
595
+ ## Hash Chain Preimage Authentication Bypass (picoCTF 2017)
596
+
597
+ **Pattern (hash_chain):** Server authenticates the Nth challenge by asking for `hash^(N-1)(seed)` given `hash^N(seed)`. The seed is derivable from public user data (e.g., `md5(username)`), so any attacker can precompute the whole chain from the start and answer any step.
598
+
599
+ **Exploit:**
600
+ ```python
601
+ import hashlib
602
+
603
+ def H(x): return hashlib.md5(x).digest()
604
+
605
+ seed = H(username.encode()) # public-derived seed
606
+ chain = [seed]
607
+ for _ in range(TARGET_N + 1):
608
+ chain.append(H(chain[-1]))
609
+
610
+ # Server sends chain[N]; answer with chain[N-1]
611
+ ```
612
+
613
+ **Key insight:** Hash chains are only one-way if the seed is secret. If the seed can be reconstructed from public inputs (username, challenge ID, timestamp), the entire chain is computable forward, and answering "give me the previous hash" is trivial. Treat the seed like a key.
614
+
615
+ **References:** picoCTF 2017 — writeup 10031
616
+
617
+ ---
618
+
619
+ ## AES-CBC Nonce Strip via Block Boundary Alignment (Trend Micro 2018)
620
+
621
+ **Pattern:** A server encrypts `nonce | padding | identity | timestamp` with AES-CBC and returns `(iv, ciphertext)`. If the attacker can choose padding such that the first *exactly one* AES block (16 bytes) holds the nonce, then shifting the IV forward by one block — reusing `ciphertext[:16]` as the new IV and `ciphertext[16:]` as the new ciphertext — yields a valid encryption of just `identity | timestamp`. No key is needed because CBC-mode decryption of block 2 is `AES⁻¹(c[16:32]) XOR c[0:16]`, which is exactly the identity-and-timestamp plaintext once the nonce block is promoted to IV.
622
+
623
+ ```python
624
+ from Crypto.Cipher import AES
625
+ import os
626
+
627
+ key = os.urandom(16)
628
+
629
+ # Server builds plaintext and encrypts
630
+ def encrypt_with_nonce(identity, timestamp):
631
+ nonce = os.urandom(8)
632
+ padding = b"\x00" * 8 # brings nonce + padding to 16 bytes
633
+ plaintext = nonce + padding + identity + timestamp
634
+ iv = os.urandom(16)
635
+ ct = AES.new(key, AES.MODE_CBC, iv).encrypt(plaintext)
636
+ return iv, ct
637
+
638
+ iv, ct = encrypt_with_nonce(b"admin___________", b"2018-11-01T00:00")
639
+
640
+ # Attacker rewrites (iv', ct') to drop the nonce block
641
+ new_iv = ct[:16]
642
+ new_ct = ct[16:]
643
+ recovered = AES.new(key, AES.MODE_CBC, new_iv).decrypt(new_ct)
644
+ assert recovered.startswith(b"admin")
645
+ ```
646
+
647
+ **Key insight:** CBC's IV is only consulted for the first block — every subsequent block uses the previous ciphertext as its "IV". That means any contiguous slice of a CBC ciphertext is itself a valid CBC ciphertext if you promote the preceding block (or a supplied IV) to the new IV. Whenever a fixed-size header (nonce, magic bytes, counter) occupies exactly one block, the attacker can strip it by reusing that block as an IV. Defend by binding the header into the authentication tag (AEAD) or including its offset in an HMAC.
648
+
649
+ **References:** Trend Micro CTF 2018 — Offensive-Analysis 400, writeup 11130