1.概述
在有趣的智能合約蜜罐中我們對古老的欺騙手段和神奇的邏輯漏洞進行了講解和復現,在下部分中我們將會對新穎的賭博游戲和黑客的漏洞利用進行講解以及復現,從而進一步增加對智能合約蜜罐的了解。
同樣的,所有的智能合約蜜罐代碼都可以GitHub上找到,這里再次給出他們的網址:
smart-contract-honey
Solidlity-Vulnerable
2.新穎的賭博游戲
賭博行業從古至今一直存在,而區塊鏈的去中心化似乎給賭博行業帶了新的機會,它的進入會讓人們覺得賭博變得公平,然而我們都知道賭博結果往往都是必輸,那么接下來就通過分析四個基于區塊鏈的賭博游戲合約來介紹莊家是如何最后穩贏的。
2.1加密輪盤賭輪:CryptoRoulette
2.1.1蜜罐分析
第一個要介紹的是CryptoRoulette,它譯為「加密輪盤賭輪」。
GutHub地址:smart-contract-honeypots/CryptoRoulette.sol
Etherscan地址: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()?函數轉走合約中的所有以太幣。
波卡創始人Gavin Wood:Web 3是“后斯諾登時代網絡”,一個非常有趣的社會實驗:金色財經報道,波卡Polkadot創始人Gavin Wood在BlockDown: DeData Conference大會上對互聯網演變發表看法,他表示,因為愛德華·斯諾登事件,讓互聯網感到創建一個全新平臺的緊迫性,他將這種在全球范圍內進行人際互動的方式稱為“后斯諾登時代網絡”(Post-Snowden Web)。展望未來10年或20年,Gavin Wood未來大規模多用戶應用程序框架可以為用戶提供“實際”保證。當被要求預測十年后的Web 3,Gavin Wood回答說:“這將是一個非常有趣的社會實驗,我們將看到世界對隱私、自我主權和透明度的關注,知道系統或服務規則如何運作,非常真實。”[2021/12/4 12:50:10]
這里會有人問了,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,由于這里還沒有初始化結構體也就不會覆蓋隨機數所以是正確的。
之后攻擊者發現了該蜜罐合約,查看?secretNumber?為1并認為該合約可以進行攻擊獲利,所以在符合?play()?函數中的第一個判斷條件情況下傳入數字1和攜帶1個以太幣進行函數調用,函數調用成功后查看賬戶余額發現賬戶余額不僅沒有得到合約中的所有代幣反而將剛才函數調用時攜帶的1個以太幣也損失掉了。
為了探究具體原因我們對剛才的函數調用進行Debug。
調試點擊下一步直到第一個條件判斷,此時?secretNumber?仍然為1。
繼續點擊按鈕進行下一步的調試,當進行到?game.player=msg.sender?時由于結構體game的初始化對存儲數據?secretNumber?進行了覆蓋,導致?secretNumber?變成了msg.sender的uint256內容,這樣一來就使得后面的if判斷條件不能成立,從而使得攻擊者不能轉走合約中的所有代幣余額。
2.2開放地址彩票:OpenAddressLottery
國際清算銀行創新樞紐負責人:薩爾瓦多采用比特幣作為法定貨幣是一個有趣的試驗:國際清算銀行創新樞紐負責人Benoit?Coeure:薩爾瓦多采用比特幣作為法定貨幣是一個有趣的試驗,我們認為比特幣是一種投機資產,應該受到監管。(金十)[2021/6/11 23:31:06]
2.2.1蜜罐分析
第二個要介紹的是OpenAddressLottery,它譯為「開發地址彩票」。
GutHub地址:Solidlity-Vulnerable/OpenAddressLottery.sol
Etherscan地址: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()?函數得到的。
最后我們來講下上面說到的?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。
馬斯克:狗狗幣是最有趣的加密貨幣:推特網友表示,人們把狗狗幣當作笑話來取笑,卻忽略了一個事實,那就是作為笑話是狗狗幣最大的優點之一。對此,馬斯克回復表示,它是最有趣的加密貨幣。[2021/2/7 19:09:16]
使用地址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
2.3.1蜜罐分析
第三個要介紹的是KingOfTheHill,它譯為「山丘之王」。
GutHub地址:Solidlity-Vulnerable/KingOfTheHill.sol
Etherscan地址:KingOfTheHill|0x4dc76cfc65b14b3fd83c8bc8b895482f3cbc150a
蜜罐的完整代碼如下:
蜜罐合約KingOfTheHill只有38行代碼,邏輯很簡單,有回退函數和?takeAll()?函數,其中?jackpot?變量是傳入合約的所有代幣之和,每次有用戶調用回退函數后如果傳入的?mag.value比?jackpot?大,就將?owner?的值賦值為?msg.sender。
當用戶獲得了合約所有者權限后,就可以調用?takeAll()?函數在延期時間到后將合約中所有余額轉走。接下來重點分析下這兩個函數。
首先是回退函數,這是用戶參與合約「漏洞」的函數,其代碼如下:
接著是?takeAll()?函數,這是能轉走合約中所有余額的函數,其代碼如下:
通過對上面兩個函數的分析,感覺該合約并沒有什么問題,但是我們說了這是個蜜罐,那么它的陷阱到底在哪兒呢?回看下「有趣的智能合約蜜罐」中的TestBank蜜罐合約就能知道原因了,它們的原理類似,都是「誰是合約主人」的問題。
KingOfTheHill中存在著Owned和KingOfTheHill兩個合約,KingOfTheHill繼承了Owned,為了方便理解,我們將KingOfTheHill改寫成一個單合約,代碼如下:
Blockstream CEO :2021年對于比特幣來說將是非常有趣的一年:Blockstream CEO Adam Back發推稱,“比特幣支持者們新年快樂!2021年對于比特幣來說似乎將是非常有趣的一年。勇往直前,天天向上。”[2021/1/1 16:13:45]
在改寫了合約代碼后很容易就可以看出問題所在,用于權限判斷的修飾器函數onlyOwner中判斷的變量是?owner1,而回退函數中修改的是原來子類新定義的owner,也就是?owner2,這就說明了合約所有者是不會被更改的,調用?takeAll()?函數的人只能是合約創建者。接下來我們通過代碼來復現一下。
2.3.2代碼復現
將蜜罐合約的代碼復制到RemixIDE中,為了方便我們復現,將回退函數中?withdrawDelay=block.timestamp5days;?修改為?withdrawDelay=block.timestamp0days;,這樣我們在測試的時候就不用等待5天后再去嘗試取款操作了。
使用地址0x5B3點擊「Deploy」部署KingOfTheHill合約,點擊?owner?查看當前值為0。
再使用0x5B3攜帶10eth調用回退函數,向合約中存入10個以太幣,此時?jackpot?為10eth,查看owned為0x5B3。
攻擊者0xAb8設置msg.value為20eth調用回退函數,查看?owner?為0xAb8。
攻擊者發現此時?owner?為自己的地址,符合了?takeAll()?函數的要求,所以去調用?takeAll()?函數,結果發現交易失敗,并且自己的余額仍然為80eth。
蜜罐部署者0x5B3發現有人上鉤了,合約中已經有了30eth,此時雖然?owner?為攻擊者地址0xAb8,但是0x5B3調用?takeAll()?函數仍然將合約中的所有余額全部轉走,查看賬戶余額,的確增加了30eth。
與之類似的智能合約還有RichestTakeAll:
GitHub地址:Solidlity-Vulnerable/RichestTakeAll.sol
智能合約地址:RichestTakeAll|0xe65c53087e1a40b7c53b9a0ea3c2562ae2dfeb24
2.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()?函數,接下來我們分析下這兩個函數。
聲音 | Joseph Young:隨著貨幣戰爭加劇 比特幣和黃金迎來了非常有趣的時刻:加密貨幣分析師Joseph Young發布推文評論“英國央行行長呼吁建立全球貨幣體系以取代美元”的消息稱,隨著地緣風險上升,貨幣戰爭加劇,比特幣和黃金迎來了非常有趣的時刻。BTC價格走勢是獨立的,短期趨勢似乎是小幅下降,但如果外匯戰繼續下去,可能會有更多投資者轉而關注其它SoV(價值存儲)選擇。[2019/8/24]
首先是?race()?函數,其代碼如下:
用戶每次調用?race()?函數都會帶入?msg.value,且?msg.value?需要大于1wei和小于步長限制,通過判斷后加到自己的總得分數?racerScore?上,接著將新的步長限制設置為當前步長限制的一半,只要總得分數大于等于了獲勝目標值就可以取走獎勵,初看合約會覺得每次增加的步數在減少,但總有一天會追上,但事實是這樣嗎?
接著是?endRace()?函數,其代碼如下:
合約所有者在上一次競賽的3天后就可以轉走合約中所有的余額了。
2.4.2代碼復現
將蜜罐合約的代碼復制到RemixIDE中,為了方便我們復現,增加了一個?publicnowScore,這樣我們在測試的時候就可以看到每次競賽后的分數了。
使用地址0x5B3點擊「Deploy」部署RACEFORETH合約。
使用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.sol
Etherscan地址: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.sol
3.2股息分配:DividendDistributor
3.2.1蜜罐分析
最后一個要介紹的是DividendDistributor,它譯為「股息分配」。
GutHub地址:Solidlity-Vulnerable/DividendDistributor.sol
Etherscan地址: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中文手冊
Gate.io已正式上線SENSO/USDT、SENSO/ETH交易對礦池,並於11:00就SENSO/USDT、SENSO/ETH交易礦池各新增額外10,345SENSO限時獎勵.
1900/1/1 0:00:00原文作者:DylanOliviaHunzeker原文編譯:TechFlow加密風險投資在外界看來往往顯得很模糊,而且很多人認為它是加密空間中最具競爭力和最殘酷的分支之一.
1900/1/1 0:00:00Ori:?現在是一個非常激動人心的時刻,有這么多的公告要公布,每個人都想知道Orca接下來會發生什么,所以我們想和你們直接談談接下來會發生什么.
1900/1/1 0:00:00NFT市場已經到達百億美金,然而NFT抵押借貸的估值多低于一億美金,巨大的價值落差隱含了巨大的機會.
1900/1/1 0:00:00頭條 以太坊和IPFS的API服務供應商Infura已恢復運行以太坊和IPFS的API服務供應商Infura已恢復運行.
1900/1/1 0:00:00Gate.io今日已正式上線OPEN、WIT、ESD、UMX、GDAO多個幣種USDT、ETH、BTC交易對礦池,並開啓新版流動性礦池獎勵.
1900/1/1 0:00:00