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,347 @@
|
|
|
1
|
+
# CTF Crypto - Elliptic Curve Attacks
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Small Subgroup Attacks](#small-subgroup-attacks)
|
|
5
|
+
- [Invalid Curve Attacks](#invalid-curve-attacks)
|
|
6
|
+
- [Singular Curves](#singular-curves)
|
|
7
|
+
- [Smart's Attack (Anomalous Curves)](#smarts-attack-anomalous-curves)
|
|
8
|
+
- [ECC Fault Injection](#ecc-fault-injection)
|
|
9
|
+
- [Clock Group DLP via Pohlig-Hellman (LACTF 2026)](#clock-group-dlp-via-pohlig-hellman-lactf-2026)
|
|
10
|
+
- [ECDSA Nonce Reuse (BearCatCTF 2026)](#ecdsa-nonce-reuse-bearcatctf-2026)
|
|
11
|
+
- [Ed25519 Torsion Side Channel (BearCatCTF 2026)](#ed25519-torsion-side-channel-bearcatctf-2026)
|
|
12
|
+
- [DSA Nonce Reuse for Private Key Recovery (VolgaCTF 2016)](#dsa-nonce-reuse-for-private-key-recovery-volgactf-2016)
|
|
13
|
+
- [DSA Limited k-Value Brute Force (ASIS CTF Finals 2016)](#dsa-limited-k-value-brute-force-asis-ctf-finals-2016)
|
|
14
|
+
- [ECC Shared Prime Factor via GCD (ASIS CTF Finals 2016)](#ecc-shared-prime-factor-via-gcd-asis-ctf-finals-2016)
|
|
15
|
+
- [DSA Key Recovery via MD5 Collision on k-Generation (CONFidence CTF 2017)](#dsa-key-recovery-via-md5-collision-on-k-generation-confidence-ctf-2017)
|
|
16
|
+
- [Ed25519 Same-Nonce Key Recovery (hxp 2018)](#ed25519-same-nonce-key-recovery-hxp-2018)
|
|
17
|
+
- [Singular Curve ECDLP to Additive/Multiplicative Group (hxp 2018)](#singular-curve-ecdlp-to-additivemultiplicative-group-hxp-2018)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Small Subgroup Attacks
|
|
22
|
+
|
|
23
|
+
- Check curve order for small factors
|
|
24
|
+
- Pohlig-Hellman: solve DLP (Discrete Logarithm Problem) in small subgroups, combine with CRT (Chinese Remainder Theorem)
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
# SageMath ECC basics
|
|
28
|
+
E = EllipticCurve(GF(p), [a, b])
|
|
29
|
+
G = E.gens()[0] # generator
|
|
30
|
+
order = E.order()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Key insight:** When the curve order has small prime factors, Pohlig-Hellman decomposes the DLP into small subgroup problems solvable independently, then combines results with CRT. Always factor the curve order first -- if it is smooth (all small factors), the DLP is trivially solvable.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Invalid Curve Attacks
|
|
38
|
+
|
|
39
|
+
If point validation is missing, send points on weaker curves. Craft points with small-order subgroups to leak secret key bits.
|
|
40
|
+
|
|
41
|
+
**Key insight:** Invalid curve attacks exploit missing point-on-curve validation. Send crafted points that lie on a different curve with a small-order subgroup, and the server will compute scalar multiplication on the weak curve, leaking secret key bits modulo the small order.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Singular Curves
|
|
46
|
+
|
|
47
|
+
If discriminant delta = 0, curve is singular. DLP becomes easy (maps to additive/multiplicative group).
|
|
48
|
+
|
|
49
|
+
**Key insight:** Check the discriminant `4a^3 + 27b^2 mod p` first. If it is zero, the curve is singular and the ECDLP reduces to a simple discrete log in the additive group (cusp) or multiplicative group (node) of the field, both solvable in polynomial time.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Smart's Attack (Anomalous Curves)
|
|
54
|
+
|
|
55
|
+
**When to use:** Curve order equals field characteristic p (anomalous curve). Solves ECDLP in O(1) via p-adic lifting.
|
|
56
|
+
|
|
57
|
+
**Key insight:** Always check `E.order() == p` first. If the curve order equals the field prime, the ECDLP is solved instantly via p-adic lifting (Smart's attack). SageMath's `discrete_log` handles this automatically, but manual p-adic lift code is needed when the built-in method fails.
|
|
58
|
+
|
|
59
|
+
**Detection:** `E.order() == p` — always check this first!
|
|
60
|
+
|
|
61
|
+
**SageMath (automatic):**
|
|
62
|
+
```python
|
|
63
|
+
E = EllipticCurve(GF(p), [a, b])
|
|
64
|
+
G = E(Gx, Gy)
|
|
65
|
+
Q = E(Qx, Qy)
|
|
66
|
+
# Sage's discrete_log handles anomalous curves automatically
|
|
67
|
+
secret = G.discrete_log(Q)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Manual p-adic lift (when Sage's auto method fails):**
|
|
71
|
+
```python
|
|
72
|
+
def smart_attack(p, a, b, G, Q):
|
|
73
|
+
E = EllipticCurve(GF(p), [a, b])
|
|
74
|
+
Qp = pAdicField(p, 2) # p-adic field with precision 2
|
|
75
|
+
Ep = EllipticCurve(Qp, [a, b])
|
|
76
|
+
|
|
77
|
+
# Lift points to p-adics
|
|
78
|
+
Gp = Ep.lift_x(ZZ(G[0]), all=True) # try both lifts
|
|
79
|
+
Qp_point = Ep.lift_x(ZZ(Q[0]), all=True)
|
|
80
|
+
|
|
81
|
+
for gp in Gp:
|
|
82
|
+
for qp in Qp_point:
|
|
83
|
+
try:
|
|
84
|
+
# Multiply by p to get points in kernel of reduction
|
|
85
|
+
pG = p * gp
|
|
86
|
+
pQ = p * qp
|
|
87
|
+
# Extract p-adic logarithm
|
|
88
|
+
x_G = ZZ(pG[0] / pG[1]) / p # or pG.xy()
|
|
89
|
+
x_Q = ZZ(pQ[0] / pQ[1]) / p
|
|
90
|
+
secret = ZZ(x_Q / x_G) % p
|
|
91
|
+
if E(G) * secret == E(Q):
|
|
92
|
+
return secret
|
|
93
|
+
except (ZeroDivisionError, ValueError):
|
|
94
|
+
continue
|
|
95
|
+
return None
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Multi-layer decryption after key recovery:** Challenge may wrap flag in AES-CBC + DES-CBC or similar — just busywork once the ECC key is recovered. Derive keys with SHA-256 of shared secret.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## ECC Fault Injection
|
|
103
|
+
|
|
104
|
+
**Pattern (Faulty Curves):** Bit flip during ECC computation reveals private key bits.
|
|
105
|
+
|
|
106
|
+
**Attack:** Compare correct vs faulty ciphertext, recover key bit-by-bit:
|
|
107
|
+
```python
|
|
108
|
+
# For each key bit position:
|
|
109
|
+
# If fault at bit i changes output -> key bit i affects computation
|
|
110
|
+
# Binary distinguisher: faulty_output == correct_output -> bit is 0
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Clock Group DLP via Pohlig-Hellman (LACTF 2026)
|
|
116
|
+
|
|
117
|
+
**Pattern (the-clock):** Diffie-Hellman on unit circle group: x^2 + y^2 = 1 (mod p).
|
|
118
|
+
|
|
119
|
+
**Key facts:**
|
|
120
|
+
- Group law: (x1,y1) * (x2,y2) = (x1*y2 + y1*x2, y1*y2 - x1*x2)
|
|
121
|
+
- **Group order = p + 1** (not p - 1!)
|
|
122
|
+
- Isomorphic to GF(p^2)* elements of norm 1
|
|
123
|
+
|
|
124
|
+
**Group operations:**
|
|
125
|
+
```python
|
|
126
|
+
def clock_mul(P, Q, p):
|
|
127
|
+
x1, y1 = P
|
|
128
|
+
x2, y2 = Q
|
|
129
|
+
return ((x1*y2 + y1*x2) % p, (y1*y2 - x1*x2) % p)
|
|
130
|
+
|
|
131
|
+
def clock_pow(P, n, p):
|
|
132
|
+
result = (0, 1) # identity
|
|
133
|
+
base = P
|
|
134
|
+
while n > 0:
|
|
135
|
+
if n & 1:
|
|
136
|
+
result = clock_mul(result, base, p)
|
|
137
|
+
base = clock_mul(base, base, p)
|
|
138
|
+
n >>= 1
|
|
139
|
+
return result
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Recovering hidden prime p:**
|
|
143
|
+
```python
|
|
144
|
+
# Given points on the curve, p divides (x^2 + y^2 - 1)
|
|
145
|
+
from math import gcd
|
|
146
|
+
vals = [x**2 + y**2 - 1 for x, y in known_points]
|
|
147
|
+
p = reduce(gcd, vals)
|
|
148
|
+
# May need to remove small factors
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Attack when p+1 is smooth:**
|
|
152
|
+
```python
|
|
153
|
+
# 1. Recover p from points: gcd(x^2 + y^2 - 1) across known points
|
|
154
|
+
# 2. Factor p+1 into small primes
|
|
155
|
+
# 3. Pohlig-Hellman: solve DLP in each small subgroup, CRT combine
|
|
156
|
+
# 4. Compute shared secret, derive AES key (e.g., via MD5)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Identification:** Challenge mentions "clock", "circle", or gives points satisfying x^2+y^2=1. Always check if p+1 (not p-1) is smooth.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Ed25519 Torsion Side Channel (BearCatCTF 2026)
|
|
164
|
+
|
|
165
|
+
**Pattern (Curvy Wurvy):** Ed25519 signing oracle derives per-user keys as `user_key = MASTER_KEY * uid mod l` (where `l` is the Ed25519 subgroup order). Goal: recover `MASTER_KEY` from oracle queries.
|
|
166
|
+
|
|
167
|
+
**The attack exploits Ed25519's cofactor h=8:**
|
|
168
|
+
- Full curve order = `8*l`, but scalars are reduced mod `l`
|
|
169
|
+
- When `MASTER_KEY * 2^t` wraps around `l`, multiplication produces a torsion component visible as y-coordinate change
|
|
170
|
+
|
|
171
|
+
**Key extraction via binary decomposition:**
|
|
172
|
+
```python
|
|
173
|
+
# Query sign(uid=3, 2^t) for t = 0..255
|
|
174
|
+
# S_t = (MASTER_KEY * 2^t mod l) * P3
|
|
175
|
+
# Check: does doubling S_t match S_{t+1}?
|
|
176
|
+
|
|
177
|
+
bits = []
|
|
178
|
+
for t in range(255):
|
|
179
|
+
S_t = query_sign(3, 2**t)
|
|
180
|
+
S_t1 = query_sign(3, 2**(t+1))
|
|
181
|
+
doubled = point_double(S_t)
|
|
182
|
+
# Wrap occurred if doubled.y != S_{t+1}.y (torsion shift)
|
|
183
|
+
bits.append(0 if doubled.y == S_t1.y else 1)
|
|
184
|
+
|
|
185
|
+
# Reconstruct: MASTER_KEY ≈ l * (0.bit0 bit1 bit2 ...)_binary
|
|
186
|
+
# Try all 8 torsion corrections for exact value
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Key insight:** Ed25519's cofactor creates an observable side channel: when scalar multiplication wraps around the subgroup order `l`, the result shifts by a torsion element (one of 8 points). By querying powers of 2 and checking y-coordinate consistency, each bit of the secret scalar is leaked. Libraries like `ecpy` that reduce mod `l` are vulnerable to this when used in multi-user key derivation schemes.
|
|
190
|
+
|
|
191
|
+
**Detection:** Ed25519 signing oracle with user-controlled UID or multiplier. Key derivation formula `key = master * uid mod l`.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## ECDSA Nonce Reuse (BearCatCTF 2026)
|
|
196
|
+
|
|
197
|
+
**Pattern (Chatroom):** ECDSA signatures on secp256k1 with constant nonce `k`. When two signatures share the same `r` value, the nonce and private key are recoverable.
|
|
198
|
+
|
|
199
|
+
**Recovery:**
|
|
200
|
+
```python
|
|
201
|
+
from hashlib import sha256
|
|
202
|
+
|
|
203
|
+
# Two signatures (r, s1) and (r, s2) with same r → same nonce k
|
|
204
|
+
h1 = int(sha256(msg1).hexdigest(), 16)
|
|
205
|
+
h2 = int(sha256(msg2).hexdigest(), 16)
|
|
206
|
+
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 # secp256k1 order
|
|
207
|
+
|
|
208
|
+
k = ((h1 - h2) * pow(s1 - s2, -1, n)) % n
|
|
209
|
+
d = ((s1 * k - h1) * pow(r, -1, n)) % n # private key
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Key insight:** Same `r` value across multiple ECDSA signatures means the nonce `k` was reused. This is the same class of bug that compromised the PlayStation 3 signing key. Always check for repeated `r` values in signature datasets.
|
|
213
|
+
|
|
214
|
+
**Detection:** Multiple ECDSA signatures with identical `r` component. Challenge mentions "nonce", "deterministic signing", or provides a signing oracle.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## DSA Nonce Reuse for Private Key Recovery (VolgaCTF 2016)
|
|
219
|
+
|
|
220
|
+
**Pattern:** Two DSA (Digital Signature Algorithm) signatures sharing the same nonce k (same r value) leak the private key. Identical in principle to ECDSA nonce reuse but uses DSA-specific group parameters.
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
# Two signatures (r, s1, H(m1)) and (r, s2, H(m2)) with same r
|
|
224
|
+
k = ((H_m1 - H_m2) * pow(s1 - s2, -1, q)) % q
|
|
225
|
+
x = ((s1 * k - H_m1) * pow(r, -1, q)) % q # private key
|
|
226
|
+
# Then forge signatures for arbitrary messages
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Key insight:** DSA nonce reuse is identical in principle to ECDSA nonce reuse. Look for repeated r values in any DSA/ECDSA signature set. The same recovery formula applies to both.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## DSA Limited k-Value Brute Force (ASIS CTF Finals 2016)
|
|
234
|
+
|
|
235
|
+
DSA implementation generates k from a restricted space (e.g., only 1024 possibilities). Given multiple signatures, brute-force k values and solve for the private key.
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from Crypto.Util.number import inverse
|
|
239
|
+
|
|
240
|
+
def recover_dsa_key(signatures, q, g, p):
|
|
241
|
+
"""Recover DSA private key when k has limited possible values"""
|
|
242
|
+
(r1, s1, h1), (r2, s2, h2) = signatures[0], signatures[1]
|
|
243
|
+
|
|
244
|
+
for k1 in range(1, 1024):
|
|
245
|
+
for k2 in range(1, 1024):
|
|
246
|
+
# From DSA: s = k^-1 * (h + x*r) mod q
|
|
247
|
+
# With two signatures: x = (s2*k2*h1 - s1*k1*h2) / (s1*k1*r2 - s2*k2*r1) mod q
|
|
248
|
+
num = (s2 * k2 * h1 - s1 * k1 * h2) % q
|
|
249
|
+
den = (s1 * k1 * r2 - s2 * k2 * r1) % q
|
|
250
|
+
if den == 0:
|
|
251
|
+
continue
|
|
252
|
+
x = (num * inverse(den, q)) % q
|
|
253
|
+
# Verify: check if r1 == (g^k1 mod p) mod q
|
|
254
|
+
if pow(g, k1, p) % q == r1:
|
|
255
|
+
return x
|
|
256
|
+
return None
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Key insight:** Standard DSA nonce reuse attacks require k1 == k2. When k values are drawn from a small space (e.g., 1024 values), brute-force all (k1, k2) pairs across two signatures to solve the linear system for private key x.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## ECC Shared Prime Factor via GCD (ASIS CTF Finals 2016)
|
|
264
|
+
|
|
265
|
+
Multiple ECC public keys generated with a flawed prime generator that filters `prime % 3 == 2`, reducing the keyspace enough for shared factors to appear.
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from math import gcd
|
|
269
|
+
from Crypto.Util.number import inverse
|
|
270
|
+
|
|
271
|
+
# Collect moduli from multiple ECC public keys
|
|
272
|
+
moduli = [key.n for key in public_keys]
|
|
273
|
+
|
|
274
|
+
# Find shared factors via pairwise GCD
|
|
275
|
+
for i in range(len(moduli)):
|
|
276
|
+
for j in range(i + 1, len(moduli)):
|
|
277
|
+
g = gcd(moduli[i], moduli[j])
|
|
278
|
+
if 1 < g < moduli[i]:
|
|
279
|
+
p = g
|
|
280
|
+
q = moduli[i] // p
|
|
281
|
+
print(f"Key {i} factored: p={p}, q={q}")
|
|
282
|
+
# Now decrypt using recovered factors
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Key insight:** When a prime generator excludes primes based on modular conditions (e.g., `p % 3 == 2`), the reduced keyspace makes GCD collisions between independently generated keys much more likely. Always try pairwise GCD across multiple public keys.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## DSA Key Recovery via MD5 Collision on k-Generation (CONFidence CTF 2017)
|
|
290
|
+
|
|
291
|
+
**Pattern:** When DSA nonce `k` is derived from `MD5(prefix + counter)`, generate MD5 prefix collisions to force two different counter values to produce the same `k`, enabling the standard nonce-reuse private key recovery.
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
# k = int(MD5("K = {n: " + str(counter) + ...))
|
|
295
|
+
# Use fastcoll to find MD5 collision on prefix "K = {n: "
|
|
296
|
+
# Two different counter values -> same MD5 -> same k -> nonce reuse
|
|
297
|
+
|
|
298
|
+
import subprocess
|
|
299
|
+
# Generate collision pair
|
|
300
|
+
subprocess.run(["fastcoll", "-p", prefix_file, "-o", "col1", "col2"])
|
|
301
|
+
|
|
302
|
+
# Get two signatures with same k (same r value)
|
|
303
|
+
sig1 = sign(msg1, counter1) # uses MD5(prefix + counter1)
|
|
304
|
+
sig2 = sign(msg2, counter2) # uses MD5(prefix + counter2) = same hash!
|
|
305
|
+
|
|
306
|
+
# Standard DSA nonce reuse recovery
|
|
307
|
+
k = (hash1 - hash2) * modinv(sig1.s - sig2.s, q) % q
|
|
308
|
+
private_key = (sig1.s * k - hash1) * modinv(sig1.r, q) % q
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Key insight:** MD5 collision generators like `fastcoll` produce pairs of inputs with identical hashes from a chosen prefix. When a signature scheme derives its nonce from an MD5 hash of controllable data, manufacturing a collision produces nonce reuse, enabling standard private key recovery.
|
|
312
|
+
|
|
313
|
+
**References:** CONFidence CTF 2017
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Ed25519 Same-Nonce Key Recovery (hxp 2018)
|
|
318
|
+
|
|
319
|
+
**Pattern:** An Ed25519 signer reuses the same private-key scalar with deterministic nonce derivation, but the public key changes between signatures (fault injection or swapped key material). Two signatures `(R1, S1, h1)` and `(R2, S2, h2)` share `a`, so `a = (S1 - S2) * inverse(h1 - h2) mod L`.
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
L = 2**252 + 27742317777372353535851937790883648493
|
|
323
|
+
a = (S1 - S2) * pow(h1 - h2, -1, L) % L # recovered scalar
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Key insight:** Ed25519 is deterministic, but any implementation bug that desyncs `(r, k)` from `(H(privkey, msg))` produces classical nonce-reuse. Check implementations that sign across key rotations — the scalar often survives rekey.
|
|
327
|
+
|
|
328
|
+
**References:** hxp CTF 2018 — writeup 12561
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Singular Curve ECDLP to Additive/Multiplicative Group (hxp 2018)
|
|
333
|
+
|
|
334
|
+
**Pattern:** Challenge publishes an "elliptic curve" that is actually singular — its discriminant is zero. Compute the singularity by finding the double root of `f(x) = x^3 + ax + b`. Map the curve to either the additive group `(GF(p), +)` (cusp) or the multiplicative group `GF(p)^*` (node) where DLP is easy.
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
# Find singular point r
|
|
338
|
+
P.<x> = PolynomialRing(GF(p))
|
|
339
|
+
f = x^3 + a*x + b
|
|
340
|
+
r = (f.derivative()).roots()[0][0]
|
|
341
|
+
# Shift curve so singularity is at origin
|
|
342
|
+
# Then map (x, y) -> (x - r) / y for nodal singularity
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Key insight:** Discriminant `-16(4a^3 + 27b^2)` zero means singular. Singular curves are either cusps (map to `(GF(p), +)`) or nodes (map to `GF(p)^*`) — both with polynomial-time DLP.
|
|
346
|
+
|
|
347
|
+
**References:** hxp CTF 2018 — writeup 12563
|