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

背景python

今天有人在羣裏說,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測試

下面我來帶你們看看,黑客是如何實現的!ui

 

咱們能夠看到執行的方法是 batchTransfer3d

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

總體邏輯是

你傳幾個地址給我(receivers),而後再傳給我你要給每一個人多少代幣(value)orm

而後你要發送的總金額 = 發送的人數* 發送的金額blog

而後 要求你當前的餘額大於 發送的總金額token

而後扣掉你發送的總金額開發

而後 給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 的時候,結果是啥呢?結果會變成255

那麼 咱們回到上面的代碼中,

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,因此當前用戶代幣的餘額沒有變更

 

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

 

這句是遍歷 _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

咱們看看他幹了啥?爲啥能保證計算安全.

 

  1. function mul(uint256 a, uint256 b) internal constant returns (uint256) {
  2. uint256 c = a * b;
  3. assert(a == 0 || c / a == b);
  4. return c;
  5. }

 

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

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

這句會執行報錯的,由於 0 / cnt 不等於 _value

因此程序會報錯!

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

那麼 還有一個小問題,這裏的 assertrequire 好像是乾的同一件事

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

那麼他倆有啥區別呢?

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

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

總結

那麼 咱們如何避免這種問題呢?

我我的見解是

  1. 只要涉及到計算,必定要用safeMath
  2. 代碼必定要測試!
  3. 代碼必定要review!
  4. 必要時,要請專門作代碼審計的公司來 測試代碼

這件過後須要如何處理呢?

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

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

我能想到的是,快照在漏洞以前,全部用戶的餘額狀況

而後發行新的token,給以前的用戶 發送等額的代幣...

相關文章
相關標籤/搜索