# Ethernaut 13: Gatekeeper 2

By [0xbanky](https://paragraph.com/@banky) · 2022-08-23

---

After solving the first gatekeeper problem, there is a second one waiting. The problem is here

[https://ethernaut.openzeppelin.com/level/0xdCeA38B2ce1768E1F409B6C65344E81F16bEc38d](https://ethernaut.openzeppelin.com/level/0xdCeA38B2ce1768E1F409B6C65344E81F16bEc38d)

Investigation
=============

The contract for this problem looks quite similar to the first one. There are multiple gates which must be passed in order to call the `enter` function.

    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) == uint64(0) - 1);
        _;
      }
    
      function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
        entrant = tx.origin;
        return true;
      }
    }
    

Solution
========

To pass `gateOne`, we can use a similar approach to the previous challenge. To ensure that `msg.sender != tx.origin`, the contract functions can be called through a relayer contract.

Passing `gateTwo` is a bit more tricky. Here we see our first example of assembly code being used in a contract. Specifically here we have the use of inline assembly. This allows contract developers to write logic that is not supported by the solidity compiler. It is thus extremely useful for library creators to have access to these low level features of the EVM. Specifically, the function being used here is the `extcodesize` function.

After doing some research, I found that `extcodesize` is used to determine the size of a contract at a given address. Some people use this to ensure that functions are being called by non-contracts. One thing to note is that this call returns 0 if the contract target has not been initialized, ie. the constructor has not completed running. Therefore, it is possible to bypass this by making calls through the constructor of my relayer contract.

Finally, `gateThree` has some binary operation logic which must pass in order for it to work. The full operation is as follows

    uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1
    

First, we can take a look at the right-hand side of the operation. `uint64(0) - 1` causes the unsigned integer to underflow. Thus, this value is the largest possible `uint64` which is `0xffffffffffffffff`. Now, we can try to match up the left side of the operation with this.

The `^` operation is an XOR, which sets each bit to 1 if both inputs are 0. eg. binary `101` ^ `011` = `110`. Thus, to get the output that we want, `_gateKey` just needs to be the exact flipped bits of `bytes8(keccak256(abi.encodePacked(msg.sender))`. We can get this by doing an XOR on `bytes8(keccak256(abi.encodePacked(msg.sender))`with `0xffffffffffffffff`. My full relayer contract code looks like this

    constructor (address _gatekeeperAddress) {
            bytes8 negatedGateKey = bytes8(keccak256(abi.encodePacked(address(this))));
            bytes8 gateKey = negatedGateKey ^ 0xffffffffffffffff;
    
            (bool success, ) = _gatekeeperAddress.call(abi.encodeWithSignature("enter(bytes8)", gateKey));
    
            require(success);
        }
    }
    

Thus successfully cracks the problem

---

*Originally published on [0xbanky](https://paragraph.com/@banky/ethernaut-13-gatekeeper-2)*
