一、前言叙述

最近,我通过一些相关文章的阅读发现了几个有趣的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 etherresponseHash == keccak256(_response) && msg.value>1 ether。如果传入的内容判定成功,也就是说回答问题正确。那么我们就可以提取出合约中的所有余额。而这函数也是人人可以参与的。

之后我们看StartGame函数。此函数为游戏开始函数,仅能够调用一次。如何进行调用呢?我们来看看代码。首先代码先对传入的参数进行判断:if(responseHash==0x0)。这里的含义是什么呢?由于默认情况下,该参数如果不进行赋值操作那么它就等于0x0 。然而倘若问题与答案进行过更新后,那么responseHash==0x0就变得很低了,几乎没有可能。所以只有第一次调用的时候能够满足这个条件。

进入函数后,合约对三个变量进行赋值。包括问题、答案以及问题发出者。

之后是游戏终止函数:StopGame ()

在此函数中,合约判断需要问题调用者为问题提出者。当满足条件时,该合约中的余额将被转到提出问题的人账户中。

最后是更新问题NewQuestion()

此函数也需要满足调用者为问题发起者,满足条件后将更新问题与答案。这些函数分析起来并不复杂。我们总结下过程。

游戏规则如下:

  • 合约创建者会设置一个问题;
  • 任何玩家都可以通过向合约打入不低于 1ETH 的手续费参与作答;
  • 若猜中答案,将得到合约里所有的 ETH 作为奖励;
  • 若猜不中,无任何奖励,且事先支付的 ETH 会转入该合约

然而同学们有没有发现,由于以太坊的特性,所以如果合约创建者调用了play函数那么由于传入的参数是string,所以在以太坊中可以完美的寻找到其痕迹。但是聪明的蜜罐合约创建者才不会就让用户轻松的取走余额。下面我们来对相关蜜罐进行复现测试。

这里我是以蜜罐合约部署者的身份进行测试

首先将合约版本调整为4.20,并部署合约。

之后,我们在rop上部署合约。

得到合约地址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");
    }
}

调用process函数之后,我们查看:

然而我们对问答合约进行交易查询,发现其并不会对中间合约的调用进行记录。

然而当我们查看参数发现问题已经更新成功。

之后我们再次调用play()合约。(用于迷惑用户)

之后我们对合约交易详细进行查看:

对第一条内容进行查看:

此时我们刚才的调用被记录下来了,这样就意味着我们已经成功做好了陷阱。然而真正的问题答案是target.NewQuestion("Who am i?","balbalbalbabal");。而非I am a white hat@@@!。然而这样会使大批用户不断的投入以太币去进行尝试。

尤其是针对那些对以太坊机制十分熟悉的人来说,他们在查看到string后以为自己成功找到了创建者的漏洞,不曾想到,这也是创建者设计的陷阱。

2 原合约交易分析

上述内容为我进行本地测试的情况。而原蜜罐合约是如此进行欺骗的。下面我们查看其交易情况:

之后我们查看其第二笔交易:

合约交易将问题设置为:Imagine you are swimming in the sea and a bunch of hungry sharks surround you. How do you get out alive?答案设置为:Stop Imagining

然而这个操作如同我们上述执行的陷阱操作一样,只是为了对用户进行欺骗。

这个操作也确实有用户上钩了:

这个用户调用了play()函数:

解码后为:

果然不出所料,他传入了陷阱string,然而并没有得到转账。

三、总结

对不同的用户有不同的蜜罐合约。而此蜜罐针对的就是对以太坊有一定基础的专业人士设计的。本合约具有很浓厚的讽刺韵味,使很多专业人士都陷入到陷阱中来。所以本文为分析者提供了一种分析思路。希望大家以后能够对此类型的合约有所判断,不要轻易的占小便宜。

四、相关链接

本稿为原创稿件,转载请标明出处。谢谢。

点击收藏 | 1 关注 | 3
  • 动动手指,沙发就是你的了!
登录 后跟帖