# Plotchy CTF Setup ## New Challenge Setup Script ```shell #! /usr/bin/env bash # chmod +x copy_challenge.sh # ./copy_challenge.sh NewChallengeName preamble=/home/plotchy/code/ctf/paradigmctf-2022 cp $preamble/test/TestTemplate.t.sol $preamble/test/$1.t.sol cp $preamble/script/ScriptTemplate.s.sol $preamble/script/$1.s.sol mkdir -p $preamble/src/$1/public/contracts cp -r $preamble/src/chal_name/public/contracts/Exploit.sol $preamble/src/$1/public/contracts/Exploit.sol sed -i "s/chal_name/$1/g" $preamble/test/$1.t.sol sed -i "s/TestTemplate/$1/g" $preamble/test/$1.t.sol sed -i "s/ScriptTemplate/$1/g" $preamble/test/$1.t.sol sed -i "s/chal_name/$1/g" $preamble/script/$1.s.sol sed -i "s/TestTemplate/$1/g" $preamble/script/$1.s.sol sed -i "s/ScriptTemplate/$1/g" $preamble/script/$1.s.sol sed -i "s/chal_name/$1/g" $preamble/src/$1/public/contracts/Exploit.sol ``` ## Test Template ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import "forge-std/Test.sol"; import "../src/chal_name/public/contracts/Setup.sol"; import "../src/chal_name/public/contracts/Exploit.sol"; bytes constant SIXTY_FOUR_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; // ctrlc+v on handcrafted calldata address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; /* Commands: # Testing command forge test --mp ./test/TestTemplate.t.sol --mc Tester --fork-url $ANVIL_URL -vvvvv # Debug command forge test --mp ./test/TestTemplate.t.sol --mc Tester --debug <function> --fork-url $ANVIL_URL # Script command forge script script/ScriptTemplate.s.sol:Scripter --rpc-url $ANVIL_URL --private-key $PRIVATE_KEY_1 --broadcast -vvvvv # Debug broadcasted tx: cast run <TXHASH> -d --rpc-url $ANVIL_URL # Exploratory forge inspect <path>:<ContractName> storage --pretty https://ethervm.io/decompile https://library.dedaub.com/decompile panoramix <bytecode> */ contract Tester is Test { Setup setup; address payable setupAddress; Challenge challenge; address payable challengeAddress; Exploit exploit; address payable exploitAddress; function setUp() public { setup = new Setup{value: 50 ether}(); setupAddress = payable(address(setup)); challenge = setup.challenge(); challengeAddress = payable(address(challenge)); } function testIsSolved() public { // useful to run at beginning to find storage slots .isSolved() uses vm.record(); setup.isSolved(); vm.accesses(challengeAddress); } function testExploit() public { console2.log("Bal after setup: setup: %s, challenge: %s", setupAddress.balance, challengeAddress.balance); exploit = new Exploit{value: 100 ether}(setup, challenge); exploitAddress = payable(address(exploit)); // // alternatively use etk code as exploit // bytes memory etkCode = etkLoad(); // address _addr; // assembly { // _addr := create(0, add(initcode, 0x20), mload(initcode)) // } // exploitAddress = address(_addr); console2.log("Bal after exp-deployed: setup: %s, challenge: %s, exploit: %s", setupAddress.balance, challengeAddress.balance, address(exploit).balance); exploit.finalize(); console2.log("Bal post finalize: setup: %s, challenge: %s, exploit: %s", setupAddress.balance, challengeAddress.balance, address(exploit).balance); console2.log("Solved: %s", exploit.checkSolved()); } function etkLoad() public returns (bytes memory etkCode){ // Helper function to load handcrafted EVM code from a file. // typically used as: // // bytes memory etkCode = etkLoad(); // vm.etch(someAddress, etkCode); // someAddress.call(hex"69696969"); string[] memory inputs = new string[](2); // /** // * windows: scripts/compile.bat // * linux : scripts/compile.sh // */ inputs[0] = "./script/compile.sh"; // // path/to/contract.etk inputs[1] = "./src/chal_name/public/contracts/exploit.etk"; etkCode = vm.ffi(inputs); } } ``` ## Script Template ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "forge-std/Script.sol"; import "../src/chal_name/public/contracts/Setup.sol"; import "../src/chal_name/public/contracts/Exploit.sol"; bytes constant SIXTY_FOUR_ZEROS = "0000000000000000000000000000000000000000000000000000000000000000"; // ctrlc+v on handcrafted calldata address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; /* Commands: # Testing command forge test --mp ./test/TestTemplate.t.sol --mc Tester --fork-url $ANVIL_URL -vvvvv # Debug command forge test --mp ./test/TestTemplate.t.sol --mc Tester --debug <function> --fork-url $ANVIL_URL # Script command forge script script/ScriptTemplate.s.sol:Scripter --rpc-url $ANVIL_URL --private-key $PRIVATE_KEY_1 --broadcast -vvvvv # Debug broadcasted tx: cast run <TXHASH> -d --rpc-url $ANVIL_URL # Exploratory forge inspect <path>:<ContractName> storage --pretty https://ethervm.io/decompile https://library.dedaub.com/decompile panoramix <bytecode> */ contract Scripter is Script { Challenge challenge; address payable challengeAddress; address payable setupAddress; address payable exploitAddress; function setUp() public {} function run() external { vm.startBroadcast(); Setup setup = new Setup{value: 100 ether}(); setupAddress = payable(address(setup)); challenge = setup.challenge(); challengeAddress = payable(address(challenge)); Exploit exploit = new Exploit{value: 100 ether}(setup, challenge); exploitAddress = payable(address(exploit)); exploit.finalize(); // // alternatively use etk code as exploit // bytes memory etkCode = etkLoad(); // address _addr; // assembly { // _addr := create(0, add(initcode, 0x20), mload(initcode)) // } // exploitAddress = address(_addr); vm.stopBroadcast(); } function etkLoad() public returns (bytes memory etkCode){ // Helper function to load handcrafted EVM code from a file. // typically used as: // // bytes memory etkCode = etkLoad(); // vm.etch(someAddress, etkCode); // someAddress.call(hex"69696969"); string[] memory inputs = new string[](2); // /** // * windows: scripts/compile.bat // * linux : scripts/compile.sh // */ inputs[0] = "./script/compile.sh"; // // path/to/contract.etk inputs[1] = "./src/chal_name/public/contracts/exploit.etk"; etkCode = vm.ffi(inputs); } } ``` ## Exploit Template ```solidity //SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import './Setup.sol'; import './Challenge.sol'; contract Exploit { Setup public setup; address payable public setupAddress; Challenge public challenge; address payable public challengeAddress; constructor(Setup _setup, Challenge _challenge) payable { setup = _setup; setupAddress = payable(address(setup)); challenge = _challenge; challengeAddress = payable(address(challenge)); } function finalize() external { } function checkSolved() public view returns(bool) { return setup.isSolved(); } fallback() external payable { } } ``` ## ETK Basics ### Generic ETK Template ```etk # set up storage here # This is a universal constructor - 600b8038035981835939f3 %push(runtimeBytecode) # %push(label) will push the pc of the label startpoint [start] dup1 # [start, start] codesize # [codesize, start, start] sub # [lengthRun, start] msize # [0, lengthRun, start] dup2 # [lengthRun, 0, lengthRun, start] dup4 # [start, lengthRun, 0, lengthRun, start] msize codecopy #[0, start, lengthRun, 0, lengthRun, start] return #[0, lengthRun, ...] runtimeBytecode: # your runtime bytecode ``` ### evmSetup Template ```solidity pragma solidity ^0.8.4; // Used when you need to locally deploy/test a non-sourced contract. contract evmSetup { address payable public challengeAddress; constructor() payable { // gather creation code through .bin file bytes memory initcode = hex"6080604052000000"; // ..... this is creationCode address _addr; assembly { _addr := create(0, add(initcode, 0x20), mload(initcode)) } /* version with constructor args: bytes memory initcode = hex"6080604052000000"; bytes memory args1 = hex"0000000000000000000000000000000000000000000000000000000000000001"; bytes memory args2 = hex"0000000000000000000000000000000000000000000000000000000000000002"; bytes memory initcode_w_args = abi.encodePacked(initcode, args1, args2); address _addr; assembly { _addr := create(0, add(initcode_w_args, 0x20), mload(initcode_w_args)) } */ challengeAddress = payable(_addr); } function isSolved() public view returns (bool) { return challengeAddress.balance == 0; } } ``` ### ETK compile helper ```shell # Basic conversion of etk to bytecode eas /src/chal_name/public/contracts/exploit.etk ``` ```shell #! /usr/bin/env bash # For use with vm.ffi() # saved as ./script/compile.sh echo "0x$(eas $1)" ``` ## General #### Create2 Factory mainnet 0x4e59b44847b379578588920cA78FbF26c0B4956C #### Using ERADICATE2 CREATE2 crunching `./ERADICATE2.x64 -A 0x4e59b44847b379578588920cA78FbF26c0B4956C -i <path/to/bytecode> --matching c0ffee` ```solidity function testCREATE2() public { bytes32 salt = 0x38f3364199653d393a7c5152d5cedbbe4e45bf616f4219edaa5e89fbd09f208b; bytes memory etkCode = etkLoad(); bytes memory data = bytes.concat(salt, etkCode); CREATE2_FACTORY.call(data); console2.logBytes(address(0xc0fFeE0925e2b93b24eF8B2f7C2adE3dd15Ee8e8).code); } ```