以太坊網(wǎng)絡上備受矚目的游戲 Fomo3D(Fomo3D:Long)其第一輪在昨天(北京時間 8 月 22 日下午 3 點左右)結束了蒿涎。最終汽纠,地址為 0xa169... 的玩家獲得了 10469.66 Eth 的獎金媳危,其取款交易被記錄在了 6191962 區(qū)塊中歪玲,該玩家在游戲中的總投入不到 0.8 Eth站绪。那么,是不是這個玩家真的是靠運氣“中了大獎”呢吱殉?當然不是忧陪,這是個有計劃、有預謀的浆洗、精心設計的“技術性攻擊”所取得的勝利結果催束。
本文將為大家講解這次“技術性攻擊”的原理、過程和攻擊中的關鍵技術細節(jié)辅髓。本文的講解假設讀者了解以太坊泣崩、智能合約、礦工/礦池洛口、交易打包確認以及 gas 等術語的基本概念矫付。
從游戲設定看達成”攻擊“所需的必要條件
Fomo3D 是近一個多月以太坊上最火爆的應用,也是個賭博游戲第焰,本文的目的是做技術分析买优,所以這里只介紹其結束的設定:
- 游戲啟動后從 24 小時開始倒計時;倒計時結束時挺举,最后一個夠買 key 的玩家將獲得獎池中 48% 的獎金杀赢;
- 每有一個玩家購買 key,倒計時會增加 30 秒湘纵。
所以脂崔,獲勝條件實際上很簡單:在自己購買 key 之后到游戲倒計時結束,不再有其他人購買 key梧喷。在現(xiàn)實世界中砌左,要做到這點不那么容易,除非所有玩家都沒錢了铺敌;但在區(qū)塊鏈的世界中汇歹,具體到以太坊上,是可以通過“技術手段”做到不讓其他人購買的(也就是不讓其他人的“購買交易”得到“網(wǎng)絡確認”)偿凭。這就是大家耳熟能詳?shù)摹熬芙^服務攻擊”(Denial of Service产弹,DoS)。
攻擊的原理
在目前成熟的 Web 服務技術里弯囊,制造 DoS 攻擊一般是通過大量的并發(fā)請求和/或大數(shù)據(jù)量的獨立請求痰哨,將 Web 服務的帶寬/服務資源占滿胶果,而使其無法再相應正常的數(shù)據(jù)請求。在以太坊中斤斧,則可以通過制造大量的“垃圾合約調用”來達到同樣的效果稽物。
這里需要來講一個機制了:交易池(transaction pool)。在礦工/礦池節(jié)點上折欠,通常都會有一個交易池,網(wǎng)絡上廣播的所有新的交易都會被首先加入這個“池”吼过,而后再由礦工/礦池選擇那些“經(jīng)濟性更好”的交易優(yōu)先打包確認锐秦。這里說的“經(jīng)濟性”,即由交易發(fā)送者在交易數(shù)據(jù)中指定的 gasPrice盗忱,gasPrice 越高酱床,執(zhí)行交易所附帶的合約代碼的執(zhí)行費用也就越高,而這些費用通常是會作為手續(xù)費支付給礦工的趟佃。所以扇谣,礦工/礦池會從交易池中選取那些 gasPrice 明顯高于其他交易的交易來優(yōu)先打包執(zhí)行(確認)。并且闲昭,礦工并不能從技術上判斷一個交易中附帶的程序代碼是否是“垃圾合約調用”(它們也沒有這個“責任”)罐寨,它們僅僅選取那些執(zhí)行費用更高的交易來優(yōu)先執(zhí)行⌒蚓兀基于這個原理鸯绿,就允許了攻擊者通過調高包含了“垃圾合約調用”的交易的 gasPrice,來在短時間內用這些“無效交易”占用區(qū)塊的可用 gas簸淀,以使其他“正常交易”無法被打包進區(qū)塊瓶蝴。
這里還有幾個基礎知識需要科普一下:
- 以太坊中的區(qū)塊可包含的交易(計算量)是由區(qū)塊的 gasLimit 來控制的,而并不是像比特幣那樣用數(shù)據(jù)大小來限制租幕;以太坊中目前區(qū)塊的 gasLimit 上限是 800 萬 gas舷手,礦工可以做 5% 以內的上下浮動;區(qū)塊內能包含多少交易劲绪,是看這些交易執(zhí)行所消耗的總 gas 是否達到這個區(qū)塊的 gasLimit男窟;
- 以太坊中執(zhí)行交易的費用,是用交易基礎執(zhí)行費用的 21000 gas珠叔,加上交易中附加的代碼的字節(jié)大小的費用(這里有一個折算公式蝎宇,不詳細講了)以及實際執(zhí)行代碼所消耗的 gas 的總和乘以交易中指定的 gasPrice 來計算的;這個交易費用祷安,會從交易發(fā)送者賬戶中自動扣除姥芥;如果交易發(fā)送者賬戶余額不足,交易不會被打包進區(qū)塊汇鞭;
- 以太坊中的交易的實際執(zhí)行所要消耗的 gas 是可以根據(jù)交易執(zhí)行時的“世界狀態(tài)”明確知道的凉唐,也就是這個交易的實際執(zhí)行費用是明確知道的庸追,礦工就是據(jù)此來判斷打包交易的“經(jīng)濟性”。
在 Fomo3D 游戲的后期(即獎池金額已經(jīng)很高)台囱,大多數(shù)玩家都會選擇在倒計時的最后數(shù)分鐘內才去購買 key淡溯,以讓游戲能繼續(xù)下去。這時簿训,如果有一個機會咱娶,在攻擊者自己購買了 key 之后(這只會給剩余時間增加 30 秒),能在其后數(shù)分鐘內讓網(wǎng)絡不再確認其他人的購買交易强品,攻擊者就可以讓游戲結束從而贏得大獎膘侮。
這里還有一個需要科普的就是所謂“30 秒規(guī)則”。以太坊網(wǎng)絡目前是基于 PoW 共識的的榛,節(jié)點之間是通過“競爭”來決定記賬權琼了,這會導致區(qū)塊鏈末端的“不穩(wěn)定”,也就是會分叉夫晌。所以實際上某個交易會包含在哪個區(qū)塊是可能在短時間內變化的雕薪,但基于過往的經(jīng)驗數(shù)據(jù),如果合約中用區(qū)塊的時間戳來判斷晓淀,那么這個時間的精度大概會有 30 秒的誤差所袁,這就是所謂的“30 秒規(guī)則”。
因為 Fomo3D 合約中的結束時間是使用 now(也就是當前區(qū)塊的時間戳)來判斷的凶掰,所以如果要攻擊的話纲熏,一定要多攻擊 30 秒。比如攻擊者在倒計時 2 分鐘時購買 key锄俄,這會使倒計時增加 30 秒局劲,然后基于 30 秒規(guī)則,就需要保證在之后的 3 分鐘內沒有其他玩家的交易被打包確認奶赠。實際的攻擊也是這樣進行的鱼填。
攻擊的過程
下面我們就來根據(jù)區(qū)塊鏈瀏覽器中可以查到的實際數(shù)據(jù)來看看這個攻擊是如何發(fā)生的:
- 區(qū)塊號 6191896:確認了一個由 0xa169… 到 Fomo3D:Long 的交易,調用了 buyXid 函數(shù)(即購買了若干 key)毅戈;這個區(qū)塊的時間戳是 06:48:22(UTC)苹丸。
- 區(qū)塊號 6191897 到 6191902:攻擊者開始使用“垃圾合約調用”來填充區(qū)塊(大概占用了這幾個區(qū)塊中的一半左右的可用 gas),但沒有刻意調高 gasPrice(使用的是平均水平苇经,20 GWei 左右)赘理,這是個非常有耐心、也非常大膽的處理扇单,在看到 6 個區(qū)塊的時間內沒有其他人購買 key 之后商模,攻擊者知道機會來了!
- 區(qū)塊號 6191903 到 6191908:從 6191903 開始,攻擊者將“垃圾合約調用”交易的 gasPrice 提高到了 190 GWei施流,即平均水平的 8 倍以上响疚,后續(xù)交易更是設置了 500 GWei 的超高 gasPrice,開始了真正的 DoS 攻擊瞪醋!直到 6191908 區(qū)塊忿晕,這 6 個區(qū)塊中只包含了不到 10 個簡單的轉賬交易(即不包含合約執(zhí)行的簡單交易,固定消耗 21000 gas)银受,其他可用 gas 完全被這些高 gasPrice 的“垃圾交易”占用践盼。
- 區(qū)塊號 6191909:網(wǎng)絡狀況恢復正常。這個區(qū)塊的時間戳是 06:51:17(UTC)宾巍。在這個區(qū)塊中宏侍,我們可以看到數(shù)個調用了Fomo3D:Long 的 buyXaddr 和 buyXid 的交易,但因為游戲合約內的時間戳判定條件已經(jīng)達到游戲結束蜀漆,所以這些購買當然就沒有效果了。
值得一提的是咱旱,在區(qū)塊 6191907 中确丢,我們會看到一個 gasPrice 高達 5559.7 GWei 的調用 Fomo3D:Long 的 buyXaddr 函數(shù)的交易,但很可惜吐限,這個交易的 gasLimit 設置過低(僅設置了 379000)導致發(fā)生了 out of gas(即交易觸發(fā)的合約執(zhí)行實際 gas 消耗超過交易的 gasLimit)的錯誤鲜侥,而白白花費了 2.1 Eth 的手續(xù)費,卻沒有搶到最終大獎诸典!這應該是某個大神在讀秒階段發(fā)現(xiàn)了攻擊者的企圖描函,但由于時間過于緊張,沒有將 gasLimit 設置到合理范圍(大概是手誤少輸入了一個 0)狐粱。是不是有點兒看黑客大片的即視感耙ㄔⅰ?
可以看到肌蜻,攻擊者的計劃互墓、準備周密,很有耐心蒋搜,且技術處理上幾乎無懈可擊篡撵,完美地達成了必要的 DoS 攻擊(短時間內阻止了其他玩家的交易被確認),從而“技術性獲勝”豆挽。
攻擊中的幾個技術細節(jié)
首先育谬,我們可以看到在上邊提到的這十幾個區(qū)塊中包含了很多“失敗”的交易,這些失敗的交易有個共同的特點帮哈,都是由 Bad Instruction
導致的膛檀。這里的 Bad Instruction
也就是以太坊協(xié)議里預設的 EVM 操作碼 0xfe(無效指令)。
這里再科普一個 Solidity 語言的技術細節(jié):
Solidity 中有三個指令可以撤銷本次合約執(zhí)行中的所有狀態(tài)修改并導致合約執(zhí)行“異常停止”:require、revert 和 assert宿刮。根據(jù) EVM 的指令設計互站,require 和 revert 實際上最終都是使用了 EVM 操作碼 0xfd(停止執(zhí)行,但會返還交易執(zhí)行所剩余的 gas僵缺,也就是會返還一部分執(zhí)行費用)胡桃,它們實際上都是 revert,只不過 require 指令在執(zhí)行 revert 之前做了一個條件檢查磕潮;而 assert翠胰,則在條件滿足時會使用 EVM 操作碼 0xfe(無效指令,會消耗交易附帶的所有可用 gas) 自脯。
攻擊者用來完成 DoS 攻擊的合約源代碼并不是公開的之景,但我們可以從實際的合約字節(jié)碼中看到一些端倪(因為過于技術化,這里不再展開討論)膏潮。
然后锻狗,從這些“垃圾交易”的整體設計上看,也是很有學問的焕参。這些交易的 gasLimit 并不都是一樣的轻纪,而是從十幾萬、幾十萬到幾百萬這樣的離散值叠纷。這是因為在啟動攻擊的時候刻帚,網(wǎng)絡狀況仍然是正常狀況,所以各大礦工/礦池可能已經(jīng)有了打包了一半的區(qū)塊涩嚣,這時崇众,當它們收到了新交易之后,除了判斷經(jīng)濟性以外航厚,還會判斷其 gas 消耗能否在當前區(qū)塊的剩余可用 gas 中包含顷歌。比如有些礦池打包的區(qū)塊中已經(jīng)只剩不到 50 萬 gas,這時那些超過百萬的大交易自然就不能包含進去幔睬;這樣衙吩,如果沒有適合的 gas 量的“垃圾交易”來填充,就有可能讓其他玩家的正常購買交易填充進去溪窒。所以坤塞,從攻擊的角度講,這些 gasLimit 比較小的“垃圾交易”同樣是非常重要也是非常必要的澈蚌!我們不得不佩服攻擊者思路的縝密摹芙。
最后,要完成這樣精確的攻擊宛瞄,攻擊者需要很多技術準備浮禾。
他們需要若干能連接到前五乃至前十礦池(或者能連接到與這些礦池節(jié)點在“網(wǎng)絡上”非常接近的全節(jié)點)交胚,這一點非常重要。因為要實施這樣的攻擊盈电,你必須具備能實時獲知各大礦池節(jié)點最新區(qū)塊數(shù)據(jù)的能力蝴簇,以便在發(fā)起最終的 DoS 攻擊之前能確定沒有其他人的正常購買交易被打包!也就是剛剛提到的 6191897 到 6191902 區(qū)塊的等待期匆帚,在越多的大礦池節(jié)點數(shù)據(jù)中得到確認熬词,攻擊成功的幾率越高。
然后吸重,在發(fā)起攻擊的時候互拾,一定要在短時間內將用來攻擊的數(shù)十個“垃圾交易”同時發(fā)送到前五乃至前十礦池,讓他們把這些交易加入“交易池”嚎幸;以最大限度地避免因為網(wǎng)絡延遲導致其他玩家的購買交易被某個大礦池先打包的情況颜矿;這同樣對攻擊的完成至關重要!
以上這兩點嫉晶,需要攻擊者同時擁有數(shù)個可以聯(lián)動的定制化的客戶端骑疆,并且有相應的程序進行監(jiān)控(檢查區(qū)塊數(shù)據(jù))并發(fā)起實際攻擊(連續(xù)發(fā)送數(shù)十個預設的交易),這大概不是通過單個客戶端或者簡單地用幾個腳本就可以做到的替废。
小結
從 Fomo3D:Long 第一輪游戲的結束來看箍铭,雖然我們可以搞懂整個過程以及其中的技術細節(jié),但能不能先于別人實施舶担、考慮到盡可能多的細節(jié)、盡量提高成功的概率就是個純粹的技術活兒了彬呻;也需要大量的時間和精力以及資金支持衣陶。不過這個例子也給了我們更大的動力去研究技術、去學習細節(jié)闸氮,只有掌握了足夠多的細節(jié)才能做到一擊必中剪况!不是么?
攻擊者在這次攻擊中的總投入成本當然不是開頭說的在合約上花的那點兒錢蒲跨,這些“垃圾交易”的執(zhí)行費用是非常高的译断,包括攻擊者先前在主網(wǎng)上做的各種試水,總成本粗略估計在 40 Eth 以上或悲。貌似也不是我等屌絲能負擔的啊……孙咪。