1. 程式人生 > >深度技術解讀:Fomo3D 遊戲第一輪是如何結束的

深度技術解讀:Fomo3D 遊戲第一輪是如何結束的

以太坊網路上備受矚目的遊戲Fomo3D(Fomo3D:Long)第一輪在前天(北京時間 8 月 22 日下午 3 點左右)結束了。最終,地址為 0xa169... 的玩家獲得了 10469.66 Eth 的獎金,其取款交易被記錄在了 6191962 區塊中,該玩家在遊戲中的總投入不到 0.8 Eth。

那麼,是不是這個玩家真的是靠運氣“中了大獎”呢?當然不是,這是個有計劃、有預謀的、精心設計的“技術性攻擊”所取得的勝利結果。

本文將為大家講解這次“技術性攻擊”的原理、過程和攻擊中的關鍵技術細節。本文的講解假設讀者瞭解以太坊、智慧合約、礦工/礦池、交易打包確認以及 gas 等術語的基本概念。

一、從遊戲設定看達成“攻擊”所需的必要條件

Fomo3D 是近一個多月以太坊上最火爆的應用,也是個賭博遊戲,本文目的是做技術分析,所以這裡只介紹其結束的設定:

1、遊戲啟動後從 24 小時開始倒計時;倒計時結束時,最後一個夠買 key 的玩家將獲得獎池中 48% 的獎金;

2、每有一個玩家購買 key,倒計時會增加 30 秒。

所以,獲勝條件實際上很簡單:在自己購買 key 之後到遊戲倒計時結束,不再有其他人購買 key。在現實世界中,要做到這點不那麼容易,除非所有玩家都沒錢了。

但在區塊鏈的世界中,具體到以太坊上,是可以通過“技術手段”做到不讓其他人購買的(也就是不讓其他人的“購買交易”得到“網路確認”)。這就是大家耳熟能詳的“拒絕服務攻擊”(Denial of Service,DoS)。

二、攻擊的原理

在目前成熟的 Web 服務技術裡,製造 DoS 攻擊一般是通過大量的併發請求和/或大資料量的獨立請求,將 Web 服務的頻寬/服務資源佔滿,而使其無法再相應正常的資料請求。在以太坊中,則可以通過製造大量的“垃圾合約呼叫”來達到同樣的效果。

這裡需要來講一個機制了:交易池(transaction pool)。在礦工/礦池節點上,通常都會有一個交易池,網路上廣播的所有新的交易都會被首先加入這個“池”,而後再由礦工/礦池選擇那些“經濟性更好”的交易優先打包確認。

這裡說的“經濟性”,即由交易傳送者在交易資料中指定的 gasPrice,gasPrice 越高,執行交易所附帶的合約程式碼的執行費用也就越高,而這些費用通常是會作為手續費支付給礦工的。所以,礦工/礦池會從交易池中選取那些 gasPrice 明顯高於其他交易的交易來優先打包執行(確認)。

並且,礦工並不能從技術上判斷一個交易中附帶的程式程式碼是否是“垃圾合約呼叫”(它們也沒有這個“責任”),它們僅僅選取那些執行費用更高的交易來優先執行。

基於這個原理,就允許了攻擊者通過調高包含了“垃圾合約呼叫”的交易的 gasPrice,來在短時間內用這些“無效交易”佔用區塊的可用 gas,以使其他“正常交易”無法被打包進區塊。

這裡還有幾個基礎知識需要科普一下:

1、以太坊中的區塊可包含的交易(計算量)是由區塊的 gasLimit 來控制的,而並不是像比特幣那樣用資料大小來限制;以太坊中目前區塊的 gasLimit 上限是 800 萬 gas,礦工可以做 5% 以內的上下浮動;區塊內能包含多少交易,是看這些交易執行所消耗的總 gas 是否達到這個區塊的 gasLimit;

2、以太坊中執行交易的費用,是用交易基礎執行費用的 21000 gas,加上交易中附加的程式碼的位元組大小的費用(這裡有一個折算公式,不詳細講了)以及實際執行程式碼所消耗的 gas 的總和乘以交易中指定的 gasPrice 來計算的;這個交易費用,會從交易傳送者賬戶中自動扣除;如果交易傳送者賬戶餘額不足,交易不會被打包進區塊;

3、以太坊中的交易的實際執行所要消耗的 gas 是可以根據交易執行時的“世界狀態”明確知道的,也就是這個交易的實際執行費用是明確知道的,礦工就是據此來判斷打包交易的“經濟性”。

在 Fomo3D 遊戲的後期(即獎池金額已經很高),大多數玩家都會選擇在倒計時的最後數分鐘內才去購買 key,以讓遊戲能繼續下去。

這時,如果有一個機會,在攻擊者自己購買了 key 之後(這隻會給剩餘時間增加 30 秒),能在其後數分鐘內讓網路不再確認其他人的購買交易,攻擊者就可以讓遊戲結束從而贏得大獎。

這裡還有一個需要科普的就是所謂“30 秒規則”。以太坊網路目前是基於 PoW 共識的,節點之間是通過“競爭”來決定記賬權,這會導致區塊鏈末端的“不穩定”,也就是會分叉。

所以實際上某個交易會包含在哪個區塊是可能在短時間內變化的,但基於過往的經驗資料,如果合約中用區塊的時間戳來判斷,那麼這個時間的精度大概會有 30 秒的誤差,這就是所謂的“30 秒規則”。

因為 Fomo3D 合約中的結束時間是使用 now(也就是當前區塊的時間戳)來判斷的,所以如果要攻擊的話,一定要多攻擊 30 秒。比如攻擊者在倒計時 2 分鐘時購買 key,這會使倒計時增加 30 秒,然後基於 30 秒規則,就需要保證在之後的 3 分鐘內沒有其他玩家的交易被打包確認。實際的攻擊也是這樣進行的。

三、攻擊的過程

下面我們就來根據區塊鏈瀏覽器中可以查到的實際資料來看看這個攻擊是如何發生的:

1、區塊號 6191896:確認了一個由 0xa169… 到 Fomo3D:Long 的交易,呼叫了 buyXid 函式(即購買了若干 key);這個區塊的時間戳是 06:48:22(UTC)。區塊號 6191897 到 6191902:攻擊者開始使用“垃圾合約呼叫”來填充區塊(大概佔用了這幾個區塊中的一半左右的可用 gas),但沒有刻意調高 gasPrice(使用的是平均水平,20 GWei 左右),這是個非常有耐心、也非常大膽的處理,在看到 6 個區塊的時間內沒有其他人購買 key 之後,攻擊者知道機會來了!

2、區塊號 6191903 到 6191908:從 6191903 開始,攻擊者將“垃圾合約呼叫”交易的 gasPrice 提高到了 190 GWei,即平均水平的 8 倍以上,後續交易更是設定了 500 GWei 的超高 gasPrice,開始了真正的 DoS 攻擊!直到 6191908 區塊,這 6 個區塊中只包含了不到 10 個簡單的轉賬交易(即不包含合約執行的簡單交易,固定消耗 21000 gas),其他可用 gas 完全被這些高 gasPrice 的“垃圾交易”佔用。

3、區塊號 6191909:網路狀況恢復正常。這個區塊的時間戳是 06:51:17(UTC)。在這個區塊中,我們可以看到數個呼叫了Fomo3D:Long 的 buyXaddr 和 buyXid 的交易,但因為遊戲合約內的時間戳判定條件已經達到遊戲結束,所以這些購買當然就沒有效果了。

值得一提的是,在區塊 6191907 中,我們會看到一個 gasPrice 高達 5559.7 GWei 的呼叫 Fomo3D:Long 的 buyXaddr 函式的交易,但很可惜,這個交易的 gasLimit 設定過低(僅設定了 379000)導致發生了 out of gas(即交易觸發的合約執行實際 gas 消耗超過交易的 gasLimit)的錯誤,而白白花費了 2.1 Eth 的手續費,卻沒有搶到最終大獎!

這應該是某個大神在讀秒階段發現了攻擊者的企圖,但由於時間過於緊張,沒有將 gasLimit 設定到合理範圍(大概是手誤少輸入了一個 0)。是不是有點兒看黑客大片的即視感啊?

可以看到,攻擊者的計劃、準備周密,很有耐心,且技術處理上幾乎無懈可擊,完美地達成了必要的 DoS 攻擊(短時間內阻止了其他玩家的交易被確認),從而“技術性獲勝”。

四、攻擊中的幾個技術細節

首先,我們可以看到在上邊提到的這十幾個區塊中包含了很多“失敗”的交易,這些失敗的交易有個共同的特點,都是由 Bad Instruction 導致的。這裡的 Bad Instruction 也就是以太坊協議裡預設的 EVM 操作碼 0xfe(無效指令)。

這裡再科普一個 Solidity 語言的技術細節:

Solidity 中有三個指令可以撤銷本次合約執行中的所有狀態修改並導致合約執行“異常停止”:require、revert 和 assert。

根據 EVM 的指令設計,require 和 revert 實際上最終都是使用了 EVM 操作碼 0xfd(停止執行,但會返還交易執行所剩餘的 gas,也就是會返還一部分執行費用),它們實際上都是 revert,只不過 require 指令在執行 revert 之前做了一個條件檢查;而 assert,則在條件滿足時會使用 EVM 操作碼 0xfe(無效指令,會消耗交易附帶的所有可用 gas) 。

攻擊者用來完成 DoS 攻擊的合約原始碼並不是公開的,但我們可以從實際的合約位元組碼中看到一些端倪(因為過於技術化,這裡不再展開討論)。

然後,從這些“垃圾交易”的整體設計上看,也是很有學問的。這些交易的 gasLimit 並不都是一樣的,而是從十幾萬、幾十萬到幾百萬這樣的離散值。

這是因為在啟動攻擊的時候,網路狀況仍然是正常狀況,所以各大礦工/礦池可能已經有了打包了一半的區塊,這時,當它們收到了新交易之後,除了判斷經濟性以外,還會判斷其 gas 消耗能否在當前區塊的剩餘可用 gas 中包含。

比如有些礦池打包的區塊中已經只剩不到 50 萬 gas,這時那些超過百萬的大交易自然就不能包含進去;這樣,如果沒有適合的 gas 量的“垃圾交易”來填充,就有可能讓其他玩家的正常購買交易填充進去。

所以,從攻擊的角度講,這些 gasLimit 比較小的“垃圾交易”同樣是非常重要也是非常必要的!我們不得不佩服攻擊者思路的縝密。

最後,要完成這樣精確的攻擊,攻擊者需要很多技術準備。他們需要若干能連線到前五乃至前十礦池(或者能連線到與這些礦池節點在“網路上”非常接近的全節點),這一點非常重要。

因為要實施這樣的攻擊,你必須具備能實時獲知各大礦池節點最新區塊資料的能力,以便在發起最終的 DoS 攻擊之前能確定沒有其他人的正常購買交易被打包!也就是剛剛提到的 6191897 到 6191902 區塊的等待期,在越多的大礦池節點資料中得到確認,攻擊成功的機率越高。

在發起攻擊的時候,一定要在短時間內將用來攻擊的數十個“垃圾交易”同時傳送到前五乃至前十礦池,讓他們把這些交易加入“交易池”;以最大限度地避免因為網路延遲導致其他玩家的購買交易被某個大礦池先打包的情況;這同樣對攻擊的完成至關重要。

以上這兩點,需要攻擊者同時擁有數個可以聯動的定製化的客戶端,並且有相應的程式進行監控(檢查區塊資料)併發起實際攻擊(連續傳送數十個預設的交易),這大概不是通過單個客戶端或者簡單地用幾個指令碼就可以做到的。

五、寫在最後的小結

首先,從 Fomo3D:Long 第一輪遊戲的結束來看,雖然我們可以搞懂整個過程以及其中的技術細節,但能不能先於別人實施、考慮到儘可能多的細節、儘量提高成功的概率就是個純粹的技術活兒了。

其次,攻擊者在這次攻擊中的總投入成本當然不是開頭說的在合約上花的那點兒錢,這些“垃圾交易”的執行費用是非常高的,包括攻擊者先前在主網上做的各種試水,總成本粗略估計在 40 Eth 以上。因此還是需要大量的時間和精力以及資金支援的。

最後這個例子也給了我們更大的動力去研究技術、去學習細節,只有掌握了足夠多的細節才能做到一擊必中!