14 - Gatekeeper Two

type conversion, extcodesize, and ^(xor)

Ethernaut Level14: Gatekeeper Two

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GatekeeperTwo {

  address public entrant;

  modifier gateOne() {
    require(msg.sender != tx.origin);
    _;
  }

  modifier gateTwo() {
    uint x;
    assembly { x := extcodesize(caller()) }
    require(x == 0);
    _;
  }

  modifier gateThree(bytes8 _gateKey) {
    require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
    _;
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}

Goal of this level

  • successfully calling enter function without revert

What you should know before

Solution

chevron-rightKey to solve this problem 🔑hashtag
  • code size is 0 when a contract is being deployed

  • An inverse operation of ^(xor) is ^(xor)!

gateTwo

-> excodesize(caller()) returns size of the code of msg.sender

This will revert if the code size is not zero, but gateOne requires msg.sender to be a contract.

We can pass this by calling enter from the constructor of our exploit contract.

While constructor is running, code size of its contract is 0. (because the contract hasn't been deployed yet)

gateThree

type(uint64).max is 0xffffffffffffffff.

Note that the inverse operation is xor, which means that when a ^ b = c, then a = c ^ b, b = a ^ c satisfies.

So this condition is equivalent to the original condition.

msg.sender here will be the address of our exploit contract.

So we just can replace msg.sender to address(this) to calculate _gateKey.

Done! 😎

Key Takeaway

  • Inverse operation of ^(xor) is ^(xor)

  • code size of the contract is 0 when constructor is running

Last updated