Ethernaut Lvl 14 Gatekeeper 2 Walkthrough: How contracts initialize (and how to do bitwise operations)

This is a in-depth series around Zeppelin team’s smart contract security puzzles. We learn key Solidity concepts to solve the puzzles 100% on your own.

Nicole Zhu
Coinmonks
Published in
4 min readSep 5, 2018

--

This levels requires you to get familiar with the Ethereum yellow paper and pass three more gates.

Inner workings of contract creation

The yellow paper formally denotes contract creation as:

Here’s a simplified flow of how contracts are created and what these variables mean:

  1. First, a transaction to create a contract is sent to the Ethereum network. This transaction contains input variables, notably:
  • Sender (s): this is the address of the immediate contract or external wallet that wants to create a new contract.
  • Original transactor (o): this is the original external wallet (a user) who created the contract. Notice that o != s if the user used a factory contract to create more contracts!
  • Available gas (g): this is the user specified, total gas allocated for contract creation.
  • Gas price (p): this is the market rate for a unit of gas, which converts the transaction cost into Ethers.
  • Endowment (v): this is the value (in Wei) that’s being transferred to seed this new contract. The default value is zero.
  • Initialization EVM code (i): this is everything inside your new contract’s constructor function and the initialization variables, in bytecode format.

2. Second, based on just the transaction’s input data, the new contract’s designated address is (pre)calculated. At this stage, the input state values are modified, but the new contract’s state is still empty.

3. Third, the initialization code kickstarts in the EVM and creates an actual contract.

4. During the process, state variables are changed, data is stored, and gas is consumed and deducted.

5. Once the contract finishes initializing, it stores its own code in association with its (pre)calculated address.

6. Finally, the remaining gas and a success/failure message is asynchronously returned to the sender s.

Hint: Notice that up until step 5, no code previously existed at the new contract’s address!

In the footnote of the yellow paper:

During initialization code execution, EXTCODESIZE on the address should return zero, which is the length of the code of the account while CODESIZE should return the length of the initialization code (as defined in H.2

Put simply, if you try to check for a smart contract’s code size before or during contract construction, you will get an empty value. This is because the smart contract hasn’t been made yet, and thus cannot be self cognizant of its own code size.

Bitwise Operations

Solidity supports the following logic gate operations:

  • &: and(x, y) bitwise and of x and y; where 1010 & 1111 == 1010
  • |: or(x, y) bitwise or of x and y; where 1010 | 1111 == 1111
  • ^: xor(x, y) bitwise xor of x and y; where 1010 ^ 1111 == 0101
  • ~: not(x) bitwise not of x; where ~1010 == 0101

Notice

  • If A xor B = C, then A xor C = B
  • In Solidity, exponentiation is handled by **, not ^

You now have all the knowledge to solve the level!

Detailed Walkthrough

  1. Pass Gate 1 by creating a smart contract middleman, in Remix IDE:
contract Hack {
}

Next, let’s pass Gate 3 before 2, as the key value is a prerequisite to invoking the function at Gate 2. To solve Gate 3, notice:

uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) — 1)

2. Recall that if If A xor B = C, then A xor C = B. This means the key is just the bytes8 typecast of:

uint64(_gateKey) = uint64(keccak256(msg.sender)) ^ uint64(0) — 1)

Finally, notice that Gate 3 uses an assembly function to check that the size of the calling contract is zero, i.e. contains 0 code. But, your calling contract has to have code in the first place in order to invoke GatekeeperTwo.

assembly { x := extcodesize(caller) }

So how is this possible?

Recall that during a contract’s construction, it deploys code from its (pre)calculated address, but that code is not yet stored in association with the contract itself!

This means extcodesize(sender) should return 0, if extcodesize is a subroutine inside the sender contract’s original constructor function.

3. Put the enter() function call inside your Hack.sol’s contract construction:

Congrats on passing both gates!

--

--