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,798 @@
|
|
|
1
|
+
# CTF Crypto - Advanced Mathematical Attacks
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Elliptic Curve Isogenies](#elliptic-curve-isogenies)
|
|
5
|
+
- [Pohlig-Hellman Attack (Weak ECC)](#pohlig-hellman-attack-weak-ecc)
|
|
6
|
+
- [Baby-Step Giant-Step for General DLP](#baby-step-giant-step-for-general-dlp)
|
|
7
|
+
- [LLL Algorithm for Approximate GCD](#lll-algorithm-for-approximate-gcd)
|
|
8
|
+
- [Merkle-Hellman Knapsack Cryptosystem via LLL (ASIS 2014)](#merkle-hellman-knapsack-cryptosystem-via-lll-asis-2014)
|
|
9
|
+
- [Coppersmith's Method (Close Private Keys)](#coppersmiths-method-close-private-keys)
|
|
10
|
+
- [Coppersmith's Method (Structured Primes, LACTF 2026)](#coppersmiths-method-structured-primes-lactf-2026)
|
|
11
|
+
- [Clock Group (x^2+y^2=1 mod p) DLP (LACTF 2026)](#clock-group-x2y21-mod-p-dlp-lactf-2026)
|
|
12
|
+
- [Quaternion RSA](#quaternion-rsa)
|
|
13
|
+
- [Polynomial Arithmetic in GF(2)\[x\]](#polynomial-arithmetic-in-gf2x)
|
|
14
|
+
- [RSA Signing Bug](#rsa-signing-bug)
|
|
15
|
+
- [Non-Permutation S-box Collision Attack (Nullcon 2026)](#non-permutation-s-box-collision-attack-nullcon-2026)
|
|
16
|
+
- [Polynomial CRT in GF(2)\[x\] (Nullcon 2026)](#polynomial-crt-in-gf2x-nullcon-2026)
|
|
17
|
+
- [Manger's RSA Padding Oracle Attack (Nullcon 2026)](#mangers-rsa-padding-oracle-attack-nullcon-2026)
|
|
18
|
+
- [LWE Lattice Attack via CVP (EHAX 2026)](#lwe-lattice-attack-via-cvp-ehax-2026)
|
|
19
|
+
- [Affine Cipher over Non-Prime Modulus (Nullcon 2026)](#affine-cipher-over-non-prime-modulus-nullcon-2026)
|
|
20
|
+
- [Introspective CRC via GF(2) Linear Algebra (Google CTF 2017)](#introspective-crc-via-gf2-linear-algebra-google-ctf-2017)
|
|
21
|
+
- [Baby-Step Giant-Step for Sparse/Low Hamming Weight Exponents (SEC-T CTF 2017)](#baby-step-giant-step-for-sparselow-hamming-weight-exponents-sec-t-ctf-2017)
|
|
22
|
+
- [Hensel's Lemma: Polynomial Root Lifting mod p^k (CONFidence CTF 2019 Teaser)](#hensels-lemma-polynomial-root-lifting-mod-pk-confidence-ctf-2019-teaser)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Elliptic Curve Isogenies
|
|
27
|
+
|
|
28
|
+
Isogeny-based crypto challenges are often **graph traversal problems in disguise**:
|
|
29
|
+
|
|
30
|
+
**Key concepts:**
|
|
31
|
+
- j-invariant uniquely identifies curve isomorphism class
|
|
32
|
+
- Curves connected by isogenies form a graph (often tree-like)
|
|
33
|
+
- Degree-2 isogenies: each node has ~3 neighbors (2 children + 1 parent)
|
|
34
|
+
|
|
35
|
+
**Modular polynomial approach:**
|
|
36
|
+
- Connected j-invariants j₁, j₂ satisfy Φ₂(j₁, j₂) = 0
|
|
37
|
+
- Find neighbors by computing roots of Φ₂(j, Y) in the finite field
|
|
38
|
+
- Much faster than computing actual isogenies
|
|
39
|
+
|
|
40
|
+
**Pathfinding in isogeny graphs:**
|
|
41
|
+
```python
|
|
42
|
+
# Height estimation via random walks to leaves
|
|
43
|
+
def estimate_height(j, neighbors_func, trials=100):
|
|
44
|
+
min_depth = float('inf')
|
|
45
|
+
for _ in range(trials):
|
|
46
|
+
depth, curr = 0, j
|
|
47
|
+
while True:
|
|
48
|
+
nbrs = neighbors_func(curr)
|
|
49
|
+
if len(nbrs) <= 1: # leaf node
|
|
50
|
+
break
|
|
51
|
+
curr = random.choice(nbrs)
|
|
52
|
+
depth += 1
|
|
53
|
+
min_depth = min(min_depth, depth)
|
|
54
|
+
return min_depth
|
|
55
|
+
|
|
56
|
+
# Find path between two nodes via LCA
|
|
57
|
+
def find_path(start, end):
|
|
58
|
+
# Ascend from both nodes tracking heights
|
|
59
|
+
# Find least common ancestor
|
|
60
|
+
# Concatenate: path_up(start) + reversed(path_up(end))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Complex multiplication (CM) curves:**
|
|
64
|
+
- Discriminant D = f² · D_K where D_K is fundamental discriminant
|
|
65
|
+
- Conductor f determines tree depth
|
|
66
|
+
- Look for special discriminants: -163, -67, -43, etc. (class number 1)
|
|
67
|
+
|
|
68
|
+
## Pohlig-Hellman Attack (Weak ECC)
|
|
69
|
+
|
|
70
|
+
For elliptic curves with smooth order (many small prime factors):
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from sage.all import *
|
|
74
|
+
|
|
75
|
+
# Factor curve order
|
|
76
|
+
E = EllipticCurve(GF(p), [a, b])
|
|
77
|
+
n = E.order()
|
|
78
|
+
factors = factor(n)
|
|
79
|
+
|
|
80
|
+
# Solve DLP in each small subgroup
|
|
81
|
+
partial_logs = []
|
|
82
|
+
for (prime, exp) in factors:
|
|
83
|
+
# Compute subgroup generator
|
|
84
|
+
cofactor = n // (prime ** exp)
|
|
85
|
+
G_sub = cofactor * G
|
|
86
|
+
P_sub = cofactor * P # Target point
|
|
87
|
+
|
|
88
|
+
# Solve small DLP
|
|
89
|
+
d_sub = discrete_log(P_sub, G_sub, ord=prime**exp)
|
|
90
|
+
partial_logs.append((d_sub, prime**exp))
|
|
91
|
+
|
|
92
|
+
# Combine with CRT
|
|
93
|
+
from sympy.ntheory.modular import crt
|
|
94
|
+
moduli = [m for (_, m) in partial_logs]
|
|
95
|
+
residues = [r for (r, _) in partial_logs]
|
|
96
|
+
private_key, _ = crt(moduli, residues)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Baby-Step Giant-Step for General DLP
|
|
100
|
+
|
|
101
|
+
**Pattern:** Compute discrete logarithm `x` where `g^x = h (mod p)` in O(sqrt(n)) time and space, where n is the group order. Works for any cyclic group — multiplicative groups mod p, elliptic curves, or abstract groups. Combined with Pohlig-Hellman for smooth-order groups, solves DLP when `p-1` (or group order) has only small prime factors.
|
|
102
|
+
|
|
103
|
+
**Baby-step giant-step algorithm:**
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from math import isqrt
|
|
107
|
+
|
|
108
|
+
def bsgs(g, h, p, order=None):
|
|
109
|
+
"""Baby-step giant-step: find x such that g^x = h (mod p).
|
|
110
|
+
|
|
111
|
+
Time/space: O(sqrt(order)). For subgroups, pass the subgroup order.
|
|
112
|
+
"""
|
|
113
|
+
if order is None:
|
|
114
|
+
order = p - 1
|
|
115
|
+
m = isqrt(order) + 1
|
|
116
|
+
|
|
117
|
+
# Baby step: build table of g^j for j in [0, m)
|
|
118
|
+
table = {}
|
|
119
|
+
power = 1
|
|
120
|
+
for j in range(m):
|
|
121
|
+
table[power] = j
|
|
122
|
+
power = (power * g) % p
|
|
123
|
+
|
|
124
|
+
# Giant step: compute g^(-m), then check h * (g^(-m))^i
|
|
125
|
+
factor = pow(g, -m, p) # g^(-m) mod p
|
|
126
|
+
gamma = h
|
|
127
|
+
for i in range(m):
|
|
128
|
+
if gamma in table:
|
|
129
|
+
return i * m + table[gamma]
|
|
130
|
+
gamma = (gamma * factor) % p
|
|
131
|
+
return None # No solution found (order was wrong)
|
|
132
|
+
|
|
133
|
+
# Example: ElGamal with smooth p-1 (MMA CTF 2015 "Alicegame")
|
|
134
|
+
# p-1 = 2 * 3^4 * 5 * 13 * 397 * 34703 * ... (all small factors)
|
|
135
|
+
# Pohlig-Hellman: solve DLP in each prime-power subgroup, combine with CRT
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Full Pohlig-Hellman + BSGS pipeline:**
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from sympy.ntheory import factorint
|
|
142
|
+
from sympy.ntheory.modular import crt
|
|
143
|
+
|
|
144
|
+
def pohlig_hellman(g, h, p):
|
|
145
|
+
"""Solve g^x = h (mod p) when p-1 is smooth."""
|
|
146
|
+
order = p - 1
|
|
147
|
+
factors = factorint(order) # {prime: exponent}
|
|
148
|
+
|
|
149
|
+
residues = []
|
|
150
|
+
moduli = []
|
|
151
|
+
for prime, exp in factors.items():
|
|
152
|
+
pe = prime ** exp
|
|
153
|
+
# Project to subgroup of order prime^exp
|
|
154
|
+
cofactor = order // pe
|
|
155
|
+
gi = pow(g, cofactor, p) # Generator of subgroup
|
|
156
|
+
hi = pow(h, cofactor, p) # Target in subgroup
|
|
157
|
+
|
|
158
|
+
# Solve DLP in small subgroup via BSGS
|
|
159
|
+
xi = bsgs(gi, hi, p, order=pe)
|
|
160
|
+
if xi is None:
|
|
161
|
+
return None
|
|
162
|
+
residues.append(xi)
|
|
163
|
+
moduli.append(pe)
|
|
164
|
+
|
|
165
|
+
# Combine via CRT
|
|
166
|
+
x, _ = crt(moduli, residues)
|
|
167
|
+
assert pow(g, x, p) == h % p
|
|
168
|
+
return x
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Key insight:** BSGS runs in O(sqrt(q)) for a subgroup of order q. Pohlig-Hellman decomposes the full DLP into subgroup DLPs. If `p-1 = q1^e1 * q2^e2 * ...` where all qi are small, the total cost is O(sum(sqrt(qi^ei))). A 1024-bit prime with smooth `p-1` (all factors under ~40 bits) is solvable in seconds. Sage's `discrete_log()` automatically applies Pohlig-Hellman + BSGS.
|
|
172
|
+
|
|
173
|
+
**When to recognize:**
|
|
174
|
+
- ElGamal, DSA, or Diffie-Hellman with a randomly generated prime — check if `p-1` is smooth: `factor(p-1)` in Sage
|
|
175
|
+
- ECC with smooth curve order — same approach, replace modular exponentiation with point multiplication
|
|
176
|
+
- Challenge generates new parameters on each connection — retry until you get a smooth prime
|
|
177
|
+
- Challenge description mentions "weak parameters" or uses suspiciously small primes
|
|
178
|
+
|
|
179
|
+
**Sage one-liner:** `discrete_log(Mod(h, p), Mod(g, p))` handles everything automatically.
|
|
180
|
+
|
|
181
|
+
**References:** MMA CTF 2015 "Alicegame", SEC-T CTF "Madlog", Crypto CTF 2021 "RoHaLd"
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## LLL Algorithm for Approximate GCD
|
|
186
|
+
|
|
187
|
+
**Pattern (Grinch's Cryptological Defense):** Server gives hints `h_i = f * p_i + n_i` where f is the flag, p_i are small primes, n_i is small noise.
|
|
188
|
+
|
|
189
|
+
**Lattice construction:**
|
|
190
|
+
```python
|
|
191
|
+
from sage.all import *
|
|
192
|
+
|
|
193
|
+
# Collect 3 hints from server
|
|
194
|
+
# h_i = f * p_i + n_i (noise is small)
|
|
195
|
+
# Construct lattice where short vector reveals primes
|
|
196
|
+
|
|
197
|
+
M = matrix(ZZ, [
|
|
198
|
+
[1, 0, 0, h1],
|
|
199
|
+
[0, 1, 0, h2],
|
|
200
|
+
[0, 0, 1, h3],
|
|
201
|
+
[0, 0, 0, -1] # Scaling factor
|
|
202
|
+
])
|
|
203
|
+
|
|
204
|
+
reduced = M.LLL()
|
|
205
|
+
# Short vector contains p1, p2, p3
|
|
206
|
+
# Recover f = (h1 - n1) / p1
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Merkle-Hellman Knapsack Cryptosystem via LLL (ASIS 2014)
|
|
210
|
+
|
|
211
|
+
The Merkle-Hellman knapsack is a broken asymmetric scheme. Given public key P = [p0, ..., pn-1] and ciphertext C (sum of selected public key elements), recover the binary plaintext vector:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
# Sage
|
|
215
|
+
nbit = len(pubKey)
|
|
216
|
+
A = Matrix(ZZ, nbit + 1, nbit + 1)
|
|
217
|
+
|
|
218
|
+
# Identity matrix in upper-left (tracks which elements are selected)
|
|
219
|
+
for i in range(nbit):
|
|
220
|
+
A[i, i] = 1
|
|
221
|
+
A[i, nbit] = pubKey[i]
|
|
222
|
+
|
|
223
|
+
# Target sum in bottom-right
|
|
224
|
+
A[nbit, nbit] = -int(encoded)
|
|
225
|
+
|
|
226
|
+
# LLL reduction finds short vector where last element is 0
|
|
227
|
+
res = A.LLL()
|
|
228
|
+
|
|
229
|
+
# Find row with last element == 0 and all others in {0, 1}
|
|
230
|
+
for row in res:
|
|
231
|
+
if row[-1] == 0 and all(b in (0, 1) for b in row[:-1]):
|
|
232
|
+
plaintext_bits = list(row[:-1])
|
|
233
|
+
break
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Key insight:** The knapsack problem becomes easy when reformulated as a shortest vector problem. The LLL-reduced basis contains a row representing the binary plaintext when the last column is zero.
|
|
237
|
+
|
|
238
|
+
## Coppersmith's Method (Close Private Keys)
|
|
239
|
+
|
|
240
|
+
**Pattern (Duality of Key):** Two RSA key pairs with d1 ≈ d2 (small difference).
|
|
241
|
+
|
|
242
|
+
**Attack:**
|
|
243
|
+
```python
|
|
244
|
+
# From e1*d1 ≡ 1 mod φ and e2*d2 ≡ 1 mod φ:
|
|
245
|
+
# d2 - d1 ≡ (e1*e2)^(-1) * (e1 - e2) mod p
|
|
246
|
+
|
|
247
|
+
# Construct polynomial f(x) = (r - x) mod p where x = d2-d1
|
|
248
|
+
# Use Coppersmith small_roots() to find x
|
|
249
|
+
|
|
250
|
+
R.<x> = PolynomialRing(Zmod(N))
|
|
251
|
+
r = inverse_mod(e1*e2, N) * (e1 - e2) % N
|
|
252
|
+
f = r - x
|
|
253
|
+
roots = f.small_roots(X=2^128, beta=0.5) # Adjust bounds
|
|
254
|
+
# x = d2 - d1, recover p from gcd(f(x), N)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Coppersmith's Method (Structured Primes, LACTF 2026)
|
|
258
|
+
|
|
259
|
+
**Pattern (six-seven-again):** p = base + 10^k · x where base is fully known, x is small.
|
|
260
|
+
|
|
261
|
+
**Condition:** x < N^{1/e} for degree-e polynomial (≈ N^0.25 for linear).
|
|
262
|
+
|
|
263
|
+
**Attack:**
|
|
264
|
+
```python
|
|
265
|
+
# p = base + 10^k * x, so x ≡ -base * (10^k)^{-1} (mod p)
|
|
266
|
+
# Since p | N, construct polynomial with root x mod N
|
|
267
|
+
R.<x> = PolynomialRing(Zmod(N))
|
|
268
|
+
inv_10k = inverse_mod(10^k, N)
|
|
269
|
+
f = x + (base * inv_10k) % N # Must be monic!
|
|
270
|
+
roots = f.small_roots(X=2^70, beta=0.5)
|
|
271
|
+
if roots:
|
|
272
|
+
x_val = int(roots[0])
|
|
273
|
+
p = base + 10^k * x_val
|
|
274
|
+
q = N // p
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Key details:**
|
|
278
|
+
- Polynomial MUST be monic (leading coefficient 1)
|
|
279
|
+
- `beta=0.5` means we're looking for a factor ≥ N^0.5
|
|
280
|
+
- `X` parameter is upper bound on root size
|
|
281
|
+
- Works for any "partially known prime" pattern
|
|
282
|
+
|
|
283
|
+
## Clock Group (x^2+y^2=1 mod p) DLP (LACTF 2026)
|
|
284
|
+
|
|
285
|
+
**Pattern (the-clock):** Diffie-Hellman on the unit circle group.
|
|
286
|
+
|
|
287
|
+
**Group structure:**
|
|
288
|
+
```python
|
|
289
|
+
# Group law: (x1,y1) * (x2,y2) = (x1*y2 + y1*x2, y1*y2 - x1*x2)
|
|
290
|
+
# Identity: (0, 1)
|
|
291
|
+
# Inverse of (x, y): (-x, y)
|
|
292
|
+
# Group order: p + 1 (NOT p - 1!)
|
|
293
|
+
|
|
294
|
+
def clock_mul(P, Q, p):
|
|
295
|
+
x1, y1 = P
|
|
296
|
+
x2, y2 = Q
|
|
297
|
+
return ((x1*y2 + y1*x2) % p, (y1*y2 - x1*x2) % p)
|
|
298
|
+
|
|
299
|
+
def clock_pow(P, n, p):
|
|
300
|
+
result = (0, 1) # identity
|
|
301
|
+
base = P
|
|
302
|
+
while n > 0:
|
|
303
|
+
if n & 1:
|
|
304
|
+
result = clock_mul(result, base, p)
|
|
305
|
+
base = clock_mul(base, base, p)
|
|
306
|
+
n >>= 1
|
|
307
|
+
return result
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Recovering hidden prime p:**
|
|
311
|
+
```python
|
|
312
|
+
# Given points on the curve, p divides (x^2 + y^2 - 1)
|
|
313
|
+
from math import gcd
|
|
314
|
+
vals = [x**2 + y**2 - 1 for x, y in known_points]
|
|
315
|
+
p = reduce(gcd, vals)
|
|
316
|
+
# May need to remove small factors
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Pohlig-Hellman when p+1 is smooth:**
|
|
320
|
+
```python
|
|
321
|
+
order = p + 1
|
|
322
|
+
factors = factor(order)
|
|
323
|
+
# Standard Pohlig-Hellman in the clock group
|
|
324
|
+
# Solve d in each prime-power subgroup, CRT combine
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**CRITICAL:** The order is p+1, isomorphic to norm-1 elements of GF(p²)*. This is different from multiplicative group (order p-1) and elliptic curves (order ≈ p).
|
|
328
|
+
|
|
329
|
+
## Quaternion RSA
|
|
330
|
+
|
|
331
|
+
**Pattern:** RSA encryption using Hamilton quaternion algebra over Z/nZ. The plaintext is embedded into quaternion components that are linear combinations of m, p, q, then the quaternion matrix is raised to power e mod n.
|
|
332
|
+
|
|
333
|
+
**Key structure:**
|
|
334
|
+
```python
|
|
335
|
+
# Quaternion q = a0 + a1*i + a2*j + a3*k
|
|
336
|
+
# Components are linear in m, p, q:
|
|
337
|
+
a0 = m
|
|
338
|
+
a1 = m + α1*p + β1*q # e.g., m + 3p + 7q
|
|
339
|
+
a2 = m + α2*p + β2*q # e.g., m + 11p + 13q
|
|
340
|
+
a3 = m + α3*p + β3*q # e.g., m + 17p + 19q
|
|
341
|
+
|
|
342
|
+
# 4x4 matrix representation:
|
|
343
|
+
# Row 0: [a0, -a1, -a2, -a3]
|
|
344
|
+
# Row 1: [a1, a0, -a3, a2]
|
|
345
|
+
# Row 2: [a2, a3, a0, -a1]
|
|
346
|
+
# Row 3: [a3, -a2, a1, a0]
|
|
347
|
+
|
|
348
|
+
# Ciphertext = first row of matrix^e mod n
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Critical property:** For quaternion `q = s + v` (scalar + vector), `q^k = s_k + t_k*v` — the vector part stays **proportional** under exponentiation. This means the ratios of imaginary components are preserved:
|
|
352
|
+
|
|
353
|
+
`c1 : c2 : c3 = a1 : a2 : a3 (mod n)`
|
|
354
|
+
|
|
355
|
+
**Factoring n (the attack):**
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
import math
|
|
359
|
+
|
|
360
|
+
# Extract quaternion components from ciphertext row [ct0, ct1, ct2, ct3]
|
|
361
|
+
# Row 0 = [c0, -c1, -c2, -c3], so negate last 3:
|
|
362
|
+
c0, c1, c2, c3 = ct[0], (-ct[1]) % n, (-ct[2]) % n, (-ct[3]) % n
|
|
363
|
+
|
|
364
|
+
# From ratio preservation: c1*a2 = c2*a1 (mod n), c1*a3 = c3*a1 (mod n)
|
|
365
|
+
# Substituting a_i = m + αi*p + βi*q and eliminating m between two equations:
|
|
366
|
+
# Result: A*p + B*q ≡ 0 (mod n=pq) => q|A, p|B
|
|
367
|
+
|
|
368
|
+
# For components a1=m+α1p+β1q, a2=m+α2p+β2q, a3=m+α3p+β3q:
|
|
369
|
+
# Eliminate m from (c1*a2=c2*a1) and (c1*a3=c3*a1):
|
|
370
|
+
A = (-(α1*c1 - α2*c2)*(c1-c3) + (α1*c1 - α3*c3)*(c1-c2)) % n
|
|
371
|
+
B = (-(β1*c1 - β2*c2)*(c1-c3) + (β1*c1 - β3*c3)*(c1-c2)) % n
|
|
372
|
+
|
|
373
|
+
# More concretely for coefficients [3,7], [11,13], [17,19]:
|
|
374
|
+
A = (-(11*c1-3*c2)*(c1-c3) + (17*c1-3*c3)*(c1-c2)) % n
|
|
375
|
+
B = (-(13*c1-7*c2)*(c1-c3) + (19*c1-7*c3)*(c1-c2)) % n
|
|
376
|
+
|
|
377
|
+
q_factor = math.gcd(A, n) # gives q
|
|
378
|
+
p_factor = math.gcd(B, n) # gives p
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Decryption after factoring:**
|
|
382
|
+
|
|
383
|
+
Over F_p, the quaternion algebra H_p ≅ M_2(F_p) (Wedderburn theorem), so the quaternion's multiplicative order divides p²-1. Decrypt using:
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
# Group order for quaternions over F_p divides p²-1
|
|
387
|
+
d_p = pow(e, -1, p**2 - 1)
|
|
388
|
+
d_q = pow(e, -1, q**2 - 1)
|
|
389
|
+
|
|
390
|
+
# Decrypt mod p and mod q separately, then CRT
|
|
391
|
+
enc_mod_p = [[x % p for x in row] for row in enc_matrix]
|
|
392
|
+
enc_mod_q = [[x % q for x in row] for row in enc_matrix]
|
|
393
|
+
dec_p = matrix_pow(enc_mod_p, d_p, p)
|
|
394
|
+
dec_q = matrix_pow(enc_mod_q, d_q, q)
|
|
395
|
+
|
|
396
|
+
# CRT combine: dec_matrix[0][0] = m (the flag)
|
|
397
|
+
m = CRT(dec_p[0][0], dec_q[0][0], p, q)
|
|
398
|
+
flag = long_to_bytes(m)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**Why it works:** The "reduced dimension" is that 4D quaternion exponentiation reduces to a 2D recurrence (scalar + magnitude of vector), and the direction of the vector part is invariant. This leaks the ratio a1:a2:a3 directly from the ciphertext, enabling factorization.
|
|
402
|
+
|
|
403
|
+
**References:** SECCON CTF 2023 "RSA 4.0", 0xL4ugh CTF "Reduced Dimension"
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Polynomial Arithmetic in GF(2)[x]
|
|
408
|
+
|
|
409
|
+
**Key operations for CTF crypto:**
|
|
410
|
+
```python
|
|
411
|
+
def poly_add(a, b):
|
|
412
|
+
"""Addition in GF(2)[x] = XOR of coefficient integers."""
|
|
413
|
+
return a ^ b
|
|
414
|
+
|
|
415
|
+
def poly_mul(a, b):
|
|
416
|
+
"""Carry-less multiplication in GF(2)[x]."""
|
|
417
|
+
result = 0
|
|
418
|
+
while b:
|
|
419
|
+
if b & 1:
|
|
420
|
+
result ^= a
|
|
421
|
+
a <<= 1
|
|
422
|
+
b >>= 1
|
|
423
|
+
return result
|
|
424
|
+
|
|
425
|
+
def poly_divmod(a, b):
|
|
426
|
+
"""Division with remainder in GF(2)[x]."""
|
|
427
|
+
if b == 0:
|
|
428
|
+
raise ZeroDivisionError
|
|
429
|
+
deg_a, deg_b = a.bit_length() - 1, b.bit_length() - 1
|
|
430
|
+
q = 0
|
|
431
|
+
while deg_a >= deg_b and a:
|
|
432
|
+
shift = deg_a - deg_b
|
|
433
|
+
q ^= (1 << shift)
|
|
434
|
+
a ^= (b << shift)
|
|
435
|
+
deg_a = a.bit_length() - 1
|
|
436
|
+
return q, a # quotient, remainder
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Applications:** CRT in GF(2)[x] for recovering secrets from polynomial remainders, Reed-Solomon-like error correction.
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## RSA Signing Bug
|
|
444
|
+
|
|
445
|
+
**Vulnerability:** Using wrong exponent for signing
|
|
446
|
+
- Correct: `sign = m^d mod n` (private exponent)
|
|
447
|
+
- Bug: `sign = m^e mod n` (public exponent)
|
|
448
|
+
|
|
449
|
+
**Exploitation:**
|
|
450
|
+
```python
|
|
451
|
+
# If signature is m^e mod n, we can "encrypt" to verify
|
|
452
|
+
# and compute e-th root to forge signatures
|
|
453
|
+
from sympy import integer_nthroot
|
|
454
|
+
|
|
455
|
+
# For small e (e.g., 3), take e-th root if m^e < n
|
|
456
|
+
forged_sig, exact = integer_nthroot(message, e)
|
|
457
|
+
if exact:
|
|
458
|
+
print(f"Forged signature: {forged_sig}")
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Non-Permutation S-box Collision Attack (Nullcon 2026)
|
|
464
|
+
|
|
465
|
+
**Detection:** Check if S-box is a permutation:
|
|
466
|
+
```python
|
|
467
|
+
sbox = [...] # 256 entries
|
|
468
|
+
if len(set(sbox)) < 256:
|
|
469
|
+
from collections import Counter
|
|
470
|
+
counts = Counter(sbox)
|
|
471
|
+
for val, cnt in counts.items():
|
|
472
|
+
if cnt > 1:
|
|
473
|
+
colliders = [i for i in range(256) if sbox[i] == val]
|
|
474
|
+
delta = colliders[0] ^ colliders[1]
|
|
475
|
+
print(f"S[{hex(colliders[0])}] = S[{hex(colliders[1])}] = {hex(val)}, delta = {hex(delta)}")
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Attack:** For each key byte position k (0-15):
|
|
479
|
+
1. Try all 256 values v: encrypt two plaintexts differing by `delta` at position k
|
|
480
|
+
2. When `ct1 == ct2`: S-box input at position k was in the collision set `{c0, c1}`
|
|
481
|
+
3. Deduce: `key[k] = v ^ round_const` OR `key[k] = v ^ round_const ^ delta`
|
|
482
|
+
4. 2-way ambiguity per byte -> 2^16 = 65,536 candidates, brute-force locally
|
|
483
|
+
|
|
484
|
+
**Total oracle queries:** 16 x 256 + 1 = 4,097 (reference ciphertext + probes).
|
|
485
|
+
|
|
486
|
+
**Key lessons:**
|
|
487
|
+
- SAT/SMT solvers time out on 15+ rounds of symbolic AES even with simplified S-box
|
|
488
|
+
- Integral/square attacks fail because non-permutation S-box breaks balance property
|
|
489
|
+
- Always check S-box for non-permutation FIRST before attempting complex cryptanalysis
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Polynomial CRT in GF(2)[x] (Nullcon 2026)
|
|
494
|
+
|
|
495
|
+
**Pattern:** Server gives `r = flag mod f` where `f` is a random polynomial over GF(2).
|
|
496
|
+
|
|
497
|
+
**Attack:** Chinese Remainder Theorem in polynomial ring GF(2)[x]:
|
|
498
|
+
1. Collect ~20 pairs `(r_i, f_i)` from server (each `f_i` is ~32-bit random polynomial)
|
|
499
|
+
2. Filter for coprime pairs using polynomial GCD
|
|
500
|
+
3. Apply CRT to combine: `flag = r_i (mod f_i)` for all i
|
|
501
|
+
4. With ~13-20 coprime 32-bit moduli (>= 400 bits combined), flag is unique
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
def poly_crt(remainders, moduli):
|
|
505
|
+
"""CRT in GF(2)[x]: combine (r_i, f_i) pairs."""
|
|
506
|
+
result, mod = remainders[0], moduli[0]
|
|
507
|
+
for i in range(1, len(remainders)):
|
|
508
|
+
g, s, t = poly_xgcd(mod, moduli[i])
|
|
509
|
+
combined_mod = poly_mul(mod, moduli[i])
|
|
510
|
+
result = poly_add(poly_mul(poly_mul(remainders[i], s), mod),
|
|
511
|
+
poly_mul(poly_mul(result, t), moduli[i]))
|
|
512
|
+
result = poly_mod(result, combined_mod)
|
|
513
|
+
mod = combined_mod
|
|
514
|
+
return result, mod
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Manger's RSA Padding Oracle Attack (Nullcon 2026)
|
|
520
|
+
|
|
521
|
+
**Setup:**
|
|
522
|
+
- Key `k < 2^64` (small), RSA modulus `n` is large (1337+ bits)
|
|
523
|
+
- Oracle: "invalid padding" = `decrypt < threshold`, "error" = `decrypt >= threshold`
|
|
524
|
+
- No modular wrap-around because `k << n`
|
|
525
|
+
|
|
526
|
+
**Attack (simplified Manger's):**
|
|
527
|
+
```python
|
|
528
|
+
# Phase 1: Find f1 where k * f1 >= threshold
|
|
529
|
+
f1 = 1
|
|
530
|
+
while oracle(encrypt(f1)) == "below": # multiply ciphertext by f1^e mod n
|
|
531
|
+
f1 *= 2
|
|
532
|
+
# f1/2 < threshold/k <= f1, so k is in [threshold/f1, threshold/(f1/2)]
|
|
533
|
+
|
|
534
|
+
# Phase 2: Binary search for exact key
|
|
535
|
+
lo, hi = 0, threshold
|
|
536
|
+
while lo < hi:
|
|
537
|
+
mid = (lo + hi) // 2
|
|
538
|
+
f_test = ceil(threshold, mid + 1) # f such that k*f >= threshold iff k > mid
|
|
539
|
+
if oracle(encrypt(f_test)) == "above":
|
|
540
|
+
hi = mid
|
|
541
|
+
else:
|
|
542
|
+
lo = mid + 1
|
|
543
|
+
key = lo # ~64 queries for 64-bit key
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
**Total queries:** ~128 (64 for phase 1 + 64 for phase 2).
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## LWE Lattice Attack via CVP (EHAX 2026)
|
|
551
|
+
|
|
552
|
+
**Pattern (Dream Labyrinth):** Multi-layer challenge ending with Learning With Errors (LWE) recovery. Secret vector `s` in {-1, 0, 1}^n, public matrix A, ciphertext `b = A*s + e (mod q)`.
|
|
553
|
+
|
|
554
|
+
**LWE solving with fpylll (CVP/Babai):**
|
|
555
|
+
```python
|
|
556
|
+
from fpylll import IntegerMatrix, LLL, CVP
|
|
557
|
+
import numpy as np
|
|
558
|
+
|
|
559
|
+
q = 3329 # Common LWE modulus (Kyber uses this)
|
|
560
|
+
n = 256 # Secret dimension
|
|
561
|
+
m = 512 # Number of samples
|
|
562
|
+
|
|
563
|
+
# A is m×n matrix, b is m-vector, all mod q
|
|
564
|
+
# Construct lattice basis for CVP approach
|
|
565
|
+
# Lattice: rows of [q*I_m | 0] on top, [A^T | I_n] below
|
|
566
|
+
# Target: b
|
|
567
|
+
|
|
568
|
+
def solve_lwe_cvp(A, b, q, n, m):
|
|
569
|
+
# Build lattice basis (m+n) × (m+n)
|
|
570
|
+
dim = m + n
|
|
571
|
+
B = IntegerMatrix(dim, dim)
|
|
572
|
+
|
|
573
|
+
# Top m rows: q*I_m (ensures solutions mod q)
|
|
574
|
+
for i in range(m):
|
|
575
|
+
B[i, i] = q
|
|
576
|
+
|
|
577
|
+
# Bottom n rows: A columns + identity
|
|
578
|
+
for j in range(n):
|
|
579
|
+
for i in range(m):
|
|
580
|
+
B[m + j, i] = int(A[i][j])
|
|
581
|
+
B[m + j, m + j] = 1
|
|
582
|
+
|
|
583
|
+
# LLL reduce the basis
|
|
584
|
+
LLL.reduction(B)
|
|
585
|
+
|
|
586
|
+
# Target vector: (b | 0...0)
|
|
587
|
+
target = [int(b[i]) for i in range(m)] + [0] * n
|
|
588
|
+
|
|
589
|
+
# CVP via Babai's nearest plane
|
|
590
|
+
closest = CVP.babai(B, target)
|
|
591
|
+
|
|
592
|
+
# Extract secret from last n components
|
|
593
|
+
s_candidate = [closest[m + j] for j in range(n)]
|
|
594
|
+
|
|
595
|
+
# Project to ternary {-1, 0, 1}
|
|
596
|
+
s = []
|
|
597
|
+
for val in s_candidate:
|
|
598
|
+
val_mod = val % q
|
|
599
|
+
if val_mod == 0:
|
|
600
|
+
s.append(0)
|
|
601
|
+
elif val_mod == 1:
|
|
602
|
+
s.append(1)
|
|
603
|
+
elif val_mod == q - 1:
|
|
604
|
+
s.append(-1)
|
|
605
|
+
else:
|
|
606
|
+
# Try closest ternary value
|
|
607
|
+
s.append(min([-1, 0, 1], key=lambda t: abs((val_mod - t) % q)))
|
|
608
|
+
return s
|
|
609
|
+
|
|
610
|
+
s = solve_lwe_cvp(A, b, q, n, m)
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**CRITICAL: Endianness gotcha.** Server may describe data as "big-endian" but actually use little-endian (or vice versa). If CVP produces garbage, try swapping byte order of the secret interpretation:
|
|
614
|
+
```python
|
|
615
|
+
# If server says big-endian but actually uses little-endian:
|
|
616
|
+
s_bytes_le = bytes([(v % 256) for v in s]) # little-endian
|
|
617
|
+
s_bytes_be = s_bytes_le[::-1] # big-endian
|
|
618
|
+
# Try both interpretations for key derivation
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
**Key derivation after LWE recovery (common pattern):**
|
|
622
|
+
```python
|
|
623
|
+
import hashlib
|
|
624
|
+
from Cryptodome.Cipher import AES
|
|
625
|
+
|
|
626
|
+
s_bytes = bytes([(v % 256) for v in s])
|
|
627
|
+
|
|
628
|
+
# Recover session nonce: XOR wrapped_nonce with hash of secret
|
|
629
|
+
session_nonce = bytes(a ^ b for a, b in
|
|
630
|
+
zip(wrapped_nonce, hashlib.sha256(s_bytes).digest()[:16]))
|
|
631
|
+
|
|
632
|
+
# Derive AES key from secret + nonce
|
|
633
|
+
aes_key = hashlib.sha256(s_bytes + session_nonce).digest()
|
|
634
|
+
|
|
635
|
+
# Decrypt AES-GCM
|
|
636
|
+
cipher = AES.new(aes_key, AES.MODE_GCM, nonce=aes_nonce)
|
|
637
|
+
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**Layer patterns in multi-stage crypto challenges:**
|
|
641
|
+
- **Layer 1 (Geometry):** Reconstruct point positions from noisy distance measurements. Use least-squares or trilateration with multiple models. Compute convex hull of recovered points.
|
|
642
|
+
- **Layer 2 (Subspace):** Find hidden low-dimensional subspace in high-dimensional data. Self-dot products of candidate vectors identify correct answers (smallest self-dot products = closest to subspace).
|
|
643
|
+
- **Layer 3 (LWE):** Recover secret vector from lattice problem. Use CVP with fpylll, project result to expected domain (ternary, binary, etc.).
|
|
644
|
+
|
|
645
|
+
**References:** EHAX CTF 2026 "Dream Labyrinth". Related: Kyber/CRYSTALS lattice cryptography.
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## Affine Cipher over Non-Prime Modulus (Nullcon 2026)
|
|
650
|
+
|
|
651
|
+
**Pattern:** `c = A @ p + b (mod m)` where A is nxn matrix, m may not be prime (e.g., 65).
|
|
652
|
+
|
|
653
|
+
**Chosen-plaintext attack:**
|
|
654
|
+
1. Send n+1 crafted inputs to get n+1 ciphertext blocks
|
|
655
|
+
2. Difference attack: `c_i - c_0 = A @ (p_i - p_0) (mod m)`
|
|
656
|
+
3. Build difference matrices D (plaintext) and E (ciphertext)
|
|
657
|
+
4. Solve: `A = E @ D^{-1} (mod m)` using Gauss-Jordan with GCD invertibility checks
|
|
658
|
+
5. Recover: `b = c_0 - A @ p_0 (mod m)`
|
|
659
|
+
|
|
660
|
+
**CRT approach for composite modulus (preferred):**
|
|
661
|
+
```python
|
|
662
|
+
def crt2(r1, m1, r2, m2):
|
|
663
|
+
"""CRT: x = r1 (mod m1) and x = r2 (mod m2)"""
|
|
664
|
+
m1_inv = pow(m1, m2 - 2, m2) # Fermat's little theorem
|
|
665
|
+
t = ((r2 - r1) * m1_inv) % m2
|
|
666
|
+
return (r1 + m1 * t) % (m1 * m2)
|
|
667
|
+
|
|
668
|
+
def gauss_elim(A, b, mod):
|
|
669
|
+
"""Gaussian elimination over Z/modZ. A=matrix, b=vector, returns solution x."""
|
|
670
|
+
n = len(b)
|
|
671
|
+
M = [list(A[i]) + [b[i]] for i in range(n)] # augmented matrix
|
|
672
|
+
for col in range(n):
|
|
673
|
+
pivot = next((r for r in range(col, n) if M[r][col] % mod), None)
|
|
674
|
+
if pivot is None: continue
|
|
675
|
+
M[col], M[pivot] = M[pivot], M[col]
|
|
676
|
+
inv = pow(M[col][col], -1, mod)
|
|
677
|
+
M[col] = [x * inv % mod for x in M[col]]
|
|
678
|
+
for r in range(n):
|
|
679
|
+
if r != col and M[r][col] % mod:
|
|
680
|
+
f = M[r][col]
|
|
681
|
+
M[r] = [(M[r][j] - f * M[col][j]) % mod for j in range(n + 1)]
|
|
682
|
+
return [M[i][n] % mod for i in range(n)]
|
|
683
|
+
|
|
684
|
+
# For m=65=5x13: Gaussian elimination in GF(5) and GF(13) separately
|
|
685
|
+
A5, b5 = A % 5, rhs % 5
|
|
686
|
+
A13, b13 = A % 13, rhs % 13
|
|
687
|
+
x5 = gauss_elim(A5, b5, mod=5)
|
|
688
|
+
x13 = gauss_elim(A13, b13, mod=13)
|
|
689
|
+
x = [crt2(x5[i], 5, x13[i], 13) for i in range(len(x5))]
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
## Introspective CRC via GF(2) Linear Algebra (Google CTF 2017)
|
|
695
|
+
|
|
696
|
+
**Pattern:** Find an ASCII string whose CRC-N value equals the string itself (self-referential CRC). Model CRC as a linear function over GF(2) and solve the resulting system.
|
|
697
|
+
|
|
698
|
+
```python
|
|
699
|
+
# CRC is linear over GF(2): CRC(a XOR b) = CRC(a) XOR CRC(b)
|
|
700
|
+
# Goal: find x where CRC(x) = x (as ASCII hex)
|
|
701
|
+
# 1. Compute CRC of all-zeros baseline
|
|
702
|
+
# 2. For each bit position, compute the CRC difference (remainder)
|
|
703
|
+
# 3. Set up GF(2) linear system: CRC(x) XOR x = 0
|
|
704
|
+
# 4. Solve with Gaussian elimination over GF(2)
|
|
705
|
+
from sage.all import *
|
|
706
|
+
F = GF(2)
|
|
707
|
+
# Build matrix where each column represents flipping one bit
|
|
708
|
+
# Rows represent the CRC output bits XOR input bits
|
|
709
|
+
M = Matrix(F, n_bits, n_bits)
|
|
710
|
+
# ... fill with CRC remainders ...
|
|
711
|
+
solution = M.solve_right(target_vector)
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Key insight:** CRC is a linear function over GF(2). The self-referential constraint CRC(x)=x becomes a system of linear equations over GF(2), solvable by Gaussian elimination. The ASCII constraint requires choosing free variables to keep all bytes in the printable range.
|
|
715
|
+
|
|
716
|
+
**References:** Google CTF 2017
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## Baby-Step Giant-Step for Sparse/Low Hamming Weight Exponents (SEC-T CTF 2017)
|
|
721
|
+
|
|
722
|
+
**Pattern:** DLP where the exponent is known to have low Hamming weight — e.g., at most k=11 bits set in a 128-bit exponent. Split the exponent into two halves `e = e1 * 2^64 + e2`. Precompute baby steps for all `e1` values with ⌊k/2⌋ = 5 bits set, then do giant steps for all `e2` values with ⌈k/2⌉ = 6 bits set.
|
|
723
|
+
|
|
724
|
+
**Complexity:** `C(128, 5) ≈ 10^8` baby steps + `C(128, 6) ≈ 10^9` giant steps — vastly less than `O(2^128)` brute force or `O(2^64)` standard BSGS.
|
|
725
|
+
|
|
726
|
+
```python
|
|
727
|
+
from itertools import combinations
|
|
728
|
+
from math import comb
|
|
729
|
+
|
|
730
|
+
# Parameters: g^x = a (mod p), x has at most k=11 bits set in 128 bits
|
|
731
|
+
# Split: x = x1 * 2^64 + x2, where x1 has 5 bits set, x2 has 6 bits set
|
|
732
|
+
half = 64
|
|
733
|
+
k_low, k_high = 5, 6
|
|
734
|
+
|
|
735
|
+
# Baby step: g^(x1 * 2^64) for all x1 with k_low bits set
|
|
736
|
+
baby = {}
|
|
737
|
+
for bit_positions in combinations(range(half), k_low):
|
|
738
|
+
x1 = sum(1 << b for b in bit_positions)
|
|
739
|
+
val = pow(g, x1 * (2**half), p)
|
|
740
|
+
baby[val] = x1
|
|
741
|
+
|
|
742
|
+
# Giant step: check if a * g^(-x2) is in baby table
|
|
743
|
+
# a = g^x = g^(x1*2^64) * g^x2, so g^(x1*2^64) = a * g^(-x2)
|
|
744
|
+
g_inv = pow(g, -1, p)
|
|
745
|
+
for bit_positions in combinations(range(half), k_high):
|
|
746
|
+
x2 = sum(1 << b for b in bit_positions)
|
|
747
|
+
candidate = (a * pow(g_inv, x2, p)) % p
|
|
748
|
+
if candidate in baby:
|
|
749
|
+
x1 = baby[candidate]
|
|
750
|
+
x = x1 * (2**half) + x2
|
|
751
|
+
assert pow(g, x, p) == a
|
|
752
|
+
print(f"Found exponent: {x}")
|
|
753
|
+
break
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
**Verification:**
|
|
757
|
+
```python
|
|
758
|
+
# Check Hamming weight of recovered exponent
|
|
759
|
+
assert bin(x).count('1') <= 11
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
**Key insight:** Sparse-exponent DLP with only k bits set is attackable with meet-in-the-middle: each half uses `C(n, k/2)` entries, reducing complexity from `O(2^k)` to `O(C(n, k/2))`. For k=11 in 128 bits, this is ~10^8 vs 2^128. Always check if the challenge reveals or constrains the Hamming weight of the exponent.
|
|
763
|
+
|
|
764
|
+
**References:** SEC-T CTF 2017
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## Hensel's Lemma: Polynomial Root Lifting mod p^k (CONFidence CTF 2019 Teaser)
|
|
769
|
+
|
|
770
|
+
**Pattern (Bro, do you even lift?):** Challenge gives a polynomial `P(x)` whose unique root mod `N = p^k` is the flag, where `p` is a small known prime and `k` is large (e.g. `p ~ 2^16`, `k = 100`). Brute force over `p^k` is hopeless, but Hensel's lemma lifts any simple root mod `p` to a unique root mod `p^k` via Newton iteration: given `P(r) ≡ 0 mod p^i`, the lift is `r' = r - P(r) * inverse(P'(r), p) mod p^(i+1)`. Factor out the intermediate reductions to `mod p^(i+1)` each step or the integers blow up exponentially.
|
|
771
|
+
|
|
772
|
+
```python
|
|
773
|
+
# sage
|
|
774
|
+
R.<x> = PolynomialRing(ZZ)
|
|
775
|
+
pol = ... # polynomial with huge coefficients
|
|
776
|
+
p = 35671
|
|
777
|
+
k = 100
|
|
778
|
+
|
|
779
|
+
# Step 1: roots mod p (small, enumerable)
|
|
780
|
+
roots_p = [r for r in range(p) if pol(r) % p == 0]
|
|
781
|
+
|
|
782
|
+
# Step 2: Newton lift to p, p^2, ..., p^k
|
|
783
|
+
def hensel_lift(pol, root, p, k):
|
|
784
|
+
dpol = pol.derivative()
|
|
785
|
+
r = root
|
|
786
|
+
mod = p
|
|
787
|
+
for i in range(1, k):
|
|
788
|
+
mod_next = mod * p
|
|
789
|
+
# r' = r - P(r) * inverse(P'(r), p) mod p^(i+1)
|
|
790
|
+
inv = inverse_mod(int(dpol(r)) % p, p)
|
|
791
|
+
r = (r - int(pol(r)) * inv) % mod_next
|
|
792
|
+
mod = mod_next
|
|
793
|
+
return r
|
|
794
|
+
|
|
795
|
+
flag_int = hensel_lift(pol, roots_p[0], p, k)
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
**Key insight:** Any polynomial equation `P(x) ≡ 0 mod p^k` with `p` small and `p` not dividing `P'(root)` collapses to enumerating roots mod `p` (cheap) plus `k - 1` Newton-style lifts. Each lift requires *reducing intermediate values mod `p^(i+1)` at every step* — Sage's naive `solve_right` or unreduced iteration grinds to a halt because `P(r)` grows as large integers. Works for any `N = p^k` or more generally `N = prod(p_i^{k_i})` by lifting each prime-power factor independently and recombining via CRT.
|