Ethernaut(Lvl  7): Take my ether once i am gone

Ethernaut(Lvl 7): Take my ether once i am gone

Self destruct to attack a smart contract

Self Destruct in Solidity

Self-destruct serves as a powerful tool in the arsenal of smart contract management, akin to a big red button. It enables contract owners to effectively abandon their smart contracts while redirecting any remaining Ether to a designated address.

This functionality, executed through the selfdestruct(address) low-level opcode call (formerly known as the less aptly named suicide() function), is unique in that it incurs negative gas consumption. Think of it as an incentivized garbage collection mechanism aimed at cleaning up defunct contracts.

When to Employ Self-Destruct

Contract owners often integrate a self-destruct feature for the following purposes:

  1. Deprecating buggy contracts: In instances where a smart contract exhibits bugs or undesired behavior, invoking self-destruct allows creators to terminate the contract and transfer remaining Ether to a backup address.

  2. Cleaning up obsolete contracts: Embraced as a best practice, self-destruct frees up storage space on the Ethereum blockchain by removing redundant or unused contracts.

An Illustrative Example of Self-Destruct Implementation:

function close() public onlyOwner {
    // It's recommended to emit an event as well
    selfdestruct(owner);
}

Although self-destruct can seemingly resolve issues, it's essential to exercise caution, as its application may not always be appropriate.

Fun Fact: Self-destruct, among other functions, serves as one of the three methods for a contract to receive Ether:

  1. Via payable functions: Fallback functions allow intentional Ether reception from other contracts and external wallets. However, in the absence of such functions, contracts still have alternative avenues for fund acquisition.

  2. Receiving mining rewards: Contract addresses can be designated as recipients of mining block rewards.

  3. From a destroyed contract: As discussed, self-destruct facilitates the transfer of remaining Ether from a destroyed contract to a specified backup address.

Cautionary Note: When forwarding self-destructed Ether to other smart contracts, exercise caution. In testing environments, such as this level, directing Ether to an empty contract devoid of withdrawal or transfer capabilities effectively renders it inaccessible. However, such actions are ill-advised on the main-net, as they may permanently tie up a finite supply of Ether, including personal funds."

The Challenge

The challenge for this contract is to make the balance of the contract greater than zero.

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

contract Force {/*

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

*/}

Take note that Force.sol is a vacant contract unable to directly accept funds through a transfer. With only methods 2 and 3 remaining, let's proceed by self-destructing an arbitrary contract and directing the remaining ether to Force.sol.

Begin by initializing the following self-destructing contract either in Remix IDE or through the Foundry framework:

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

// Contract ForceHacker
contract ForceHacker {
    // Constructor that initializes the contract and sends Ether to the specified address
    constructor(address _forceAddress) payable {
        // Convert the address to a payable address
        address payable forceAddress = payable(_forceAddress);
        // Self-destruct the contract and transfer any remaining Ether to the specified address
        selfdestruct(forceAddress);
    }
}
  1. Constructor Function: The contract has a constructor function that accepts one argument - _forceAddress, which is the address where the Ether will be sent. The constructor is marked as payable, indicating that it can receive Ether upon deployment.

  2. Conversion to Payable Address: Inside the constructor, the _forceAddress argument is converted to a payable address type. This conversion is necessary to enable the contract to send Ether to the specified address.

  3. Self-Destruct: After converting the address, the selfdestruct() function is called with the forceAddress as an argument. This function initiates the self-destruction of the contract. When a contract self-destructs, it sends any remaining Ether in its balance to the specified address (forceAddress in this case) and permanently removes itself from the blockchain.

In summary, when the ForceHacker contract is deployed with a specified _forceAddress, it immediately transfers any Ether sent to it to that address and then self-destructs, effectively removing itself from the blockchain and leaving the transferred Ether in the possession of the designated address. This functionality makes it useful for scenarios where one wants to transfer Ether to a specific address and then remove the contract from the blockchain entirely.

Developer TidBits

It's essential to adopt a cautious approach towards managing contract balances, even if you're the owner of the contract. Despite being the owner, you don't have direct control over your contract's balance. Therefore, it's crucial to refrain from relying on the contract balance for accounting or authentication checks. Additionally, vulnerabilities stemming from delegatecall() can potentially lead to unauthorized access to the contract balance, even without the implementation of a selfdestruct() function. In cases where a selfdestruct() function is implemented, it's imperative to take two important steps: i) verify that the msg.sender matches the owner's address to ensure proper authentication, and ii) emit an event to notify external dependencies on the contract and for future reference purposes.