一、前言叙述
最近,我通过一些相关文章的阅读发现了几个有趣的DAPP。而深入研究后我准备将分析过程记录下来,并分享给读者。本文包括两则DAPP,第一个通过代码审计发现了其中存在的一处溢出漏洞;第二个为一则答题类游戏,然而深入分析后发现,这款游戏不仅仅只简单的问答接口,然而对于了解以太坊的人是一个陷阱。所以我对其进行了复现、分析操作,并记录下来引以为戒。
二、合约分析与测试
1 蜜罐合约漏洞
我们首先根据代码来对合约进行分析。
//Question and answer honeypot.
pragma solidity ^0.4.20;
contract QUESTION
{
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
函数。此函数需要msg.sender == tx.origin
。这句话的意思就是说我函数的调用方不能是由合约进行中间调用的,而是需要我直接调用。关于msg.sender与tx.origin
的区别,我在前面的文章中讲述过,这里就不再继续详细讲述了。
之后,合约对传入的答案进行判断,并且需要要求传入的value大于1 ether
。responseHash == keccak256(_response) && msg.value>1 ether
。如果传入的内容判定成功,也就是说回答问题正确。那么我们就可以提取出合约中的所有余额。而这函数也是人人可以参与的。
之后我们看StartGame
函数。此函数为游戏开始函数,仅能够调用一次。如何进行调用呢?我们来看看代码。首先代码先对传入的参数进行判断:if(responseHash==0x0)
。这里的含义是什么呢?由于默认情况下,该参数如果不进行赋值操作那么它就等于0x0 。然而倘若问题与答案进行过更新后,那么responseHash==0x0
就变得很低了,几乎没有可能。所以只有第一次调用的时候能够满足这个条件。
进入函数后,合约对三个变量进行赋值。包括问题、答案以及问题发出者。
之后是游戏终止函数:StopGame ()
。
在此函数中,合约判断需要问题调用者为问题提出者。当满足条件时,该合约中的余额将被转到提出问题的人账户中。
最后是更新问题NewQuestion()
。
此函数也需要满足调用者为问题发起者,满足条件后将更新问题与答案。这些函数分析起来并不复杂。我们总结下过程。
游戏规则如下:
- 合约创建者会设置一个问题;
- 任何玩家都可以通过向合约打入不低于 1ETH 的手续费参与作答;
- 若猜中答案,将得到合约里所有的 ETH 作为奖励;
- 若猜不中,无任何奖励,且事先支付的 ETH 会转入该合约
然而同学们有没有发现,由于以太坊的特性,所以如果合约创建者调用了play函数那么由于传入的参数是string,所以在以太坊中可以完美的寻找到其痕迹。但是聪明的蜜罐合约创建者才不会就让用户轻松的取走余额。下面我们来对相关蜜罐进行复现测试。
这里我是以蜜罐合约部署者的身份进行测试
首先将合约版本调整为4.20,并部署合约。
之后,我们在rop上部署合约。
image.png得到合约地址0xb4769ece1229d32cb6e94ec69b8018e42b043640
。
然而,我们并不需要直接进行使用创建者账户直接进行调用play来创建合约问题。我们通过中间合约来进行跳板进行调用。
中间合约如下:
contract middle{
address addr = 0xb4769ece1229d32cb6e94ec69b8018e42b043640;
QUESTION target = QUESTION(addr);
function process() public{
target.StartGame("Who am i?","Pinging");
target.NewQuestion("Who am i?","balbalbalbabal");
}
}
image.png
调用process
函数之后,我们查看:
然而我们对问答合约进行交易查询,发现其并不会对中间合约的调用进行记录。
image.png然而当我们查看参数发现问题已经更新成功。
image.png之后我们再次调用play()
合约。(用于迷惑用户)
之后我们对合约交易详细进行查看:
image.png对第一条内容进行查看:
image.png此时我们刚才的调用被记录下来了,这样就意味着我们已经成功做好了陷阱。然而真正的问题答案是target.NewQuestion("Who am i?","balbalbalbabal");
。而非I am a white hat@@@!
。然而这样会使大批用户不断的投入以太币去进行尝试。
尤其是针对那些对以太坊机制十分熟悉的人来说,他们在查看到string后以为自己成功找到了创建者的漏洞,不曾想到,这也是创建者设计的陷阱。
2 原合约分析
上述内容为我进行本地测试的情况。而原蜜罐合约是如此进行欺骗的。下面我们查看其交易情况:
image.png之后我们查看其第二笔交易:
image.png image.png合约交易将问题设置为:Imagine you are swimming in the sea and a bunch of hungry sharks surround you. How do you get out alive?�
答案设置为:Stop Imagining
。
然而这个操作如同我们上述执行的陷阱操作一样,只是为了对用户进行欺骗。
这个操作也确实有用户上钩了:
image.png这个用户调用了play()
函数:
解码后为:
image.png果然不出所料,他传入了陷阱string,然而并没有得到转账。
三、总结
对不同的用户有不同的蜜罐合约。而此蜜罐针对的就是对以太坊有一定基础的专业人士设计的。本合约具有很浓厚的讽刺韵味,使很多专业人士都陷入到陷阱中来。所以本文为分析者提供了一种分析思路。希望大家以后能够对此类型的合约有所判断,不要轻易的占小便宜。
四、相关链接
-
合约地址:https://etherscan.io/address/0xcEA86636608BaCB632DfD1606A0dC1728b625387
-
测试合约地址:https://ropsten.etherscan.io/address/0xb4769ece1229d32cb6e94ec69b8018e42b043640
-
中间合约地址:https://ropsten.etherscan.io/address/0x1e8d2daf0622b769064c88a9feed9b6badb9bdad
-
交易记录:https://etherscan.io/address/0xcEA86636608BaCB632DfD1606A0dC1728b625387
网友评论