如今進入你仍是先行者,最後觀望者進場纔是韭菜。python
美圖董事長蔡文勝曾在三點鐘羣,高調的說出了這句話,隨即被大衆瘋傳。安全
在他發表完言論沒多久,2月美鏈(BEC)上交易所會暴漲4000%,後又暴跌。儘管他屢次否定,聰明的網友早已扒出,他與BEC千絲萬縷的關係。測試
莊家坐莊操控幣價,美圖的股價隨之暴漲,蔡文勝順利完成了他的韭菜收割大計。ui
但在幣圈,割人者,人恆割之。spa
隨着BEC智能合約的漏洞的爆出,被黑客利用,瞬間套現拋售大額BEC,6億在瞬間歸零。3d
而這一切,居然是由於一個簡單至極的程序Bug。code
今天有人在羣裏說,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 個 BECblog
那筆操做記錄是 0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660ftoken
下面我來帶你們看看,黑客是如何實現的!開發
咱們能夠看到執行的方法是 batchTransfer
那這個方法是幹嗎的呢?(給指定的幾個地址,發送相同數量的代幣)
你傳幾個地址給我(_receivers),而後再傳給我你要給每一個人多少代幣(_value)
而後你要發送的總金額 = 發送的人數* 發送的金額
而後 要求你當前的餘額大於 發送的總金額
而後扣掉你發送的總金額
而後 給_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,給以前的用戶 發送等額的代幣...