区块链安全

math类型ctf-03

题目:https://capturetheether.com/challenges/math/retirement-fund/

这个题目挑战成功的条件是合约地址的余额为0。所以切入点在于找到一个方法从合约里面向外转出钱。

<address>.transfer(uint256 amount)
向address发送amount数量的Wei(注意单位),如果执行失败则throw。发送的同时传输2300gas,gas数量不可调整。
所以实际上可以把 transfer() 这个转账函数理解为 transferto,注意上面没有参数是 from 的地址,因为不需要,from 的地址就是 msg.sender。

所以会调用 transfer 从 this(合约地址)向 msg.sender 转出钱的函数有两个:

  • withdraw()
  • colletPenalty() 注:收罚金

withdraw() 函数要求 msg.sender == owner,但是作为 player 我们不是 owner,所以这个函数无法调用。实际上我尝试调用过但是失败了。

collectPenalty() 函数要求 msg.sender == beneficiary,题目中提到我们 player 就是这个 beneficiary。所以说满足 require 条件,可以调用此函数的。进入此函数之后:

  1. require(msg.sender == beneficiary); 满足
  2. uint256 withdrawn = startBalance – address(this).balance; 因为 startBalance 在构造器函数中被赋值为 1,当前合约余额也为1,所以 withdrawn == 0。
  3. require(withdrawn > 0); 不满足
  4. msg.sender.transfer(address(this).balance); 无法执行。

回看第2步,startBalance 的值我们无法改变,但是如果有一种方法能够改变 address(this).balance 的值,使得这个值增加。那么就可以造成下溢,导致 withdrawn 是一个很大的值,满足第 3 步的条件,执行第四步。

我们可以通过部署一个合约,给合约转入一些余额。然后使得合约自毁,将合约里面的钱转入 RetirementFundChallenge 合约,这样 RetirementFundChallenge 合约的余额就会增加,大于 startBalance。

攻击合约代码:

pragma solidity ^0.4.21;

// Relevant part of the CaptureTheEther contract.
contract tmp {
    function tmp() public payable {
        require(msg.value == 0.1 ether);
    }
    function kill() public{
        selfdestruct(address(0x5C27632538FC584FE5F15deB6dAA60C4ED856ED2));  //我的 RetirementFundChallenge 合约地址
    }
}

部署攻击合约:(注意:这里的 value 是 10**17 wei)

然后调用 kill 函数自毁,可以看到攻击合约的余额变为了 0:

但是 RetirementFundChallenge 合约的余额变为了1.1:

注意:自毁时候手续费是调用自毁函数的地址出的,不用被自毁或转入的地址出。

此时再调用 collectPenalty() 函数,则 withdrawn 就会等于 -0.1ether 下溢等于 1e+17 wei。则 transfer 成功。

  function collectPenalty() public {
        require(msg.sender == beneficiary);

        uint256 withdrawn = startBalance - address(this).balance;

        // an early withdrawal occurred
        require(withdrawn > 0);

        // penalty is what's left
        msg.sender.transfer(address(this).balance);
    }

挑战成功:

总结:

事实上这里的考点是以太坊中合约的自毁机制,这是通过selfdestruct函数来实现的,如它的名字所显示的,这是一个自毁函数,当你调用它的时候,它会使该合约无效化并删除该地址的字节码,然后它会把合约里剩余的balance发送给参数所指定的地址,比较特殊的是这笔ether的发送将无视被自毁合约实际的 fallback 函数,所以它是强制性的。

https://www.anquanke.com/post/id/153375