Ethernaut Level 12 —Privacy

Tellico Lungrevink
2 min readAug 25, 2022

The Ethernaut is a Web3/Solidity based wargame inspired on overthewire.org. Here’s the solution to the Level 12 — Privacy.

The level introduction states:

The creator of this contract was careful enough to protect the sensitive areas of its storage.Unlock this contract to beat the level.Things that might help:
- Understanding how storage works
- Understanding how parameter parsing works
- Understanding how casting works
Tips:
- Remember that metamask is just a commodity. Use another tool if it is presenting problems. Advanced gameplay could involve using remix, or your own web3 provider.

This time we are given only a part of the contract:

Similarly to the Vault level we have an instance of storing the unlock secret on the blockchain. As the blockchain is a public ledger, no secret can really stay a secret here. Here, the user-supplied data is compared to the element indexed 2 in the data array. Contrary to the Vault level though here’s a couple of additional things going on.

First, how to read the data array? Ethereum Virtual Machine (EVM) stores the state in 32 bytes slots. If a variable takes less space than 32 bytes it will be stacked with its neighbors into one slot. Static length array is stored in consecutive slots. So let’s count the slots: locked variable is in the slot 0 (yeah, booleans are padded to entire 32 byte slots), variable id takes entire slot 1, flattening, denomination and awkwardness are short integers stacked together into slot 2, so the data array starts in the slot 3. We are interested in the index 2 of this array, so we need the value from the slot 5.

The following command can read the value from this slot:

> await web3.eth.getStorageAt(contract.address, 5)
'0xe79163a8fa507fab123fc37fde708bd362aeced7a4be6dda8b3ccdc163f02c5c'

That’s not the end though. The unlock function receives a bytes16 argument, but the data[2] is bytes32. Data[2] is casted to bytes16 before comparison. During the cast, the more significant bytes are used, while less significant ones are discarded. Therefore, we need to take only first half of the read number:

> let data = await web3.eth.getStorageAt(contract.address, 5)
> data.slice(2).substring(0,32)
'e79163a8fa507fab123fc37fde708bd3'
> await contract.unlock('0xe79163a8fa507fab123fc37fde708bd3')

After running the unlock function with the retrieved value, the contract will unlock. We can submit the challenge.

--

--