# Ethernaut 12: Gatekeeper **Published by:** [0xbanky](https://paragraph.com/@banky/) **Published on:** 2022-08-22 **URL:** https://paragraph.com/@banky/ethernaut-12-gatekeeper ## Content Solving the 12th ethernaut challenge, Gatekeeper. This challenge makes use of a few techniques that have been developed in some previous exercises. https://ethernaut.openzeppelin.com/level/0x9b261b23cE149422DE75907C6ac0C30cEc4e652AInvestigationFor this challenge, we want to pass three require blocks to allow our user to enter. The contract looks like thisimport '@openzeppelin/contracts/math/SafeMath.sol'; contract GatekeeperOne { using SafeMath for uint256; address public entrant; modifier gateOne() { require(msg.sender != tx.origin); _; } modifier gateTwo() { require(gasleft().mod(8191) == 0); _; } modifier gateThree(bytes8 _gateKey) { require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one"); require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two"); require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three"); _; } function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; } } There are three modifiers that we need to successfully passSolutionTo pass gateOne, I simply needed to pass the message through a proxy contract. This way, msg.sender != tx.originwill return true. This works because tx.origin is the initiator of the transaction while msg.sender is the caller of the function. So tx.origin will be my wallet and msg.sender will be the proxy contract. I’ll skip gateTwo for now since it is more difficult. To get through gateThree, a few casting checks need to be passed. From the last require statement, we see that uint32(uint64(_gateKey)) == uint16(tx.origin) must be true. Casting an address to uint16 will take only the last two bytes of tx.origin and convert them to decimal. Thus, the last two bytes of _gateKey must be the last four hex values of my wallet. In my case 0xc252. Moving upwards in the require chain, we see that uint64(_gateKey) must be different from uint32(_gateKey). We can make this happen by setting a bit that is out of reach for uint32. I did this by setting the highest bit in a 64 bit value. So my new _gateKey is 0x100000000000c352. To verify this, I used a solidity repl which I found here: https://github.com/raineorshine/solidity-repluint32(uint64(gatekey)); // 49746 uint64(gatekey); // 1152921504606896978 The last require statement just makes sure that the uint32 and uint16 values of my gate key are the same. These values are equal here since the lower 4 bytes and lower 8 bytes of 0x100000000000c352are equal. The final modifier is gateTwo. This is tricky because it is very difficult to calculate exact gas costs of a call chain on the EVM. This is because different solidity compiler versions may use different gas costs for the same operations. So that I don’t lose my mind, I just did a brute force calculation in my proxy contract by setting different gas values until one of them worked. My proxy contract looks like thiscontract Gatekeeper1Relay { address public gatekeeper1Address; constructor(address _gatekeeper1Address) { gatekeeper1Address = _gatekeeper1Address; } function enter() public { bytes8 gateKey = 0x100000000000c252; for (uint gas = 0; gas < 8191; ++gas) { //for loop example (bool success, ) = gatekeeper1Address.call{gas: 100000 + gas}(abi.encodeWithSignature("enter(bytes8)", gateKey)); if (success) { return; } } require(false); } } I use the gateKey that was determined previously. Then, I call the enter function on the target contract with an initial gas of 100000. This call then loops through the gas values until one of them successfully breaks in to the contract. If none of them work, then I force the transaction to fail with require(false). Note that I used ++gas instead of gas++ in the for loop. I did this because I recently learned that the prefix increment is actually cheaper than postfix since it does not need to do an assignment. Small optimization but worth noting. By calling enter on my proxy contract, the problem gets solved.What I learnedThe solidity repl I linked is extremely useful for running single lines of solidity code to see how the platform worksCasting to smaller values in solidity. I’m working on a bigger writeup about this because there are some interesting caveats between the way casting works for bytes versus other types such as uint and intWhen all else fails, brute force 😏 ## Publication Information - [0xbanky](https://paragraph.com/@banky/): Publication homepage - [All Posts](https://paragraph.com/@banky/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@banky): Subscribe to updates - [Twitter](https://twitter.com/0xbanky): Follow on Twitter