Writeup for Force

  • Hello h4ck3r, welcome to the world of smart contract hacking. Solving the challenges from Ethernaut will help you understand Solidity well. For each challenge, they will deploy the contract and provide us with the instance of that contract. Our task is to interact with the contract and exploit it. Don’t worry if you are completely new to Solidity and have never deployed a smart contract. You can learn how to deploy a contract using Remix here.

Challenge

  • The goal of this level is to make the balance of the contract greater than zero.

Contract Explanation

Click to view source contract
        
 1// SPDX-License-Identifier: MIT
 2pragma solidity ^0.8.0;
 3
 4contract Force { /*
 5                   MEOW ?
 6         /\_/\   /
 7    ____/ o o \
 8    /~____  =ø= /
 9    (______)__m_m)
10                   */ }

If you understand the contract, you can move to the exploit part. If you are a beginner, please go through the Contract Explanation as well. It will help you understand Solidity better.

Before starting with contracts, I would like to explain an inbuilt function named selfdestruct() in Solidity.

selfdestruct: selfdestruct() is an inbuilt function in Solidity that takes an address parameter as input. When selfdestruct() is executed, it will delete the contract code from Ethereum. If that contract has any ether, it will be sent to the address passed to selfdestruct().

The behavior of selfdestruct was changed with the implementation of EIP-6780 in the Dencun upgrade that went live on March 13, 2024. Now it will only send the ether from that contract to the address passed into selfdestruct(). After this change, the contract code will be deleted only if the contract creation and selfdestruct() happen in the same transaction.

If we see the contract, we don’t have any functions in the contract. It is just an empty contract. We can send ether to a contract only if it has some payable functions or a receive() function. As there are no functions, we cannot send ether directly to the contract.

Exploit

As there is no function in the contract, we need to find another method to send ether to the contract.

The only way we can send ether to this contract is by creating an exploit contract with a function that executes selfdestruct() and passing the address of the instance contract.

1function Exploit(address _force) public {
2    selfdestruct(payable(address(_force)));
3}

We need to send some ether to the exploit contract so that when we call the Exploit() function, it will send the ether it has to the force contract, increasing the force contract’s balance. We can send ether to the exploit contract during deployment itself.

When we use selfdestruct(), it will send ether directly to the address passed as an argument, regardless of whether there is a receive() function or any payable function existing in the address passed to selfdestruct.

Click to view Exploit contract
        
 1// SPDX-License-Identifier: MIT
 2pragma solidity ^0.8.0;
 3
 4contract ExploitForce {
 5    constructor() {}
 6
 7    function Exploit(address _force) public payable{
 8        selfdestruct(payable(address(_force)));
 9    }
10}

Once you call the Exploit() function, the challenge will be solved. When calling Exploit(), make sure to send some ether so that the function transfers the ether you sent to the Force contract.

Key Takeaways

  • Ether Transfer: selfdestruct() can send ether to any address, regardless of whether the target address has a receive() or payable function.

  • Contract Code Deletion: Post-EIP-6780, the contract code is deleted only if selfdestruct() is called in the same transaction as the contract creation.

***Hope you enjoyed this write-up. Keep on hacking and learning!***