背景
今天有人在群里說谦炒,Beauty Chain 美蜜?代碼里面有bug贯莺,已經(jīng)有人利用該bug獲得了 57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968 個(gè) BEC
那筆操作記錄是?0xad89ff16fd1ebe3a0a7cf4ed282302c06626c1af33221ebe0d3a470aba4a660f
?
下面我來帶大家看看,黑客是如何實(shí)現(xiàn)的宁改!
我們可以看到執(zhí)行的方法是?batchTransfer
那這個(gè)方法是干嘛的呢缕探?(給指定的幾個(gè)地址,發(fā)送相同數(shù)量的代幣)
整體邏輯是
你傳幾個(gè)地址給我(receivers),然后再傳給我你要給每個(gè)人多少代幣(value)
然后你要發(fā)送的總金額 = 發(fā)送的人數(shù)* 發(fā)送的金額
然后 要求你當(dāng)前的余額大于 發(fā)送的總金額
然后扣掉你發(fā)送的總金額
然后 給receivers 里面的每個(gè)人發(fā)送 指定的金額(value)
從邏輯上看还蹲,這邊是沒有任何問題的爹耗,你想給別人發(fā)送代幣耙考,那么你本身的余額一定要大于發(fā)送的總金額的!
但是這段代碼卻犯了一個(gè)很傻的錯(cuò)!
代碼解釋
?
這個(gè)方法會(huì)傳入兩個(gè)參數(shù)
?
_receivers 的值是個(gè)列表潭兽,里面有兩個(gè)地址
0x0e823ffe018727585eaf5bc769fa80472f76c3d7
0xb4d30cac5124b46c2df0cf3e3e1be05f42119033
_value 的值是?8000000000000000000000000000000000000000000000000000000000000000
我們?cè)俨榭创a(如下圖)
?
我們一行一行的來解釋
?
則
?
但是此時(shí) _value 是16進(jìn)制的倦始,我們把他轉(zhuǎn)成 10進(jìn)制
(python 16進(jìn)制轉(zhuǎn)10進(jìn)制)
?
可以看到 _value =?57896044618658097711785492504343953926634992332820282019728792003956564819968
那么amount = _value*2 =?115792089237316195423570985008687907853269984665640564039457584007913129639936
可以在查看上面看到 uint256取值范圍最大為?115792089237316195423570985008687907853269984665640564039457584007913129639935
此時(shí),amout已經(jīng)超過了最大值讼溺,溢出 則?amount?=?0
下一行代碼?require(cnt?>?0?&&?cnt?<=?20);?require 語句是表示該語句一定要是正確的楣号,也就是 cnt 必須大于0 且 小于等于20
我們的cnt等于2,通過!
?
這句要求?value 大于0怒坯,我們的value是大于0 的 且,當(dāng)前用戶擁有的代幣余額大于等于 amount,因?yàn)閍mount等于0炫狱,所以 就算你一個(gè)代幣沒有,也是滿足的剔猿!
?
這句是當(dāng)前用戶的余額 – amount
當(dāng)前amount 是0视译,所以當(dāng)前用戶代幣的余額沒有變動(dòng)
?
這句是遍歷 _receivers中的地址, 對(duì)每個(gè)地址做以下操作
?
_receivers中的地址的余額 = 原本余額+value
所以 _receivers 中地址的余額 則加了57896044618658097711785492504343953926634992332820282019728792003956564819968 個(gè)代幣9榫础?岷!
Transfer(msg.sender,?_receivers[i],?_value);?}?這句則只是把贈(zèng)送代幣的記錄存下來M艏搿R窝恰!
總結(jié)
就一個(gè)簡單的溢出漏洞舱污,導(dǎo)致BEC代幣的市值接近歸0
那么呀舔,開發(fā)者有沒有考慮到溢出問題呢?
其實(shí)他考慮了,
?
可以看如上截圖
除了amount的計(jì)算外, 其他的給用戶轉(zhuǎn)錢 都用了safeMath 的方法(sub,add)
那么 為啥就偏偏這一句沒有用safeMath的方法呢扩灯。媚赖。。
這就要用寫代碼的人了珠插。惧磺。。
啥是safeMath
?
safeMath 是為了計(jì)算安全 而寫的一個(gè)library
我們看看他干了啥捻撑?為啥能保證計(jì)算安全.
?
如上面的乘法. 他在計(jì)算后磨隘,用assert 驗(yàn)證了下結(jié)果是否正確!
如果在上面計(jì)算 amount的時(shí)候顾患,用了 mul的話琳拭, 則?c?/?a?==?b?也就是 驗(yàn)證 amount / cnt == _value
這句會(huì)執(zhí)行報(bào)錯(cuò)的,因?yàn)?0 / cnt 不等于 _value
所以程序會(huì)報(bào)錯(cuò)描验!
也就不會(huì)發(fā)生溢出了…
那么 還有一個(gè)小問題,這里的?assert?好?require?好像是干的同一件事
都是為了驗(yàn)證 某條語句是否正確坑鱼!
那么他倆有啥區(qū)別呢膘流?
用了assert的話絮缅,則程序的gas limit 會(huì)消耗完畢
而require的話,則只是消耗掉當(dāng)前執(zhí)行的gas
總結(jié)
那么 我們?nèi)绾伪苊膺@種問題呢呼股?
我個(gè)人看法是
只要涉及到計(jì)算耕魄,一定要用safeMath
代碼一定要測(cè)試!
代碼一定要review彭谁!
必要時(shí)吸奴,要請(qǐng)專門做代碼審計(jì)的公司來 測(cè)試代碼
這件事后需要如何處理呢?
目前缠局,該方法已經(jīng)暫停了(還好可以暫停)所以看過文章的朋友 不要去測(cè)試了…
?
不過已經(jīng)發(fā)生了的事情咋辦呢则奥?
我能想到的是,快照在漏洞之前狭园,所有用戶的余額情況
然后發(fā)行新的token读处,給之前的用戶 發(fā)送等額的代幣…