Ethernaut(Lvl 14): Empty Contracts ?

Ethernaut(Lvl 14): Empty Contracts ?

Contract's state at initialization and bitwise operations.

Step 1: Sending a Transaction to Create a Contract

Imagine you're sending a letter to a friend to ask them to create a new contract for you. This letter contains several pieces of information:

  • Sender (s): This is like your address. It tells the Ethereum network who is asking for the contract to be created.

  • Original Transactor (o): This is the address of the person who actually sent the letter. It might be different from the sender if you used a friend's address to send the letter.

  • Available Gas (g): This is like the budget you're giving your friend to create the contract. It's the total amount of "fuel" they can use to do the job.

  • Gas Price (p): This is the current rate for using gas. It's like the cost of gas per mile.

  • Endowment (v): This is the amount of money you're sending to your friend to help them create the contract. It's usually zero, but it can be more if you want to give them a head start.

  • Initialization EVM Code (i): This is the blueprint for the contract. It's like the instructions your friend needs to follow to create the contract.

Step 2: Calculating the Contract's Address

Before your friend starts creating the contract, they calculate where the contract will live on the Ethereum network. This is like deciding on a house number for your friend's new contract.

Step 3: Creating the Contract

Now, your friend starts building the contract using the blueprint you provided. They change the state variables, store data, and use gas as they go along.

Step 4: The Contract is Born

Once your friend finishes building the contract, they store the blueprint at the address they calculated earlier. This is like your friend moving into the house they built.

Step 5: Returning the Remaining Gas and a Success/Failure Message

After the contract is created, your friend sends you a message to let you know if they were successful and how much gas they used up.

Important Note:

During the contract creation process, if you try to check the size of the contract's code, you'll find it's empty. This is because the contract doesn't exist yet, so it can't have any code.

Bitwise Operations in Solidity

Solidity also supports some basic logic operations:

  • & (and): This is like asking if both conditions are true. For example, if you have 1010 and 1111, the result is 1010.

  • | (or): This is like asking if at least one condition is true. For example, if you have 1010 and 1111, the result is 1111.

  • ^ (xor): This is like asking if only one condition is true. For example, if you have 1010 and 1111, the result is 0101.

  • ~ (not): This is like asking if the condition is not true. For example, if you have 1010, the result is 0101.

Remember, in Solidity, you use ** for exponentiation, not ^.

The Challenge

The challenge , similar to gatekeeper one requires us to register as an entrant to pass the level.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.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) == type(uint64).max);
    _;
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}
  1. Constructor Execution: When the attack contract is deployed, its constructor should be immediately executed. The constructor should take the address of the GatekeeperTwo contract as an argument.

  2. Gate Key Calculation:

    • The attack contract calculates a gate key (gateKey) using the address of the hacker contract (address(this)).

    • The keccak256 hash function is applied to the address of the hacker contract, resulting in a 32-byte hash value.

    • The first 8 bytes of this hash value are converted into a bytes8 type and then cast to a uint64.

    • This uint64 value is bitwise XORed with the maximum uint64 value (uint64(0) - 1). The purpose of this XOR operation is to flip all the bits of the hash value.

  3. Interface Interaction:

    • The attack contract should interacts with the GatekeeperTwo contract via an interfaceIGatekeeperTwoInterface.

    • It calls the enter function of the GatekeeperTwo contract, passing the crafted gate key (bytes8(gateKey)).

  4. GatekeeperTwo Vulnerability:

    • The vulnerability lies in the GatekeeperTwo contract's gatekeeping mechanism. It incorrectly trusts the gate key provided by external contracts without proper validation.

    • Due to the specific crafting of the gate key in the GatekeeperTwoHacker contract, the gatekeeper mechanism of GatekeeperTwo fails to recognize it as unauthorized entry.

  5. Attack Execution:

    • The attack contract should successfully bypass the gatekeeper mechanism of the GatekeeperTwo contract by providing the crafted gate key.

    • As a result, the attacker gains unauthorized access to the protected functionality or resources controlled by the GatekeeperTwo contract.

  6. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;

interface IGatekeeperTwoInterface { function enter(bytes8 _gateKey) external returns (bool); }

contract GatekeeperTwoHacker{ constructor(address _gatekeeperTwo){ uint64 gateKey; unchecked{ gateKey = uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ (uint64(0) - 1); } IGatekeeperTwoInterface victim = IGatekeeperTwoInterface(_gatekeeperTwo); victim.enter(bytes8(gateKey)); } }
//Had to add unchecked cause the compiler kept on //throwing erros due to the overflowing of values to achieve //desired result ```

Developer TidBits

Aside from contract blackholes, another possibility arises: Zombie contracts can be crafted by interrupting contract initialization. These resulting contracts possess an address but are forever void of any code, rendering them incapable of returning the initial purpose to you.