Skip to content

Preservation

// 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;
  }
}

Goal

The goal of this level is to claim ownership of the Preservation instance.

Exploit

In order to finish this CTF we will have to use another smart contract. We can name this contract Attacker.

This contract utilizes a library to store two different times for two different timezones. The constructor creates two instances of the library for each time to be stored.

The first issue that arises is related to the LibraryContract. If this contract was defined as library instead of contract, we would not have been able to exploit it, because library keyword prevents the libraries from storing and accessing state variables.

When using delegatecall we must have in mind that the storage layout is very important, therefore the order of the state variables in the caller and the callee should be consistent and same.

If we take a closer look we can see that when we call setFirstTime or setSecondTime functions from Preservation, we delegatecall to the LibraryContract. To be more specific, it means we will call setTime function inside LibraryContract and update storedTime variable to the value passed in setTime function. When we write to storedTime variable we are writing to the first storage slot inside LibraryContract, but since we are using delegatecall this will update the first storage slot of Preservation contract and it will update the timeZone1Library variable of the contract, and we want this variable to hold the value of our Attacker contract address.

It means we need to figure out how to call setFristTime with the desired input, and that would be the uint256 value of our Attacker contract address. To do that we need to cast the address to uint256.

After calling setFirstTime for the first time, we can call it once again inside our Attacker contract but since timeZone1Library is already updated to hold the value of the Attacker contract address, it will delegatecall to it, calling the setTime function. This means we need to have a setTime function in our Attacker contract. Inside setTime we will update the value of the owner variable, and this will update the owner variable in the Preservation contract.

One thing to note is that the Attacker contract should have the same storage layout as the Preservation contract.

To exploit the contract we need to:

  1. create Attacker contract
  2. create attack(Preservation preservation) external function inside the Attacker contract, passing in the Preservation contract as function argument, and inside this function we will:

    • call setFristTime function from the Preservation contract and pass the uint256 value representation of the Attacker contract address as argument, this will update timeZone1Library variable inside Preservation contract to the value of the Attacker contract address
    • call setFristTime again, this will call cause the Preservation contract to delegatecall to the Attacker contract by calling setTime inside the Attacker contract
    • check if the owner variable inside Preservation is the same as msg.sender, otherwise revert

  3. add setTime function, inside this function we will update the owner of the Preservation contract by assigning a value to the owner state variable of the Attacker contract

One question that bugs me is, why this solution won't work if we omit the require statement inside the attack function!?

Attacker contract code

contract Attacker {
    address public timeZone1Library;
    address public timeZone2Library;
    address public owner; 
    uint storedTime;

    function attack(Preservation preservation) external {
        preservation.setFirstTime(uint256(uint160(address(this)))); //set the value of timeZone1Library to address(this)
        preservation.setFirstTime(uint256(uint160(msg.sender))); // call setFristTime from preservation again to execute setTime inside this contract
        require(preservation.owner() == msg.sender, "hack failed");
    }

    function setTime(uint _time) public {
        owner = address(uint160(_time));
    }    
}