美鏈BEC合約漏洞技術分析

Tiny熊發表於2018-04-26

這兩天幣圈鏈圈被美鏈BEC智慧合約的漏洞導致代幣價值幾乎歸零的事件刷遍朋友圈。這篇文章就來分析下BEC智慧合約的漏洞

漏洞攻擊交易

我們先來還原下攻擊交易,這個交易可以在這個連結查詢到。
我截圖給大家看一下:
美鏈BEC合約漏洞技術分析

攻擊者向兩個賬號轉移57896044618...000.792003956564819968個BEC,相當於BEC憑空進行了一個巨大的增發,幾乎導致BEC價格瞬間歸零。
下面我們來分析下這個攻擊過程。

合約漏洞分析

我們先來看看BEC智慧合約的程式碼
BEC在合約中加入一個批量轉賬的函式,它的實現如下:

function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
    uint cnt = _receivers.length;
    uint256 amount = uint256(cnt) * _value;
    require(cnt > 0 && cnt <= 20);
    require(_value > 0 && balances[msg.sender] >= amount);

    balances[msg.sender] = balances[msg.sender].sub(amount);
    for (uint i = 0; i < cnt; i++) {
        balances[_receivers[i]] = balances[_receivers[i]].add(_value);
        Transfer(msg.sender, _receivers[i], _value);
    }
    return true;

這個函式的作用是,呼叫者傳入若干個地址和轉賬金額,在經過一些條件檢查之後,對msg.sender的餘額進行減操作,對每一個對每一個傳入的地址進行加操作,以實現BEC的轉移。
問題出在 uint256 amount = uint256(cnt) * _value; 這句程式碼,當傳入值_value過大時(接近uint256的取值範圍的最大值),uint256 amount = uint256(cnt) * _value計算時會發生溢位,導致amount實際的值是一個非常小的數(此時amount不再是cnt * _value的實際值),amount很小,也使得後面對呼叫者餘額校驗可正常通過(即require(_value > 0 && balances[msg.sender] >= amount)語句通過)。

我們來結合實際攻擊交易使用的引數來分析一下:

美鏈BEC合約漏洞技術分析

batchTransfer的引數_value值為16進位制的800000000000000000000...,引數_receivers陣列的大小為2,相乘之後剛好可超過uint256所能表示的整數大小上限,引發溢位問題amount實際的值為0,後面的轉賬操作實際上msg.sender的餘額減0, 而對兩個賬號進行了加16進位制的800000000000000000000...,最終的結果是相當於增發了2 * 16進位制的800000000000000000000...

實際上對於這種整數溢位漏洞,最簡單的方法是採用 SafeMath 數學計算庫來避免。有趣的是BEC智慧合約程式碼中,其實其他的都使用了SafeMath, 而關鍵的uint256 amount = uint256(cnt) * _value卻沒有使用。
心痛程式設計師,也心痛韭菜。這句程式碼改為uint256 amount = _value.mul(uint256(cnt));就可以防止溢位問題

所以在做加減乘除的時候請記得一定使用:SafeMath,程式碼在這裡

溢位補充說明

Solidity最大可以處理256位數字, 最大值為 2**256 - 1, 對(2**256 - 1) 加1的結果會溢位歸0。2**255 乘2也同樣會溢位歸0。
對無符號型別最小值是零,對零做減1會得到 (2**256 - 1)。

我們用一段程式碼驗證一下:

pragma solidity 0.4.20;
contract TestFlow {
    uint256 public zero = 0;
    uint256 public max = 2**256 - 1;
    uint256 public mm = 2**255;

    function subUnderFlow() public constant returns (uint) {
        uint256 a =  zero - 1;
        return a;
    }

    function addOverFlow() public constant returns (uint) {
        uint256 a =  max + 1;
        return a;
    }

    function mulOverFlow() public constant returns (uint) {
        uint256 a =  mm * 2;
        return a;
    }
}

合約部署和執行,大家可請前往我的小專欄閱讀。

知識星球深入淺出區塊鏈做好的區塊鏈技術問答社群,歡迎來提問,作為星球成員福利,成員可加入區塊鏈技術付費交流群。
深入淺出區塊鏈 - 系統學習區塊鏈,打造最好的區塊鏈技術部落格。

相關文章