前言
前段時間看了有關智能合約里蜜罐合約的一些資料,感覺還是非常有意思的,這些蜜罐合約的利用點大都很巧妙,目的都是為了誘惑你往合約里送錢,而且目標人群也不是什么小白,恰恰是相關的技術人員反而容易著了他們的道。這里我也想起了早前見到的某個合約,現(xiàn)在再看確實也是個蜜罐合約,下面我們來看看它的利用點。
開端
說起來這份合約當時也是某位師傅分享給我,因為乍看起來問題很大,當時還在開玩笑要不要拿下它把錢轉出來合約的代碼很簡單,如下
pragma solidity ^0.4.20;
contract GUESS_IT
{
function Play(string _response)
external
payable
{
require(msg.sender == tx.origin);
if(responseHash == keccak256(_response) && msg.value>1 ether)
{
msg.sender.transfer(this.balance);
}
}
string public question;
address questionSender;
bytes32 responseHash;
function StartGame(string _question,string _response)
public
payable
{
if(responseHash==0x0)
{
responseHash = keccak256(_response);
question = _question;
questionSender = msg.sender;
}
}
function StopGame()
public
payable
{
require(msg.sender==questionSender);
msg.sender.transfer(this.balance);
}
function NewQuestion(string _question, bytes32 _responseHash)
public
payable
{
require(msg.sender==questionSender);
question = _question;
responseHash = _responseHash;
}
function() public payable{}
}
乍一看是不是問題多多,實際上也確實是問題多多,要成功地play game需要給出正確的response,經(jīng)過sha3加密后與responseHash進行比較即可成功提取所有的eth,同時我們又發(fā)現(xiàn)在StartGame函數(shù)中response直接作為參數(shù)傳送進來了,我們知道鏈上的交易都是公開透明的,所以合約的創(chuàng)建者執(zhí)行這一函數(shù)時我們是可以看到他傳遞的值的,所以我們直接去查看到response的值就可以成功play game了,當時這個合約里還存入了一個eth,相當于發(fā)送一個eth過去可以拿到兩個eth,想想還是挺刺激的,不過也只能是想想,真的發(fā)了你就得哭了
這個合約看起來其實看起來跟一個叫新年禮物的蜜罐合約有點像,404的團隊的文章里也有提到以太坊蜜罐智能合約分析,不過實際上利用點還是有些區(qū)別。
初看完這個合約,你可能會覺得這個作者是不是個小白,一點都不了解以太坊運行的機制就隨便在主鏈上創(chuàng)建了合約,而且還存入了一個以太幣,更是把源碼都發(fā)布上來讓你參觀,其實這時候你應該有點感覺到不對勁了,這天上難道還真能掉餡餅么,當時一個以太幣也不是個小數(shù)目了,不過怎么看也找不到問題所在,不管了,先動手試試再說。
嘗試
第一步我們當然要先確定response的值,前面也提到這個可以在調(diào)用startgame函數(shù)時查看,我們在etherscan上查看該合約的交易記錄

第一步創(chuàng)建了合約,那么下一步應該就是startgame了,我們查看該交易的內(nèi)容

在這里我們可以直接選擇將交易的內(nèi)容解碼,這樣就可以看到里面包含的這部分信息,所以respose的值就是A snowflakE了,看到這個是不是有點激動,說實話我當時也有點激動,不過現(xiàn)在還是得冷靜,后面一個交易是創(chuàng)建者往里面沖了一個ether,再下面竟然是一個老哥發(fā)了一個交易把eth給提出來了,不過我看的時候比較早,那時候還沒有這筆交易,其實這是合約主人把幣給提出來而已,現(xiàn)在看來應該是別人部署來測試的,再看這筆交易的內(nèi)容

竟然真的是用的前面的response進行提幣的,難道這個合約真的可以利用么?其實這里就是創(chuàng)建者的惡趣味了,我們接著往下看。
深入
前面我們已經(jīng)在交易里看到了response的值,我相信很多人可能已經(jīng)蠢蠢欲動了,不過為了以防萬一我們還是多做點驗證工作
我們知道合約里使用storage存儲的變量都是可以在鏈上查到的,所以此處的responseHash是可以讀取的,那么我們可以用它來進行驗證,按照變量定義的順序,存儲位slot 0存放的是string變量question的長度,slot 1存放的是questionsender地址,slot 2存放的是responseHash,所以我們讀取slot 2里的值,然后與前面的response的sha3進行比較
這里因為這個蜜罐已經(jīng)被廢棄了,所以值確實是一樣的,然而當時我進行嘗試的時候slot 2里存儲的并不是這串hash,當時我得到的是下面這串
0x490a2750bb759c739d4e8657ebad54ae2175d222146b95118e76f6c9a6f9bf6a
當時我真的是非常納悶,這咋就對不上號呢,我又看了其它變量存儲的位置

這question的長度倒還對得上號,但是這個sender的地址是怎么冒出來的,按道理不是應該是合約創(chuàng)建者的地址么,前面我們可以看到其地址如下
0xac413e7f9c2a5ed2fde919ce3d1e1e98f8d33a55
而存儲里的這串卻是個合約地址,這讓我很是頭疼,后來找到相關資料才知道玄機在于etherscan上可見交易的機制,在etherscan上對于合約與合約之間的消息傳送,當msg.value為0時它是不顯示的,因為它們被視為合約間的相互調(diào)用而不是一筆交易,但這部分的信息可以通過etherchain來查看,現(xiàn)在我們使用它來查看該合約間的調(diào)用信息

果然,現(xiàn)在交易信息就多了很多,我們發(fā)現(xiàn)在創(chuàng)建合約后的第一步行動并不是來自創(chuàng)建者,而是來自一個合約,而這正是我們前面讀取到的sender的地址,這下子就都說得通了,我們來看看這個合約在這次調(diào)用里都干了些啥,進入以后我們點擊Parity Trace來追蹤這兩次調(diào)用的信息,于是得到了這兩部分inputdata

因為這里不像etherscan那樣自帶decode,所以需要我們手動進行解碼,這里我們可以使用abi-decoder工具,這個可以用node.js部署,不過簡單點我們直接把abi-decoder.js下載下來就行了,然后我們直接在瀏覽器里使用
首先導入abi,可以直接在etherscan的源碼部分復制,然后將inputdata放入進行解碼即可
const abi =[{"constant":false,"inputs":[{"name":"_question","type":"string"},{"name":"_response","type":"string"}],"name":"StartGame","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_question","type":"string"},{"name":"_responseHash","type":"bytes32"}],"name":"NewQuestion","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"question","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_response","type":"string"}],"name":"Play","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"StopGame","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"}];
abiDecoder.addABI(abi);
const input1='0x1f1c827f000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004f5768617420666c696573207768656e206974e280997320626f726e2c206c696573207768656e206974e280997320616c6976652c20616e642072756e73207768656e206974e280997320646561643f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003735a730000000000000000000000000000000000000000000000000000000000';
const input2='0x3e3ee8590000000000000000000000000000000000000000000000000000000000000040490a2750bb759c739d4e8657ebad54ae2175d222146b95118e76f6c9a6f9bf6a000000000000000000000000000000000000000000000000000000000000004f5768617420666c696573207768656e206974e280997320626f726e2c206c696573207768656e206974e280997320616c6976652c20616e642072756e73207768656e206974e280997320646561643f0000000000000000000000000000000000';
結果如下

果然玄機就在這里,真正的responsehash是在這里設置的,該合約首先調(diào)用startgame來使自己成為questionSender,然后再設置全新的resonseHash,這里的response也不過是瞎寫的,真是很狡詐啊,至于后面的那幾個調(diào)用感覺就是創(chuàng)建者的惡趣味了,在接下來的幾個調(diào)用里就是拿他的地址假裝調(diào)用startgame設置了response值,然而事實上這里responseHash已經(jīng)不為0,所以是沒有響應的,然后就等你上鉤了,然后他又使用前面已經(jīng)成為questionSender的合約將responseHash的值改為了我們在etherscan上交易里看到的response的hash值,接下來他便使用另一錢包發(fā)送1 ether來提取合約內(nèi)的ether,正常來講這里是多次一舉的,因為他直接調(diào)用stopgame就可以拿回錢了,而他還費gas取改responseHash,大概是想偽造出一種有人成功拿錢走人的錯覺吧。
結語
希望這次的分析能讓大家感受到蜜罐合約的趣味性,對于那些對以太坊有一定了解手上又有點幣的人來說就得小心了,講道理第一次見的話是很容易被忽悠到的,畢竟以太坊上神奇的機制這么多,稍不小心可能就會栽了跟頭,最好是時刻牢記天上是不會掉餡餅的。
|