1.概述
在有趣的智能合約蜜罐中我們對古老的欺騙手段和神奇的邏輯漏洞進行了講解和復現,在下部分中我們將會對新穎的賭博游戲和黑客的漏洞利用進行講解以及復現,從而進一步增加對智能合約蜜罐的了解。同樣的,所有的智能合約蜜罐代碼都可以GitHub上找到,這里再次給出他們的網址:smart-contract-honeySolidlity-Vulnerable2.新穎的賭博游戲
賭博行業從古至今一直存在,而區塊鏈的去中心化似乎給賭博行業帶了新的機會,它的進入會讓人們覺得賭博變得公平,然而我們都知道賭博結果往往都是必輸,那么接下來就通過分析四個基于區塊鏈的賭博游戲合約來介紹莊家是如何最后穩贏的。2.1加密輪盤賭輪:CryptoRoulette
2.1.1蜜罐分析第一個要介紹的是CryptoRoulette,它譯為「加密輪盤賭輪」。GutHub地址:smart-contract-honeypots/CryptoRoulette.solEtherscan地址:CryptoRoulette|0x94602b0E2512DdAd62a935763BF1277c973B2758蜜罐的完整代碼如下:
該合約設置了一個私有屬性的隨機數secretNumber,在shuffle()函數中被指定范圍在1-20,玩家可以通過play()函數去盲猜這個隨機數,如果猜對了就可以將合約中的所有錢取走,每次調用play()函數后都會重置隨機數。這么看來這個合約好像沒有什么問題,隨著猜錯的玩家越來越多,合約中的代幣余額也會積累的越多,如果碰巧猜對了就可以獲取所有的獎金,然而事實是這樣的嘛?我們可以看到在這個蜜罐合約中,最重要的就是shuffle()和play()這兩個函數,下面就來分析下這兩個函數。初始的secretNumber是在構造函數CryptoRoulette中調用shuffle()函數,而shuffle()函數中只有一行代碼,就是設置secretNumber的值,從代碼中也可以看出secretNumber的值既和區塊的數目有關,也和時間有關。函數代碼如下:
而play()函數就是提供給用戶進行賭博來猜這個隨機數的,玩家攜帶不小于0.1eth并傳入自己猜的數字number,玩家猜的這個數字number去和secretNumber進行比較,如果相等就可以獲勝,轉走合約中的所有以太幣,但是在函數的開頭中有一個檢查require,其中后面要求玩家猜的數字不能大于10,而secretNumber我們在上面的函數中講到范圍是1-20,這樣看來雖然加大了難度,但是也存在猜對可能性,然而事實是secretNumber一定會大于10,玩家永遠都不可能猜對數字,合約所有者卻可以通過調用kill()函數轉走合約中的所有以太幣。
這里會有人問了,secretNumber為啥一定會大于10呢?原因就是結構體game的初始化對存儲數據secretNumber的覆蓋,我們在函數里直接初始化結構體必須加memory關鍵字,因為memory是使用內存來進行存儲,這樣一來就可以避免占用storage的存儲位,而蜜罐合約中并未使用memory關鍵字,從而導致了變量覆蓋。該問題在Solidity0.5.0版本以前只是進行了提示,并沒有做出錯誤警告,所以在老版本編譯器中要注意該問題。在下面的代碼復現中可以看到問題所在。2.1.2代碼復現
將蜜罐合約的代碼復制到RemixIDE中,為了方便我們查看secretNumber的值,我們將secretNumber的類型設置為public,這樣就可以在RemixIDE中直接看到它的值了。甚至有些蜜罐部署者為了誘惑攻擊者來攻擊合約,也可以設置為public屬性,因為就算告訴攻擊者secretNumber的值他也不能猜對這個數字。使用地址0x5B3點擊「Deploy」部署合約,調用secretNumber查看初始隨機數為1,由于這里還沒有初始化結構體也就不會覆蓋隨機數所以是正確的。
債券市場資深人士:拉丁美洲對比特幣的采用是世界上最有趣的地緣發展:債券市場資深人士、南加州大學馬歇爾商學院金融學和經濟學副教授Nik Bhatia發推表示,拉丁美洲對比特幣的采用是世界上最有趣的地緣發展,我全力支持。[2021/6/20 23:50:25]
之后攻擊者發現了該蜜罐合約,查看secretNumber為1并認為該合約可以進行攻擊獲利,所以在符合play()函數中的第一個判斷條件情況下傳入數字1和攜帶1個以太幣進行函數調用,函數調用成功后查看賬戶余額發現賬戶余額不僅沒有得到合約中的所有代幣反而將剛才函數調用時攜帶的1個以太幣也損失掉了。
為了探究具體原因我們對剛才的函數調用進行Debug。
調試點擊下一步直到第一個條件判斷,此時secretNumber仍然為1。
繼續點擊按鈕進行下一步的調試,當進行到game.player=msg.sender時由于結構體game的初始化對存儲數據secretNumber進行了覆蓋,導致secretNumber變成了msg.sender的uint256內容,這樣一來就使得后面的if判斷條件不能成立,從而使得攻擊者不能轉走合約中的所有代幣余額。
2.2開放地址彩票:OpenAddressLottery
2.2.1蜜罐分析第二個要介紹的是OpenAddressLottery,它譯為「開發地址彩票」。GutHub地址:Solidlity-Vulnerable/OpenAddressLottery.solEtherscan地址:OpenAddressLottery|0xd1915A2bCC4B77794d64c4e483E43444193373Fa蜜罐的完整代碼如下:
蜜罐合約OpenAddressLottery的游戲邏輯很簡單,合約中有一個初始值為1的狀態變量LuckyNumber,競猜者每次競猜時都會根據其地址隨即生成0或者1,如果生成的值和LuckyNumber一樣,那么競猜者就可以獲得1.9倍的獎金,且每個地址只能贏得一次游戲勝利,之后將無法繼續參加競猜。該蜜罐合約的重點就在于participate()、luckyNumberOfAddress()和forceReseed()函數,下面來對這3個函數進行依次講解。首先是participate()函數,這是用戶參與競猜的函數:
接著是luckyNumberOfAddress()函數,將競猜者的地址作為參數傳入,通過n=uint(keccak256(uint(addr),secretSeed))%2;來計算競彩時競猜者對應的數字,由于是對2取余,所以得到的結果只能為0或者1。在計算這個數字時使用了變量secretSeed,而該變量總是通過reseed()函數得到的。
Ripple CTO:“收集東西的動力”與“數字版權概念”是NFT有趣的兩個點:Ripple首席技術官David Schwartz在最近的一次采訪中表示,他認為與NFT相關的兩個點很有趣,一個是“人們收集東西的動力”,另一個是數字版權的概念。Schwartz指出,盡管許多人目前擁有“捆綁的數字版權”(例如從谷歌Play商店購買的書籍或歌曲),但這些版權的用戶體驗并不好。(CryptoGlobe)[2021/3/22 19:06:28]
最后我們來講下上面說到的reseed()函數,通過keccak256算法將傳入的4個參數來生成secretSeed。
通過上面對合約的分析,看起來合約沒有什么問題,中獎率也是50%,但其實是有陷阱的,這就要說到Solidity0.4.x結構體局部變量引起的變量覆蓋漏洞,也就是給未初始化的結構體局部變量賦值時會直接覆蓋掉智能合約中定義的前幾個變量,這樣就使得合約中forceReseed()函數被調用后,第四個定義的參數LuckyNumber會被s.component4=tx.gasprice*7給覆蓋并將其設置為7,該蜜罐合約原理和上一個蜜罐合約類似。查看該合約的交易內容,可以發現OpenAddressLottery的交易數量很多,這也說明了蜜罐合約OpenAddressLottery的欺騙性。
2.2.2代碼復現將蜜罐合約的代碼復制到RemixIDE中,為了方便我們查看LuckyNumber的值,我們將LuckyNumber的類型設置為public,這樣就可以在RemixIDE中就有獲取其值的getter()函數了。同樣的,蜜罐部署者也可以將該變量設置為public屬性讓攻擊者誤以為有利可圖,因為LuckyNumber的值會被覆蓋永遠為7。使用地址0x5B3點擊「Deploy」部署合約,調用LuckyNumber查看其值為1,由于這里還沒有初始化SeedComponent結構體也就不會覆蓋掉LuckyNumber的值,所以它還是1。
使用合約所有者0x5B3調用forceReseed()函數來初始化SeedComponent中的四個變量,可以看到LuckyNumber的值由于初始化已經變成了7。
攻擊者0x4B2看到該合約后認為其存在漏洞,攜帶10eth調用participate()函數,調用后查看余額發現并沒有增加。查看自己的地址對應的luckyNumberOfAddress的值為1,但是卻沒有得到獎勵,再查看LuckyNumber的值發現一直為7。其原因就是在部署者調用forceReseed()函數初始化后LuckyNumber的值就被覆蓋為了7,而攻擊者地址生成的隨機數只能是0或1,這就意味著永遠不會有人獲得勝利。這就是利用了編譯器的漏洞,該問題已經在Solidity0.5.0中修復,所以這種蜜罐合約只有在Solidity0.4.x中才會生效。
2.3山丘之王:KingOfTheHill
YFI創始人:流動性挖礦保險轉售衍生出一些有趣的現象:9月14日午間,YFI項目創始人AndreCronje今日發推提到了加密保險的一些有趣現象:
1,人們在做流動性挖礦時正在大量購買保險;
2,流動性挖礦結束后,保險對他們而言就失效了;
3,Nexus(DeFi保險平臺)表示,用戶可以無風險持有資產,這意味著,如果發生了可索賠事件,保險可以按保險費轉售。這使得市場上出現折扣保險,協議可以在二級市場上以便宜的價格為系統購買廣泛的保險,為可索賠事件創造一個安全網;這是一個好的公益事業,如果用戶不做流動性挖礦了,可以考慮把保險捐贈給社區,這樣社區就能在意外發生時有后備方案。[2020/9/14]
2.3.1蜜罐分析第三個要介紹的是KingOfTheHill,它譯為「山丘之王」。GutHub地址:Solidlity-Vulnerable/KingOfTheHill.solEtherscan地址:KingOfTheHill|0x4dc76cfc65b14b3fd83c8bc8b895482f3cbc150a蜜罐的完整代碼如下:
蜜罐合約KingOfTheHill只有38行代碼,邏輯很簡單,有回退函數和takeAll()函數,其中jackpot變量是傳入合約的所有代幣之和,每次有用戶調用回退函數后如果傳入的mag.value比jackpot大,就將owner的值賦值為msg.sender。當用戶獲得了合約所有者權限后,就可以調用takeAll()函數在延期時間到后將合約中所有余額轉走。接下來重點分析下這兩個函數。首先是回退函數,這是用戶參與合約「漏洞」的函數,其代碼如下:
接著是takeAll()函數,這是能轉走合約中所有余額的函數,其代碼如下:
通過對上面兩個函數的分析,感覺該合約并沒有什么問題,但是我們說了這是個蜜罐,那么它的陷阱到底在哪兒呢?回看下「有趣的智能合約蜜罐」中的TestBank蜜罐合約就能知道原因了,它們的原理類似,都是「誰是合約主人」的問題。KingOfTheHill中存在著Owned和KingOfTheHill兩個合約,KingOfTheHill繼承了Owned,為了方便理解,我們將KingOfTheHill改寫成一個單合約,代碼如下:
在改寫了合約代碼后很容易就可以看出問題所在,用于權限判斷的修飾器函數onlyOwner中判斷的變量是owner1,而回退函數中修改的是原來子類新定義的owner,也就是owner2,這就說明了合約所有者是不會被更改的,調用takeAll()函數的人只能是合約創建者。接下來我們通過代碼來復現一下。2.3.2代碼復現將蜜罐合約的代碼復制到RemixIDE中,為了方便我們復現,將回退函數中withdrawDelay=block.timestamp+5days;修改為withdrawDelay=block.timestamp+0days;,這樣我們在測試的時候就不用等待5天后再去嘗試取款操作了。使用地址0x5B3點擊「Deploy」部署KingOfTheHill合約,點擊owner查看當前值為0。
再使用0x5B3攜帶10eth調用回退函數,向合約中存入10個以太幣,此時jackpot為10eth,查看owned為0x5B3。
聲音 | 王新喜:將在1-2天內發布一些關于LTC有趣的內容:萊特幣基金會聯合創始人王新喜在推特表示,將在1-2天內發布一些關于LTC的有趣的內容。[2019/9/6]
攻擊者0xAb8設置msg.value為20eth調用回退函數,查看owner為0xAb8。
攻擊者發現此時owner為自己的地址,符合了takeAll()函數的要求,所以去調用takeAll()函數,結果發現交易失敗,并且自己的余額仍然為80eth。
蜜罐部署者0x5B3發現有人上鉤了,合約中已經有了30eth,此時雖然owner為攻擊者地址0xAb8,但是0x5B3調用takeAll()函數仍然將合約中的所有余額全部轉走,查看賬戶余額,的確增加了30eth。
與之類似的智能合約還有RichestTakeAll:GitHub地址:Solidlity-Vulnerable/RichestTakeAll.sol智能合約地址:RichestTakeAll|0xe65c53087e1a40b7c53b9a0ea3c2562ae2dfeb242.4以太幣競爭游戲:RACEFORETH
2.4.1蜜罐分析第四個要介紹的是RACEFORETH,它譯為「以太坊競爭游戲」。GutHub地址:Solidlity-Vulnerable/RACEFORETH.sol蜜罐的完整代碼如下:
蜜罐合約RACEFORETH中有一個SCORE_TO_WIN參數,其值為100finney,字面意思我們也可以知道該參數的作用是勝利的分數,然后合約還有兩個映射,其中racerScore是競爭者當前得分數,racerSpeedLimit是每步的限制。競爭者通過每次的轉賬金額來積累自己的分數racerScore,當自己的得分racerScore大于等于SCORE_TO_WIN時就能獲得勝利,取走合約創建者一開始存入的獎勵PRIZE。蜜罐合約的核心內容就是race()函數和endRace()函數,接下來我們分析下這兩個函數。首先是race()函數,其代碼如下:
用戶每次調用race()函數都會帶入msg.value,且msg.value需要大于1wei和小于步長限制,通過判斷后加到自己的總得分數racerScore上,接著將新的步長限制設置為當前步長限制的一半,只要總得分數大于等于了獲勝目標值就可以取走獎勵,初看合約會覺得每次增加的步數在減少,但總有一天會追上,但事實是這樣嗎?接著是endRace()函數,其代碼如下:
合約所有者在上一次競賽的3天后就可以轉走合約中所有的余額了。2.4.2代碼復現將蜜罐合約的代碼復制到RemixIDE中,為了方便我們復現,增加了一個publicnowScore,這樣我們在測試的時候就可以看到每次競賽后的分數了。使用地址0x5B3點擊「Deploy」部署RACEFORETH合約。
聲音 | DRW控股創始人:對我來說,更有趣的是比特幣的應用方法:DRW控股公司的創始人兼交易商大亨Don Wilson稱,有很多關于比特幣存儲價值的爭論。但對我來說更有趣的是比特幣的應用方法,在一個系統中沒有信任的傳輸能力是具有巨大破壞性的。[2018/9/19]
使用0xAb8作為攻擊者,根據代碼的要求,第一次最大只能為50Finney,所以將msg.value也設置為50Finney,之后查看當前分數為50Finney。
攻擊者0xAb8第二次嘗試將msg.value設置為大于上一次競賽的50Finney一半的26Finney,調用race()函數后發現調用失敗,原因則是因為我們的26Finney不滿足require中小于等于上一次競賽一半的條件。
每次我們都傳入上一次最大值的一半,執行多次后發現仍然未到100Finney。因為如下的公式只能無限趨于100卻用于不能等于100。
其中:
永遠是小于2的,那么50乘上這個式子就永遠不可能等于100了,也就永遠無法到達終點,所以對于該蜜罐合約,即使我們多次調用race()函數,每次都轉入最大限制值,也不可能達到目標分數,那么我們就不能取出合約中的獎勵了。
3.黑客的漏洞利用
3.1僅僅是測試?(整數溢出):For_Test
3.1.1蜜罐分析第五個要介紹的是For_Test,它譯為「僅僅是測試?」。GutHub地址:Solidlity-Vulnerable/For_Test.solEtherscan地址:For_Test|0x2eCF8D1F46DD3C2098de9352683444A0B69Eb229蜜罐的完整代碼如下:
蜜罐合約For_Test的邏輯很簡單,核心函數只有Test()一個,在該函數中當傳入的msg.value大于0.1eth時,根據for循環的內容,最終會得到amountToTransfer的值,也就是說函數調用者會獲得4倍轉入金額的獎勵。接下來我們分析函數的主要內容。
仔細分析代碼邏輯可以發現for循環中if判斷中有個條件,當條件為真時會跳出循環,但是這個判斷條件很詭異,因為amountToTransfer初始為0,在跳出之前amountToTransfer=multi,而在下一次循環時multi變為2倍的i,這就意味著multi是永遠大于amountToTransfer的值,相應的這個判斷條件不是會永遠也不成立了嗎?在最終揭秘這個蜜罐合約前我們還需要了解下幾個知識。msg.value的單位是wei,而1eth=1018wei。當一個參數變量被定義為var時,其數據類型為uint8,其取值范圍為。再次看到Test()函數中的循環,msg.value的最小值為0.1eth,而msg.value*2的值就會超過uint8的取值范圍,也就是說此處會存在整形溢出,在i=255時再執行i++就會導致i上溢變為0,此時的multi為0從而小于amountToTransfer的值,這樣就滿足了if的判斷條件,循環也會提前結束。根據代碼內容,最終轉給調用者的金額為amountToTransfer=255*2=510wei,無論調用者傳入了大于0.1eth的任何金額,最后都只會得到510wei。3.1.2代碼復現將蜜罐合約的代碼復制到RemixIDE中,使用地址0x5B3點擊「Deploy」部署For_Test合約,此時0x5B3的賬戶余額為100eth。
選擇0xAb8作為攻擊者,將msg.value設置為10eth,調用Test()函數,調用成功后發現賬戶余額不但沒有增加反而減少了剛才傳入的10eth。
當攻擊者將代幣轉入合約后,合約所有者調用withdraw()函數進行取款,將剛才攻擊者調用Test()函數傳入的10eth轉走,賬戶余額增加到110eth。
與之類似的智能合約還有Test1:Github地址:smart-contract-honeypots/Test1.sol3.2股息分配:DividendDistributor
3.2.1蜜罐分析最后一個要介紹的是DividendDistributor,它譯為「股息分配」。GutHub地址:Solidlity-Vulnerable/DividendDistributor.solEtherscan地址:DividendDistributorv3|0x858c9eaf3ace37d2bedb4a1eb6b8805ffe801bba蜜罐的完整代碼如下:
蜜罐合約DividendDistributor的邏輯不算太難,主要有投資、取錢、計算股息等功能,合約中有一個結構體類型的investor,其作用為存儲投資人的投資信息包括投資額度和股息,并且該結構體通過mapping實現賬戶地址到investor的映射。通篇看來下合約并沒有任何的問題,并且如果編譯器版本設置正確的話合約也不會出現任何問題。看一下合約關鍵的函數,invest()、divest()、loggedTransfer()和payDividend(),接下來我們就對這4個函數進行詳細分析。先是invest()函數,其函數功能為用戶調用該函數進行投資,每次的投資數量不能小于要求的最低數量0.4eth,投資后更新相關的變量。完整代碼如下:
divest()函數作為和上面的函數剛好相反,是取出自己投資的金額,函數中一開始就要檢查調用者投資的數量或者調用函數傳入的參數不為0,接著減去該次取錢操作的金額數量,最后從合約所有者賬戶中轉走amount金額給調用者。完整代碼如下:
loggedTransfer()函數的功能非常簡單,就是轉賬和記錄轉賬操作。完整代碼如下:
payDividend()函數為獲得由合約所有者設置的股息。完整代碼如下:
通過分析上面的4個函數,我們發現該蜜罐合約的誘惑點在于投資者不僅能夠隨時存取投資,還可以通過payDividend()函數獲取股息,這樣的合約好像是有利可圖的,然而事實是這是一個陷阱,它利用的就是舊版本編譯器中的漏洞,在Solidity0.4.12之前存在一個漏洞,如果將空字符串作為函數調用時的參數那么編譯器就會跳過該參數。
而在上面的幾個核心函數中,divest()函數就是存在這樣的問題,根據漏洞說明,調用this.loggedTransfer(amount,"",msg.sender,owner);后會變成loggedTransfer(uintamount,bytes32msg.sender,addressowner,address空)最終給owner用戶轉賬owner.call.value(amount)()。下面我們就通過代碼來復現這個蜜罐合約,揭開它的真面目。3.2.2代碼復現
將蜜罐合約的代碼復制到RemixIDE中,將編譯器Solidity的版本設置為0.4.11。
選擇0x5B3作為合約部署者和所有者,點擊「Deploy」進行部署,隨后將VALUE設置為10eth并調用distributeDividends函數設置股息。
將0xAb8作為攻擊者,設置VALUE為10eth并調用invest()函數進行投資。
使用0xAb8調用下圖中的函數獲取該蜜罐合約的相關信息,包括計算股息,自己的投資數額,最小投資數額,合約所有者owner,總的股息和總的投資數額。
繼續使用0xAb8調用divest()函數并設置其傳入參數為5000000000000000000想要取出剛才投資的10eth的一半,發現該交易被確認,查看該交易的logs可以發現和上面我們分析的一樣,target參數變成了owner的地址,第二個參數也被msg.sender所取代,返回查看賬戶當前余額,發現剛才調用divest()函數取出的5eth被轉到了owner賬戶0x5B3中。
4.總結
通過對以太坊蜜罐智能合約的分析,我們可以發現在智能合約中這些有趣的蜜罐合約更像是釣魚,通過各種欺騙手法誘使他人將代幣轉入合約中從而進一步獲取這些代幣。當然蜜罐合約也不是完全沒有學習價值的,我們從蜜罐合約中可以看到合約的攻擊思路以及Solidity的很多新舊特性。在平時的合約審計中也需要考慮這些問題,否則這些合約就可能被黑客攻擊導致合約代幣被盜取。即使是現在,同樣有人編寫蜜罐合約進行誘騙,只是他們的思路不再僅限于那些想要靠天上掉餡餅獲取利益的人,各種機器人也成為了他們的誘騙目標。所以我們一定要重視合約的功能邏輯,防止合約因為功能邏輯被攻擊的同時還要防止合約所有者跑路等各種因素。5.文獻參考
蜜罐技術_百度百科(baidu.com)以太坊蜜罐智能合約分析(seebug.org)Solidity中文手冊
貢獻者:Dewei-DAOrayaki什么是可組合性? 可組合性是系統的組件重新組合成更大的結構并將一個的輸出作為另一個的輸入的一般能力.
1900/1/1 0:00:0012月21日,跨鏈橋龍頭Multichain宣布完成6000萬美元融資,本輪融資由BinanceLabs領投.
1900/1/1 0:00:00北京時間12月29日,一個名為「GasDAO」的項目再次引發加密社區的關注。GasDAO是一個去中心化自治組織,宣稱其愿景是成為以太坊網絡上最活躍Web3用戶的代表,連接DeFi、NFT和Dap.
1900/1/1 0:00:00Odaily星球日報譯者|念銀思唐1月2日,Airbnb聯合創始人兼首席執行官BrianChesky發布了一條推文.
1900/1/1 0:00:00人們對以太坊2.0抱著極大期望,但普遍存在誤解,認為“以太坊2.0將降低Gas費”。這句話有兩處需要解釋:首先,2.0的升級是一個長期的過程,預計年內完成的“合并”,并不會降低Gas費,而真正能.
1900/1/1 0:00:0012月11日,基于NEAR生態的流動性質押協議MetaPool宣布完成種子輪融資,DragonflyCapital、A&TCapital、MoveCapital、Blockwall、D1.
1900/1/1 0:00:00