BEC和SMT出现的合约漏洞是怎么回事

最近在币圈很多人讨论的BEC代码漏洞,损失了64亿人民币。那黑客是如何做到的?其实是通过bec代币合约的整型溢出漏洞,让自己的地址凭空产生了大量的bec代币。

什么是整型溢出

那什么是整型溢出呢?在solidity编写合约时,定义整型一般是用uint8, uint256。一个变量如果定义为uint8表示的无符号的8位整型,即表示的范围为0-255。当给这个变量赋值256时,即整型溢出变成了0,以此类推257变成了1。

下面通过合约代码实例说明:


pragma solidity ^0.4.21;

contract HelloWorld{
    

    function add(uint8 a, uint8 b) returns (uint8){
        
        uint8 result = a + b;
        
        return result;
        
    }
    
}

这个合约代码很简单,将传入的两个整数相加,但是我定义的返回类型是uint8,即最多表示255。

这时我们传入参数255和1,即255+1,按照我们前面说的,这时会出现整型溢出,result为0。

BEC和SMT出现的合约漏洞是怎么回事_第1张图片

通过remix 执行add函数结果也为0。

BEC源码分析

回到BEC的问题上,它的问题也是类似的,只不过BEC合约是uint256的整型溢出。先看一下这笔漏洞的交易:https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f

BEC和SMT出现的合约漏洞是怎么回事_第2张图片

可以看到这笔交易是通过调用bec合约的方法,分别转了57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968到两个账户。

BEC和SMT出现的合约漏洞是怎么回事_第3张图片

通过Input Data 可以看出是调用了batchTransfer方法实现bec代币的转账。

打开BEC代币合约源码,找到batchTransfer这个方法:

BEC和SMT出现的合约漏洞是怎么回事_第4张图片

这个函数的功能是:
调用该方法的人可以从自己的账户扣除相应的bec代币,给其他账户发送等额的代币。_receivers为需要发送bec的地址数组,_value表示每个地址发送多少bec。

再来看该函数的逻辑:

uint cnt = _receivers.length;

首先取出接受地址个数,这笔交易发送给两个地址,cnt为2,这没什么问题。

uint256 amount = uint256(cnt) * _value;

然后算出总共需要消耗多少个代币,看似也没什么问题,但问题就出现在这里,我们继续看后面的逻辑。

require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);

这里做了两个判断,主要看下面那个:发送者(sender)的代币余额要大于等于刚刚算出来的amount才能够继续操作进行转账,这样的逻辑判断也很合理,钱不够银行也不会给你转账。

但是问题来了:
如果在之前amount的乘法计算时,amount溢出了为0,那这个require(_value > 0 && balances[msg.sender] >= amount);判断不就失效了。

黑客正是利用了这个漏洞,因为uint256表达的范围是0到(2的256次方减1),黑客只需要向两个地址分别转入(2的255次方)数量的代币,最后合约计算出amount为2的256次方,刚好溢出为0。导致balances[msg.sender] >= amount判断失效。这样黑客就可以凭空在自己的两个账户产生大量的bec代币。

SMT合约漏洞

smt出现的合约漏洞也类似,也是通过整型溢出。
合约地址:https://etherscan.io/address/0x55f93985431fc9304077687a35a1ba103dc1e081#code
溢出攻击交易:
https://etherscan.io/tx/0x1abab4c8db9a30e703114528e31dee129a3a758f7f8abc3b6494aad3d304e43f

BEC和SMT出现的合约漏洞是怎么回事_第5张图片

可以看出如果feeSmt和_value相加的结果刚好为2的256次方,出现整型溢出结果为0,第206行的判断将失效,让攻击者凭空产生代币。

如何防止整型溢出

使用SafeMath库来进行算数运算,在合约中添加代码:

library SafeMath {
  function mul(uint256 a, uint256 b) internal constant returns (uint256) {
    if (a == 0) {
      return 0;
    }

    uint256 c = a * b;
    assert(c / a == b);
    return c;
    
  }

  function div(uint256 a, uint256 b) internal constant returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal constant returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal constant returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

将BEC合约257行的乘法运算改为:

uint256 amount = uint256(cnt).mul(_value);

这样在SafeMath执行mul时,由于c计算出为0,所以assert(c / a == b);将不通过,抛出异常。
这里讲一下require和assert的区别,assert会消耗执行该函数的gas,而require只会消耗当前执行的gas。

你可能感兴趣的:(BEC和SMT出现的合约漏洞是怎么回事)