by yudan@慢霧安全團隊、Jerry@EOS Live 錢包
本篇攻擊手法技術分析基於 EOS 1.8 以前版本,在新版本中未做測試。
0x01 事件回顧
根據慢霧區情報,EOS DApp EOSPlay 中的 DICE 遊戲於9月14日晚遭受新型隨機數攻擊,損失金額高達數萬 EOS。經過慢霧安全團隊的分析,發現攻擊者(賬號:muma******mm)在此次攻擊過程中利用 EOS 中的經濟模型的缺陷,使用了一種新型的隨機數攻擊手法對專案方進行攻擊。
在開始詳細的細節剖析之前,需要先了解一些技術背景作為鋪墊。
0x02 技術背景
我們知道,在 EOS 系統中,資源是保證整個 EOS 系統安全執行的關鍵,在 EOS 上發出的每一筆交易都要消耗對應的資源,如 CPU、NET 或 RAM,而這些資源都是需要一定量的 EOS 作為抵押換取的,如果抵押的 EOS 的數量不夠,則無法換取足夠的資源發起交易。而本次的攻擊行為則是利用了 EOS 資源裡面的 CPU 資源模型進行攻擊。我們知道,在 EOS 上 CPU 是一個十分重要,也是消耗最多的資源。在 EOS DApp 活躍的時候,會經常出現 CPU 不足的情況,為此,還催生出許多做 CPU 抵押的第三方 DApp,緩解資源使用緊張的問題。那麼 CPU 資源的份額具體是怎麼計算的呢?
參考 MEETONE 的文章(https://github.com/meet-one/documentation/blob/master/docs/EOSIO-CPU.md) 我們可以看到 CPU 的計算方式為:
max_block_cpu_usage *
(account_cpu_usage_average_window_ms / block_interval_ms)
* your_staked_cpu_count / total_cpu_count
其中,max_block_cpu_usage * (account_cpu_usage_average_window_ms / block_interval_ms) 是一個固定的變數,也就是常量。那麼真正影響帳號的 CPU 使用情況的是後面的公式 your_staked_cpu_count / total_cpu_count,這裡的意思就是,你抵押的 CPU 總量除以總的 CPU 抵押總量,那麼,如果總量變多了,如果你的抵押數量沒跟上去,能用的 CPU 就會變得越來越少。而 CPU 不足,則無法正常地發起交易。在瞭解完 CPU 的相關知識後,就可以開始相關的分析了。
0x03 攻擊細節剖析
本次的攻擊帳號為 muma******mm,透過分析該帳號行為,發現該帳號透過 start 操作傳送了大量的 defer 交易,這些 defer 操作裡面又發起了另一個 start 操作,然後無限迴圈下去。
由於這些大量的交易充斥著區塊,為分析工作帶來了困難,所以首先要對這些交易進行篩選。檢視攻擊者帳號的接收開獎通知的交易,透過查詢遊戲的開獎區塊,我們發現一個問題,就是在多個開獎區塊內只有系統的 onblock 一筆交易,說明這個區塊為空塊。很明顯,這是因為 CPU 價格的太高導致交易無法發出而出現的情況。而本次受攻擊的 EOS Play 採用的是使用未來區塊雜湊進行開獎。
那麼攻擊者為什麼要選擇這樣做呢?我們來分析下區塊雜湊的計算方法。
透過上圖我們可以看到,區塊雜湊的計算最終呼叫了 digest() 方法,而 digest() 方法的實現為對 block_header 結構體本身進行雜湊。我們繼續看 block_header 的定義。
可以看到 block_header 結構體的變數分別有
1、confrimed
2、previous
3、transaction_mroot
4、action_mroot
5、schedule_version
6、new_producers
7、header_extensions
其代表的含義分別為:
1、每一輪開始前需要確認的上一輪生產的區塊數
2、之前區塊的區塊雜湊
3、區塊 transaction 的默克爾根雜湊
4、區塊 action 的默克爾根雜湊
5、節點出塊順序表版本,這個在一段時間內是不會改變的
6、新的出塊節點,可為空
7、拓展,1.8 之前這個變數為空
到了這裡,我們可以清楚的看到,除了 action_mroot 和 transaction_mroot 外,所有的其他變數都是可以輕鬆獲取的,然後我們繼續深入看下 transaction_mroot 和 action_mroot 的計算方法。
可以看到 transaction_mroot 和 action_mroot 是分別對 pending_block 內的 action 和 transaction 的總的 digests 進行一個 merkle 運算最終得到一個 transaction_mroot 和 action_mroot。我們繼續追蹤各自的 digest 的生成方式。
以上兩條分別是 transaction digest 和 action digest 的生成方式,我們可以看到,transsaction digest 的計算使用到了 cpu 和 net 作為因子,這兩個因子取決於交易執行時 bp 的節點狀態,雖然透明但較難準確預測,而 action digest 沒有使用到這兩個變數,可以輕鬆的算出來。那麼分析到這裡,我們可以確定的是,要預測一個區塊的雜湊,需要引入不確定的 cpu 和 net 的使用量,那麼,有沒有辦法不將這兩個因子引入到計算當中呢?
答案是必然的!
我們把目光聚集到專案方開獎區塊的唯一一個交易 onblock 中,首先我們先看看 onblock 的生成定義。
以上是構造 onblock 操作的一些定義,可以看到,onblock 操作的 data 欄位為 header_block_header,就是當前的區塊頭資訊,即相對於當前 pending 區塊上一個塊的區塊資訊,接下來我們追蹤該函式的呼叫過程。
發現一個點,就是這裡會把 onblock 交易的 implict 設定為 true,這樣設定有什麼用效果呢?我們接下來看下交易的打包過程。
這裡看到,由於在區塊開始的時候,implict 屬性被設定為 true,導致交易在被打包時 onblock 交易不會進入 pending 區塊的 transaction 序列中,但是卻進入了 pending 區塊的 action 序列。也就是說,專案方的開獎區塊的 action 序列不為空,而 transaction 序列為空。所以說開獎區塊的 transaction_mroot 必然為 0,這樣,就避開了計算 transaction_mroot 時引入的 cpu 和 net 這些不確定因素,只計算 action_mroot 即可。而 action_mroot 的計算只需要對 onblock action 本身進行計算即可,且 onblock 的 data 是可以拿到的,即當前區塊頭資訊,也就是相對於當前 pending 區塊的上一個區塊的資訊。那麼到了這裡,攻擊者已經完全可以拿到可計算開獎區塊雜湊 的所有資訊,計算出開獎區塊雜湊就不是什麼難事了。
0x04 攻擊情況總結
在完成攻擊細節剖析後,我們可以知道,攻擊者在下注時,並不知道開獎區塊的區塊雜湊,因為該雜湊和開獎區塊的前一個塊相關,所以攻擊者能做的是在開獎區塊的前一個塊對開獎區塊的資訊進行計算。那萬一計算不對怎麼辦呢?關於這點,我們發現一個現象,當區塊計算結果和預期有誤差時,攻擊者會在開獎區塊傳送一筆 hi 交易來企圖改變結果,但是由於新引入的 hi 交易會引入 cpu 和 net 使用量這些變數,所以引入 hi 交易並不能確保結果可控,但是已經將贏的概率大大提升了,相當於重新開獎。
總結下來攻擊者的攻擊手法為:
1、透過 REX 購買大量的 CPU,導致正常交易無法發出
2、在假設開獎區塊為空塊的情況下對開獎區塊的雜湊進行計算
3、當發現計算結果不對時,向開獎區塊內傳送一筆 hi 交易,嘗試改變結果。
0x05 預防方案
EOS Play 由於使用了未來區塊雜湊作為開獎因子,導致開獎結果被預測從而導致攻擊發生。這次的例子再一次的告訴我們,所有使用區塊資訊進行開獎的開獎方案都是不成熟的,都存在被預測的可能。特別是 REX 出現以後,抬高 CPU 價格比以往變得更為容易。
慢霧安全團隊在此提醒,DApp 專案方在使用隨機數時,特別是涉及到關鍵操作的隨機數,應儘量採用更為安全的線下隨機數生成方案,降低隨機數被攻擊的風險。除此之外,同時還可以接入慢霧安全團隊孵化的 EOS 智慧合約防火牆 FireWall.X(https://firewallx.io),為合約安全保駕護航。