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,456 @@
|
|
|
1
|
+
# CTF Crypto - ZKP, Solvers & Advanced Techniques
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [ZKP Attacks](#zkp-attacks)
|
|
5
|
+
- [Graph 3-Coloring](#graph-3-coloring)
|
|
6
|
+
- [Z3 SMT Solver Guide](#z3-smt-solver-guide)
|
|
7
|
+
- [Garbled Circuits: Free XOR Delta Recovery (LACTF 2026)](#garbled-circuits-free-xor-delta-recovery-lactf-2026)
|
|
8
|
+
- [Bigram/Trigram Substitution -> Constraint Solving (LACTF 2026)](#bigramtrigram-substitution---constraint-solving-lactf-2026)
|
|
9
|
+
- [Shamir Secret Sharing with Deterministic Coefficients (LACTF 2026)](#shamir-secret-sharing-with-deterministic-coefficients-lactf-2026)
|
|
10
|
+
- [Race Condition in Crypto-Protected Endpoints (LACTF 2026)](#race-condition-in-crypto-protected-endpoints-lactf-2026)
|
|
11
|
+
- [Garbled Circuits: AES Key Recovery via Metadata Leakage (srdnlenCTF 2026)](#garbled-circuits-aes-key-recovery-via-metadata-leakage-srdnlenctf-2026)
|
|
12
|
+
- [Post-Quantum Signature Fault Injection: MAYO (srdnlenCTF 2026)](#post-quantum-signature-fault-injection-mayo-srdnlenctf-2026)
|
|
13
|
+
- [Lattice-Based Threshold Signature Attack: FROST (srdnlenCTF 2026)](#lattice-based-threshold-signature-attack-frost-srdnlenctf-2026)
|
|
14
|
+
- [Groth16 Broken Trusted Setup — delta == gamma (DiceCTF 2026)](#groth16-broken-trusted-setup--delta--gamma-dicectf-2026)
|
|
15
|
+
- [Groth16 Proof Replay — Unconstrained Nullifier (DiceCTF 2026)](#groth16-proof-replay--unconstrained-nullifier-dicectf-2026)
|
|
16
|
+
- [DV-SNARG Forgery via Verifier Oracle (DiceCTF 2026)](#dv-snarg-forgery-via-verifier-oracle-dicectf-2026)
|
|
17
|
+
- [KZG Pairing Oracle for Permutation Recovery (UNbreakable 2026)](#kzg-pairing-oracle-for-permutation-recovery-unbreakable-2026)
|
|
18
|
+
- [Shamir Secret Sharing with Reused Polynomial Coefficients (PoliCTF 2017)](#shamir-secret-sharing-with-reused-polynomial-coefficients-polictf-2017)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ZKP Attacks
|
|
23
|
+
|
|
24
|
+
- Look for information leakage in proofs
|
|
25
|
+
- If proving IMPOSSIBLE problem (e.g., 3-coloring K4), you must cheat
|
|
26
|
+
- Find hash collisions to commit to one value but reveal another
|
|
27
|
+
- PRNG state recovery: salts generated from seeded PRNG can be predicted
|
|
28
|
+
- Small domain brute force: if you know `commit(i) = sha256(salt(i), color(i))` and have salt, brute all colors
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Graph 3-Coloring
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import networkx as nx
|
|
36
|
+
nx.coloring.greedy_color(G, strategy='saturation_largest_first')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Z3 SMT Solver Guide
|
|
42
|
+
|
|
43
|
+
Z3 solves constraint satisfaction - useful when crypto reduces to finding values satisfying conditions.
|
|
44
|
+
|
|
45
|
+
**Basic usage:**
|
|
46
|
+
```python
|
|
47
|
+
from z3 import *
|
|
48
|
+
|
|
49
|
+
# Boolean variables (for bit-level problems)
|
|
50
|
+
bits = [Bool(f'b{i}') for i in range(64)]
|
|
51
|
+
|
|
52
|
+
# Integer/bitvector variables
|
|
53
|
+
x = BitVec('x', 32) # 32-bit bitvector
|
|
54
|
+
y = Int('y') # arbitrary precision int
|
|
55
|
+
|
|
56
|
+
solver = Solver()
|
|
57
|
+
solver.add(x ^ 0xdeadbeef == 0x12345678)
|
|
58
|
+
solver.add(y > 100, y < 200)
|
|
59
|
+
|
|
60
|
+
if solver.check() == sat:
|
|
61
|
+
model = solver.model()
|
|
62
|
+
print(model.eval(x))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**BPF/SECCOMP filter solving:**
|
|
66
|
+
|
|
67
|
+
When challenges use BPF bytecode for flag validation (e.g., custom syscall handlers):
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from z3 import *
|
|
71
|
+
|
|
72
|
+
# Model flag as array of 4-byte chunks (how BPF sees it)
|
|
73
|
+
flag = [BitVec(f'f{i}', 32) for i in range(14)]
|
|
74
|
+
s = Solver()
|
|
75
|
+
|
|
76
|
+
# Constraint: printable ASCII
|
|
77
|
+
for f in flag:
|
|
78
|
+
for byte in range(4):
|
|
79
|
+
b = (f >> (byte * 8)) & 0xff
|
|
80
|
+
s.add(b >= 0x20, b < 0x7f)
|
|
81
|
+
|
|
82
|
+
# Extract constraints from BPF dump (seccomp-tools dump ./binary)
|
|
83
|
+
mem = [BitVec(f'm{i}', 32) for i in range(16)]
|
|
84
|
+
|
|
85
|
+
# Example BPF constraint reconstruction
|
|
86
|
+
s.add(mem[0] == flag[0])
|
|
87
|
+
s.add(mem[1] == mem[0] ^ flag[1])
|
|
88
|
+
s.add(mem[4] == mem[0] + mem[1] + mem[2] + mem[3])
|
|
89
|
+
s.add(mem[8] == 4127179254) # From BPF if statement
|
|
90
|
+
|
|
91
|
+
if s.check() == sat:
|
|
92
|
+
m = s.model()
|
|
93
|
+
flag_bytes = b''
|
|
94
|
+
for f in flag:
|
|
95
|
+
val = m[f].as_long()
|
|
96
|
+
flag_bytes += val.to_bytes(4, 'little')
|
|
97
|
+
print(flag_bytes.decode())
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Converting bits to flag:**
|
|
101
|
+
```python
|
|
102
|
+
from Crypto.Util.number import long_to_bytes
|
|
103
|
+
|
|
104
|
+
if solver.check() == sat:
|
|
105
|
+
model = solver.model()
|
|
106
|
+
flag_bits = ''.join('1' if model.eval(b) else '0' for b in bits)
|
|
107
|
+
print(long_to_bytes(int(flag_bits, 2)))
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**When to use Z3:**
|
|
111
|
+
- Type system constraints (OCaml GADTs, Haskell types)
|
|
112
|
+
- Custom hash/cipher with algebraic structure
|
|
113
|
+
- Equation systems over finite fields
|
|
114
|
+
- Boolean satisfiability encoded in challenge
|
|
115
|
+
- Constraint propagation puzzles
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Garbled Circuits: Free XOR Delta Recovery (LACTF 2026)
|
|
120
|
+
|
|
121
|
+
**Pattern (sisyphus):** Yao's garbled circuit with free XOR optimization. Circuit designed so normal evaluation only reaches one wire label, but the other is needed.
|
|
122
|
+
|
|
123
|
+
**Free XOR property:** Wire labels satisfy `W_0 XOR W_1 = delta` for global secret delta.
|
|
124
|
+
|
|
125
|
+
**Attack:** XOR three of four encrypted truth table entries to cancel AES terms:
|
|
126
|
+
```python
|
|
127
|
+
# Encrypted rows: E_i = AES(key_a_i XOR key_b_i, G_out_f(a,b))
|
|
128
|
+
# XOR of three rows where AES inputs differ by delta causes cancellation
|
|
129
|
+
# Reveals delta directly, then compute: W_1 = W_0 XOR delta
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**General lesson:** In garbled circuits, if you can obtain any two labels for the same wire, you recover delta and can compute all labels.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Bigram/Trigram Substitution -> Constraint Solving (LACTF 2026)
|
|
137
|
+
|
|
138
|
+
**Pattern (lazy-bigrams):** Bigram substitution cipher where plaintext has known structure (NATO phonetic alphabet).
|
|
139
|
+
|
|
140
|
+
**OR-Tools CP-SAT approach:**
|
|
141
|
+
1. Model substitution as injective mapping (IntVar per bigram)
|
|
142
|
+
2. Add crib constraints from known flag prefix
|
|
143
|
+
3. Add **regular language constraint** (automaton) for valid NATO word sequences
|
|
144
|
+
4. Solver finds unique solution
|
|
145
|
+
|
|
146
|
+
**Pattern (not-so-lazy-trigrams):** "Trigram substitution" that decomposes into three independent monoalphabetic ciphers on positions mod 3.
|
|
147
|
+
|
|
148
|
+
**Decomposition insight:** If cipher uses `shuffle[pos % n][char]`, each residue class `pos = k (mod n)` is an independent monoalphabetic substitution. Solve each separately with frequency analysis or known-plaintext.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Shamir Secret Sharing with Deterministic Coefficients (LACTF 2026)
|
|
153
|
+
|
|
154
|
+
**Pattern (spreading-secrets):** Coefficients `a_1...a_9` are deterministic functions of secret s (via RNG seeded with s). One share (x_0, y_0) is revealed.
|
|
155
|
+
|
|
156
|
+
**Vulnerability:** Given one share, the equation `y_0 = s + g(s)*x_0 + g^2(s)*x_0^2 + ... + g^9(s)*x_0^9` is **univariate** in s.
|
|
157
|
+
|
|
158
|
+
**Root-finding via Frobenius:**
|
|
159
|
+
```python
|
|
160
|
+
# In GF(p), find roots of h(s) via gcd with x^p - x
|
|
161
|
+
# h(s) = s + g(s)*x_0 + ... + g^9(s)*x_0^9 - y_0
|
|
162
|
+
# Compute x^p mod h(x) via binary exponentiation with polynomial reduction
|
|
163
|
+
# gcd(x^p - x, h(x)) = product of (x - root_i) for all roots
|
|
164
|
+
R.<x> = PolynomialRing(GF(p))
|
|
165
|
+
h = construct_polynomial(x0, y0)
|
|
166
|
+
xp = pow(x, p, h) # Fast modular exponentiation
|
|
167
|
+
g = gcd(xp - x, h) # Extract linear factors
|
|
168
|
+
roots = [-g[0]/g[1]] if g.degree() == 1 else g.roots()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**General lesson:** If ALL Shamir coefficients are derived from the secret, a single share creates a univariate equation. This completely breaks the (k,n) threshold scheme.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Race Condition in Crypto-Protected Endpoints (LACTF 2026)
|
|
176
|
+
|
|
177
|
+
**Pattern (misdirection):** Endpoint has TOCTOU vulnerability: `if counter < 4` check happens before increment, allowing concurrent requests to all pass the check.
|
|
178
|
+
|
|
179
|
+
**Exploitation:**
|
|
180
|
+
1. **Cache-bust signatures:** Modify each request slightly (e.g., prepend zeros to nonce) so server can't use cached verification results
|
|
181
|
+
2. **Synchronize requests:** Use multiprocessing with barrier to send ~80 simultaneous requests
|
|
182
|
+
3. All pass `counter < 4` check before any increments -> counter jumps past limit
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from multiprocessing import Process, Barrier
|
|
186
|
+
barrier = Barrier(80)
|
|
187
|
+
|
|
188
|
+
def make_request(barrier, modified_sig):
|
|
189
|
+
barrier.wait() # Synchronize all processes
|
|
190
|
+
requests.post(url, json={"sig": modified_sig})
|
|
191
|
+
|
|
192
|
+
# Launch 80 processes with unique signature modifications
|
|
193
|
+
processes = [Process(target=make_request, args=(barrier, modify_sig(i))) for i in range(80)]
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Key insight:** TOCTOU in `check-then-act` patterns. Look for read-modify-write without atomicity/locking.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Garbled Circuits: AES Key Recovery via Metadata Leakage (srdnlenCTF 2026)
|
|
201
|
+
|
|
202
|
+
**Pattern (FHAES):** Service evaluates AES via garbled circuits with a fixed per-connection key. Exploit garbling metadata rather than AES cryptanalysis.
|
|
203
|
+
|
|
204
|
+
**Attack:**
|
|
205
|
+
1. Construct a custom circuit with one attacker-controlled AND gate that leaks the global Free-XOR offset delta
|
|
206
|
+
2. Use delta to locally evaluate the key-schedule section (first 1360 AND gates) as the evaluator
|
|
207
|
+
3. For each of the first 16 key-schedule S-box calls, brute-force the input byte by re-garbling the S-box chunk and comparing observed AND tables
|
|
208
|
+
4. Reconstruct key words from S-box outputs and recover the full 128-bit key through algebraic manipulation of the AES-128 schedule recurrence
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
def garble_and(A, B, D, and_idx):
|
|
212
|
+
"""Reproduce garbling with proper parity handling."""
|
|
213
|
+
r = B & 1
|
|
214
|
+
alpha = A & 1
|
|
215
|
+
beta = B & 1
|
|
216
|
+
# Computes gate0, gate1, z output via hash-based approach
|
|
217
|
+
return gate0, gate1, z
|
|
218
|
+
|
|
219
|
+
def evaluator_and(A, B, gate0, gate1, and_idx):
|
|
220
|
+
"""Evaluate AND gate using hash-based approach."""
|
|
221
|
+
hashA = h_wire(A, and_idx)
|
|
222
|
+
hashB = h_wire(B, and_idx)
|
|
223
|
+
L = hashA if (A & 1) == 0 else (hashA ^ gate0)
|
|
224
|
+
R = hashB if (B & 1) == 0 else (hashB ^ gate1)
|
|
225
|
+
return L ^ R ^ (A * (B & 1))
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Key insight:** Garbled circuits that use free XOR optimization with fixed keys across sessions leak key material through the AND gate truth tables. Each S-box has a small enough input space (256 values) to brute-force when you know delta. This extends the LACTF technique from "recovering delta" to "recovering the entire AES key."
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Post-Quantum Signature Fault Injection: MAYO (srdnlenCTF 2026)
|
|
233
|
+
|
|
234
|
+
**Pattern (Faulty Mayo):** One-byte fault injection window in `mayo_sign_signature` before final `s = v + O*x` construction. Controlled bit flips across 64 signature queries recover the secret matrix O row by row.
|
|
235
|
+
|
|
236
|
+
**Attack:**
|
|
237
|
+
1. Reverse binary to map fault offsets to `mayo_sign_signature` instructions
|
|
238
|
+
2. For each of 64 rows of secret matrix O, use faulted signatures to extract linear equations over GF(16)
|
|
239
|
+
3. Solve 17-variable linear systems over GF(16) for each row using Gaussian elimination
|
|
240
|
+
4. Rebuild equivalent signer using recovered O and public seed from compressed public key
|
|
241
|
+
5. Forge valid signature for challenge message
|
|
242
|
+
|
|
243
|
+
**GF(16) Gaussian elimination:**
|
|
244
|
+
```python
|
|
245
|
+
# Precompute multiplication and inverse tables for GF(16)
|
|
246
|
+
# GF(16) = GF(2)[x] / (x^4 + x + 1), elements 0-15
|
|
247
|
+
INV = [0] * 16 # multiplicative inverses
|
|
248
|
+
MUL = [[0]*16 for _ in range(16)] # multiplication table
|
|
249
|
+
|
|
250
|
+
def solve_linear_gf16(equations, nvars=17):
|
|
251
|
+
"""Gaussian elimination over GF(16)."""
|
|
252
|
+
A = [x[:] + [y] for x, y in equations]
|
|
253
|
+
m, row = len(A), 0
|
|
254
|
+
for col in range(nvars):
|
|
255
|
+
piv = next((r for r in range(row, m) if A[r][col] != 0), None)
|
|
256
|
+
if piv is None: continue
|
|
257
|
+
A[row], A[piv] = A[piv], A[row]
|
|
258
|
+
invp = INV[A[row][col]]
|
|
259
|
+
A[row] = [MUL[invp][v] for v in A[row]]
|
|
260
|
+
for r in range(m):
|
|
261
|
+
if r != row and A[r][col] != 0:
|
|
262
|
+
f = A[r][col]
|
|
263
|
+
A[r] = [A[r][c] ^ MUL[f][A[row][c]] for c in range(nvars + 1)]
|
|
264
|
+
row += 1
|
|
265
|
+
return [A[i][nvars] for i in range(nvars)]
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Key insight:** Post-quantum signature schemes like MAYO can be broken with fault injection if you can cause controlled bit flips during signing. Each fault creates a linear equation over GF(16), and 17+ equations per row suffice to recover the secret. This is analogous to DFA on classical schemes but over extension fields.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Lattice-Based Threshold Signature Attack: FROST (srdnlenCTF 2026)
|
|
273
|
+
|
|
274
|
+
**Pattern (Threshold):** Preprocessing queue capacity allows collecting many signatures. Fixed challenge construction enables solving 1D noisy linear equations per coefficient.
|
|
275
|
+
|
|
276
|
+
**Attack:**
|
|
277
|
+
1. Exploit queue-depth cap (≤8 active) rather than total-usage cap by alternating menu options
|
|
278
|
+
2. Force fixed challenge `c` by choosing commitment `w₀` each query to zero aggregate commitment before high-bit extraction
|
|
279
|
+
3. With fixed `c`, each coefficient becomes: `z = λ·u + noise (mod q)`
|
|
280
|
+
4. Select multiple signer subsets to obtain different Lagrange coefficient scales (small/mid/huge) for each target signer
|
|
281
|
+
5. Solve via interval intersection and maximum-likelihood selection
|
|
282
|
+
6. Recover 7 signer shares; combine with own share; reconstruct master secret via Lagrange interpolation
|
|
283
|
+
|
|
284
|
+
**Interval intersection algorithm:**
|
|
285
|
+
```python
|
|
286
|
+
from math import ceil, floor
|
|
287
|
+
|
|
288
|
+
def intersect_intervals(intervals, lam, z, q, B):
|
|
289
|
+
"""Refine candidate intervals using one (λ, z) observation with noise bound B."""
|
|
290
|
+
out = []
|
|
291
|
+
for lo, hi in intervals:
|
|
292
|
+
if lam > 0:
|
|
293
|
+
kmin = ceil((lam * lo - z - B) / q)
|
|
294
|
+
kmax = floor((lam * hi - z + B) / q)
|
|
295
|
+
for k in range(kmin, kmax + 1):
|
|
296
|
+
a = (z + q * k - B) / lam
|
|
297
|
+
b = (z + q * k + B) / lam
|
|
298
|
+
lo2, hi2 = max(lo, a), min(hi, b)
|
|
299
|
+
if lo2 <= hi2:
|
|
300
|
+
out.append((lo2, hi2))
|
|
301
|
+
# Merge overlapping intervals
|
|
302
|
+
out.sort()
|
|
303
|
+
merged = [out[0]] if out else []
|
|
304
|
+
for lo, hi in out[1:]:
|
|
305
|
+
if lo <= merged[-1][1]:
|
|
306
|
+
merged[-1] = (merged[-1][0], max(merged[-1][1], hi))
|
|
307
|
+
else:
|
|
308
|
+
merged.append((lo, hi))
|
|
309
|
+
return merged
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Key insight:** Threshold signature schemes can leak individual shares when the challenge value is controlled. By querying with different signer subsets, you get different Lagrange coefficient scales for the same unknown share, allowing iterative interval refinement. With enough observations, the interval converges to a unique value.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Groth16 Broken Trusted Setup — delta == gamma (DiceCTF 2026)
|
|
317
|
+
|
|
318
|
+
**Pattern (Housing Crisis):** Groth16 verifier has `vk_delta_2 == vk_gamma_2`, which breaks soundness entirely. Proofs are trivially forgeable.
|
|
319
|
+
|
|
320
|
+
**Forgery:**
|
|
321
|
+
```python
|
|
322
|
+
from py_ecc.bn128 import G1, G2, multiply, add, neg, pairing
|
|
323
|
+
from py_ecc.bn128 import curve_order as q
|
|
324
|
+
|
|
325
|
+
# When delta == gamma, the pairing equation simplifies:
|
|
326
|
+
# e(A, B) = e(alpha, beta) * e(vk_x + C, gamma)
|
|
327
|
+
# Set A = vk_alpha1, B = vk_beta2, then:
|
|
328
|
+
# e(alpha, beta) * e(vk_x + C, gamma) = e(alpha, beta)
|
|
329
|
+
# → e(vk_x + C, gamma) = 1 → C = -vk_x (point negation)
|
|
330
|
+
|
|
331
|
+
forged_A = vk_alpha1 # alpha point from verification key
|
|
332
|
+
forged_B = vk_beta2 # beta point from verification key
|
|
333
|
+
forged_C = neg(vk_x) # negate the public input accumulator
|
|
334
|
+
|
|
335
|
+
# This proof verifies for ANY public inputs
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Detection:** Compare `vk_delta_2` and `vk_gamma_2` in the verifier contract. If equal, the entire Groth16 scheme collapses — any statement can be "proven."
|
|
339
|
+
|
|
340
|
+
**When to check:** Always inspect Groth16 verification key constants before attempting complex attacks. A broken trusted setup makes everything else unnecessary.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Groth16 Proof Replay — Unconstrained Nullifier (DiceCTF 2026)
|
|
345
|
+
|
|
346
|
+
**Pattern (Housing Crisis):** DAO governance never tracks used `proposalNullifierHash` values, and the circuit leaves the nullifier unconstrained. A valid proof from the setup transaction can be replayed infinitely.
|
|
347
|
+
|
|
348
|
+
**Attack:**
|
|
349
|
+
1. Find the DAO contract's deployment/setup transaction
|
|
350
|
+
2. Extract constructor arguments containing valid Groth16 proof
|
|
351
|
+
3. Replay the same proof for every proposal — it always verifies
|
|
352
|
+
4. Use proposals to control DAO actions (betting, market creation, resolution)
|
|
353
|
+
|
|
354
|
+
**Key insight:** ZK circuits that leave inputs unconstrained and systems that don't track nullifiers are vulnerable to replay. Always check: does the verifier contract track proof nullifiers? Does the circuit actually constrain all declared public inputs?
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## DV-SNARG Forgery via Verifier Oracle (DiceCTF 2026)
|
|
359
|
+
|
|
360
|
+
**Pattern (Dot):** DV-SNARG (Designated Verifier Succinct Non-interactive ARGument) for an adder circuit. Must produce 20 valid proofs for **wrong** answers.
|
|
361
|
+
|
|
362
|
+
**Key insight:** DV-SNARGs explicitly lose soundness when the prover has oracle access to the verifier (ePrint 2024/1138). The verifier's secret randomness can be extracted through query patterns.
|
|
363
|
+
|
|
364
|
+
**DPP (Dot Product Proof) structure:**
|
|
365
|
+
```text
|
|
366
|
+
q[i] = v[i] + b*(tensor[i] - constraint[i])
|
|
367
|
+
where b = fixed constant (e.g., 162817)
|
|
368
|
+
v[i] = random in [-256, 256]
|
|
369
|
+
constraint weights r = random in [-2^40, 2^40]
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Forgery via CRS entry cancellation:**
|
|
373
|
+
For a wrong answer, only the output constraint (wire N) is violated. Find two CRS entries whose constraint contributions cancel:
|
|
374
|
+
|
|
375
|
+
1. Wire N is touched by gate G AND the output constraint
|
|
376
|
+
2. `pair(input1, input2)` of gate G is touched ONLY by gate G
|
|
377
|
+
3. Adding `CRS[wire_N]` and subtracting `CRS[pair]` to the wrong proof cancels `b*r_G` terms
|
|
378
|
+
4. The remaining deficit `b*r_output` also cancels
|
|
379
|
+
5. Adjust `delta = -v[N] + 2*b*v[input1]*v[input2]` via `delta*G` on h2
|
|
380
|
+
|
|
381
|
+
**Learning secret v values via oracle:**
|
|
382
|
+
```python
|
|
383
|
+
# At streak=0, submitting correct answer is "safe" — doesn't reset streak
|
|
384
|
+
# Use oracle to learn |v[i]| from unconstrained diagonal pairs:
|
|
385
|
+
|
|
386
|
+
for guess in range(257): # v[i] in [-256, 256], |v[i]| in [0, 256]
|
|
387
|
+
# Set pair(i,i) coefficient to guess^2
|
|
388
|
+
# If guess == |v[i]|, specific oracle response differs
|
|
389
|
+
response = oracle_query(guess)
|
|
390
|
+
if response == "hit":
|
|
391
|
+
abs_v_i = guess
|
|
392
|
+
break
|
|
393
|
+
|
|
394
|
+
# Learn signs from off-diagonal unconstrained pairs (1 query each)
|
|
395
|
+
# Learn product sign: v[a]*v[b] sign from pair(a,b)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Performance:** ~364 oracle queries for Phase 1 (~97s), ~300s for 20 forged proofs ≈ 400s total.
|
|
399
|
+
|
|
400
|
+
**Key insight:** When attacking DV-SNARGs with oracle access, the strategy is: (1) learn a small number of secret values from the verifier's randomness, (2) use algebraic cancellation between CRS entries to forge proofs. Unconstrained pair indices expose pure tensor products of the secret vector.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## KZG Pairing Oracle for Permutation Recovery (UNbreakable 2026)
|
|
405
|
+
|
|
406
|
+
**Pattern (toxicwaste):** KZG commitment scheme publishes shuffled points `{alpha^i * G1}` for i=0..n. The shuffle hides which point corresponds to which exponent. Recover the exponent ordering using bilinear pairings as an oracle, then extract the toxic waste `alpha`.
|
|
407
|
+
|
|
408
|
+
**Distortion map technique:** On supersingular pairing-friendly curves, a distortion map `psi((x,y)) = (zeta*x, y)` (where `zeta^3 = 1`) enables additive exponent comparisons:
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
from sage.all import *
|
|
412
|
+
|
|
413
|
+
# For points P_i = alpha^a_i * G1 and P_j = alpha^a_j * G1:
|
|
414
|
+
# e(P_i, psi(P_j)) = e(G1, psi(G1))^(alpha^(a_i + a_j))
|
|
415
|
+
# If e(P_i, psi(P_j)) == e(P_k, psi(G1)), then a_i + a_j == a_k
|
|
416
|
+
|
|
417
|
+
# Step 1: Identify G1 (alpha^0) — the only point where e(P, psi(P)) == e(G1, psi(G1))
|
|
418
|
+
g1 = None
|
|
419
|
+
base_pairing = None
|
|
420
|
+
for P in shuffled_points:
|
|
421
|
+
val = P.weil_pairing(psi(P), order)
|
|
422
|
+
if base_pairing is None:
|
|
423
|
+
base_pairing = val
|
|
424
|
+
g1 = P
|
|
425
|
+
elif val == base_pairing:
|
|
426
|
+
g1 = P
|
|
427
|
+
break
|
|
428
|
+
|
|
429
|
+
# Step 2: Walk the chain — find alpha*G1 via e(P_?, psi(G1)) comparisons
|
|
430
|
+
# Then alpha^2*G1 via e(alpha*G1, psi(alpha*G1)) == e(alpha^2*G1, psi(G1))
|
|
431
|
+
# Continue until full ordering recovered
|
|
432
|
+
|
|
433
|
+
# Step 3: With ordered points, solve A(x) = 0 over GF(q) to get alpha
|
|
434
|
+
# Step 4: Forge KZG opening proofs using recovered alpha
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Key insight:** Bilinear pairings reveal additive relationships between exponents without solving discrete log. The pairing `e(P_i, psi(P_j))` depends on `alpha^(a_i + a_j)`, so comparing against known pairing values identifies which shuffled point has which exponent. This turns a cryptographic shuffle into a solvable ordering problem.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Shamir Secret Sharing with Reused Polynomial Coefficients (PoliCTF 2017)
|
|
442
|
+
|
|
443
|
+
**Pattern:** When a Shamir SSS implementation reuses the same random polynomial coefficients for every character of the secret, share subtraction cancels the higher-order terms.
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
# Standard Shamir: y_i = f_i + a1*x + a2*x^2 + ... (different a_j per character)
|
|
447
|
+
# Broken: y_i = f_i + a1*x + a2*x^2 + ... (SAME a_j for all characters)
|
|
448
|
+
# Since higher-order terms are identical:
|
|
449
|
+
# y_1[i] - y_1[0] = f[i] - f[0] (for share x=1)
|
|
450
|
+
# If f[0] is known (e.g., 'f' from flag prefix):
|
|
451
|
+
flag = ''.join(chr(shares[i] - shares[0] + ord('f')) for i in range(len(shares)))
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Key insight:** In correct Shamir SSS, each secret byte uses independent random coefficients. When coefficients are reused, subtracting any two shares at the same evaluation point cancels all randomness, leaving only the difference between the corresponding secret bytes.
|
|
455
|
+
|
|
456
|
+
**References:** PoliCTF 2017
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# CTF Forensics - 3D Printing / CAD File Forensics
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [PrusaSlicer Binary G-code (.g / .bgcode)](#prusaslicer-binary-g-code-g--bgcode)
|
|
5
|
+
- [QOIF (Quite OK Image Format)](#qoif-quite-ok-image-format)
|
|
6
|
+
- [G-code Analysis Tips](#g-code-analysis-tips)
|
|
7
|
+
- [G-code Side View Visualization (0xFun 2026)](#g-code-side-view-visualization-0xfun-2026)
|
|
8
|
+
- [Uncommon File Magic Bytes](#uncommon-file-magic-bytes)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## PrusaSlicer Binary G-code (.g / .bgcode)
|
|
13
|
+
|
|
14
|
+
**File magic:** `GCDE` (4 bytes)
|
|
15
|
+
|
|
16
|
+
The `.g` extension is PrusaSlicer's binary G-code format (bgcode). It stores G-code in a block-based structure with compression.
|
|
17
|
+
|
|
18
|
+
**File structure:**
|
|
19
|
+
```text
|
|
20
|
+
Header: "GCDE"(4) + version(4) + checksum_type(2)
|
|
21
|
+
Blocks: [type(2) + compression(2) + uncompressed_size(4)
|
|
22
|
+
+ compressed_size(4) if compressed
|
|
23
|
+
+ type-specific fields
|
|
24
|
+
+ data + CRC32(4)]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Block types:**
|
|
28
|
+
- 0 = FileMetadata (has encoding field, 2 bytes)
|
|
29
|
+
- 1 = GCode (has encoding field, 2 bytes)
|
|
30
|
+
- 2 = SlicerMetadata (has encoding field, 2 bytes)
|
|
31
|
+
- 3 = PrinterMetadata (has encoding field, 2 bytes)
|
|
32
|
+
- 4 = PrintMetadata (has encoding field, 2 bytes)
|
|
33
|
+
- 5 = Thumbnail (has format(2) + width(2) + height(2))
|
|
34
|
+
|
|
35
|
+
**Compression types:** 0=None, 1=Deflate, 2=Heatshrink(11,4), 3=Heatshrink(12,4)
|
|
36
|
+
|
|
37
|
+
**Thumbnail formats:** 0=PNG, 1=JPEG, 2=QOI (Quite OK Image)
|
|
38
|
+
|
|
39
|
+
**Parsing and extracting G-code:**
|
|
40
|
+
```python
|
|
41
|
+
import struct, zlib
|
|
42
|
+
import heatshrink2 # pip install heatshrink2
|
|
43
|
+
|
|
44
|
+
with open('file.g', 'rb') as f:
|
|
45
|
+
data = f.read()
|
|
46
|
+
|
|
47
|
+
pos = 10 # After header
|
|
48
|
+
while pos < len(data) - 8:
|
|
49
|
+
block_type = struct.unpack('<H', data[pos:pos+2])[0]
|
|
50
|
+
compression = struct.unpack('<H', data[pos+2:pos+4])[0]
|
|
51
|
+
uncompressed_size = struct.unpack('<I', data[pos+4:pos+8])[0]
|
|
52
|
+
pos += 8
|
|
53
|
+
if compression != 0:
|
|
54
|
+
compressed_size = struct.unpack('<I', data[pos:pos+4])[0]
|
|
55
|
+
pos += 4
|
|
56
|
+
else:
|
|
57
|
+
compressed_size = uncompressed_size
|
|
58
|
+
# Type-specific extra header fields
|
|
59
|
+
if block_type in [0,1,2,3,4]:
|
|
60
|
+
pos += 2 # encoding field
|
|
61
|
+
elif block_type == 5:
|
|
62
|
+
pos += 6 # format + width + height
|
|
63
|
+
block_data = data[pos:pos+compressed_size]
|
|
64
|
+
pos += compressed_size + 4 # data + CRC32
|
|
65
|
+
|
|
66
|
+
if block_type == 1: # GCode block
|
|
67
|
+
if compression == 3: # Heatshrink 12/4
|
|
68
|
+
gcode = heatshrink2.decompress(block_data, window_sz2=12, lookahead_sz2=4)
|
|
69
|
+
elif compression == 1: # Deflate (zlib)
|
|
70
|
+
gcode = zlib.decompress(block_data)
|
|
71
|
+
# Search gcode for hidden comments/flags
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Common hiding spots:**
|
|
75
|
+
- G-code comments (`;=== FLAG_CHAR ... ===`) at specific layer heights
|
|
76
|
+
- Custom G-code sections (`;TYPE:Custom`)
|
|
77
|
+
- Metadata fields (object names, filament info)
|
|
78
|
+
- Thumbnail images (extract and view QOIF/PNG)
|
|
79
|
+
|
|
80
|
+
## QOIF (Quite OK Image Format)
|
|
81
|
+
|
|
82
|
+
**Magic:** `qoif` (4 bytes) + width(4 BE) + height(4 BE) + channels(1) + colorspace(1)
|
|
83
|
+
|
|
84
|
+
Lightweight image format used in PrusaSlicer thumbnails. Decode with Python struct or use the `qoi` library.
|
|
85
|
+
|
|
86
|
+
## G-code Analysis Tips
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Search for flag patterns in decompressed gcode
|
|
90
|
+
grep -i "flag\|meta\|ctf\|secret" output.gcode
|
|
91
|
+
|
|
92
|
+
# Look for custom comments at layer changes
|
|
93
|
+
grep ";.*FLAG\|;.*LAYER_CHANGE" output.gcode
|
|
94
|
+
|
|
95
|
+
# Extract XY coordinates for visual patterns
|
|
96
|
+
grep "^G1" output.gcode | awk '{print $2, $3}' > coords.txt
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## G-code Side View Visualization (0xFun 2026)
|
|
100
|
+
|
|
101
|
+
**Pattern (PrintedParts):** Plot X vs Z (side view) with Y filtering. Extrusion segments at specific Y ranges form readable text.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Extract XY coordinates from G-code
|
|
105
|
+
grep "^G1" output.gcode | awk '{print $2, $3}' > coords.txt
|
|
106
|
+
# Plot with matplotlib for visual patterns
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Lesson:** G-code is just coordinate lists. Side projections (XZ or YZ) reveal embossed/engraved text.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Uncommon File Magic Bytes
|
|
114
|
+
|
|
115
|
+
| Magic | Format | Extension | Notes |
|
|
116
|
+
|-------|--------|-----------|-------|
|
|
117
|
+
| `GCDE` | PrusaSlicer binary G-code | `.g`, `.bgcode` | 3D printing, heatshrink compressed |
|
|
118
|
+
| `qoif` | Quite OK Image Format | `.qoi` | Lightweight image format, often embedded |
|
|
119
|
+
| `OggS` | Ogg container | `.ogg` | Audio/video |
|
|
120
|
+
| `RIFF` | RIFF container | `.wav`,`.avi` | Check subformat |
|
|
121
|
+
| `%PDF` | PDF | `.pdf` | Check metadata & embedded objects |
|