Plotchy CTF Setup

New Challenge Setup Script

#! /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

// 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

// 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

//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

# 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

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

# Basic conversion of etk to bytecode
eas /src/chal_name/public/contracts/exploit.etk
#! /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

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);
    }