• <option id="cacee"><noscript id="cacee"></noscript></option>
  • <table id="cacee"><noscript id="cacee"></noscript></table>
  • <td id="cacee"></td>
  • <option id="cacee"></option>
  • <table id="cacee"></table>
  • <option id="cacee"><option id="cacee"></option></option>
  • <table id="cacee"><source id="cacee"></source></table><td id="cacee"><rt id="cacee"></rt></td>
    <option id="cacee"><option id="cacee"></option></option>
     找回密碼
     立即注冊

    掃一掃,登錄網站

    首頁 自媒體 查看內容
    • 3900
    • 0
    • 分享到

    一行代碼蒸發了¥6,447,277,680 人民幣!

    2018-4-24 08:41

    來源: python_crawler

    美圖董事長蔡文勝曾在三點鐘群,高調的說出了這句話,隨即被大眾瘋傳。

    在他發表完言論沒多久,2月美鏈(BEC)上交易所會暴漲4000%,后又暴跌。盡管他多次否認,聰明的網友早已扒出,他與BEC千絲萬縷的關系。

    莊家坐莊操控幣價,美圖的股價隨之暴漲,蔡文勝順利完成了他的韭菜收割大計。

    但在幣圈,割人者,人恒割之。

    隨著BEC智能合約的漏洞的爆出,被黑客利用,瞬間套現拋售大額BEC,6億在瞬間歸零。

    而這一切,竟然是因為一個簡單至極的程序Bug。

    背景

    今天有人在群里說,Beauty Chain 美蜜 代碼里面有bug,已經有人利用該bug獲得了 57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968 個 BEC

    那筆操作記錄是 0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f(https://etherscan.io/tx/0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f)

    我們可以看到執行的方法是 batchTransfer

    那這個方法是干嘛的呢?(給指定的幾個地址,發送相同數量的代幣)

    整體邏輯是

    你傳幾個地址給我(receivers),然后再傳給我你要給每個人多少代幣(value)

    然后你要發送的總金額 = 發送的人數* 發送的金額

    然后 要求你當前的余額大于 發送的總金額

    然后扣掉你發送的總金額

    然后 給receivers 里面的每個人發送 指定的金額(value)

    從邏輯上看,這邊是沒有任何問題的,你想給別人發送代幣,那么你本身的余額一定要大于發送的總金額的!

    但是這段代碼卻犯了一個很傻的錯!

    代碼解釋

    這個方法會傳入兩個參數

    1. _receivers

    2. _value

    _receivers 的值是個列表,里面有兩個地址

    0x0e823ffe018727585eaf5bc769fa80472f76c3d7

    0xb4d30cac5124b46c2df0cf3e3e1be05f42119033

    _value 的值是 8000000000000000000000000000000000000000000000000000000000000000

    我們再查看代碼(如下圖)

    我們一行一行的來解釋

    uint cnt = _receivers.length;

    是獲取 _receivers 里面有幾個地址,我們從上面可以看到 參數里面只有兩個地址,所以 cnt=2,也就是 給兩個地址發送代幣

    uint256 amount = uint256(cnt) * _value;

    uint256

    首先 uint256(cnt) 是把cnt 轉成了 uint256類型

    那么,什么是uint256類型?或者說uint256類型的取值范圍是多少...

    uintx 類型的取值范圍是 0 到 2的x次方 -1

    也就是 假如是 uint8的話

    則 uint8的取值范圍是 0 到 2的8次方 -1

    也就是 0 到255

    那么uint256 的取值范圍是

    0 - 2的256次方-1 也就是 0 115792089237316195423570985008687907853269984665640564039457584007913129639935

    python 算 2的256次方是多少

    那么假如說 設置的值超過了 取值范圍怎么辦?這種情況稱為 溢出

    舉個例子來說明

    因為uint256的取值太大了,所以用uint8來 舉例。。。

    從上面我們已經知道了 uint8 最小是0,最大是255

    那么當我 255 + 1 的時候,結果是啥呢?結果會變成0

    那么當我 255 + 2 的時候,結果是啥呢?結果會變成1

    那么當我 0 - 1 的時候,結果是啥呢?結果會變成255

    那么當我 0 - 2 的時候,結果是啥呢?結果會變成254

    那么 我們回到上面的代碼中,

    amount = uint256(cnt) * _value

    則 amount = 2* _value

    但是此時 _value 是16進制的,我們把他轉成 10進制

    (python 16進制轉10進制)

    可以看到 _value = 57896044618658097711785492504343953926634992332820282019728792003956564819968

    那么amount = _value*2 = 115792089237316195423570985008687907853269984665640564039457584007913129639936

    可以在查看上面看到 uint256取值范圍最大為 115792089237316195423570985008687907853269984665640564039457584007913129639935

    此時,amout已經超過了最大值,溢出 則 amount = 0

    下一行代碼 require(cnt > 0 && cnt <= 20); require 語句是表示該語句一定要是正確的,也就是 cnt 必須大于0 且 小于等于20

    我們的cnt等于2,通過!

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

    這句要求 value 大于0,我們的value是大于0 的 且,當前用戶擁有的代幣余額大于等于 amount,因為amount等于0,所以 就算你一個代幣沒有,也是滿足的!

    balances[msg.sender] = balances[msg.sender].sub(amount);

    這句是當前用戶的余額 - amount

    當前amount 是0,所以當前用戶代幣的余額沒有變動

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

    這句是遍歷 _receivers中的地址, 對每個地址做以下操作

    balances[_receivers[i]] = balances[_receivers[i]].add(_value); _receivers中的地址 的余額 = 原本余額+value

    所以 _receivers 中地址的余額 則加了57896044618658097711785492504343953926634992332820282019728792003956564819968 個代幣!!!

    Transfer(msg.sender, _receivers[i], _value); } 這句則只是把贈送代幣的記錄存下來!!!

    總結

    就一個簡單的溢出漏洞,導致BEC代幣的市值接近歸0

    那么,開發者有沒有考慮到溢出問題呢?

    其實他考慮了,

    可以看如上截圖

    除了amount的計算外, 其他的給用戶轉錢 都用了safeMath 的方法(sub,add)

    那么 為啥就偏偏這一句沒有用safeMath的方法呢。。。

    這就要用寫代碼的人了。。。

    啥是safeMath

    safeMath 是為了計算安全 而寫的一個library

    我們看看他干了啥?為啥能保證計算安全.

    function mul(uint256 a, uint256 b) internal constant returns (uint256) {uint256 c = a * b;assert(a == 0 || c / a == b);return c;}

    如上面的乘法. 他在計算后,用assert 驗證了下結果是否正確!

    如果在上面計算 amount的時候,用了 mul的話, 則 / a == b 也就是 驗證 amount / cnt == _value

    這句會執行報錯的,因為 0 / cnt 不等于 _value

    所以程序會報錯!

    也就不會發生溢出了...

    那么 還有一個小問題,這里的 assert 好 require 好像是干的同一件事

    都是為了驗證 某條語句是否正確!

    那么他倆有啥區別呢?

    用了assert的話,則程序的gas limit 會消耗完畢

    而require的話,則只是消耗掉當前執行的gas

    總結

    那么 我們如何避免這種問題呢?

    我個人看法是

    1. 只要涉及到計算,一定要用safeMath

    2. 代碼一定要測試!

    3. 代碼一定要review!

    4. 必要時,要請專門做代碼審計的公司來 測試代碼

    這件事后需要如何處理呢?

    目前,該方法已經暫停了(還好可以暫停)所以看過文章的朋友 不要去測試了...

    不過已經發生了的事情咋辦呢?

    我的想法是,快照在漏洞之前,所有用戶的余額情況

    然后發行新的token,給之前的用戶 發送等額的代幣...


    版權申明:本內容來自于互聯網,屬第三方匯集推薦平臺。本文的版權歸原作者所有,文章言論不代表鏈門戶的觀點,鏈門戶不承擔任何法律責任。如有侵權請聯系QQ:3341927519進行反饋。
    相關新聞
    發表評論

    請先 注冊/登錄 后參與評論

      回頂部
    • <option id="cacee"><noscript id="cacee"></noscript></option>
    • <table id="cacee"><noscript id="cacee"></noscript></table>
    • <td id="cacee"></td>
    • <option id="cacee"></option>
    • <table id="cacee"></table>
    • <option id="cacee"><option id="cacee"></option></option>
    • <table id="cacee"><source id="cacee"></source></table><td id="cacee"><rt id="cacee"></rt></td>
      <option id="cacee"><option id="cacee"></option></option>
      妖精视频