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,792 @@
1
+ # CTF Crypto - RSA Attacks (Part 2: Specialized Techniques)
2
+
3
+ ## Table of Contents
4
+ - [RSA p=q Validation Bypass (BearCatCTF 2026)](#rsa-pq-validation-bypass-bearcatctf-2026)
5
+ - [RSA Cube Root CRT when gcd(e, phi) > 1 (BearCatCTF 2026)](#rsa-cube-root-crt-when-gcde-phi--1-bearcatctf-2026)
6
+ - [Factoring n from Multiple of phi(n) (BearCatCTF 2026)](#factoring-n-from-multiple-of-phin-bearcatctf-2026)
7
+ - [RSA Signature Forgery via Multiplicative Homomorphism (MMA CTF 2015)](#rsa-signature-forgery-via-multiplicative-homomorphism-mma-ctf-2015)
8
+ - [Weak RSA Key Generation via Base Representation (Sharif CTF 2016)](#weak-rsa-key-generation-via-base-representation-sharif-ctf-2016)
9
+ - [RSA with gcd(e, phi(n)) > 1 (CSAW 2015)](#rsa-with-gcde-phin--1-csaw-2015)
10
+ - [Batch GCD for Shared Prime Factoring (BSidesSF 2025)](#batch-gcd-for-shared-prime-factoring-bsidessf-2025)
11
+ - [RSA Partial Key Recovery from dp dq qinv (0CTF 2016)](#rsa-partial-key-recovery-from-dp-dq-qinv-0ctf-2016)
12
+ - [RSA-CRT Fault Attack / Bit-Flip Recovery (CSAW CTF 2016)](#rsa-crt-fault-attack--bit-flip-recovery-csaw-ctf-2016)
13
+ - [RSA Homomorphic Decryption Oracle Bypass (ECTF 2016)](#rsa-homomorphic-decryption-oracle-bypass-ectf-2016)
14
+ - [RSA with Small Prime Factors and CRT Decomposition (Hack The Vote 2016)](#rsa-with-small-prime-factors-and-crt-decomposition-hack-the-vote-2016)
15
+ - [RSA Timing Attack on Montgomery Reduction (DEF CON 2017)](#rsa-timing-attack-on-montgomery-reduction-def-con-2017)
16
+ - [Bleichenbacher Low-Exponent RSA Signature Forgery (Google CTF 2017)](#bleichenbacher-low-exponent-rsa-signature-forgery-google-ctf-2017)
17
+ - [Coppersmith Small Roots for Linearly Related Primes (Tokyo Westerns 2017)](#coppersmith-small-roots-for-linearly-related-primes-tokyo-westerns-2017)
18
+ - [ROCA Attack on RSA CVE-2017-15361 (EasyCTF IV)](#roca-attack-on-rsa-cve-2017-15361-easyctf-iv)
19
+ - [RSA Signature Bypass with e=1 and Crafted Modulus (BackdoorCTF 2018)](#rsa-signature-bypass-with-e1-and-crafted-modulus-backdoorctf-2018)
20
+ - [Dependent-Prime RSA: q = e^-1 mod p (TokyoWesterns CTF 4th 2018)](#dependent-prime-rsa-q--e-1-mod-p-tokyowesterns-ctf-4th-2018)
21
+ - [RSA Three-Key Pairwise GCD Triangle (Trend Micro 2018)](#rsa-three-key-pairwise-gcd-triangle-trend-micro-2018)
22
+ - [RSA n = p^2*q Schmidt-Samoa Variant (ASIS Finals 2018)](#rsa-n--p2q-schmidt-samoa-variant-asis-finals-2018)
23
+ - [Modulus Recovery via GCD of Encryption Residuals (X-MAS CTF 2018)](#modulus-recovery-via-gcd-of-encryption-residuals-x-mas-ctf-2018)
24
+ - [Textbook RSA Negation via encrypt(-1) (X-MAS CTF 2018)](#textbook-rsa-negation-via-encrypt-1-x-mas-ctf-2018)
25
+ - [Poly-Exponent RSA: GCD of p^p Combinations (ASIS Finals 2018)](#poly-exponent-rsa-gcd-of-pp-combinations-asis-finals-2018)
26
+ - [Biased LSB Oracle with Mode-of-Runs Recovery (CSAW CTF 2018)](#biased-lsb-oracle-with-mode-of-runs-recovery-csaw-ctf-2018)
27
+ - [Cube-Root Wraparound via AES-CTR Length Hint (hxp 2018)](#cube-root-wraparound-via-aes-ctr-length-hint-hxp-2018)
28
+ - [RSA p = next_prime(2^k + small) Shared-Prime Batch GCD (ASIS Finals 2018)](#rsa-p--next_prime2k--small-shared-prime-batch-gcd-asis-finals-2018)
29
+ - [PNG Encryption Bounded by 512-bit Key → Trailer Replacement (ASIS Finals 2018)](#png-encryption-bounded-by-512-bit-key--trailer-replacement-asis-finals-2018)
30
+ - [Modulus Recovery via Plaintext Malleability (X-MAS 2018)](#modulus-recovery-via-plaintext-malleability-x-mas-2018)
31
+ - [RSA CRT d_p NULL-Byte Overflow Primes Leak (P.W.N. CTF 2018)](#rsa-crt-d_p-null-byte-overflow-primes-leak-pwn-ctf-2018)
32
+ - [Textbook RSA Signature Blinding via Message Factoring (P.W.N. CTF 2018)](#textbook-rsa-signature-blinding-via-message-factoring-pwn-ctf-2018)
33
+ - [Last-Byte Modulus Overwrite via strlen-1 Null Truncation (OTW Advent 2018)](#last-byte-modulus-overwrite-via-strlen-1-null-truncation-otw-advent-2018)
34
+ - [CRC32 Collision Oracle + RSA Homomorphic Signature Forgery (BSidesSF 2019)](#crc32-collision-oracle--rsa-homomorphic-signature-forgery-bsidessf-2019)
35
+
36
+ See also: [rsa-attacks.md](rsa-attacks.md) for foundational RSA attacks (small e, Wiener, Fermat, Pollard, Hastad, common modulus, Manger oracle, Coppersmith).
37
+
38
+ ---
39
+
40
+ ## RSA p=q Validation Bypass (BearCatCTF 2026)
41
+
42
+ **Pattern (Pickme):** Server validates user-submitted RSA key (checks `n`, `e`, `d`, `p*q=n`, `e*d ≡ 1 mod phi`), encrypts the flag, then tries test decryption. If decryption fails, leaks ciphertext in error message.
43
+
44
+ **Exploit:** Set `p = q`. The server computes `phi = (p-1)*(q-1) = (p-1)^2` (incorrect — real totient of `p^2` is `p*(p-1)`). All validation checks pass, but decryption with the wrong `d` fails, leaking the ciphertext.
45
+
46
+ ```python
47
+ from Crypto.Util.number import getPrime, inverse
48
+
49
+ p = getPrime(512)
50
+ q = p # p = q!
51
+ n = p * q # = p^2
52
+ e = 65537
53
+ wrong_phi = (p - 1) * (q - 1) # = (p-1)^2
54
+ d = inverse(e, wrong_phi) # passes server validation
55
+
56
+ # Server encrypts flag with our key, test decryption fails → leaks ciphertext c
57
+ # Decrypt with correct totient:
58
+ real_phi = p * (p - 1)
59
+ real_d = inverse(e, real_phi)
60
+ flag = pow(c, real_d, n)
61
+ ```
62
+
63
+ **Key insight:** `phi(p^2) = p*(p-1)`, NOT `(p-1)^2`. When a server validates RSA parameters but uses `(p-1)*(q-1)` without checking `p != q`, setting `p=q` creates a working key that the server will miscompute the private exponent for, causing decryption failure and error-path data leakage.
64
+
65
+ ---
66
+
67
+ ## RSA Cube Root CRT when gcd(e, phi) > 1 (BearCatCTF 2026)
68
+
69
+ **Pattern (Kidd's Crypto):** RSA with `e=3`, modulus composed of many small primes all ≡ 1 (mod 3). Since each `p-1` is divisible by 3, `gcd(e, phi(n)) = 3^k` and the standard modular inverse `d = e^-1 mod phi` doesn't exist.
70
+
71
+ **Solution:** Compute cube roots per-prime via CRT:
72
+ ```python
73
+ from sympy.ntheory.residues import nthroot_mod
74
+ from sympy.ntheory.modular import crt
75
+
76
+ primes = [p1, p2, ..., p13] # All ≡ 1 mod 3
77
+
78
+ # For each prime, find all 3 cube roots of c mod p
79
+ roots_per_prime = []
80
+ for p in primes:
81
+ roots = nthroot_mod(c % p, 3, p, all_roots=True)
82
+ roots_per_prime.append(roots)
83
+
84
+ # Try all 3^13 = 1,594,323 combinations
85
+ from itertools import product
86
+ for combo in product(*roots_per_prime):
87
+ result, mod = crt(primes, list(combo))
88
+ try:
89
+ text = long_to_bytes(result).decode('ascii')
90
+ if text.isprintable():
91
+ print(f"Flag: {text}")
92
+ break
93
+ except:
94
+ continue
95
+ ```
96
+
97
+ **Key insight:** When `gcd(e, phi(n)) > 1`, standard RSA decryption fails. Factor `n`, compute eth roots modulo each prime separately (each prime ≡ 1 mod e gives `e` roots), then enumerate all CRT combinations. Feasible when the number of primes is small (3^13 ≈ 1.6M combinations).
98
+
99
+ ---
100
+
101
+ ## Factoring n from Multiple of phi(n) (BearCatCTF 2026)
102
+
103
+ **Pattern (Twisted Pair):** Given RSA `n` and a leaked pair `(re, rd)` where `re * rd ≡ 1 (mod k*phi(n))`. The value `re*rd - 1` is a multiple of `phi(n)`, enabling probabilistic factoring.
104
+
105
+ ```python
106
+ import random
107
+ from math import gcd
108
+
109
+ def factor_from_phi_multiple(n, phi_multiple):
110
+ """Factor n given any multiple of phi(n) using Miller-Rabin variant."""
111
+ # Write phi_multiple = 2^s * d where d is odd
112
+ s, d = 0, phi_multiple
113
+ while d % 2 == 0:
114
+ s += 1
115
+ d //= 2
116
+
117
+ for _ in range(100): # 100 attempts
118
+ a = random.randrange(2, n - 1)
119
+ x = pow(a, d, n)
120
+ if x == 1 or x == n - 1:
121
+ continue
122
+ for _ in range(s - 1):
123
+ prev = x
124
+ x = pow(x, 2, n)
125
+ if x == n - 1:
126
+ break
127
+ if x == 1:
128
+ # prev is non-trivial square root of 1
129
+ p = gcd(prev - 1, n)
130
+ if 1 < p < n:
131
+ return p, n // p
132
+ # Check final
133
+ if x != n - 1:
134
+ p = gcd(x - 1, n)
135
+ if 1 < p < n:
136
+ return p, n // p
137
+ return None
138
+
139
+ phi_mult = re * rd - 1
140
+ p, q = factor_from_phi_multiple(n, phi_mult)
141
+ ```
142
+
143
+ **Key insight:** Any multiple of `phi(n)` — not just `phi(n)` itself — enables factoring via the Miller-Rabin square root technique. If a server leaks `e*d` for any key pair, or if `re*rd - 1` is given, compute `gcd(a^(m/2) - 1, n)` for random `a` values. Succeeds with probability ≥ 1/2 per attempt.
144
+
145
+ ---
146
+
147
+ ## RSA Signature Forgery via Multiplicative Homomorphism (MMA CTF 2015)
148
+
149
+ **Pattern:** Signing oracle refuses to sign the target message `m` but will sign other values. Unpadded RSA is multiplicatively homomorphic: `S(a) * S(b) mod n == S(a * b) mod n`.
150
+
151
+ ```python
152
+ # Factor target message and sign each factor separately
153
+ divisor = 2
154
+ assert target_msg % divisor == 0
155
+ sig_a = sign_oracle(target_msg // divisor)
156
+ sig_b = sign_oracle(divisor)
157
+ forged_sig = (sig_a * sig_b) % n
158
+ ```
159
+
160
+ **Key insight:** Textbook RSA signatures are homomorphic: `m^d mod n` preserves multiplication. If the oracle blacklists `m` but signs its factors, multiply the partial signatures. To find a suitable factorization, try small divisors (2, 3, ...) until `m / divisor` also passes the blacklist check. This is why PKCS#1 padding is essential — padded messages cannot be factored into other valid padded messages.
161
+
162
+ ---
163
+
164
+ ## Weak RSA Key Generation via Base Representation (Sharif CTF 2016)
165
+
166
+ When RSA primes are generated as `p = kp * B + tp` where B = product_of_small_primes * 2^400 and kp is small (< 2^12):
167
+
168
+ 1. **Compute n mod B^2:** Since n = p*q and both p,q have form k*B + t, expansion gives: `n = kp*kq*B^2 + (kp*tq + kq*tp)*B + tp*tq`
169
+ 2. **Recover kp*kq:** Brute-force 2^24 possibilities for (kp, kq) where each < 2^12
170
+ 3. **Solve quadratic:** From known kp*kq and the middle coefficient, recover tp and tq
171
+
172
+ ```python
173
+ B = product_of_first_443_primes * (2**400)
174
+ B2 = B * B
175
+
176
+ # n = A*B^2 + C*B + D where A=kp*kq, D=tp*tq
177
+ A = n // B2
178
+ D = n % B
179
+
180
+ # Brute-force kp, kq such that kp*kq == A
181
+ for kp in range(1, 2**12):
182
+ if A % kp == 0:
183
+ kq = A // kp
184
+ # Solve for tp, tq from remaining equations
185
+ ```
186
+
187
+ **Key insight:** Structured prime generation creates a mixed-radix representation of n, allowing efficient factorization by reducing the search space from exponential to polynomial.
188
+
189
+ ---
190
+
191
+ ## RSA with gcd(e, phi(n)) > 1 (CSAW 2015)
192
+
193
+ When `gcd(e, phi(n)) = g > 1`, standard RSA decryption fails because `d = e^(-1) mod phi(n)` doesn't exist. Instead:
194
+
195
+ 1. Compute `e' = e / g` (reduced exponent)
196
+ 2. Compute `d' = e'^(-1) mod phi(n)` (now coprime)
197
+ 3. Compute `m^g = pow(c, d', n)` (partial decryption)
198
+ 4. Take g-th root: iterate candidate m values where `pow(m, g, n) == m^g`
199
+
200
+ ```python
201
+ from sympy import factorint, mod_inverse
202
+ from gmpy2 import iroot
203
+
204
+ g = gcd(e, phi_n)
205
+ e_prime = e // g
206
+ d_prime = mod_inverse(e_prime, phi_n)
207
+ m_g = pow(c, d_prime, n)
208
+
209
+ # For small g, try integer root first
210
+ m, is_exact = iroot(m_g, g)
211
+ if is_exact:
212
+ plaintext = int(m)
213
+ else:
214
+ # Brute-force: m_g + k*n for small k
215
+ for k in range(10000):
216
+ m, exact = iroot(m_g + k * n, g)
217
+ if exact:
218
+ plaintext = int(m)
219
+ break
220
+ ```
221
+
222
+ **Key insight:** Reduce e by the GCD to make decryption possible, then recover the g-th root. Filter candidates by checking plaintext length or ASCII validity.
223
+
224
+ ---
225
+
226
+ ## Batch GCD for Shared Prime Factoring (BSidesSF 2025)
227
+
228
+ When multiple RSA moduli share a common prime factor (due to faulty hardware RNG, smartcard bugs, or weak seeding):
229
+
230
+ ```python
231
+ from math import gcd
232
+ from functools import reduce
233
+
234
+ def batch_gcd(moduli):
235
+ """Find shared factors among a list of RSA moduli"""
236
+ # Product tree
237
+ product = reduce(lambda a, b: a * b, moduli)
238
+
239
+ factors = {}
240
+ for n in moduli:
241
+ g = gcd(n, product // n)
242
+ if g != 1 and g != n:
243
+ p = g
244
+ q = n // p
245
+ factors[n] = (p, q)
246
+ return factors
247
+
248
+ # Usage: given list of public keys from smartcards
249
+ moduli = [key.n for key in public_keys]
250
+ shared = batch_gcd(moduli)
251
+ for n, (p, q) in shared.items():
252
+ phi = (p - 1) * (q - 1)
253
+ d = pow(e, -1, phi) # Private exponent
254
+ ```
255
+
256
+ For keys with patterned primes (hardware RNG faults producing primes with fixed bit patterns), combine with Coppersmith's method to recover remaining random bits. See [advanced-math.md](advanced-math.md) for Coppersmith.
257
+
258
+ **Key insight:** A single shared prime compromises both keys. Batch GCD runs in O(n log n) time via product/remainder trees, making it feasible for thousands of keys. Real-world incidents: Taiwanese citizen smartcards (2013), many IoT device certificates.
259
+
260
+ ---
261
+
262
+ ## RSA Partial Key Recovery from dp dq qinv (0CTF 2016)
263
+
264
+ **Pattern:** Given only the CRT (Chinese Remainder Theorem) exponents (dp, dq, qinv) from a partial PEM (Privacy Enhanced Mail) key leak (e.g., bottom portion of private key file), recover the full key. Since `dp = d mod (p-1)`, iterate k and check if `p = (dp * e - 1) / k + 1` is prime.
265
+
266
+ ```python
267
+ import gmpy2
268
+ # dp, dq, qinv extracted from partial PEM; e is known (usually 65537)
269
+ for k in range(3, e):
270
+ p_candidate = (dp * e - 1) // k + 1
271
+ if gmpy2.is_prime(p_candidate):
272
+ p = p_candidate
273
+ break
274
+ # Similarly recover q from dq; verify qinv * q % p == 1
275
+ ```
276
+
277
+ **Key insight:** Leaking just the CRT exponents from an RSA private key is sufficient to fully recover p and q. Recovery is O(e) -- essentially instant for e=65537.
278
+
279
+ ---
280
+
281
+ ## RSA-CRT Fault Attack / Bit-Flip Recovery (CSAW CTF 2016)
282
+
283
+ RSA signing service with intermittent single-bit errors in private exponent d during CRT computation. Collect valid and faulty signatures, then detect which bit flipped.
284
+
285
+ ```python
286
+ from Crypto.Util.number import inverse
287
+
288
+ def recover_d_bits(n, e, valid_sig, faulty_sigs, msg):
289
+ """Recover private key d bit-by-bit from CRT fault signatures"""
290
+ d_bits = [0] * 1024
291
+ m = pow(msg, 1, n)
292
+ s_good = valid_sig
293
+ for s_bad in faulty_sigs:
294
+ # ratio reveals which bit was flipped
295
+ ratio = (s_bad * inverse(s_good, n)) % n
296
+ for k in range(1024):
297
+ if ratio == pow(2, pow(2, k, n), n) or ratio == pow(inverse(2, n), pow(2, k, n), n):
298
+ d_bits[k] = 1
299
+ break
300
+ return d_bits
301
+ ```
302
+
303
+ **Key insight:** When RSA-CRT has a single-bit fault in d, the ratio `faulty_sig * valid_sig^(-1) mod n` equals `2^(2^k) mod n` for the flipped bit position k, enabling bit-by-bit private key recovery.
304
+
305
+ ---
306
+
307
+ ## RSA Homomorphic Decryption Oracle Bypass (ECTF 2016)
308
+
309
+ Service decrypts any ciphertext except the target flag ciphertext. Exploit RSA's multiplicative homomorphism: `Dec(a * b mod n) = Dec(a) * Dec(b) mod n`.
310
+
311
+ ```python
312
+ from Crypto.Util.number import long_to_bytes, inverse
313
+
314
+ # Server refuses to decrypt enc_flag directly
315
+ # But RSA is homomorphic: Dec(A*B) = Dec(A) * Dec(B) mod n
316
+ enc_2 = pow(2, e, n) # encrypt the number 2
317
+ enc_flag_times_2 = (enc_flag * enc_2) % n # = Enc(flag * 2)
318
+
319
+ dec_flag_times_2 = oracle_decrypt(enc_flag_times_2) # server allows this
320
+ dec_2 = oracle_decrypt(enc_2) # server allows this
321
+
322
+ # Recover flag: (flag * 2) * inverse(2) mod n = flag
323
+ flag = (dec_flag_times_2 * inverse(dec_2, n)) % n
324
+ print(long_to_bytes(flag))
325
+ ```
326
+
327
+ **Key insight:** RSA without padding is multiplicatively homomorphic. Multiply the forbidden ciphertext by `Enc(r)` for any `r`, decrypt the product, then divide by `r` to recover the original plaintext.
328
+
329
+ ---
330
+
331
+ ## RSA with Small Prime Factors and CRT Decomposition (Hack The Vote 2016)
332
+
333
+ RSA modulus composed of many small prime factors (primes < 251, each appearing ~1500 times). Factor n, then use CRT to decompose decryption.
334
+
335
+ ```python
336
+ from sympy import factorint
337
+ from sympy.ntheory.residues import primitive_root
338
+ from functools import reduce
339
+
340
+ n = ... # modulus with small factors
341
+ e = 65537
342
+ c = ... # ciphertext
343
+
344
+ factors = factorint(n) # {p1: k1, p2: k2, ...} where pi are small primes
345
+
346
+ # Decrypt modulo each prime power, then combine with CRT
347
+ from sympy.ntheory.modular import crt as chinese_remainder_theorem
348
+
349
+ remainders = []
350
+ moduli = []
351
+ for p, k in factors.items():
352
+ pk = p ** k
353
+ phi_pk = (p - 1) * p ** (k - 1) # Euler's totient for prime power
354
+ d_pk = pow(e, -1, phi_pk)
355
+ m_pk = pow(c, d_pk, pk)
356
+ remainders.append(m_pk)
357
+ moduli.append(pk)
358
+
359
+ # Combine using CRT
360
+ m = chinese_remainder_theorem(moduli, remainders)[0]
361
+ ```
362
+
363
+ **Key insight:** When n has many small prime factors, compute `d mod phi(p^k)` for each prime power independently, decrypt mod each, then combine via CRT. Much faster than computing `d mod phi(n)` directly.
364
+
365
+ ---
366
+
367
+ ## RSA Timing Attack on Montgomery Reduction (DEF CON 2017)
368
+
369
+ **Pattern:** Recover RSA private key bits via Kocher's timing attack when the number of modular subtractions during Montgomery reduction is leaked.
370
+
371
+ ```python
372
+ # Montgomery multiplication: extra subtraction when result >= modulus
373
+ # Leaked: count of extra subtractions per signature operation
374
+ # Attack: predict subtraction count for each private key bit guess
375
+
376
+ # For each bit position i (MSB to LSB):
377
+ # Guess bit = 0: predict timing for square only
378
+ # Guess bit = 1: predict timing for square + multiply
379
+ # Compare predictions against observed timing data
380
+ # Correct guess produces statistically significant correlation
381
+
382
+ # ~200K signatures needed for 768-bit key recovery
383
+ # Attacking squaring reduction is more effective than multiply
384
+ import numpy as np
385
+ for bit_pos in range(key_bits):
386
+ for guess in [0, 1]:
387
+ predicted = predict_reductions(known_bits + [guess], messages)
388
+ correlation = np.corrcoef(predicted, observed)[0, 1]
389
+ known_bits.append(0 if corr_0 > corr_1 else 1)
390
+ ```
391
+
392
+ **Key insight:** Montgomery multiplication performs an extra conditional subtraction when the intermediate result exceeds the modulus. If this count leaks (via timing, power, or as in this CTF -- directly), each bit of the private exponent can be determined by comparing predicted vs. observed subtraction patterns across many operations.
393
+
394
+ **References:** DEF CON 2017
395
+
396
+ ---
397
+
398
+ ## Bleichenbacher Low-Exponent RSA Signature Forgery (Google CTF 2017)
399
+
400
+ **Pattern:** Forge PKCS#1 v1.5 RSA signatures when e=3 by constructing a value whose cube root produces valid padding without knowing the private key.
401
+
402
+ ```python
403
+ # PKCS#1 v1.5 signature format:
404
+ # 00 01 FF FF ... FF 00 [DigestInfo] [Hash]
405
+ # With e=3, forge signature s where s^3 has correct prefix
406
+
407
+ # Construct target block (2048-bit key, SHA-256):
408
+ # 00 01 FF ... FF 00 [SHA-256 DigestInfo] [hash] [garbage]
409
+ import gmpy2
410
+ prefix = b'\x00\x01' + b'\xff' * padding_len + b'\x00' + digest_info + hash_value
411
+ # Convert to integer, append zeros for garbage bytes
412
+ target = int.from_bytes(prefix + b'\x00' * garbage_len, 'big')
413
+ # Cube root (rounds down, garbage absorbs the remainder)
414
+ forged_sig = gmpy2.iroot(target, 3)[0] + 1 # +1 to round up
415
+ # Verify: forged_sig^3 starts with correct PKCS#1 prefix
416
+ ```
417
+
418
+ **Key insight:** PKCS#1 v1.5 signature verification checks that `sig^e mod n` starts with `00 01 FF...FF 00 DigestInfo Hash`. With e=3, an attacker computes the cube root of a carefully constructed value with the correct prefix and ignores trailing bytes. Implementations that don't verify the padding extends to the full block length (CVE-2006-4339) accept the forgery.
419
+
420
+ **References:** Google CTF 2017
421
+
422
+ ---
423
+
424
+ ## Coppersmith Small Roots for Linearly Related Primes (Tokyo Westerns 2017)
425
+
426
+ **Pattern:** RSA where `q = k*p + delta` for a known small constant `k` and unknown small `delta`. Since `p ≈ sqrt(N/k)`, approximate `q_approx = k * isqrt(N // k) + 2^512` as an upper bound. The univariate polynomial `F(x) = q_approx - x` has `delta` as a small root modulo `q` (which divides N). Coppersmith's method finds this root when `delta < N^(1/4)`.
427
+
428
+ ```python
429
+ from sage.all import *
430
+
431
+ N, e, c = ... # RSA parameters
432
+ k = 19 # known relationship: q = k*p + delta
433
+
434
+ # Approximate q from sqrt(N/k)
435
+ q_approx = k * isqrt(N // k) + 2**512
436
+
437
+ # Build univariate polynomial with q_approx as approximate root of N mod q
438
+ R.<x> = PolynomialRing(Zmod(N))
439
+ f = q_approx - x # root is delta = q_approx - q
440
+
441
+ # Coppersmith: find small root x = delta < N^0.25
442
+ roots = f.small_roots(X=2**512, beta=0.5)
443
+ if roots:
444
+ q = int(q_approx - roots[0])
445
+ p = N // q
446
+ assert p * q == N
447
+ phi = (p - 1) * (q - 1)
448
+ d = pow(e, -1, phi)
449
+ flag = long_to_bytes(pow(c, d, N))
450
+ ```
451
+
452
+ **Key insight:** When `q ≈ k*p`, approximately half the bits of `p` (and `q`) are recoverable from `sqrt(N/k)`. The remaining unknown `delta` is small enough for Coppersmith when `delta < N^(1/4)`. The upper bound `q_approx` must exceed `q`; add a safety margin of `2^(bitlen/2)` to ensure the root is captured.
453
+
454
+ **References:** Tokyo Westerns CTF 2017
455
+
456
+ ---
457
+
458
+ ## ROCA Attack on RSA CVE-2017-15361 (EasyCTF IV)
459
+
460
+ **Pattern:** Infineon RSA library generates keys with structured primes (detectable via fingerprint). Factor 512-bit keys in minutes:
461
+
462
+ ```bash
463
+ # Detect ROCA vulnerability
464
+ pip install roca-detect
465
+ roca-detect rsa_key.pub
466
+ # Factor with neca tool
467
+ git clone https://gitlab.com/jix/neca.git
468
+ cd neca && cargo build --release
469
+ ./target/release/neca <N_decimal>
470
+ # Or use: https://github.com/crocs-muni/roca (original research tool)
471
+ ```
472
+
473
+ **Key insight:** CVE-2017-15361 affects Infineon TPMs, smart cards, and YubiKey 4. Primes have the form `p = k * M + (65537^a mod M)` where M is the product of first n primes. Detection is instant (check `65537^x mod M` divides `n mod M`). Factoring uses Coppersmith's method on the known structure. Keys up to 2048 bits are practically attackable. For CTF use: if `roca-detect` reports vulnerable, use `neca` for 512-bit keys or the `crocs-muni/roca` tool for larger keys.
474
+
475
+ **References:** EasyCTF IV (2018)
476
+
477
+ ---
478
+
479
+ ### RSA Signature Bypass with e=1 and Crafted Modulus (BackdoorCTF 2018)
480
+
481
+ **Pattern:** Server generates RSA signature and asks for `(n, e)` to verify it. With `e=1`, `pow(s, 1, n) = s mod n`. Set `n = signature - PKCS1_pad(message)` so verification passes. (BackdoorCTF 2018)
482
+
483
+ ```python
484
+ e = 1
485
+ n = signature ** e - PKCS1_pad(h.hexdigest())
486
+ # Now pow(signature, 1, n) == PKCS1_pad(message)
487
+ ```
488
+
489
+ **Key insight:** When the verifier accepts user-supplied public key parameters without constraints, setting `e=1` makes modular exponentiation trivial. Choose `n` such that `signature mod n` equals the expected padded hash.
490
+
491
+ ---
492
+
493
+ ## Dependent-Prime RSA: q = e^-1 mod p (TokyoWesterns CTF 4th 2018)
494
+
495
+ **Pattern:** Key generation picks prime `p`, then sets `q = e^(-1) mod p` and rerolls until `q` is prime. The dependency `e*q ≡ 1 (mod p)` means `e*q = k*p + 1` for some small positive integer `k`, so `n = p*q = p*(k*p + 1)/e` — a quadratic in `p` that factors after enumerating a few candidate `k` values.
496
+
497
+ **Exploit:**
498
+ ```python
499
+ from sage.all import PolynomialRing, ZZ
500
+
501
+ def factor_dependent_n(n, e, max_k=100000):
502
+ P = PolynomialRing(ZZ, 'p'); p = P.gen()
503
+ for k in range(2, max_k, 2):
504
+ # e*q = k*p + 1 and n = p*q => e*n = p*(k*p + 1)
505
+ poly = k * p * p + p - e * n
506
+ roots = poly.roots()
507
+ for root, _ in roots:
508
+ if root > 1 and n % root == 0:
509
+ return int(root), n // int(root)
510
+ return None
511
+ ```
512
+
513
+ **Key insight:** Any key generator that derives `q` from `p` via a public arithmetic relation collapses RSA security to a small search. Write the relation as a polynomial `f_k(p) = 0` parameterized by a small integer, and use `.roots()` in Sage to recover `p`. The search space for `k` is typically under 2^16 because `q < p` forces `k ≈ e`.
514
+
515
+ **References:** TokyoWesterns CTF 4th 2018 — writeup 10862
516
+
517
+ ---
518
+
519
+ ## RSA Three-Key Pairwise GCD Triangle (Trend Micro 2018)
520
+
521
+ **Pattern:** Three RSA moduli `N1 = p1*p2`, `N2 = p1*p3`, `N3 = p2*p3` share primes pairwise (every pair has exactly one common factor). A single pairwise `gcd` reveals the shared prime, giving a complete factorisation of all three moduli without running batch-GCD.
522
+
523
+ ```python
524
+ from math import gcd
525
+
526
+ def factor_triangle(n1, n2, n3):
527
+ p1 = gcd(n1, n2) # shared between N1 and N2
528
+ p2 = gcd(n1, n3) # shared between N1 and N3
529
+ p3 = gcd(n2, n3) # shared between N2 and N3
530
+ assert n1 == p1 * p2 and n2 == p1 * p3 and n3 == p2 * p3
531
+ return p1, p2, p3
532
+ ```
533
+
534
+ **Key insight:** Batch-GCD handles arbitrary sets of moduli that might share a prime, but the three-key triangle is a closed-form case: every prime appears in exactly two moduli, so three `gcd()` calls factor all three keys. Spot the pattern when a challenge hands you exactly three public keys with matching bit-lengths and no other hint — contrast with [batch GCD for shared prime factoring](#batch-gcd-for-shared-prime-factoring-bsidessf-2025), which is the general case over larger key sets.
535
+
536
+ **References:** Trend Micro CTF 2018 — Offensive-Analysis 300, writeup 11129
537
+
538
+ ---
539
+
540
+ ## RSA n = p^2*q Schmidt-Samoa Variant (ASIS Finals 2018)
541
+
542
+ **Pattern:** Modulus is generated as `n = p*p*q` (not the usual `p*q`). Naive `phi = (p-1)*(q-1)` is wrong — real totient is `phi = p*(p-1)*(q-1)`. If `gcd(e, phi) != 1`, reduce ciphertext to smaller field `q` and invert there.
543
+
544
+ ```python
545
+ phi = p*(p-1)*(q-1)
546
+ # Reduce enc to mod-q by inverse_mod(q, phi)
547
+ qinv = inverse_mod(q, phi)
548
+ enc = pow(enc, qinv, n) % q
549
+ # Now m < q; invert e*p^2 over phi(q) = q-1
550
+ pinv = inverse_mod(p*p, q-1)
551
+ m = pow(enc, pinv, q)
552
+ ```
553
+
554
+ **Key insight:** Spot `n = p^2*q` by trying `iroot(n, 3)` near its cube root and running Pollard/Lenstra on small fraction of `n`. Once factored, switch to the correct totient and field before inversion.
555
+
556
+ **References:** ASIS CTF Finals 2018 — John-Bull, writeup 12401
557
+
558
+ ---
559
+
560
+ ## Modulus Recovery via GCD of Encryption Residuals (X-MAS CTF 2018)
561
+
562
+ **Pattern:** Oracle encrypts arbitrary plaintexts with a hidden RSA key but does not disclose `n`. Compute `m^e - enc(m)` for two plaintexts; both differences are multiples of `n`, so their GCD equals `n` (or a small multiple).
563
+
564
+ ```python
565
+ e = 65537
566
+ r1 = bytes_to_long(b'a')**e - encrypt(b'a')
567
+ r2 = bytes_to_long(b'b')**e - encrypt(b'b')
568
+ n = gcd(r1, r2)
569
+ ```
570
+
571
+ **Key insight:** Works whenever the oracle is a black box but accepts chosen plaintexts. Strip small prime factors from the GCD to recover the true `n`.
572
+
573
+ **References:** X-MAS CTF 2018 — Santa's list, writeups 12701-12800
574
+
575
+ ---
576
+
577
+ ## Textbook RSA Negation via encrypt(-1) (X-MAS CTF 2018)
578
+
579
+ **Pattern:** Decrypt oracle refuses to decrypt the target ciphertext directly but accepts multiplications. Because `(-1)^e ≡ -1 (mod n)`, multiplying by `pow(-1, e, n)` flips the plaintext sign modulo `n`.
580
+
581
+ ```python
582
+ ct_mutated = (ct_flag * pow(-1, e, n)) % n
583
+ plaintext = (-decrypt(ct_mutated)) % n
584
+ ```
585
+
586
+ **Key insight:** Any odd `e` makes `encrypt(-1) = -1 mod n`, so the oracle returns `-m mod n` for the original ciphertext even if the original `m` is blacklisted.
587
+
588
+ **References:** X-MAS CTF 2018 — Santa's list 2.0, writeups 12701-12800
589
+
590
+ ---
591
+
592
+ ## Poly-Exponent RSA: GCD of p^p Combinations (ASIS Finals 2018)
593
+
594
+ **Pattern:** Challenge publishes several linear/polynomial combinations of `p` and `q`, e.g. `c1 = p^p mod q`, `c2 = (p+q)^(p+q) mod n`. Form two expressions both divisible by the target prime, then take their GCD to factor `n`.
595
+
596
+ ```python
597
+ p = gcd(c2*c4 - c3, pow(c4, c4, c2*c4 - c3) - c3)
598
+ q = gcd(c1*c4 - c3, pow(c4, c4, c1*c4 - c3) - c3)
599
+ for x in small_primes:
600
+ while p % x == 0: p //= x
601
+ ```
602
+
603
+ **Key insight:** If two quantities share a secret factor but differ only by known scalars, `gcd(a, b)` recovers that factor. Strip cofactors with trial division on small primes.
604
+
605
+ **References:** ASIS CTF Finals 2018 — NMC, writeup 12427
606
+
607
+ ---
608
+
609
+ ## Biased LSB Oracle with Mode-of-Runs Recovery (CSAW CTF 2018)
610
+
611
+ **Pattern:** LSB oracle leaks the least significant bit of `m * 2^i mod n`, but the binary search converges inconsistently (oracle gives slightly wrong answers at random). Run the full recovery loop many times and take the per-byte mode across runs — correct bytes appear most often even when the full plaintext never converges.
612
+
613
+ ```python
614
+ def recover():
615
+ beg, end = 0, n - 1
616
+ for bit in bits:
617
+ mid = (beg + end) // 2
618
+ if bit: beg = mid
619
+ else: end = mid
620
+ return long_to_bytes(end)
621
+
622
+ byte_counts = [Counter() for _ in range(flag_len)]
623
+ for _ in range(N):
624
+ flag = recover()
625
+ for i, b in enumerate(flag):
626
+ byte_counts[i][b] += 1
627
+ flag = bytes(c.most_common(1)[0][0] for c in byte_counts)
628
+ ```
629
+
630
+ **Key insight:** Noisy side channels still leak the correct byte per position if each run independently biases toward the true value. Vote across runs rather than trying to perfect a single decryption.
631
+
632
+ **References:** CSAW CTF 2018 — Lost Mind, writeup 12490
633
+
634
+ ---
635
+
636
+ ## Cube-Root Wraparound via AES-CTR Length Hint (hxp 2018)
637
+
638
+ **Pattern:** Low-exponent RSA (`e = 3`) with padded plaintext where `m^3 > n`, so naive cube root fails. AES-CTR ciphertext reveals the plaintext length (CTR preserves length), so you know exactly how many `n` to add. Rescale with `inverse(2, n)^k` to cancel padding shifts, then try `iroot(c + k*n, 3)` for small `k`.
639
+
640
+ ```python
641
+ inv = pow(inverse(2, n), 2040, n)
642
+ c = c * inv % n
643
+ for k in range(1000):
644
+ m, ok = gmpy2.iroot(c + k*n, 3)
645
+ if ok: flag = long_to_bytes(int(m)); break
646
+ ```
647
+
648
+ **Key insight:** When padding shifts the plaintext past `n`, the correct root lies in `c + k*n` for some small `k`; AES-CTR leaks the exact plaintext length so you can size the search.
649
+
650
+ **References:** hxp CTF 2018 — daring, writeup 12588
651
+
652
+ ---
653
+
654
+ ## RSA p = next_prime(2^k + small) Shared-Prime Batch GCD (ASIS Finals 2018)
655
+
656
+ **Pattern:** Key generator constructs `p = next_prime(2^k + random_small_delta)` where `delta` is bounded by a few bits. Two independent keys generated with the same `k` converge on the same next prime whenever their `delta` values fall between the same two consecutive primes — common enough that any pair of collected moduli has `gcd(n1, n2) > 1`.
657
+
658
+ ```python
659
+ from math import gcd
660
+ for n_a in collected:
661
+ for n_b in collected:
662
+ if n_a == n_b: continue
663
+ p = gcd(n_a, n_b)
664
+ if 1 < p < n_a:
665
+ q = n_a // p
666
+ d = pow(e + 2, -1, (p-1)*(q-1)) # NMC-style
667
+ break
668
+ ```
669
+
670
+ **Key insight:** Any RSA keygen that sources primes from a low-entropy neighborhood (`next_prime(constant + small)`, `2^k + random_byte`, `Mersenne ± delta`) collapses to pairwise GCD because the `next_prime` step collapses many deltas onto the same prime.
671
+
672
+ **References:** ASIS CTF Finals 2018 — Ariogen, writeup 12414
673
+
674
+ ---
675
+
676
+ ## PNG Encryption Bounded by 512-bit Key → Trailer Replacement (ASIS Finals 2018)
677
+
678
+ **Pattern:** Custom "polynomial bit sum" cipher encrypts PNG bytes as `C = sum(bit_i * (exp^i + (-1)^i))`. The key length (≤512 bits) means at most 64 bytes of plaintext are affected. Since PNG's trailing IDAT + IEND chunks are format-recoverable from any reference image, simply splice a valid trailer onto the decrypted prefix.
679
+
680
+ ```python
681
+ # Only the first 64 bytes of output depend on the key; the rest matches plaintext bit-for-bit
682
+ first64 = decrypt_affected_prefix(ct)
683
+ rest = original_png[64:] # copy from any reference PNG with same image data
684
+ open('recovered.png', 'wb').write(first64 + rest)
685
+ ```
686
+
687
+ **Key insight:** Whenever the key size caps the *number of affected bytes*, tail-of-file reconstruction beats full cryptanalysis. Works for any format (PNG, ZIP, MP3, PDF) where the tail has standard chunks or a parser tolerates garbage.
688
+
689
+ **References:** ASIS CTF Finals 2018 — Made by baby, writeup 12423
690
+
691
+ ---
692
+
693
+ ## Modulus Recovery via Plaintext Malleability (X-MAS 2018)
694
+
695
+ **Pattern:** Oracle decrypts ciphertext and returns the plaintext but hides the modulus `N`. Send two related ciphertexts `c` and `c * 2^e`. The decrypted plaintexts are `m` and `(2m) mod N`. If `2*m != (2m mod N)`, the difference `2m - (2m mod N) = N` recovers the modulus.
696
+
697
+ ```python
698
+ m1 = decrypt(ct) # m
699
+ m2 = decrypt((ct * pow(2, e, m1)) % m1) # 2m mod N
700
+ N = 2*m1 - m2 if 2*m1 != m2 else None
701
+ ```
702
+
703
+ **Key insight:** RSA's multiplicative homomorphism leaks the modulus whenever you can send `c * k^e` and observe the wraparound. Use any small multiplier that nudges the plaintext past `N`.
704
+
705
+ **References:** X-MAS CTF 2018 — Santa's list, writeup 12658
706
+
707
+ ---
708
+
709
+ ## RSA CRT d_p NULL-Byte Overflow Primes Leak (P.W.N. CTF 2018)
710
+
711
+ **Pattern:** Server reads `d_p` into a buffer via `fgets()` that lacks a bounds check. Sending 33+ null bytes NULL-terminates `d_p_str` so the parsed integer is 0. The CRT path computes `m_2 = 0^0 = 1`, then signs `m - m_2 = m - 1`, which when squared in the gcd step yields `p = gcd(sig, N)`.
712
+
713
+ ```python
714
+ io.send(b'\x00' * 40) # overflow d_p buffer
715
+ sig = io.recvline()
716
+ p = gcd(sig - 1, N) # factored!
717
+ ```
718
+
719
+ **Key insight:** Any CRT-based RSA implementation that parses private key fields from network input is vulnerable to "make `d_p` equal to 0" tricks. The gcd of a forged signature and the modulus reveals a prime.
720
+
721
+ **References:** P.W.N. CTF 2018 — City RSA, writeup 12052
722
+
723
+ ---
724
+
725
+ ## Textbook RSA Signature Blinding via Message Factoring (P.W.N. CTF 2018)
726
+
727
+ **Pattern:** Unpadded RSA refuses to sign certain messages (blacklist), but multiplicative homomorphism lets you decompose the forbidden message `m` into coprime factors `m = x * y mod N`, request signatures for `x` and `y` separately, and combine: `sig(m) = sig(x) * sig(y) mod N`.
728
+
729
+ ```python
730
+ for x in range(2, 10000):
731
+ if (m * pow(x, -1, N)) % N == allowed_y:
732
+ y = allowed_y
733
+ sig = (sign(x) * sign(y)) % N
734
+ break
735
+ ```
736
+
737
+ **Key insight:** Any unpadded RSA signing oracle is vulnerable to factoring-based blinding. The attacker only needs two valid-looking messages whose product equals the target.
738
+
739
+ **References:** P.W.N. CTF 2018 — City RSA, writeup 12052
740
+
741
+ ---
742
+
743
+ ## Last-Byte Modulus Overwrite via strlen-1 Null Truncation (OTW Advent 2018)
744
+
745
+ **Pattern:** Server truncates `username[strlen(username)-1] = 0` to strip the trailing newline. Passing an empty username writes the null byte to the byte *before* the buffer — which turns out to be the last byte of `N`. The modified `N` is composite in a small way (differs from the real `N` by at most 255) and is factorable by Carmichael or `sage.factor`.
746
+
747
+ ```python
748
+ io.sendline(b'') # empty username overwrites N[-1]
749
+ N_corrupt = recv_N()
750
+ for delta in range(256):
751
+ if is_factorable(N_corrupt + delta - 255):
752
+ N_real = N_corrupt + delta - 255; break
753
+ ```
754
+
755
+ **Key insight:** "Strip the last character" idioms (`buf[strlen-1] = 0`) are classic off-by-one when `strlen == 0`. They write one byte backwards into whatever sits before the buffer — often a crypto constant.
756
+
757
+ **References:** OverTheWire Advent Bonanza 2018 — Day 14, writeup 12752
758
+
759
+ ---
760
+
761
+ ## CRC32 Collision Oracle + RSA Homomorphic Signature Forgery (BSidesSF 2019)
762
+
763
+ **Pattern (rsaos):** Shell exposes `RSA(foldhash(cmd))` as a "signature" where `foldhash` is a 10-byte digest built from a CRC-like, factorable fold. Privileged commands are blocked, but signatures for arbitrary strings are handed out. Pick a target privileged command whose fold value factors entirely into primes `< 2^32`. For each small factor `f_i`, use [crchack](https://github.com/resilar/crchack) to craft an innocuous command whose CRC32 equals `f_i` exactly, get its signature, and multiply them modulo `N`. RSA's multiplicativity means the product is the signature of the fold *product*, which is the target fold.
764
+
765
+ ```python
766
+ from primefac import primefac
767
+ import subprocess, random
768
+
769
+ def find_cmd_crc(target_crc):
770
+ while True:
771
+ open('/tmp/cmd', 'w').write(f'echo {random.randint(0, 10000)}')
772
+ cmd = subprocess.check_output(['./crchack/crchack', '/tmp/cmd', hex(target_crc)])
773
+ if b'\n' not in cmd:
774
+ return cmd
775
+
776
+ def find_cmd_fac(priv):
777
+ while True:
778
+ c = f'{priv} {random.randint(0, 10000)}'.encode()
779
+ fs = list(primefac(foldhash(c)))
780
+ if all(f < 2**32 for f in fs):
781
+ return c, fs
782
+
783
+ priv_cmd, factors = find_cmd_fac('get-flag')
784
+ t = 1
785
+ for f in factors: # each factor fits in CRC32 range
786
+ cmd = find_cmd_crc(f)
787
+ _, sig = get_sig(cmd) # oracle signs arbitrary cmd
788
+ t = (t * sig) % N # RSA: s1*s2 = sign(h1*h2)
789
+ send_priv(priv_cmd, sig=hex(t % N)) # forged signature for get-flag
790
+ ```
791
+
792
+ **Key insight:** Textbook RSA signatures are multiplicative: `sign(a) * sign(b) = sign(a*b) mod N`. If the "hash" is actually a linear/factorable function (CRC32, fold-XOR), factor the target digest into pieces small enough to fit in CRC output space, then use a CRC collision finder (`crchack`) to realise each factor as an innocuous message the oracle will sign. Multiply the signatures mod `N` to forge the privileged signature. Works for any signature scheme over a hash that is both homomorphic-friendly *and* collidable to specific targets.