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.
- package/AGENTS.md +131 -0
- package/Dockerfile +192 -0
- package/README.md +522 -0
- package/agents/ctf-analyzer.md +58 -0
- package/agents/ctf-controller.md +151 -0
- package/agents/ctf-speedrun.md +74 -0
- package/bin/ctf-agent.js +119 -0
- package/docker-compose.yml +13 -0
- package/mcp_config.json +19 -0
- package/package.json +44 -0
- package/references/ctf-triage-ladder.md +68 -0
- package/references/exploit-databases.md +575 -0
- package/references/llm-safety-and-policy-compliance.md +173 -0
- package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
- package/references/security-events-and-intelligence.md +566 -0
- package/references/version-matrix.md +59 -0
- package/rules/ctf-execution-rules.md +44 -0
- package/rules/ctf-safety-framing-rules.md +140 -0
- package/rules/flag-validation-rules.md +25 -0
- package/scripts/__init__.py +1 -0
- package/scripts/ctf-tools.lock +205 -0
- package/scripts/ctf_init.py +1026 -0
- package/scripts/ctf_update.py +583 -0
- package/scripts/ctfd_client.py +81 -0
- package/scripts/cve_lookup.py +453 -0
- package/scripts/extract_flags.py +155 -0
- package/scripts/install_as_agent.py +278 -0
- package/scripts/install_ctf_tools.sh +603 -0
- package/scripts/parallel_triage.py +395 -0
- package/scripts/prompt_policy_sanitizer.py +283 -0
- package/scripts/scope_guard.py +444 -0
- package/scripts/skill_validator.py +504 -0
- package/scripts/workspace_cleaner.py +141 -0
- package/skills/ctf-ai-ml/SKILL.md +120 -0
- package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
- package/skills/ctf-ai-ml/llm-attacks.md +487 -0
- package/skills/ctf-ai-ml/model-attacks.md +422 -0
- package/skills/ctf-crypto/SKILL.md +320 -0
- package/skills/ctf-crypto/advanced-math.md +798 -0
- package/skills/ctf-crypto/classic-ciphers.md +651 -0
- package/skills/ctf-crypto/ecc-attacks.md +347 -0
- package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
- package/skills/ctf-crypto/exotic-crypto.md +528 -0
- package/skills/ctf-crypto/historical.md +113 -0
- package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
- package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
- package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
- package/skills/ctf-crypto/modern-ciphers.md +649 -0
- package/skills/ctf-crypto/prng-attacks.md +257 -0
- package/skills/ctf-crypto/prng.md +664 -0
- package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
- package/skills/ctf-crypto/rsa-attacks.md +487 -0
- package/skills/ctf-crypto/stream-ciphers.md +390 -0
- package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
- package/skills/ctf-forensics/3d-printing.md +121 -0
- package/skills/ctf-forensics/SKILL.md +379 -0
- package/skills/ctf-forensics/disk-advanced.md +497 -0
- package/skills/ctf-forensics/disk-and-memory.md +491 -0
- package/skills/ctf-forensics/disk-recovery.md +699 -0
- package/skills/ctf-forensics/linux-forensics.md +511 -0
- package/skills/ctf-forensics/network-advanced.md +583 -0
- package/skills/ctf-forensics/network.md +645 -0
- package/skills/ctf-forensics/peripheral-capture.md +287 -0
- package/skills/ctf-forensics/signals-and-hardware.md +713 -0
- package/skills/ctf-forensics/steganography.md +694 -0
- package/skills/ctf-forensics/stego-advanced-2.md +475 -0
- package/skills/ctf-forensics/stego-advanced.md +481 -0
- package/skills/ctf-forensics/stego-image.md +691 -0
- package/skills/ctf-forensics/windows.md +625 -0
- package/skills/ctf-malware/SKILL.md +181 -0
- package/skills/ctf-malware/c2-and-protocols.md +274 -0
- package/skills/ctf-malware/pe-and-dotnet.md +108 -0
- package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
- package/skills/ctf-misc/SKILL.md +498 -0
- package/skills/ctf-misc/bashjails.md +323 -0
- package/skills/ctf-misc/ctfd-navigation.md +465 -0
- package/skills/ctf-misc/dns.md +255 -0
- package/skills/ctf-misc/encodings-advanced.md +504 -0
- package/skills/ctf-misc/encodings.md +431 -0
- package/skills/ctf-misc/games-and-vms-2.md +254 -0
- package/skills/ctf-misc/games-and-vms-3.md +690 -0
- package/skills/ctf-misc/games-and-vms-4.md +229 -0
- package/skills/ctf-misc/games-and-vms.md +529 -0
- package/skills/ctf-misc/linux-privesc.md +333 -0
- package/skills/ctf-misc/pyjails.md +671 -0
- package/skills/ctf-misc/rf-sdr.md +91 -0
- package/skills/ctf-osint/SKILL.md +198 -0
- package/skills/ctf-osint/geolocation-and-media.md +464 -0
- package/skills/ctf-osint/social-media.md +312 -0
- package/skills/ctf-osint/web-and-dns.md +341 -0
- package/skills/ctf-pwn/SKILL.md +214 -0
- package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
- package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
- package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
- package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
- package/skills/ctf-pwn/advanced-exploits.md +773 -0
- package/skills/ctf-pwn/advanced.md +326 -0
- package/skills/ctf-pwn/field-notes.md +245 -0
- package/skills/ctf-pwn/format-string.md +694 -0
- package/skills/ctf-pwn/heap-fsop.md +285 -0
- package/skills/ctf-pwn/heap-techniques-2.md +333 -0
- package/skills/ctf-pwn/heap-techniques.md +513 -0
- package/skills/ctf-pwn/kernel-bypass.md +421 -0
- package/skills/ctf-pwn/kernel-techniques.md +366 -0
- package/skills/ctf-pwn/kernel.md +636 -0
- package/skills/ctf-pwn/overflow-basics.md +611 -0
- package/skills/ctf-pwn/rop-advanced.md +725 -0
- package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
- package/skills/ctf-pwn/sandbox-escape.md +313 -0
- package/skills/ctf-reverse/SKILL.md +163 -0
- package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
- package/skills/ctf-reverse/anti-analysis.md +693 -0
- package/skills/ctf-reverse/field-notes.md +376 -0
- package/skills/ctf-reverse/languages-compiled.md +666 -0
- package/skills/ctf-reverse/languages-platforms.md +592 -0
- package/skills/ctf-reverse/languages.md +553 -0
- package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
- package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
- package/skills/ctf-reverse/patterns-ctf.md +670 -0
- package/skills/ctf-reverse/patterns-runtime.md +274 -0
- package/skills/ctf-reverse/patterns.md +572 -0
- package/skills/ctf-reverse/platforms-hardware.md +387 -0
- package/skills/ctf-reverse/platforms.md +664 -0
- package/skills/ctf-reverse/tools-advanced-2.md +421 -0
- package/skills/ctf-reverse/tools-advanced.md +407 -0
- package/skills/ctf-reverse/tools-dynamic.md +679 -0
- package/skills/ctf-reverse/tools-emulation.md +319 -0
- package/skills/ctf-reverse/tools.md +573 -0
- package/skills/ctf-web/SKILL.md +153 -0
- package/skills/ctf-web/auth-and-access-2.md +82 -0
- package/skills/ctf-web/auth-and-access.md +783 -0
- package/skills/ctf-web/auth-infra.md +321 -0
- package/skills/ctf-web/auth-jwt.md +186 -0
- package/skills/ctf-web/client-side-advanced.md +739 -0
- package/skills/ctf-web/client-side.md +529 -0
- package/skills/ctf-web/cves.md +373 -0
- package/skills/ctf-web/field-notes.md +482 -0
- package/skills/ctf-web/node-and-prototype.md +200 -0
- package/skills/ctf-web/server-side-2.md +337 -0
- package/skills/ctf-web/server-side-advanced-2.md +559 -0
- package/skills/ctf-web/server-side-advanced-3.md +125 -0
- package/skills/ctf-web/server-side-advanced-4.md +480 -0
- package/skills/ctf-web/server-side-advanced.md +378 -0
- package/skills/ctf-web/server-side-deser.md +443 -0
- package/skills/ctf-web/server-side-exec-2.md +799 -0
- package/skills/ctf-web/server-side-exec.md +457 -0
- package/skills/ctf-web/server-side.md +629 -0
- package/skills/ctf-web/sql-injection.md +790 -0
- package/skills/ctf-web/web3.md +374 -0
- package/skills/ctf-writeup/SKILL.md +90 -0
- package/skills/solve-challenge/SKILL.md +269 -0
- package/skills.json +16 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# CTF Crypto - RSA Attacks
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Small Public Exponent (Cube Root)](#small-public-exponent-cube-root)
|
|
5
|
+
- [Common Modulus Attack](#common-modulus-attack)
|
|
6
|
+
- [Wiener's Attack (Small Private Exponent)](#wieners-attack-small-private-exponent)
|
|
7
|
+
- [Pollard's p-1 Factorization](#pollards-p-1-factorization)
|
|
8
|
+
- [Hastad's Broadcast Attack](#hastads-broadcast-attack)
|
|
9
|
+
- [RSA with Consecutive Primes (Fermat Factorization)](#rsa-with-consecutive-primes-fermat-factorization)
|
|
10
|
+
- [Multi-Prime RSA](#multi-prime-rsa)
|
|
11
|
+
- [RSA with Restricted-Digit Primes (LACTF 2026)](#rsa-with-restricted-digit-primes-lactf-2026)
|
|
12
|
+
- [Coppersmith for Structured RSA Primes (LACTF 2026)](#coppersmith-for-structured-rsa-primes-lactf-2026)
|
|
13
|
+
- [Manger's RSA Padding Oracle Attack (Nullcon 2026)](#mangers-rsa-padding-oracle-attack-nullcon-2026)
|
|
14
|
+
- [Manger's Attack on RSA-OAEP via Timing Oracle (HTB Early Bird)](#mangers-attack-on-rsa-oaep-via-timing-oracle-htb-early-bird)
|
|
15
|
+
- [Polynomial Hash with Trivial Root (Pragyan 2026)](#polynomial-hash-with-trivial-root-pragyan-2026)
|
|
16
|
+
- [Polynomial CRT in GF(2)\[x\] (Nullcon 2026)](#polynomial-crt-in-gf2x-nullcon-2026)
|
|
17
|
+
- [Affine Cipher over Non-Prime Modulus (Nullcon 2026)](#affine-cipher-over-non-prime-modulus-nullcon-2026)
|
|
18
|
+
- [Hastad Broadcast Attack with Linear Padding -- Coppersmith (PlaidCTF 2017)](#hastad-broadcast-attack-with-linear-padding----coppersmith-plaidctf-2017)
|
|
19
|
+
- [Franklin-Reiter Related Message Attack on RSA e=3 (N1CTF 2018)](#franklin-reiter-related-message-attack-on-rsa-e3-n1ctf-2018)
|
|
20
|
+
- [Coppersmith Attack on Linearly-Related RSA Primes (ASIS CTF 2018)](#coppersmith-attack-on-linearly-related-rsa-primes-asis-ctf-2018)
|
|
21
|
+
- [rsa-attacks-2.md: RSA p=q Validation Bypass (BearCatCTF 2026)](rsa-attacks-2.md#rsa-pq-validation-bypass-bearcatctf-2026)
|
|
22
|
+
- [rsa-attacks-2.md: RSA Cube Root CRT when gcd(e, phi) > 1 (BearCatCTF 2026)](rsa-attacks-2.md#rsa-cube-root-crt-when-gcde-phi--1-bearcatctf-2026)
|
|
23
|
+
- [rsa-attacks-2.md: Factoring n from Multiple of phi(n) (BearCatCTF 2026)](rsa-attacks-2.md#factoring-n-from-multiple-of-phin-bearcatctf-2026)
|
|
24
|
+
- [rsa-attacks-2.md: RSA Signature Forgery via Multiplicative Homomorphism (MMA CTF 2015)](rsa-attacks-2.md#rsa-signature-forgery-via-multiplicative-homomorphism-mma-ctf-2015)
|
|
25
|
+
- [rsa-attacks-2.md: Weak RSA Key Generation via Base Representation (Sharif CTF 2016)](rsa-attacks-2.md#weak-rsa-key-generation-via-base-representation-sharif-ctf-2016)
|
|
26
|
+
- [rsa-attacks-2.md: RSA with gcd(e, phi(n)) > 1 (CSAW 2015)](rsa-attacks-2.md#rsa-with-gcde-phin--1-csaw-2015)
|
|
27
|
+
- [rsa-attacks-2.md: Batch GCD for Shared Prime Factoring (BSidesSF 2025)](rsa-attacks-2.md#batch-gcd-for-shared-prime-factoring-bsidessf-2025)
|
|
28
|
+
- [rsa-attacks-2.md: RSA Partial Key Recovery from dp dq qinv (0CTF 2016)](rsa-attacks-2.md#rsa-partial-key-recovery-from-dp-dq-qinv-0ctf-2016)
|
|
29
|
+
- [rsa-attacks-2.md: RSA-CRT Fault Attack / Bit-Flip Recovery (CSAW CTF 2016)](rsa-attacks-2.md#rsa-crt-fault-attack--bit-flip-recovery-csaw-ctf-2016)
|
|
30
|
+
- [rsa-attacks-2.md: RSA Homomorphic Decryption Oracle Bypass (ECTF 2016)](rsa-attacks-2.md#rsa-homomorphic-decryption-oracle-bypass-ectf-2016)
|
|
31
|
+
- [rsa-attacks-2.md: RSA with Small Prime Factors and CRT Decomposition (Hack The Vote 2016)](rsa-attacks-2.md#rsa-with-small-prime-factors-and-crt-decomposition-hack-the-vote-2016)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Small Public Exponent (Cube Root)
|
|
36
|
+
|
|
37
|
+
**Pattern:** Small `e` (typically 3) with small message. When `m^e < n`, the ciphertext is just `m^e` without modular reduction — take the integer eth root.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import gmpy2
|
|
41
|
+
|
|
42
|
+
def small_e_attack(c, e):
|
|
43
|
+
"""Recover plaintext when m^e < n (no modular wrap)."""
|
|
44
|
+
m, exact = gmpy2.iroot(c, e)
|
|
45
|
+
if exact:
|
|
46
|
+
return int(m)
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
# Usage
|
|
50
|
+
m = small_e_attack(c, e=3)
|
|
51
|
+
print(bytes.fromhex(hex(m)[2:]))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**When it fails:** If `m^e > n` (message padded or large), the modular reduction destroys the simple root. In that case, try Hastad's broadcast attack or Coppersmith's short-pad attack.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Common Modulus Attack
|
|
59
|
+
|
|
60
|
+
**Pattern:** Same message encrypted with same `n` but two different public exponents `e1`, `e2` where `gcd(e1, e2) = 1`. Recover plaintext without factoring `n`.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from math import gcd
|
|
64
|
+
|
|
65
|
+
def common_modulus_attack(c1, c2, e1, e2, n):
|
|
66
|
+
"""Recover plaintext from two encryptions with same n, coprime e1/e2."""
|
|
67
|
+
# Extended GCD: find a, b such that a*e1 + b*e2 = 1
|
|
68
|
+
def extended_gcd(a, b):
|
|
69
|
+
if a == 0: return b, 0, 1
|
|
70
|
+
g, x, y = extended_gcd(b % a, a)
|
|
71
|
+
return g, y - (b // a) * x, x
|
|
72
|
+
|
|
73
|
+
g, a, b = extended_gcd(e1, e2)
|
|
74
|
+
assert g == 1, "e1 and e2 must be coprime"
|
|
75
|
+
|
|
76
|
+
# m = c1^a * c2^b mod n
|
|
77
|
+
# Handle negative exponent by using modular inverse
|
|
78
|
+
if a < 0:
|
|
79
|
+
c1 = pow(c1, -1, n)
|
|
80
|
+
a = -a
|
|
81
|
+
if b < 0:
|
|
82
|
+
c2 = pow(c2, -1, n)
|
|
83
|
+
b = -b
|
|
84
|
+
m = (pow(c1, a, n) * pow(c2, b, n)) % n
|
|
85
|
+
return m
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Key insight:** Two encryptions of the same message under the same modulus but different exponents leak the plaintext via Bezout's identity. No factoring required.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Wiener's Attack (Small Private Exponent)
|
|
93
|
+
|
|
94
|
+
**Pattern:** Private exponent `d` is small (d < N^0.25). The continued fraction expansion of `e/n` reveals `d`.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
def wiener_attack(e, n):
|
|
98
|
+
"""Recover d when d < N^0.25 using continued fraction expansion of e/n."""
|
|
99
|
+
def continued_fraction(num, den):
|
|
100
|
+
cf = []
|
|
101
|
+
while den:
|
|
102
|
+
q, r = divmod(num, den)
|
|
103
|
+
cf.append(q)
|
|
104
|
+
num, den = den, r
|
|
105
|
+
return cf
|
|
106
|
+
|
|
107
|
+
def convergents(cf):
|
|
108
|
+
convs = []
|
|
109
|
+
h0, h1 = 0, 1
|
|
110
|
+
k0, k1 = 1, 0
|
|
111
|
+
for a in cf:
|
|
112
|
+
h0, h1 = h1, a * h1 + h0
|
|
113
|
+
k0, k1 = k1, a * k1 + k0
|
|
114
|
+
convs.append((h1, k1))
|
|
115
|
+
return convs
|
|
116
|
+
|
|
117
|
+
cf = continued_fraction(e, n)
|
|
118
|
+
for k, d in convergents(cf):
|
|
119
|
+
if k == 0:
|
|
120
|
+
continue
|
|
121
|
+
# Check if d is valid: phi = (e*d - 1) / k must be integer
|
|
122
|
+
if (e * d - 1) % k != 0:
|
|
123
|
+
continue
|
|
124
|
+
phi = (e * d - 1) // k
|
|
125
|
+
# phi = (p-1)(q-1) = n - p - q + 1, so p+q = n - phi + 1
|
|
126
|
+
s = n - phi + 1
|
|
127
|
+
# p and q are roots of x^2 - s*x + n = 0
|
|
128
|
+
discriminant = s * s - 4 * n
|
|
129
|
+
if discriminant < 0:
|
|
130
|
+
continue
|
|
131
|
+
from math import isqrt
|
|
132
|
+
t = isqrt(discriminant)
|
|
133
|
+
if t * t == discriminant:
|
|
134
|
+
return d
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
# Usage
|
|
138
|
+
d = wiener_attack(e, n)
|
|
139
|
+
m = pow(c, d, n)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**When to use:** Very large `e` (close to `n`) often indicates small `d`. Also try `owiener` Python package: `pip install owiener`.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Pollard's p-1 Factorization
|
|
147
|
+
|
|
148
|
+
**Pattern:** One prime factor `p` has a smooth `p-1` (all prime factors of `p-1` are small). Compute `a^(B!) mod n`; GCD with `n` reveals `p`.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from math import gcd
|
|
152
|
+
|
|
153
|
+
def pollard_p1(n, B=100000):
|
|
154
|
+
"""Factor n when p-1 is B-smooth for some prime factor p."""
|
|
155
|
+
a = 2
|
|
156
|
+
for j in range(2, B + 1):
|
|
157
|
+
a = pow(a, j, n)
|
|
158
|
+
d = gcd(a - 1, n)
|
|
159
|
+
if 1 < d < n:
|
|
160
|
+
return d, n // d
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
# Usage
|
|
164
|
+
result = pollard_p1(n)
|
|
165
|
+
if result:
|
|
166
|
+
p, q = result
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Key insight:** By Fermat's little theorem, if `p-1` divides `B!`, then `a^(B!) ≡ 1 (mod p)`, so `gcd(a^(B!) - 1, n)` gives `p`. Increase `B` for larger smooth bounds. CTF primes generated with `getStrongPrime()` or similar are resistant.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Hastad's Broadcast Attack
|
|
174
|
+
|
|
175
|
+
**Pattern:** Same plaintext `m` encrypted with `e` different public keys (all with exponent `e`, typically `e=3`). Use CRT to reconstruct `m^e`, then take the eth root.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from functools import reduce
|
|
179
|
+
|
|
180
|
+
def hastad_broadcast(ciphertexts, moduli, e):
|
|
181
|
+
"""Recover m from e encryptions with the same exponent e."""
|
|
182
|
+
assert len(ciphertexts) >= e and len(moduli) >= e
|
|
183
|
+
|
|
184
|
+
# Chinese Remainder Theorem
|
|
185
|
+
def crt(remainders, moduli):
|
|
186
|
+
N = reduce(lambda a, b: a * b, moduli)
|
|
187
|
+
result = 0
|
|
188
|
+
for r, m in zip(remainders, moduli):
|
|
189
|
+
Ni = N // m
|
|
190
|
+
Mi = pow(Ni, -1, m)
|
|
191
|
+
result += r * Ni * Mi
|
|
192
|
+
return result % N
|
|
193
|
+
|
|
194
|
+
# CRT gives m^e (mod N1*N2*...*Ne)
|
|
195
|
+
# Since m < each Ni, m^e < N1*N2*...*Ne, so no modular reduction occurred
|
|
196
|
+
me = crt(ciphertexts[:e], moduli[:e])
|
|
197
|
+
|
|
198
|
+
import gmpy2
|
|
199
|
+
m, exact = gmpy2.iroot(me, e)
|
|
200
|
+
if exact:
|
|
201
|
+
return int(m)
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
# Usage (e=3, three encryptions)
|
|
205
|
+
m = hastad_broadcast([c1, c2, c3], [n1, n2, n3], e=3)
|
|
206
|
+
print(bytes.fromhex(hex(m)[2:]))
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Key insight:** CRT reconstructs `m^e` exactly (no modular reduction) because `m < min(n_i)` and therefore `m^e < n_1 * n_2 * ... * n_e`. Taking the integer eth root recovers `m`.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Hastad Broadcast Attack with Linear Padding -- Coppersmith (PlaidCTF 2017)
|
|
214
|
+
|
|
215
|
+
**Pattern:** Extension of Hastad's broadcast attack when each recipient applies a known linear transform `c_i = (a_i * m + b_i)^e mod n_i` before encryption.
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
# Standard Hastad requires identical plaintext
|
|
219
|
+
# With linear padding: each ciphertext encrypts a_i*m + b_i
|
|
220
|
+
# Use CRT + Coppersmith's small_roots on the resulting polynomial
|
|
221
|
+
|
|
222
|
+
from sage.all import *
|
|
223
|
+
# Combine via CRT
|
|
224
|
+
N = prod(n_values)
|
|
225
|
+
T = [crt_coefficient(i, n_values) for i in range(e)]
|
|
226
|
+
|
|
227
|
+
P = PolynomialRing(Zmod(N), 'x')
|
|
228
|
+
x = P.gen()
|
|
229
|
+
poly = sum(T[i] * ((a[i]*x + b[i])**e - c[i]) for i in range(e))
|
|
230
|
+
poly = poly.monic()
|
|
231
|
+
|
|
232
|
+
# Coppersmith's method finds small root
|
|
233
|
+
roots = poly.small_roots(epsilon=1/30)
|
|
234
|
+
flag = int(roots[0])
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Key insight:** When the same message is encrypted with `e` different moduli but each applies a known affine transform `a_i * m + b_i`, CRT combines the congruences into a single polynomial of degree `e` over `Z/NZ`. Coppersmith's method recovers `m` as a small root, generalizing Hastad's attack beyond identical plaintexts.
|
|
238
|
+
|
|
239
|
+
**References:** PlaidCTF 2017
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
### Franklin-Reiter Related Message Attack on RSA e=3 (N1CTF 2018)
|
|
244
|
+
|
|
245
|
+
**Pattern:** When server encrypts `m+padding` where `padding = sha256(user_input)` and `e=3`, two ciphertexts with known padding difference allow polynomial GCD in `Zmod(n)` to recover `m`. (N1CTF 2018)
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
# SageMath
|
|
249
|
+
def franklin_reiter(n, pad1, pad2, c1, c2):
|
|
250
|
+
R.<X> = PolynomialRing(Zmod(n))
|
|
251
|
+
f1 = (X + pad1)^3 - c1
|
|
252
|
+
f2 = (X + pad2)^3 - c2
|
|
253
|
+
return -gcd(f1, f2).coefficients()[0]
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Key insight:** With RSA e=3, if the same message `m` is encrypted with two known affine transformations (`m+pad1`, `m+pad2`), polynomial GCD over `Zmod(n)` recovers `m` directly. Works whenever the padding difference is known, even without knowing the full padding.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### Coppersmith Attack on Linearly-Related RSA Primes (ASIS CTF 2018)
|
|
261
|
+
|
|
262
|
+
**Pattern:** When RSA primes have a near-linear relation `q ~ 4p`, approximate `q` from `sqrt(4*n)`, then use Coppersmith's `small_roots` to find the error term. (ASIS CTF 2018)
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
# SageMath
|
|
266
|
+
qbar = isqrt(4 * n)
|
|
267
|
+
R.<x> = PolynomialRing(Zmod(n))
|
|
268
|
+
f = x + qbar
|
|
269
|
+
roots = f.small_roots(X=2^200, beta=0.5) # find small error term
|
|
270
|
+
q = qbar + int(roots[0])
|
|
271
|
+
p = n // q
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Key insight:** When `q ~ k*p` for known `k`, then `q ~ sqrt(k*n)`. The difference between `q` and this approximation is small enough for Coppersmith's method. This generalizes Fermat factorization to non-consecutive primes with known ratio.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## RSA with Consecutive Primes (Fermat Factorization)
|
|
279
|
+
|
|
280
|
+
**Pattern (Loopy Primes):** q = next_prime(p), making p ~ q ~ sqrt(N). Also known as Fermat factorization — works whenever `|p - q|` is small.
|
|
281
|
+
|
|
282
|
+
**Factorization:** Find first prime below sqrt(N):
|
|
283
|
+
```python
|
|
284
|
+
from sympy import nextprime, prevprime, isqrt
|
|
285
|
+
|
|
286
|
+
root = isqrt(n)
|
|
287
|
+
p = prevprime(root + 1)
|
|
288
|
+
while n % p != 0:
|
|
289
|
+
p = prevprime(p)
|
|
290
|
+
q = n // p
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Multi-layer variant:** 1024 nested RSA encryptions, each with consecutive primes of increasing bit size. Decrypt in reverse order.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Multi-Prime RSA
|
|
298
|
+
|
|
299
|
+
When N is product of many small primes (not just p*q):
|
|
300
|
+
```python
|
|
301
|
+
# Factor N (easier when many primes)
|
|
302
|
+
from sympy import factorint
|
|
303
|
+
factors = factorint(n) # Returns {p1: e1, p2: e2, ...}
|
|
304
|
+
|
|
305
|
+
# Compute phi using all factors
|
|
306
|
+
phi = 1
|
|
307
|
+
for p, e in factors.items():
|
|
308
|
+
phi *= (p - 1) * (p ** (e - 1))
|
|
309
|
+
|
|
310
|
+
d = pow(e, -1, phi)
|
|
311
|
+
plaintext = pow(ciphertext, d, n)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## RSA with Restricted-Digit Primes (LACTF 2026)
|
|
317
|
+
|
|
318
|
+
**Pattern (six-seven):** RSA primes p, q composed only of digits {6, 7}, ending in 7.
|
|
319
|
+
|
|
320
|
+
**Digit-by-digit factoring from LSB:**
|
|
321
|
+
```python
|
|
322
|
+
# At each step k, we know p mod 10^k -> compute q mod 10^k = n * p^{-1} mod 10^k
|
|
323
|
+
# Prune: only keep candidates where digit k of both p and q is in {6, 7}
|
|
324
|
+
candidates = [(6,), (7,)] # p ends in 6 or 7
|
|
325
|
+
for k in range(1, num_digits):
|
|
326
|
+
new_candidates = []
|
|
327
|
+
for p_digits in candidates:
|
|
328
|
+
for d in [6, 7]:
|
|
329
|
+
p_val = sum(p_digits[i] * 10**i for i in range(len(p_digits))) + d * 10**k
|
|
330
|
+
q_val = (n * pow(p_val, -1, 10**(k+1))) % 10**(k+1)
|
|
331
|
+
q_digit_k = (q_val // 10**k) % 10
|
|
332
|
+
if q_digit_k in {6, 7}:
|
|
333
|
+
new_candidates.append(p_digits + (d,))
|
|
334
|
+
candidates = new_candidates
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**General lesson:** When prime digits are restricted to a small set, digit-by-digit recovery from LSB with modular arithmetic prunes exponentially. Works for any restricted character set.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Coppersmith for Structured RSA Primes (LACTF 2026)
|
|
342
|
+
|
|
343
|
+
**Pattern (six-seven-again):** p = base + 10^k * x where base is fully known and x is small (x < N^0.25).
|
|
344
|
+
|
|
345
|
+
**Attack via SageMath:**
|
|
346
|
+
```python
|
|
347
|
+
# Construct f(x) such that f(x_secret) = 0 (mod p) and thus (mod N)
|
|
348
|
+
# p = base + 10^k * x -> x + base * (10^k)^{-1} = 0 (mod p)
|
|
349
|
+
R.<x> = PolynomialRing(Zmod(N))
|
|
350
|
+
f = x + (base * inverse_mod(10**k, N)) % N
|
|
351
|
+
roots = f.small_roots(X=2**70, beta=0.5) # x < N^0.25
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**When to use:** Whenever part of a prime is known and the unknown part is small enough for Coppersmith bounds (< N^{1/e} for degree-e polynomial, approximately N^0.25 for linear).
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Manger's RSA Padding Oracle Attack (Nullcon 2026)
|
|
359
|
+
|
|
360
|
+
**Pattern (TLS, Nullcon 2026):** RSA-encrypted key with threshold oracle. Phase 1: double f until `k*f >= threshold`. Phase 2: binary search. ~128 total queries for 64-bit key.
|
|
361
|
+
|
|
362
|
+
See [advanced-math.md](advanced-math.md) for full implementation.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Manger's Attack on RSA-OAEP via Timing Oracle (HTB Early Bird)
|
|
367
|
+
|
|
368
|
+
**Pattern:** Flask app implements RSA-OAEP with custom hash (PBKDF2, 2M iterations). Python's short-circuit `or` evaluation creates a timing oracle: if the first byte Y != 0, PBKDF2 is never called (~0.6s). If Y == 0, PBKDF2 runs (~2s).
|
|
369
|
+
|
|
370
|
+
**Vulnerable code pattern:**
|
|
371
|
+
```python
|
|
372
|
+
if Y != 0 or not self.H_verify(self.L, DB[:self.hLen]) or self.os2ip(PS) != 0:
|
|
373
|
+
return {"ok": False, "error": "decryption error"}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Oracle mapping:** Fast response → Y != 0 (decrypted message >= B). Slow response → Y == 0 (decrypted message < B = 2^(8*(k-1))).
|
|
377
|
+
|
|
378
|
+
**Calibration for network reliability:**
|
|
379
|
+
```python
|
|
380
|
+
def calibrate(n, e, k):
|
|
381
|
+
B = pow(2, 8 * (k - 1))
|
|
382
|
+
slow_times, fast_times = [], []
|
|
383
|
+
for i in range(5):
|
|
384
|
+
# Known-slow: encrypt values < B
|
|
385
|
+
enc = pow(B - 1 - i*100, e, n).to_bytes(k, 'big')
|
|
386
|
+
slow_times.append(measure(enc))
|
|
387
|
+
# Known-fast: encrypt values > B
|
|
388
|
+
enc = pow(B + 1 + i*100, e, n).to_bytes(k, 'big')
|
|
389
|
+
fast_times.append(measure(enc))
|
|
390
|
+
FAST_UPPER = max(fast_times) * 1.5
|
|
391
|
+
SLOW_LOWER = min(slow_times) * 0.9
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Oracle with retry for ambiguous results:**
|
|
395
|
+
```python
|
|
396
|
+
def padding_oracle(c_int):
|
|
397
|
+
while True:
|
|
398
|
+
total = measure_response_time(c_int)
|
|
399
|
+
if SLOW_LOWER < total < SLOW_UPPER:
|
|
400
|
+
return True # Y == 0 (below B)
|
|
401
|
+
elif total < FAST_UPPER:
|
|
402
|
+
return False # Y != 0 (above B)
|
|
403
|
+
# Ambiguous: retry
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Full 3-step Manger's attack (~1024 iterations for 1024-bit RSA):**
|
|
407
|
+
```python
|
|
408
|
+
# Step 1: Find f1 where f1 * m >= B
|
|
409
|
+
f1 = 2
|
|
410
|
+
while oracle((pow(f1, e, n) * c) % n):
|
|
411
|
+
f1 *= 2
|
|
412
|
+
|
|
413
|
+
# Step 2: Find f2 where n <= f2 * m < n + B
|
|
414
|
+
f2 = (n + B) // B * f1 // 2
|
|
415
|
+
while not oracle((pow(f2, e, n) * c) % n):
|
|
416
|
+
f2 += f1 // 2
|
|
417
|
+
|
|
418
|
+
# Step 3: Binary search narrowing m to exact value
|
|
419
|
+
mmin, mmax = ceil_div(n, f2), floor_div(n + B, f2)
|
|
420
|
+
while mmin < mmax:
|
|
421
|
+
f = floor_div(2 * B, mmax - mmin)
|
|
422
|
+
i = floor_div(f * mmin, n)
|
|
423
|
+
f3 = ceil_div(i * n, mmin)
|
|
424
|
+
if oracle((pow(f3, e, n) * c) % n):
|
|
425
|
+
mmax = floor_div(i * n + B, f3)
|
|
426
|
+
else:
|
|
427
|
+
mmin = ceil_div(i * n + B, f3)
|
|
428
|
+
m = mmin
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Post-recovery OAEP decode:**
|
|
432
|
+
```python
|
|
433
|
+
from Crypto.Signature.pss import MGF1
|
|
434
|
+
maskedSeed = EM[1:hLen+1]
|
|
435
|
+
maskedDB = EM[hLen+1:]
|
|
436
|
+
seed = bytes(a ^ b for a, b in zip(maskedSeed, MGF1(maskedDB, hLen, HF)))
|
|
437
|
+
DB = bytes(a ^ b for a, b in zip(maskedDB, MGF1(seed, k - hLen - 1, HF)))
|
|
438
|
+
# DB[:hLen] should match lHash; rest is 0x00...0x01 || message
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Key insight:** Python's `or` short-circuits left-to-right. When expensive operations (PBKDF2, bcrypt, argon2) appear in chained conditions, the first condition becomes a timing oracle. RFC 8017 explicitly warns implementations must not let attackers distinguish error conditions — timing differences violate this.
|
|
442
|
+
|
|
443
|
+
**Detection:** RSA-OAEP with custom hash or slow KDF. Flask/Python backend. `/verify-token` or similar decryption endpoint returning generic errors. Timing differences between responses.
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Polynomial Hash with Trivial Root (Pragyan 2026)
|
|
448
|
+
|
|
449
|
+
**Pattern (!!Cand1esaNdCrypt0!!):** RSA signature scheme using polynomial hash `g(x,a,b) = x(x^2 + ax + b) mod P`.
|
|
450
|
+
|
|
451
|
+
**Vulnerability:** `g(0) = 0` for all parameters `a,b`. RSA signature of 0 is always 0 (`0^d mod n = 0`).
|
|
452
|
+
|
|
453
|
+
**Exploitation:** Craft message suffix so `bytes_to_long(prefix || suffix) = 0 (mod P)`:
|
|
454
|
+
```python
|
|
455
|
+
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 # 128-bit prime
|
|
456
|
+
# Compute required suffix value mod P
|
|
457
|
+
req = (-prefix_val * pow(256, suffix_len, P)) % P
|
|
458
|
+
# Brute-force partial bytes until all printable ASCII
|
|
459
|
+
while True:
|
|
460
|
+
high = os.urandom(32).translate(printable_table)
|
|
461
|
+
low_val = (req - int.from_bytes(high, 'big') * shift) % P
|
|
462
|
+
low = low_val.to_bytes(16, 'big')
|
|
463
|
+
if all(32 <= b <= 126 for b in low):
|
|
464
|
+
suffix = high + low
|
|
465
|
+
break
|
|
466
|
+
# Signature is simply 0
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**General lesson:** Always check if hash function has trivial inputs (0, 1, -1). Factoring the polynomial often reveals these.
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Polynomial CRT in GF(2)[x] (Nullcon 2026)
|
|
474
|
+
|
|
475
|
+
**Pattern (Going in Circles, Nullcon 2026):** `r = flag mod f` where f is random GF(2) polynomial. Collect ~20 pairs, filter coprime, CRT combine.
|
|
476
|
+
|
|
477
|
+
See [advanced-math.md](advanced-math.md) for GF(2)[x] polynomial arithmetic and CRT implementation.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Affine Cipher over Non-Prime Modulus (Nullcon 2026)
|
|
482
|
+
|
|
483
|
+
**Pattern (Matrixfun, Nullcon 2026):** `c = A @ p + b (mod m)` with composite m. Chosen-plaintext difference attack. For composite modulus, solve via CRT in each prime factor field separately.
|
|
484
|
+
|
|
485
|
+
See [advanced-math.md](advanced-math.md) for CRT approach and Gauss-Jordan implementation.
|
|
486
|
+
|
|
487
|
+
See also: [rsa-attacks-2.md](rsa-attacks-2.md) for specialized RSA techniques (p=q bypass, cube root CRT, phi(n) multiple factoring, signature forgery, weak keygen, batch GCD, partial key recovery, CRT fault attack, homomorphic bypass).
|