通過etherscan可以看到攻擊者的以太幣提取交易:
交易詳情如下,即攻擊者0xc30e89db73798e4cb3b204be0a4c735c453e5c74(簡稱攻擊者1)調用了God合約的withdraw函數進行提幣:
查看攻擊者1在God合約中是否持有token,接近20萬的數量。
查看攻擊者1在withdraw調用之前對God合約的調用,如下:
從攻擊者1的交易來看,它發往God合約的最早交易是sell調用,說明在sell之前它就已經有了God合約的token。那麼攻擊合約在此之前,肯定有其它帳戶給它轉移過token。否則,它不會有可以sell的token。
在追蹤攻擊者1的token變化過程中,我們發現另外一個攻擊者(簡稱攻擊者2,地址為0x2368beb43da49c4323e47399033f5166b5023cda),它調用了一個攻擊合約(地址為0x7f325efc3521088a225de98f82e6dd7d4d2d02f8)給攻擊者1轉移了20萬token:
攻擊者2調用攻擊合約的transfer函數,目標地址為攻擊者1,數量為20萬。由於攻擊合約並沒有開放源碼,因此這裡的transfer函數僅僅是函數籤名匹配的結果(有一定機率是其它名字)。那麼,攻擊合約的20萬token是從哪裡得到的?
繼續跟蹤攻擊者2和攻擊合約,發現攻擊合約是由攻擊者2創建的,且攻擊者2對攻擊合約的調用就是在God合約被攻擊提現的時間窗口中。
從攻擊者2的交易行為,可以看出他先給攻擊合約轉入4.3 token,然後從攻擊合約轉出4.3 token。此時,攻擊合約的token為0。隨後,攻擊合約直接轉20萬token給攻擊者1(還轉移了21萬給另外一個地址),這表明攻擊者在調用reinvest函數時應該使攻擊合約的token發生了某種變化。
接著分析這個reinvest交易,它是直接調用不開源的攻擊合約,其內部機制我們並不清楚。但是,這個交易過程會觸發God合約的兩個事件,onTokenPurchase和onReinvestment:
通過這個事件的記錄數據,可以看到該reinvest調用使得合約判定購買token的以太代幣數量為一個大數,並且遠遠超出以太幣發行總量。這個信息也反應出reinvest函數內部邏輯一定產生了某種非預期的行為。
通過分析God合約源碼,發現onReinvestment事件僅在God合約的reinvest函數中觸發:
可見,onReinvestment的以太幣參數的最終計算方式為:
這一行代碼明顯存在整數溢出的理論可能,因為它沒有使用SafeMath等類似安全運算操作。但這裡溢出的值並不是經典0xfffff…等類似的大數,而是一個夠大但又遠不及uint256極大值的數。
仔細觀察發現,magnitude變量是2的64次方,然後我們做一個等式變換:
這樣我們就找到了經典整數溢出的第一現場指紋,只需要上面減法操作的第一操作數比第二操作數略小即可。很顯然,第一操作數又可劃分為兩個子操作數的乘法,只要任一個為零即導致結果為零。此時,第二操作數隻要是任意一個小正數即可產生上面的經典指紋。
繼續構造上述的操作數。首先,tokenBalanceLedger_[_customerAddress]在合約調用的上下文中表示調用者持有的token。因此,只要調用者不持有合約token,這個值就是零。此時無論profitPerShare_值為多少,乘法結果都為零。這樣減法的第一操作數為零的條件,就輕易構造出來了,即調用者不持有God合約token。然後,payoutsTo_是一個mapping對象,合約調用者的初始值為零,需要使其為一個正數。
分析God合約中修改payoutsTo_的代碼有:
攻擊合約在reinvest調用之前只執行過transfer調用和withdraw調用。其中transfer調用從攻擊合約轉token到外部帳戶,所以不會修改合約的payoutsTo_值,但withdraw函數會直接修改合約的payoutsTo_值。因此,只要在reinvest之前調用一次withdraw函數就可以使得減法的第二操作數為一個正數。
最後,第一個操作數為零值,第二個操作數為正數,並且減法結果強制轉換為無符號整數,在沒有運用安全運算庫的前提下直接使用減法操作就會導致溢出,結果為一個很大的正數。至此,攻擊者的完整攻擊過程如下:
在分析了完整攻擊路徑後,我們可以構造出如下的攻擊合約:
在remix中按照如下步驟進行操作:
(1)部署God合約(為了方便追蹤內部數據結構的變化,直接把全部成員和函數都重新定義為public);
(2)用1eth,購買第一次token,引用地址設置為0x00…;
(3)再用相同的參數來購買一次(一定要再來一次,因為此時合約的profitPerShare_仍然是零值,這會導致withdraw調用的函數修飾符失敗);
(4)部署攻擊合約Test(傳遞God合約地址給Test);
(5)調用God合約的Transfer給Test發送Token(這裡直接把購買的全部token都發送過去);
(6)調用攻擊合約Test的withdraw函數,攻擊合約的payoutsTo_已經被修改為大數;
(7)調用攻擊合約Test的transfer函數把token全部給創建者,Test此時擁有的token為0,payoutsTo_為大數;
(8)調用攻擊合約的reinvest函數,在日誌中可以看到記錄購買token的eth為海量,並且成功購買了大量token;
(9)攻擊合約Test通過溢出獲得了大量token,攻擊者就可以從這個合約給其它地址轉移token,並進行售賣套取eth。
God合約被攻擊的漏洞點比較簡單,即標準的整數溢出。它的複雜在於整數溢出的利用有多個約束條件,並且是在不同的業務邏輯中:
(1)在溢出攻擊的業務邏輯中,攻擊者必須沒有God的token,且payoutsTo_值必須為正數;
(2)要使payoutsTo_為正數,攻擊者就必須在其它業務邏輯中修改,比如withdraw;
(3)要執行withdraw,攻擊者就必須持有God的token(最終溢出時又不能持有token)。
因此,攻擊者需要通過多次觸發God合約的不同業務邏輯才能最終造成整數溢出。
God合約的代碼編寫存在多處缺陷:
(1)給管理員留下任意地址的token操控能力,並且操控不觸發事件。這意味著修改是悄無聲息的,除非有人去輪詢監控每個地址的token變化;
(2)Token的某些轉移過程沒有調用標準ERC20事件接口,導致etherscan上看到的token變化是極度不準確的,不利於公開透明監督;
(3)代碼中不考慮限制循環,無意義的gas浪費(這也導致了在Remix調試中經常崩潰);
(4)合約中的業務邏輯沒有說明規範,僅開放合約代碼並不能等價於項目透明。
ADLab成立於1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員,「黑雀攻擊」概念首推者。截止目前,ADLab已通過CVE累計發布安全漏洞近1000個,通過 CNVD/CNNVD累計發布安全漏洞近500個,持續保持國際網絡安全領域一流水準。實驗室研究方向涵蓋作業系統與應用系統安全研究、移動智能終端安全研究、物聯網智能設備安全研究、Web安全研究、工控系統安全研究、雲安全研究。研究成果應用於產品核心技術研究、國家重點科技項目攻關、專業安全服務等。