智能合約遊戲之殤——God.Game 事件分析

作者:Sissel@知道創宇404區塊鏈安全研究團隊 發佈時間:2018/08/24

0x00 前言

當你凝視深淵時,深淵也在凝視着你。

越來越多的樂透、賭博遊戲與區塊鏈體系結合起來,步入衆多投資者和投機者的視野中。區塊鏈可以說是這類遊戲的溫牀。正面來說,區塊鏈的可信機制與合約的公開,保證了遊戲的中立。另一方面,區塊鏈的非實名性,也讓玩家的個人信息得以隱藏。

分紅、邀約、股息,這些遊戲看似利益誘人,實則一個個都是龐氏騙局。遊戲火了,詐騙滿滿皆是。每個人都信心滿滿地走進遊戲,投入大筆資金,希望自己成爲受益者,別人都是自己的接盤俠。這樣的遊戲,只有兩個結局,不是遊戲所有者獲益,就是半路殺進遊戲的區塊鏈黑客捲走一切,讓玩家血本無歸,無一例外。日復一日,無數投機者交了學費,空手而歸,卻又毫不死心,重入深淵。

遊戲依然層出不窮,不信邪的人也是接連不斷。近日,國內出現了一款類PoWH的銀行遊戲,在兩週的宣傳過後,短短數日,就完成了遊戲創建、集資、黑客卷錢走人這一整個流程,讓無數玩家措手不及。

時間線

  • 2018年08月19日晚十一點半,宣傳良久的區塊鏈賭博遊戲God.Game合約被創建於以太坊6176235區塊。在之後的兩天時間,遊戲內加入了大量玩家,合約內存儲的以太幣也增加到了243eth。
  • 2018年08月21日凌晨一點鐘,攻擊者經過簡單的測試,部署了一個攻擊合約。短短幾分鐘時間,利用遊戲合約漏洞,將合約賬戶的eth洗劫爲空。

知道創宇404區塊鏈安全研究團隊得知此事件後,對遊戲合約進行了仔細審計,復現了攻擊者的手法,接下來,將對整個事件進行完整的分析,並給出一種簡潔的利用方式。

0x01 合約介紹

智能合約名爲God,地址爲 0xca6378fcdf24ef34b4062dda9f1862ea59bafd4d,部署於 6176235,發行了名爲God幣的代幣(erc20 token)。

God.Game主要是一個銀行合約,代碼有上千行,較爲複雜。如果之前對PoWH3D等類似合約有過接觸,God便不難理解。下面我們介紹些簡單概念。

ERC20 token

token代表數字資產,具有價值,通過智能合約發行於區塊鏈上,我們可以稱之爲代幣。符合ERC20協議的代幣可以更容易互換,方便的在交易所上市。God幣便是符合ERC20協議的代幣。

合約功能

在God.Game中,你可以通過eth購買token(god幣),當你擁有了token,相當於參加了這個遊戲。

  • 購買token:會產生一定的手續費,除了主辦方會收取一部分外,還有一部分將會均分給所有token持有者,也就是所謂的分紅。
  • 轉賬token:你可以將手中的token轉賬給他人。
  • 出售token:將手中的token出售爲可提款。
  • 提取紅利:將分紅轉爲以太幣提取出來。
  • 邀請機制:當你擁有多於100個token,將開啓邀請系統。他人使用你的地址,你將會獲得較多的手續費提成作爲分紅。【攻擊未涉及該功能】

token與eth的兌換、分紅的多少,都與token的總量以及持有者有關,不斷變化。

代碼淺析

我們將簡要介紹合約中出現的幾個重要變量。

在開始介紹前,請先記住一個概念:紅利由 賬戶token的價值 - payout 得到,時常變化,而不是記錄這個變量。

用戶信息
  • token【代幣】是確定的數量,用戶的token僅可通過自己buy、sell、transfer變動。
  • token * profitPerShare 可以看作是賬戶token的價值。
  • payouts 我們稱之爲已經用過的錢。【這個定義並不嚴謹,可以叫控制賬戶紅利的值】
  • token * profitPerShare - payoutsTo_ 可以看作用戶在此合約內現在可以使用的錢, 定義爲紅利。

合約通過控制payoutsTo的值,來控制用戶可用的錢,即紅利【用來提eth,或再向God合約購買token】。

全局變量

以下變量是全局中浮動的

重要的臨時變量

dividends = 賬戶總價值 - 已用的錢【payout】

dividends這個變量並不存儲,不然每當其他參數變動時,需要計算所有人的分紅。

每次使用時,通過myDividends(false)計算,而這個函數在不涉及推薦功能時,僅調用了dividendsOf(address customerAddress)。

這裏也是本次攻擊的溢出點。

0x02 漏洞點

漏洞點有兩處,簡而言之,是當被轉賬賬戶是合約賬戶時,處理有誤造成的。

計算分紅

function dividendsOf(address _customerAddress) view public returns (uint256) { return (uint256) ((int256)(profitPerShare_ * tokenBalanceLedger_[_customerAddress]) - payoutsTo_[_customerAddress]) / magnitude; }

從上面得知,分紅可用來提eth,或再次購買token。 分紅本應永遠爲正數,這裏的減法未使用safeMath,最後還強制轉換uint,會造成整數溢出。 我們需要控制payoutsTo和token的關係。

轉賬transfer()

// exchange tokens tokenBalanceLedger_[_from] = SafeMath.sub(tokenBalanceLedger_[_from], _amountOfTokens); tokenBalanceLedger_[_toAddress] = SafeMath.add(tokenBalanceLedger_[_toAddress], _amountOfTokens);

我們看到,如論如何轉賬,token一定是一方減少,另一方增加,符合代幣的特點。

if (fromLength > 0 && toLength <= 0) { // contract to human contractAddresses[_from] = true; contractPayout -= (int) (_amountOfTokens); tokenSupply_ = SafeMath.add(tokenSupply_, _amountOfTokens); payoutsTo_[_toAddress] += (int256) (profitPerShare_ * _amountOfTokens); } 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);

這裏是God中,針對轉賬雙方的賬戶類型【外部賬戶、合約賬戶】採取的不同操作。

我們會發現,transfer()函數並未對合約賬戶的payoutsTo進行操作。而是僅修改了contractPayout這個和God合約參數有關的全局變量。

導致合約賬戶中 token(很多) * profitPerShare(常量) - payoutsTo(0) 非常大。正常來講,payoutsTo應該變大,令賬戶的dividends爲 0。

這種寫法非常奇怪,在ERC20的協議中,當被轉賬賬戶爲合約時,只需要合約擁有該代幣的回調函數即可,沒有別的要求。

0x03 攻擊鏈

這樣我們就可以得到大致的攻擊鏈: 再次注意,紅利 dividens = token * token價值 - payout(用戶已經花了的部分)。 即 可用的錢 = 總價值 - 已用的錢

  1. 攻擊者 ==轉賬==> 攻擊合約 合約狀況:
  1. 攻擊合約 withdraw() 合約狀況:
  1. 攻擊合約 ==轉賬==> 攻擊者 合約狀況:
  1. 攻擊合約 reinvest() 合約狀況:

再投資【使用紅利購買token】,通過大量的紅利,可以隨意購買token,進而sell()+withdraw()提出eth,完成攻擊。

0x04 實際流程

攻擊者首先部署了幾個測試的攻擊合約,因爲一些原因之後未使用,可能僅供測試。

攻擊合約逆向

知道創宇404區塊鏈安全研究團隊使用昊天塔,對攻擊者部署的合約進行了逆向,得到了攻擊合約大致代碼。

得到的函數列表

0x0: main() 0xa2: withdraw() 0xb7: ownerWithdraw() 0xcc: owner() 0xfd: myTokens() 0x124: transfer(address,uint256) 0x148: tokenFallback(address,uint256,bytes) 0x1c5: sell(uint256) 0x1dd: exit() 0x1f2: func_ee2ece60 0x207: buy(address) 0x21b: func_f6613ff5 0x230: reinvest()

而具體分析函數內容,發現該合約大部分函數都是以本合約發起對God合約的調用,例如:

function withdraw() public { if (msg.sender == 0x2368beb43da49c4323e47399033f5166b5023cda){ victim.call(bytes4(keccak256("withdraw()"))); } }

對照攻擊者交易明細,我們來複現攻擊流程。我們假設token對應紅利是1:1,便於解釋。

  1. 部署攻擊合約 tx:1. 部署合約 攻擊者部署合約,準備攻擊。 合約地址:0x7F325efC3521088a225de98F82E6dd7D4d2D02f8
  2. 購買token tx:2. 購買token 攻擊者購買一定量token,準備攻擊。
  1. 向攻擊合約轉賬token tx:3. transfer(attacker -> attack-contract) 攻擊者本身購買了少量token,使用遊戲合約中的transfer(),向攻擊合約轉賬。
  1. 攻擊合約withdraw() tx:4. withdraw() 攻擊合約調用了God的withdraw(),攻擊合約因此獲得了紅利對應以太幣【不重要】
  1. 攻擊合約transfer() tx:5. transfer(attack-contract -> attacker) 將token轉回,攻擊合約token不變,紅利溢出。
  1. 攻擊合約reinvest() tx:6. reinvest() 再投資,將紅利買token,可以大量購買token。
  1. 攻擊合約sell() tx:7. sell() 賣出一部分token,因爲發行的token過多,會導致token價值太低,提取以太幣較少。
  2. 攻擊合約transfer() tx:8. transfer(attack-contract -> 受益者) 把智能合約賬戶的token轉給受益者(0xc30e)一部分。
  3. 受益者sell()+withdraw() 受益者(0xc30e)賣掉token,並withdraw()紅利,得到以太幣。

0x05 更簡單的攻擊手法

回顧上述攻擊流程,攻擊成立主要依賴紅利由 token - payout 得到,時常變化,而不是記錄這個特性。

在交易token時,變化的只是雙方持有的token數,雙方的紅利應該不變,換言之,就是用戶的payout也需要變化才能保證紅利變化。

漏洞就在於在用戶和合約交易token時,合約方的payout並沒有相應的增加,導致紅利平白無故的多出來,最終導致了憑空生幣。

這樣一來,我們就可以使用更簡單的攻擊手法。

下面是詳細的介紹:

  1. 攻擊者 ==轉賬==> 攻擊合約 合約收到轉賬時,紅利本應爲0,卻變得很多,賬戶可用資金變得很多。
  2. 攻擊合約 withdraw() 把可用的錢提款爲eth,token不變。
  3. 攻擊合約 ==轉賬==> 攻擊者 token原路返回攻擊者,token不變,但合約中多出了 eth 。

我們發現智能合約在這個過程中,因爲接受轉賬未增加payout,導致在第二步中可以提取不少的以太幣,並在第三步將token原路轉回。 這一過程,合約賬戶便可憑空得到以太幣。而只需要支付一部分手續費以及token的輕微貶值。如此反覆創建新的合約,並按以上步驟,可以提出God.Game中大量的以太幣。

注意事項

此攻擊方法理論成立,還需仔細考察手續費和token價值變化等細節問題,但從合約中提取部分以太幣是可行的。

具體分析

  1. 購買token 攻擊者購買一定量token,準備攻擊。
  1. 向攻擊合約轉賬token 攻擊者本身購買了少量token,使用遊戲合約中的transfer(),向攻擊合約轉賬。
  1. 攻擊合約調用 withdraw() withdraw() 的主要邏輯如下: 攻擊合約調用withdraw(),通過以太幣的形式取出利息 dividents。
  1. 攻擊合約transfer() 將token轉回,攻擊者token恢復爲1000。

0x06 總 結

以上就是God.Game合約的分析,以及本次攻擊的復現。這次攻擊的發生距離合約部署僅有兩天,整個攻擊流程非常巧妙。按照前面的分析,僅通過合約賬戶的withdraw()就可以提出以太幣。但攻擊者還利用了紅利溢出,進而獲得了大量的token。根據上面多方面因素,雖然主辦方在事件發生後聲明自己是受害者。但是根據telegram上記錄,主辦方在遊戲開始之前就再未查看玩家羣。這些現像,引人深思。

區塊鏈遊戲看似充滿誘惑,實則迷霧重重。無論如何謹慎,都有可能跌入深淵。誰也不知道遊戲背後的創建者究竟有什麼打算,但人皆貪婪,有錢財的地方,必有隱患。

0x07 相關鏈接

  • PoWH 3D 源碼分析 https://github.com/Fabsqrt/BitTigerLab/tree/master/Blockchain/Classes/PoWH3D
  • God.Game官網 http://god.game/#/

附錄1 此次事件相關地址

  • God合約創建者 0x802dF0C73EB17E540b39F1aE73C13dcea5A1CAAa
  • God合約地址 0xCA6378fcdf24Ef34B4062Dda9F1862Ea59BaFD4d
  • 最終以太幣存儲的賬戶 0xC30E89DB73798E4CB3b204Be0a4C735c453E5C74
  • 攻擊者 0x2368beb43da49c4323e47399033f5166b5023cda
  • 攻擊合約 0x7f325efc3521088a225de98f82e6dd7d4d2d02f8

附錄2 God.Game合約的函數分析

  • buy() - 購買token
  • sell() - 出售token

未使用的分紅增加,可用來withdraw(提款)或reinvest(再投資)。

  • withdraw() - 將分紅清0,分紅換爲eth取出

清零分紅,獲得相應的eth。

  • reinvest() - 再投資

消耗掉賬戶的分紅,換成token。

  • transfer() - 轉賬

from:

to:

附錄3 根據昊天塔逆向結果,構造的攻擊合約

pragma solidity ^0.4.23; contract Attack { address public owner; address public victim; function Attack() payable { owner = msg.sender; } function setVictim(address target) public { victim = target; } function withdraw() payable public { victim.call(bytes4(keccak256("withdraw()"))); } function reinvest() payable public { victim.call(bytes4(keccak256("reinvest()"))); } function transfer(address to_, uint256 amount) payable public{ victim.call(bytes4(keccak256("transfer(address,uint256)")),to_,amount); } function () payable public{} function tokenFallback(address _from, uint _amountOfTokens, bytes _data) public returns (bool){ return true; } }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章