背景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
咱們能夠看到執行的方法是 batchTransfer
3d
那這個方法是幹嗎的呢?(給指定的幾個地址,發送相同數量的代幣)code
你傳幾個地址給我(receivers),而後再傳給我你要給每一個人多少代幣(value)orm
而後你要發送的總金額 = 發送的人數* 發送的金額blog
而後 要求你當前的餘額大於 發送的總金額token
而後扣掉你發送的總金額開發
而後 給receivers 裏面的每一個人發送 指定的金額(value)
從邏輯上看,這邊是沒有任何問題的,你想給別人發送代幣,那麼你自己的餘額必定要大於發送的總金額的!
可是這段代碼卻犯了一個很傻的錯!
這個方法會傳入兩個參數
_receivers 的值是個列表,裏面有兩個地址
0x0e823ffe018727585eaf5bc769fa80472f76c3d7
0xb4d30cac5124b46c2df0cf3e3e1be05f42119033
_value 的值是 8000000000000000000000000000000000000000000000000000000000000000
咱們再查看代碼(以下圖)
咱們一行一行的來解釋
uint
cnt
=
_receivers.length;
是獲取 _receivers 裏面有幾個地址,咱們從上面能夠看到 參數裏面只有兩個地址,因此 cnt=2,也就是 給兩個地址發送代幣
uint256 amount
=
uint256(cnt)
*
_value;
首先 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,因此當前用戶代幣的餘額沒有變更
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 是爲了計算安全 而寫的一個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的話, 則 c
/
a
==
b
也就是 驗證 amount / cnt == _value
這句會執行報錯的,由於 0 / cnt 不等於 _value
因此程序會報錯!
也就不會發生溢出了...
那麼 還有一個小問題,這裏的 assert
好 require
好像是乾的同一件事
都是爲了驗證 某條語句是否正確!
那麼他倆有啥區別呢?
用了assert的話,則程序的gas limit 會消耗完畢
而require的話,則只是消耗掉當前執行的gas
那麼 咱們如何避免這種問題呢?
我我的見解是
這件過後須要如何處理呢?
目前,該方法已經暫停了(還好能夠暫停)因此看過文章的朋友 不要去測試了...
不過已經發生了的事情咋辦呢?
我能想到的是,快照在漏洞以前,全部用戶的餘額狀況
而後發行新的token,給以前的用戶 發送等額的代幣...