The Rabbit Sends in a Little Contract

Description

problem description

0x046ca747d8472d2f8c070655aed06b841215d4b3

Solution

This is the contract we got and it looks like we need to pass the flag as bytes and pass the verifier conditions.

/**
 *Submitted for verification at Etherscan.io on 2022-12-11
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes calldata flag) external returns(bool);
}

contract CheckFlag {
    IVerifier _verifier;

    constructor(address verifier) {
        _verifier = IVerifier(verifier);
    }

    function check(bytes calldata flag) payable external returns(bool){

        require(msg.value > 13333333333333333337 ether, "Please pay rabbit hole entrance fee");
        require(flag.length == 18);
        require(uint256(keccak256(abi.encodePacked(flag[:7], flag[17]))) == 49459084011290387902369587151867275004690538990200813105748590866129266398873);

        return _verifier.verify(flag[7:17]);
    }
}

I'm using https://emn178.github.io/online-tools/keccak_256.html to calculate keccak256

The flag is 18 bytes long and the first contract trim the first 7 seven bytes and the last one so we can guess it is INTENT{ and }

abi.encodePacked(flag[:7], flag[17]) = "INTENT{}"
keccak256(abi.encodePacked(flag[:7], flag[17])) = "6d58d97a386ee471a3357f7561e3659cdd5e5cd80d0b698a0afa88f7a58bd699"
uint256(keccak256(abi.encodePacked(flag[:7], flag[17]))) = 49459084011290387902369587151867275004690538990200813105748590866129266398873

The verifier passed to the constructor:

000000000000000000000000726afed38eaa7aab9540c0b4b4adc66c1fb14a41

-----Decoded View---------------
Arg [0] : verifier (address): 0x726aFED38Eaa7aaB9540c0b4b4ADc66c1Fb14a41

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000726afed38eaa7aab9540c0b4b4adc66c1fb14a41

The flag [7:17] bytes are passed to the verifier.

Verifier 1 0x726aFED38Eaa7aaB9540c0b4b4ADc66c1Fb14a41

/**
 *Submitted for verification at Etherscan.io on 2022-12-11
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier1 {
    IVerifier _verifier;
    uint value = 0x72;

    constructor(address verifier) {
        _verifier = IVerifier(verifier);
    }

    function verify(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[0])) == value);
        return _verifier.verify(flag);
    }
}

flag[0] = 0x72 = 'r'

Trimmed Flag = r[X][X][X][X][X][X][X][X][X]

Verifier 2 0x207197Bd280527E0ef590E46C6BBEBb5AeF66094

/**
 *Submitted for verification at Etherscan.io on 2022-12-11
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier2 {
    address public owner;
    IVerifier _verifier;
    uint _value;

    constructor(address verifier) {
        owner = msg.sender;
        _verifier = IVerifier(verifier);
    }

    function verify(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[1])) == _value);
        return _verifier.verify(flag);
    }

    function setValue (uint value) external{
        require(msg.sender == owner);
        _value = value;
    }
}

The flag stored in value of the contract so we can see the transaction 0xe0f9a294f62146eb1170e9a6426d70d4e6e522f36533db98d067a3c269e3cdfd

Function: setValue(uint256 value)

MethodID: 0x55241077
[0]:  0000000000000000000000000000000000000000000000000000000000000034

flag[1] = 0x34 = '4'

Trimmed Flag = r4[X][X][X][X][X][X][X][X]

Verifier 3 0xAaD84ecaD496f819f20F06DD6EC34b8d2dE270cc

/**
 *Submitted for verification at Etherscan.io on 2022-12-11
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier3 {
    address public alice;
    IVerifier _verifier;
    address _target;
    uint value = 0x66;

    constructor(address verifier, address target) {
        _verifier = IVerifier(verifier);
        _target = target;
    }

    function verify(bytes memory flag) external returns(bool){
        uint size = getSize(_target);
        require(uint(uint8(flag[2])) == size-265);
        return _verifier.verify(flag);
    }

    function getSize(address _addr) internal returns (uint) {
        uint32 size;
        assembly {
            size := extcodesize(_addr)
        }
        return size;
    }
}

The address was set in the constructor to 0x9e69f292F83ED4145ed87EA21f55BefF37342781

We got the extcode size using python:

infura_url='https://mainnet.infura.io/v3/TOKEN'

w3 = Web3(Web3.HTTPProvider(infura_url))
#Check Connection
print(w3.isConnected())

print(len(w3.eth.get_code("0x9e69f292F83ED4145ed87EA21f55BefF37342781")))

Reault: 363

size = extcodesize(_addr) = 363

flag[2] = size - 265 = 363 - 265 = 0x62 = 'b'

Trimmed Flag = r4b[X][X][X][X][X][X][X]

Verifier 4 0x10a29fCA0D0661f8bCe563EB8beC5e0BCf529101

We need to decompile the code and understand it so we will brute-force it later :)

The same address created all the contracts so we can see Verifier 5 in 0x9ddf5b332cd24d62807b3e2e7362792897fc387c

Verifier 5 0xcfddc063dd95ffaf4e2f32e6fbc9e49a02dec0ae

/**
 *Submitted for verification at Etherscan.io on 2022-12-11
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier5 {
    address public owner;
    address _verifier;
    address _impl;
    uint[79] public values = [126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48]; //FLAG
    uint index;

    constructor(address verifier, address implementation) {
        owner = msg.sender;
        _verifier = verifier;
        _impl = implementation;
    }
    
    function verify(bytes memory flag) external returns(bool){
        (bool success, bytes memory returnData) = _impl.delegatecall(
            abi.encodeWithSignature("verify(bytes)", flag)
        );
        return abi.decode(returnData, (bool));
    }

    function setIndex (uint i) external{
        require(msg.sender == owner);
        index = i;
    }
}

It is just calling the verifier implementation that passed to the constuctor 0x2B5F0F6a2492Fd99691e6513D96826E263989E21

delegatecall:

When contract A executes delegatecall to contract B, B's code is executed with contract A's storage, msg.sender and msg.value.

/**
 *Submitted for verification at Etherscan.io on 2022-12-11
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract ImplVerifier5 {
    address public owner;
    IVerifier _verifier;
    address magic = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    uint[79] public values = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126];
    uint index = 0;

    constructor(address verifier) {
        owner = msg.sender;
        _verifier = IVerifier(verifier);
    }
    
    function verify(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[4])) == values[index+10]);
        return _verifier.verify(flag);
    }

    function setIndex (uint i) external{
        require(msg.sender == owner);
        index = i;
    }
}

The index of Verifier 5 was set in this transaction to 0 0xc221f790e5947955d7b2c94b142639d362821ff2fffe20cb599a386266096320

flag[4] = values[index+10] = values[10] = 116 = "t"

Trimmed Flag = r4b[X]t[X][X][X][X][X]

Verifier 6 0x0C24243F50d67f3E96ac30EB74eCe6Be8feA8EE1

/**
 *Submitted for verification at Etherscan.io on 2022-12-11
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier6 {
    address public alice;
    IVerifier _verifier;
    uint8 value = 0x89 + 0x66;

    constructor(address verifier) {
        _verifier = IVerifier(verifier);
    }

    function verify(bytes memory flag) external returns(bool){
        uint8 mod = 0x74;
        value += mod;
        require(uint(uint8(flag[5])) == value);
        return _verifier.verify(flag);
    }
}

flag[5] = (0x89 + 0x66 + 0x74) % 256 = 99 = "c"

Trimmed Flag = r4b[X]tc[X][X][X][X]

Verifier 7 0xc3b47aA7c213279DECd826243F98D25eaf8Ffacb

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier7 {
    address public alice;
    IVerifier _verifier;
    IERC20 _rhol;
    uint value = 0xFF;

    constructor(address verifier, address token) {
        _verifier = IVerifier(verifier);
        _rhol = IERC20(token);
    }

    function verify(bytes memory flag) external returns(bool){
        value = _rhol.totalSupply() / 10**18;
        value += _rhol.balanceOf(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
        value = value - 1331;
        require(uint(uint8(flag[6])) == value);
        return _verifier.verify(flag);
    }
}

We need to get the totalSupply and balance of 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE in token 0x0f117cE0915a7e4aFff87f180c87491ce3bf4E49

infura_url='https://mainnet.infura.io/v3/TOKEN'

w3 = Web3(Web3.HTTPProvider(infura_url))
#Check Connection
print(w3.isConnected())

abi=json.loads('[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]')

address="0x0f117cE0915a7e4aFff87f180c87491ce3bf4E49"
address=Web3.toChecksumAddress(address)

contract=w3.eth.contract(address=address, abi=abi)

totalSupply=contract.functions.totalSupply().call()
print(f"totalSupply={totalSupply}")
balanceOf=contract.functions.balanceOf("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE").call()
print(f"balanceOf={balanceOf}")

Result:

totalSupply=1337000000000000000042 
balanceOf=42

flag[6] = (1337000000000000000042 / 10**18) + 42 - 1331 = "0"

Trimmed Flag = r4b[X]tc0[X][X][X]

Verifier 8 0x639D7b5a3cbc0338660f445e08D961e4bcfBd9c1

/**
 *Submitted for verification at Etherscan.io on 2022-12-11
*/

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier8 {
    address public alice;
    IVerifier _verifier;

    constructor(address verifier) {
        _verifier = IVerifier(verifier);
    }
    uint value = 0x1F;

    function verify(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[7])) == value ^ uint(uint8(msg.data[1])));
        return _verifier.verify(flag);
    }
}

msg.data[1] is the second byte of the method id https://ethereum.stackexchange.com/a/50616

The method id is 0x8e760afe as we can see in writeContract

msg.data[1] = 0x76

flag[7] = 0x1F ^ msg.data[1] = 0x1F ^ 0x76 = 0x69 = "i"

Trimmed Flag = r4b[X]tc0i[X][X]

Verifier 9 0xCE43F918E590B5A4f47B30574069a94F2473693b

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier9 {
    address public alice;
    address _verifier;
    uint mod = 0x49;
    mapping(string => uint256[]) tea;

    constructor(address verifier) {
        _verifier = verifier;
        tea["green"] = [0x41,0x42,0x43,0x44,0x45];
        tea["black"] = [0x46,0x47,0x48,0x49,0x4A];
        tea["oolong"] = [0x4B,0x4C,0x4D,0x4E,0x4F];
        tea["masala"] = [0x50,0x51,0x52,0x53,0x54];
        tea["earlgrey"] = [0x55,0x56,0x57,0x58,0x59];
        tea["white"] = [0x5A,0x5B,0x5C,0x5D,0x5E];
        tea["ginger"] = [0x5F,0x60,0x61,0x62,0x63];
        tea["mint"] = [0x64,0x65,0x66,0x67,0x68];
        tea["lemon"] = [0x69,0x6A,0x6B,0x6C,0x6D];
        tea["chamomile"] = [0x6E,0x6F,0x70,0x71,0x72];
        tea["hibiscus"] = [0x73,0x74,0x75,0x76,0x77];
        tea["rooibos"] = [0x78,0x79,0x7A,0x7B,0x7C];
    }

    function verify(bytes memory flag) external returns(bool){
        uint value;
        uint slot;
        slot = uint256(keccak256(abi.encode(3326828573661424032217781112562256063166426064292145799638376177296211491616)));

        assembly {
            value := sload(slot)
        }
        require(uint(uint8(flag[8])) == value);
        _verifier.call(
                abi.encodeWithSignature("verify(bytes )", flag)
        );
        return true;
    }
}

We will brute-force it later :)

Verifier 10 0x44aC5B57C74FC129c10DCbd1d0F6c30e179Ced9a

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

interface IVerifier {
    function verify(bytes memory flag) external returns(bool);
}

contract Verifier10 {
    address public alice;
    IVerifier _verifier;
    uint value = 0x55;

    constructor(address verifier) {
        _verifier = IVerifier(verifier);
    }

    function verify(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[9])) == value);
        return _verifier.verify(flag);
    }
    function verify1(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[9])) == 0x55);
        return _verifier.verify(flag);
    }
    function verify2(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[9])) == 0x44);
        return _verifier.verify(flag);
    }
    function verify3(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[9])) == 0x33);
        return _verifier.verify(flag);
    }
    function verify4(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[9])) == 0x61);
        return _verifier.verify(flag);
    }
    function verify5(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[9])) == 0x38);
        return _verifier.verify(flag);
    }
    function verify6(bytes memory flag) external returns(bool){
        require(uint(uint8(flag[9])) == 0x70);
        return _verifier.verify(flag);
    }
    fallback() external {
        bytes memory flag = abi.decode(msg.data[4:], (bytes));
        require(uint(uint8(flag[9])) == 0x35);
    }
}

In order to get the last char we need to see how Verifier 9 called this verifier

_verifier.call(
                abi.encodeWithSignature("verify(bytes )", flag)
        );

There is no matching function in verifier 10 because of the whitespace after the bytes, so it will get to the fallback()

flag[9] = 0x35 = "5"

Trimmed Flag = r4b[X]tc0i[X]5

Let brute-force the 2 missing chars

we will brute-force it from the last unsolved verifier in order to get valid verifier on success

Verifier 9 (brute-force)

abi=json.loads('[{"inputs":[{"internalType":"address","name":"verifier","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"alice","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"flag","type":"bytes"}],"name":"verify","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]')

address="0xCE43F918E590B5A4f47B30574069a94F2473693b"
address=Web3.toChecksumAddress(address)

contract=w3.eth.contract(address=address, abi=abi)

found = False

for f9 in [0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77, 0x78,0x79,0x7A,0x7B,0x7C]:
   try:
      res=contract.functions.verify(b"r4b" + b"\xFF" + b"tc0i" + f9.to_bytes(1, byteorder="little") + b"5").call()
      print(res)
      found = True
   except:
      pass
   if found:
      exit()

Result: 110 = "n"

Trimmed Flag = r4b[X]tc0in5

Verifier 4 (brute-force)

We will do it using the Verifier 1 because we know it's abi and we got the rest of the flag

abi=json.loads('[{"inputs":[{"internalType":"address","name":"verifier","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes","name":"flag","type":"bytes"}],"name":"verify","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]')
address="0x726aFED38Eaa7aaB9540c0b4b4ADc66c1Fb14a41"
address=Web3.toChecksumAddress(address)

contract=w3.eth.contract(address=address, abi=abi)

found = False

for f4 in range(0x30, 0x7F):
   try:
      res=contract.functions.verify(b"r4b" + f4.to_bytes(1, byteorder="little") + b"tc0in5").call()
      print(f4)
      found = True
   except:
      pass
   if found:
      exit()

Result: 49 = "1"

Trimmed Flag = r4b1tc0in5

The full flag is INTENT{r4b1tc0in5}