8 月 22 日,一個(gè)名不見經(jīng)傳的游戲 God.Game 發(fā)出通告,聲稱遭遇黑客攻擊,游戲內(nèi)的以太幣被黑客全部轉(zhuǎn)走。這一消息一時(shí)間傳遍各大媒體以及討論群,人們?cè)谫|(zhì)疑相關(guān)游戲安全性之余,也在揣測(cè)項(xiàng)目方動(dòng)機(jī),一時(shí)間難以分清到底是黑客攻擊,還是游戲開發(fā)方留有后門跑路。

安比(SECBIT)實(shí)驗(yàn)室的小伙伴在得到消息后,迅速開展了追蹤分析。
God.Game 簡(jiǎn)單規(guī)則如下:GOD 股份購(gòu)買的所有數(shù)量的 10% 被征稅并且作為被動(dòng)的 ETH 收入分配給所有GOD所有者。
通過游戲官網(wǎng)規(guī)則,以及合約源碼分析,不少人會(huì)認(rèn)為 God.Game 是 PoWH3D 的直接仿品,安比(SECBIT)實(shí)驗(yàn)室仔細(xì)分析后發(fā)現(xiàn),它其實(shí)是綜合“借鑒” PoWH3D 和 Zethr 的混合仿品。
PoWH3D 是最近大熱的 Fomo3D 游戲團(tuán)隊(duì)上一款作品。而 Zethr 則是在 PoWH3D 基礎(chǔ)上進(jìn)一步開發(fā)優(yōu)化,新增不同玩法的另一款熱門游戲。二者在玩法規(guī)則層面的細(xì)節(jié)差別,這里不做討論。
God.Game 游戲漏洞本身不復(fù)雜,不少安全公司也發(fā)出相關(guān)攻擊手法的短篇快訊消息,部分不夠詳盡,安比(SECBIT)實(shí)驗(yàn)室通過本文全面分析該漏洞,重點(diǎn)分享漏洞追蹤和定位的詳細(xì)過程與大家討論交流。
疑點(diǎn)一:攻擊是無意還是刻意
一下子提走合約內(nèi)所有以太幣的人,是參與游戲過程中無意發(fā)現(xiàn)了漏洞,還是刻意進(jìn)行的操作?
安比(SECBIT)實(shí)驗(yàn)室認(rèn)為是后者,因?yàn)榉治鱿聛?,該漏洞雖然隱藏不是很深,但實(shí)際觸發(fā)需要一系列組合操作(類似玩游戲按上上下下左左右右開啟作弊),正常游戲玩家的普通參與幾乎不可能觸發(fā)漏洞。安比(SECBIT)實(shí)驗(yàn)室分析攻擊者的交易記錄,發(fā)現(xiàn)其手段十分嫻熟,目的性極強(qiáng),明顯是奔著該漏洞而來。
疑點(diǎn)二:攻擊者的時(shí)機(jī)選擇
攻擊者為什么恰好選擇在獎(jiǎng)池金額 243 個(gè)以太幣時(shí)發(fā)動(dòng)攻擊?

上面是游戲合約賬戶余額趨勢(shì)圖,橫坐標(biāo)是區(qū)塊高度,縱坐標(biāo)是 God.Game 合約賬戶余額。此圖可反映隨著時(shí)間推進(jìn),入場(chǎng)資金情況。安比(SECBIT)實(shí)驗(yàn)室發(fā)現(xiàn),God.Game 合約部署上線后,合約資金量長(zhǎng)期一直在 50 以太幣以下,但在 6179500 區(qū)塊高度以后,入場(chǎng)資金開始猛增到接近 260 個(gè)以太幣(大量韭菜入場(chǎng)),隨后略有衰退(一部分人選擇提現(xiàn)退出),進(jìn)入一段很長(zhǎng)的調(diào)整停滯期。
無論是 Fomo3D 還是 PoWH3D,這類龐氏游戲最早期入場(chǎng)資金優(yōu)勢(shì)都很大,可以較快速回本。后續(xù)只有在持續(xù)大規(guī)模地運(yùn)營(yíng)宣傳下,才可能有大量新資金入場(chǎng)參與游戲。因此游戲第一時(shí)間內(nèi)的入場(chǎng)資金,基本決定了游戲資金量的總體規(guī)模。
而攻擊者正是在資金規(guī)模就快回升到前期高點(diǎn)時(shí),果斷出擊,利用漏洞果斷提走游戲內(nèi)的所有以太幣。于是形成了上圖類似“高臺(tái)跳水”的有趣情景。
這蹊蹺的手法、這迷人的走勢(shì)是不是有些眼熟?
疑點(diǎn)三:是漏洞還是后門
到底是無意引入的漏洞還是刻意暗留的后門?
前面提到,God.Game 游戲代碼重度參考了 PoWH3D 和 Zethr。而與漏洞相關(guān)的關(guān)鍵函數(shù)名 transferFromInternal() 只在 Zethr 合約代碼中有出現(xiàn)。

上圖所示問題代碼,“創(chuàng)新”地增加了一組關(guān)于轉(zhuǎn)賬雙方是合約、還是普通賬戶的分支情況處理。面對(duì)這一串冗長(zhǎng)的代碼,只要清楚 PoWH3D 工作原理,就很容易能發(fā)現(xiàn)此處代碼邏輯根本說不通,也無法在游戲?qū)嶋H規(guī)則中找到適配點(diǎn)。并且這種根據(jù)賬戶類型分別處理賬本的邏輯在原版 PoWH3D 和 Zethr 中根本沒有出現(xiàn)。
God.Game 代碼此處函數(shù)命名仿照了 Zethr,但是具體變量名卻是仿造 Fomo3D,代碼風(fēng)格十分詭異。可以推斷以下兩種情況:代碼作者有可能是因?yàn)闆]有理解 PoWH3D 游戲機(jī)制引入了漏洞;也有可能是故意增加代碼混亂度來迷惑他人,以達(dá)到埋藏后門之目的。

特別地,安比(SECBIT)實(shí)驗(yàn)室還發(fā)現(xiàn)同樣是 transferFromInternal() 函數(shù),God.Game 代碼中唯一與 Zethr 相近的地方,就是上圖示例中針對(duì) ERC223 代碼做的回落處理,似乎是想通過引入此段代碼(需要判斷目標(biāo)地址是否是合約)來為前面提到的不合邏輯代碼打掩護(hù)。
異常:飆升的 Token 售價(jià)
下面讓我們進(jìn)入安比(SECBIT)實(shí)驗(yàn)室安全研究員 sha3 的第一視角,讓 TA 為你撥開漫漫迷霧找出漏洞。
Good Luck Have Fun.
我們首先進(jìn)入游戲首頁,發(fā)現(xiàn)單個(gè)God token的售價(jià)已經(jīng)飆升至300ETH,而被God.Game抄襲的火爆原版游戲PoWH3D的單價(jià)才0.02ETH,面對(duì)不尋常的數(shù)值,敏感的安比(SECBIT)實(shí)驗(yàn)室小伙伴首先懷疑該游戲合約可能存在整數(shù)溢出漏洞。
為了考證我們的想法,我們想到了回溯God.Game過往的售價(jià)變化過程(感謝區(qū)塊鏈技術(shù),讓我們無法消滅歷史),尋找在何處觸發(fā)了整數(shù)溢出漏洞。
通過遍歷區(qū)塊歷史數(shù)據(jù),發(fā)現(xiàn)在6182409高度時(shí),buyPrice/sellPrice等數(shù)據(jù)飆升,于是我們便仔細(xì)分析該區(qū)塊中和God.Game相關(guān)的交易。
分析發(fā)現(xiàn),在6182409區(qū)塊中,唯一和God.Game產(chǎn)生關(guān)聯(lián)的交易是0x368688a944059fdd657e7842d8762b05250bd45f3a2a16cbae1b29727023b00f。
在該交易中,0x2368Beb4調(diào)用0x7f325EfC的reinvest()后,繼而調(diào)用了0xca6378fc中的reinvest(),(通過簡(jiǎn)單的逆向分析,我們暫時(shí)認(rèn)為0x7f325EfC是一個(gè)簡(jiǎn)單的代理合約,實(shí)現(xiàn)了God.Game游戲的基本接口)。
一番操作:定位到攻擊源
我們不禁問道,為何調(diào)用一次reinvest()就可以將游戲中的各個(gè)數(shù)據(jù)全部暴增,我們懷疑創(chuàng)建這個(gè)合約的賬戶就是攻擊者。
順著這條線索,我們觀察到這個(gè)合約的創(chuàng)建者,即0x2368Beb4,在6182409高度之前通過合約0x7f325EfC進(jìn)行了另外幾次操作。
具體操作歷史如下:
| 區(qū)塊高度 | From | To | call |
|---|---|---|---|
| 6182399 | 0x2368Beb4 | 0xca6378fc | transfer(address,uint256) |
| 6182399 | 0xca6378fc | 0x7f325EfC | tokenFallback(address,uint256,bytes) |
| 6182403 | 0x2368Beb4 | 0x7f325EfC | withdraw() 3ccfd60b |
| 6182403 | 0x7f325EfC | 0xca6378fc | withdraw() 3ccfd60b |
| 6182403 | 0xca6378fc | 0x7f325EfC | |
| 6182406 | 0x2368Beb4 | 0x7f325EfC | transfer(address,uint256) |
| 6182406 | 0x7f325EfC | 0xca6378fc | transfer(address,uint256) |
| 6182409 | 0x2368Beb4 | 0x7f325EfC | reinvest() fdb5a03e |
| 6182409 | 0x7f325EfC | 0xca6378fc | reinvest() fdb5a03e |
| 6182419 | 0x2368Beb4 | 0x7f325EfC | sell(uint256) |
| 6182419 | 0x7f325EfC | 0xca6378fc | sell(uint256) |
| 6182439 | 0x2368Beb4 | 0x7f325EfC | transfer(address,uint256) |
| 6182439 | 0x7f325EfC | 0xca6378fc | transfer(address,uint256) |
| 6182462 | 0x2368Beb4 | 0x7f325EfC | transfer(address,uint256) |
| 6182462 | 0x7f325EfC | 0xca6378fc | transfer(address,uint256) |
再看看reinvest()函數(shù)調(diào)用時(shí)產(chǎn)生的event。

我們看到了0000000000000000fffffffffffffffffffffffffffffffffffcf2ac578ec8d9這個(gè)極像溢出的值。
隨即我們打開God.Game源代碼,搜尋其中的蛛絲馬跡。
我們將目標(biāo)鎖定到reinvest()函數(shù)。
function reinvest()
onlyProfitsHolders()
public
{
// fetch dividends
uint256 _dividends = myDividends(false);
// retrieve ref. bonus later in the code
// pay out the dividends virtually
address _customerAddress = msg.sender;
payoutsTo_[_customerAddress] += (int256) (_dividends * magnitude);
// retrieve ref. bonus
_dividends += referralBalance_[_customerAddress];
referralBalance_[_customerAddress] = 0;
// dispatch a buy order with the virtualized "withdrawn dividends"
uint256 _tokens = purchaseTokens(_dividends, 0x0);
// fire event
emit onReinvestment(_customerAddress, _dividends, _tokens);
}
首先大概瀏覽一下reinvest()函數(shù)主體,僅有兩個(gè)簡(jiǎn)單的加法,但是通常在減法溢出中會(huì)出現(xiàn)巨型整數(shù),我們便開始探索reinvest()調(diào)用的函數(shù)。
首先引起我們注意的便是myDividends(false)函數(shù),由于參數(shù)傳入了false,我們就直接研究它最終調(diào)用的函數(shù)dividendsOf(_customerAddress)。
function dividendsOf(address _customerAddress)
view
public
returns (uint256)
{
return (uint256) ((int256)(profitPerShare_ * tokenBalanceLedger_[_customerAddress]) - payoutsTo_[_customerAddress]) / magnitude;
}
Aha,看到減法了!
會(huì)不會(huì)就是在這里發(fā)生了溢出呢?
歷史回放:精確找到溢出點(diǎn)
帶著這樣的疑問,我們回溯了0x368688a944059fdd657e7842d8762b05250bd45f3a2a16cbae1b29727023b00f這筆交易的trace,我們跟著靜態(tài)分析結(jié)果,直接在trace中將PC指針定位到了dividendsOf函數(shù)中。


發(fā)現(xiàn)在0x0a8c指針處執(zhí)行了sub指令,并且sub指令在棧上的兩個(gè)參數(shù)分別為0和0x30d53a87137270000000000000000減法、除法執(zhí)行完畢后棧頂變成了0xfffffffffffffffffffffffffffffffffffcf2ac578ec8d9,和event中的值完全相同。
果然,我們找到了發(fā)生溢出的地方,并且確定reinvest()函數(shù)使用了溢出后的值。
順藤摸瓜:推理怎么構(gòu)造溢出條件
那么問題來了,我們有什么辦法能讓這個(gè)減法溢出呢?
回想sub指令的參數(shù):
- 第一個(gè)參數(shù) 0 對(duì)應(yīng)
profitPerShare_ * tokenBalanceLedger_[_customerAddress] - 第二個(gè)參數(shù)
0x30d53a87137270000000000000000對(duì)應(yīng)payoutsTo_[_customerAddress]
思路來了,我們需要達(dá)成2個(gè)目標(biāo):
- 使得
profitPerShare_ * tokenBalanceLedger_[_customerAddress]的計(jì)算結(jié)果為0, - 使得
payoutsTo_[_customerAddress]中存儲(chǔ)的值為正數(shù)
首先我們看目標(biāo)1,對(duì)于profitPerShare_變量,縱觀全部代碼,只有增加沒有減,無法有效地將這個(gè)值變?yōu)?,那么只有tokenBalanceLedger_[_customerAddress]才能給我們修改的機(jī)會(huì)。
然后目光看向目標(biāo)2,payoutsTo_[_customerAddress]必須設(shè)置為正值。
我們看到在transferFromInternal()、withdraw()函數(shù)均中對(duì)該值有操作,不由得想到了上文看到攻擊者的4步組合拳,這4步具體發(fā)生的操作如下:
- transfer (攻擊者調(diào)用God.Game合約的transfer函數(shù)將token轉(zhuǎn)移到到攻擊者創(chuàng)建的合約)
- withdraw (攻擊者調(diào)用代理合約的withdraw函數(shù),代理合約調(diào)用God.Game的withdraw函數(shù)講ETH提入代理合約)
- transfer (攻擊者調(diào)用代理合約的transfer函數(shù),將God.Game中代理合約的token轉(zhuǎn)移到攻擊者賬戶)
- reinvest (攻擊者調(diào)用代理合約的reinvest函數(shù),代理合約調(diào)用God.Game的reinvest函數(shù)觸發(fā)溢出)
謎底揭開:漂亮的組合拳
我們不妨看看在God.Game合約中發(fā)生了什么?
攻擊者第1步:從自己賬戶轉(zhuǎn)移token到合約,觸發(fā)transferFromInternal函數(shù)中的第一個(gè)else if分支:
else if (fromLength <= 0 && toLength > 0) {
// human to contract
contractAddresses[_toAddress] = true;
contractPayout += (int) (_amountOfTokens);
tokenSupply_ = SafeMath.sub(tokenSupply_, _amountOfTokens);
payoutsTo_[_from] -= (int256) (profitPerShare_ * _amountOfTokens);
}
攻擊者將自身的payoutsTo_[_customerAddress]被減為負(fù)數(shù),緊接著修改``tokenBalanceLedger_[from/to]`進(jìn)行普通的token轉(zhuǎn)賬操作,給代理合約一些token以便擁有dividens。
攻擊者第2步:代理調(diào)用withdraw(),payoutsTo_[_customerAddress]值增加。
攻擊者第3步:將代理合約的token轉(zhuǎn)移到攻擊者賬戶,觸發(fā)transferFromInternal中的if分支:
if (fromLength > 0 && toLength <= 0) {
// contract to human
contractAddresses[_from] = true;
contractPayout -= (int) (_amountOfTokens);
tokenSupply_ = SafeMath.add(tokenSupply_, _amountOfTokens);
payoutsTo_[_toAddress] += (int256) (profitPerShare_ * _amountOfTokens);
}
攻擊者的payoutsTo_[_customerAddress]值增加,同時(shí)將代理合約中的token全部轉(zhuǎn)移到攻擊賬戶中。
可以看到,這3步組合讓代理合約滿足了減法溢出所需條件:
- 通過第1、第3兩步將代理合約的
tokenBalanceLedger_[_customerAddress]置為0 - 通過第2步將
payoutsTo_[_customerAddress]值置為正數(shù)
攻擊者第4步,猛烈一擊,調(diào)用reinvest()觸發(fā)dividendsOf()的減法溢出,攻擊者獲取巨量的dividens,將divisions轉(zhuǎn)換為token。
由于token數(shù)量過多,合約儲(chǔ)備金額不夠withdraw(),攻擊者便將部分token轉(zhuǎn)移到0xC30E89DB73798E4CB3b204Be0a4C735c453E5C74,通過0xC30E89DB73798E4CB3b204Be0a4C735c453E5C74進(jìn)行sell操作賣出token換取ETH,合約儲(chǔ)備金面對(duì)巨量增發(fā)的token,瞬間消耗殆盡,攻擊者成功提取了幾乎所有的ETH。
Good Game.
God.Game 風(fēng)波帶來的思考
近來各類山寨版本智能合約游戲盛行,已經(jīng)發(fā)生很多起安全事件,安比(SECBIT)實(shí)驗(yàn)室不久前已針對(duì) Fomo3D 和 Last Winner 進(jìn)行了一系列詳細(xì)的漏洞披露和解析。而這次 God.Game 甚至上線不久就宣告被黑客攻擊游戲結(jié)束,很多玩家的投入無法兌現(xiàn),導(dǎo)致出現(xiàn)一些「游戲(投資)群」秒變「維權(quán)群」的鬧劇。
安比(SECBIT)實(shí)驗(yàn)室提醒廣大智能合約游戲愛好者,務(wù)必要擦亮雙眼,提高安全意識(shí),謹(jǐn)慎參與不明游戲(小心漏洞與后門),并且對(duì)于任何游戲都不要投入超出承受能力范圍的資金。
游戲智能合約在一定程度上比一般 Token 合約更復(fù)雜,一些漏洞會(huì)隱藏得更深,觸發(fā)條件更苛刻。很多游戲甚至?xí)懈邔哟蔚年P(guān)于公平性、博弈機(jī)制的漏洞存在,需要從游戲模型設(shè)計(jì)、代碼實(shí)現(xiàn)等各種角度進(jìn)行安全評(píng)估。安比(SECBIT)實(shí)驗(yàn)室建議所有負(fù)責(zé)任的智能合約游戲開發(fā)商,都應(yīng)該提高安全意識(shí),加大安全投入。
參考文獻(xiàn)
- [1] God.Game 合約地址,https://etherscan.io/address/0xca6378fcdf24ef34b4062dda9f1862ea59bafd4d
- [2] PowH3D (P3D) 合約地址,https://etherscan.io/address/0xb3775fb83f7d12a36e0475abdd1fca35c091efbe
- [3] Zethr (ZTH) 合約地址,https://etherscan.io/address/0xD48B633045af65fF636F3c6edd744748351E020D
- [4] BCSEC:GodGame漏洞原理以及黑客攻擊手法分析,https://bcsec.org/index/detail/tag/2/id/252
- [5] 安比實(shí)驗(yàn)室:Fomo3D 千萬大獎(jiǎng)獲得者“特殊攻擊技巧”最全揭露,https://mp.weixin.qq.com/s/MCuGJepXr_f18xrXZsImBQ
- [6] 安比實(shí)驗(yàn)室:智能合約史上最大規(guī)模攻擊手法曝光,盤點(diǎn)黑客團(tuán)伙作案細(xì)節(jié),https://mp.weixin.qq.com/s/YBG8YyPwh374HbGWcUKTdQ
以上數(shù)據(jù)均由安比(SECBIT)實(shí)驗(yàn)室提供,合作交流請(qǐng)聯(lián)系info@secbit.io。
安比(SECBIT)實(shí)驗(yàn)室
安比(SECBIT)實(shí)驗(yàn)室專注于區(qū)塊鏈與智能合約安全問題,全方位監(jiān)控智能合約安全漏洞、提供專業(yè)合約安全審計(jì)服務(wù),在智能合約安全技術(shù)上開展全方位深入研究,致力于參與共建共識(shí)、可信、有序的區(qū)塊鏈經(jīng)濟(jì)體。
安比(SECBIT)實(shí)驗(yàn)室創(chuàng)始人郭宇,中國(guó)科學(xué)技術(shù)大學(xué)博士、耶魯大學(xué)訪問學(xué)者、曾任中科大副教授。專注于形式化證明與系統(tǒng)軟件研究領(lǐng)域十余年,具有豐富的金融安全產(chǎn)品研發(fā)經(jīng)驗(yàn),是國(guó)內(nèi)早期關(guān)注并研究比特幣與區(qū)塊鏈技術(shù)的科研人員之一。研究專長(zhǎng):區(qū)塊鏈技術(shù)、形式化驗(yàn)證、程序語言理論、操作系統(tǒng)內(nèi)核、計(jì)算機(jī)病毒。