作者:啟明星辰ADLab

8月19日,God.Game在以太坊區塊鏈上部署了自己的智能合約(地址位于https://etherscan.io/token/0xca6378fcdf24ef34b4062dda9f1862ea59bafd4d,簡稱God合約),時隔一天攻擊者就盜取了該合約的243個以太幣,價值超過6萬美元。啟明星辰ADLab在監控到該事件后,對該攻擊進行了詳>,簡稱God合約),時隔一天攻擊者就盜取了該合約的243個以太幣,價值超過6萬美元。啟明星辰ADLab在監控到該事件后,對該攻擊進行了詳細的分析和重現。

攻擊回溯

通過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復現

在分析了完整攻擊路徑后,我們可以構造出如下的攻擊合約:

在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)

ADLab成立于1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員。截止目前,ADLab通過CVE發布Windows、Linux、Unix等操作系統安全或軟件漏洞近400個,持續保持國際網絡安全領域一流水準。實驗室研究方向涵蓋操作系統與應用系統安全研究、移動智能終端安全研究、物聯網智能設備安全研究、Web安全研究、工控系統安全研究、云安全研究。研究成果應用于產品核心技術研究、國家重點科技項目攻關、專業安全服務等。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/684/