智能合约蜜罐-区块链浏览器

一篇以前写的文,现在修改补充了些许,发了上来

前言

智能合约蜜罐相对于互联网蜜罐的目的有着本质的区别:后者着重在于诱导攻击,然后做检测分析,来收集攻击手法与漏洞;而前者更像是一场赌博的骗局,利用种种方法,诱导目标转账进入合约,完成韭菜收割。但是这个蜜罐的名词也是挺恰当的,就也这么叫了。

有趣的是智能合约蜜罐其目标锁定在智能合约开发者,智能合约代码审计人员,略懂区块链技术的信息安全人员(emmmm)

通常而言智能合约蜜罐的欺骗性在于区块链漏洞,逻辑漏洞;又或是赌博合约。
此处介绍的是利用第三方组件导致的智能合约蜜罐。

智能合约蜜罐的奇特组件——区块链浏览器

蜜罐合约地址:0xcEA86636608BaCB632DfD1606A0dC1728b625387

我们可以通过Etherscan浏览器看到该合约的外部交易,内部调用,代码,代码abi等信息

合约.png

智能合约代码分析

先来看关键的智能合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
pragma solidity ^0.4.20;

contract QUESTION
{
//玩家 输入答案字符串开始玩游戏
function Play(string _response)
external
payable
{
//需要该玩家地址不为智能合约地址
require(msg.sender == tx.origin);
//如果答案的sha256哈希=答案hash 并且 传入的押金超过1ether
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
{
//如果答案hash没有被赋值
if(responseHash==0x0)
{
//计算sha256赋值答案hash
responseHash = keccak256(_response);
//赋值题目字符串
question = _question;
//赋值题目发送者的地址,为调用者的地址
questionSender = msg.sender;
}
}

function StopGame()
public
payable
{
//需要调用者等于题目发送者
require(msg.sender==questionSender);
//给调用者转账所有eth
msg.sender.transfer(this.balance);
}

//更新一个新的问题,传入题目字符串,答案hash
function NewQuestion(string _question, bytes32 _responseHash)
public
payable
{
//需要调用者等于题目发送者
require(msg.sender==questionSender);
//更新题目
question = _question;
//更新答案hash
responseHash = _responseHash;
}

//该智能合约fallback函数可以接受钱
function() public payable{}
}

很简单的一个合约,大致就是猜答案:hash符合就给所有钱

这是明显存在漏洞的智能合约:

  1. 在区块链上的交易调用都是可见的,我们可以在区块链浏览器中看到(相当于是默认允许中间人攻击的监听)。在Etherscan中可以直接解密。而在函数的StartGame函数中,response是直接明文传入然后再进行hash存储,即问题设置的答案完全可以被知道
  2. Play()只允许由用户账户调用,而不允许由合约账户调用(require(msg.sender == tx.origin);)。这意味着当答题者发布交易调用Play(),问题的部署者可能会在交易池中一直监听。当监听到Play()交易时,问题部署者用更高的gasprice提交newQuestion函数去修改答案。从而使答题者的答案错误,完成欺骗。但是这种攻击较为繁琐,也具有一定风险。

也有一些奇怪的地方。

疑点1

  • NewQuestion(string _question, bytes32 _responseHash)是用responseHash设置答案。
  • StartGame(string _question,string _response)却是用respon明文设置答案

既然有函数知道用responseHash传入,也就代表着开发者应该是意识到了这个问题。这里是故意为之还是萌新犯蠢?

疑点2

  • if(responseHash==0x0) 其他地方用require作为异常抛出,唯独这里用if做判断,将会不抛出异常。代码风格有点不统一。

Etherscan区块链浏览器上的交易分析

由于这是一个已经收网成功的蜜罐合约,我们队区块链浏览器上已经产生的交易进行分析,看看这个蜜罐钓鱼的过程。

Etherscan上的交易记录:0xCEA86636608BACB632DFD1606A0DC1728B625387

包括:4个外部交易,1个内部交易

4个交易.png

1个调用.png

在2020.02.14去看Etherscan,会发现交易不会直接帮我们解析出调用过程的参数,但是可以用下面介绍了另一种方法得到解析。

按照时间线排序分析

外部交易0xf9f25d… 0x8F1F6FEb78BA90ad003E1B7408caA164aD90830d地址创建合约(创建合约)

外部交易0x41365… 创建者使用交易调用合约函数Startgame(),带上1.03Ether(部署问题和答案,从结果来看,这应该是一个抛饵行为)

外部交易0xcb589e… 受害者使用交易调用合约函数Play(),没有value,即没有带钱(受害者试探,由于没有带钱是不会通过if判断的)

外部交易0x8486f4… 受害者使用交易调用合约函数Play(),带上1.05Ether的钱(受害者上钩,提交了1Ether以上的钱,讲道理按照逻辑这里应该获得合约返回的亲,但是并没有这笔交易)

内部调用0xb68f60… 合约把所有钱转账给了另一个合约0x4B2838d9326bD5126F0573D9b5c71C0626Ab28f2(创建者收网,暴露出了一个合约地址)

我们得到两个地址,我们给他取个别名:
钓鱼者:0x8F1F6FEb78BA90ad003E1B7408caA164aD90830d
还引出了一个奇怪的创建者收网用的智能合约:
钓鱼中间合约:0x4B2838d9326bD5126F0573D9b5c71C0626Ab28f2

总体流程总结如下:

流程1.png

尝试分析中间智能合约

尝试在Etherscan中查看0x4B2838d9326bD5126F0573D9b5c71C0626Ab28f2钓鱼中间合约

发现该智能合约源码不公开,由0x78d39cDf39e80498237BC330e752DaBd8f90AC2f(从转钱结果推断,取名为钓鱼者小号)进行创建,并且该地址对钓鱼中间合约进行了几次调用。就触发了0xcEA86636608BaCB632DfD1606A0dC1728b625387(钓鱼合约)给该中间智能合约转钱。

但是拥有钓鱼合约源码的我们可以知道,只有对合约的Stopgame()函数产生调用,该智能合约才会对外转钱。中间合约肯定调用了钓鱼合约,才导致钓鱼合约会给中间合约转钱

其中一定有一些我们通过区块链浏览器看不见的调用在发生。

而钓鱼合约运行不在我们预期之内,就是因为这些看不见的调用。

寻找缺失的版图

四处寻找有没有能显示这些预计之外的调用的区块链浏览器。

然后就找到了完全暴露的智能合约内部call调用etherchain:https://www.etherchain.org/account/cea86636608bacb632dfd1606a0dc1728b625387

一个合约创建,3个外部交易,4个call调用

真实交易分析

在完整的调用分析前,重新理一下相关地址:

命名 地址
钓鱼者 0x8F1F6FEb78BA90ad003E1B7408caA164aD90830d
钓鱼者创建鱼钩智能合约 0xcEA86636608BaCB632DfD1606A0dC1728b625387
钓鱼者小号 0x78d39cDf39e80498237BC330e752DaBd8f90AC2f
钓鱼中间智能合约 0x4B2838d9326bD5126F0573D9b5c71C0626Ab28f2

真实交易分析

与原先Etherscan交易对比可知,与之前相比多了3个call调用,均是由中间智能合约发起。(交易调用时间顺序整体是下面的条目时间早,但是同一区块的条目,上面的时间早)

下面的两个call调用发生在部署智能合约之后,部署者调用StartGame之前,受害者输入答案之前。应该就是我们之前疏忽的关键调用。

这两个call调用都是属于一笔之前没有出现过的交易0x1754a4ecaecff5e6f3d6fd6384f80e00535fa50318de369b57fbb4dc2495defa中

在Etherscan中查看该笔交易

由于这样看到的交易的input是钓鱼者小号调用钓鱼中间合约的input。我们想看到的两个call调用在虚拟机调用层面,才能查看。

查看Tools&Utilities -> Parity Trace 查看智能合约中函数调用栈的情况。(虽然Etherchain也有Parity Trace,但是Etherscan较为友好,Etherscan作为用的最多的区块链浏览器也是有原因的)

有三个调用栈:

第一个调用是钓鱼者小号0x78d39c…对于中间合约的调用。不管。

第二个调用:

call1

第三个调用:

call2

都对钓鱼合约进行了调用。

关注其中input数据到底调用了什么,利用ethereum-input-decoder解密

  • 从Etherscan中钓鱼智能合约的code中获取abi填入,填入input

第二个调用:调用StartGame,传入问题,答案
call1_de

第三个调用:调用NewQuestion,传入问题,答案hash
call2_de

可以看出,其实真正的答案是先用了StartGame设定,再NewQuestion修改。
之后我们看到的钓鱼者的StartGame调用,由于if(responseHash==0x0)验证不通过,不会对智能合约答案造成影响,只是一个烟雾弹。

同理看一下多出来的上面的那个call调用(在受害者上钩之后):
交易:https://etherscan.io/tx/0xb86f60ff9a075a30aa4008c1cd70ed15f424d141c4de5b3afbadd9d7a18f97b4
函数调用情况:https://etherscan.io/vmtrace?txhash=0xb86f60ff9a075a30aa4008c1cd70ed15f424d141c4de5b3afbadd9d7a18f97b4&type=parity

call3

call3_de

利用中间钓鱼合约完成收网。

真实交易时间线

  1. block:5806406 | 钓鱼小号部署中间合约(钓鱼准备)
  2. block:5873826 | 钓鱼者创建钓鱼合约(准备出发钓鱼)
  3. block:5873890 | 钓鱼小号控制中间合约调用钓鱼合约StartGame()函数,传入问题,sZs答案(钓鱼准备ing)
  4. block:5873890 | 钓鱼小号控制中间合约调用钓鱼合约newQuestion()函数,传入问题,一个答案hash(钓鱼准备ing)
  5. block:5873943 | 钓鱼者使用交易调用钓鱼合约Startgame(),传入问题,带上1.03Ether(烟雾弹+抛饵)
  6. block:5881051 | 受害者使用交易调用合约函数Play(),没有带钱(鱼儿试探)
  7. block:5881054 | 受害者使用交易调用合约函数Play(),带上1.05Ether的钱(鱼儿上钩)
  8. block:5881321 | 钓鱼小号控制中间合约调用钓鱼合约StopGame()函数,撤回钱

至此完美完成了一次智能合约蜜罐攻击,一次利用以太坊最流行区块链浏览器Ethersacn的缺陷,打一个信息差的蜜罐钓鱼

流程图如下:

流程2.png

再读智能合约代码

再回过头去去看看似乎萌新弱鸡的代码,处处用心险恶

之前疑点1:

  • NewQuestion(string _question, bytes32 _responseHash)是用responseHash设置答案。
  • StartGame(string _question,string _response)却是用respon明文设置答案

StartGame用于钓鱼,NewQuestion用于传递真实答案,即使被发现了Etherscan以太坊浏览器存在该不会完全显示call调用的问题也不能被破解。

之前疑点2:

  • if(responseHash==0x0) 其他地方用require这里用if

如果这个地方也用require就会导致用于诱惑别人的钓鱼者StartGame调用报错失败,而引起别人怀疑。

假设是我们

那么假设我们不知道Etherscan有隐藏调用的情况,是否就肯定会上当受骗呢?

其实也不是的,因为智能合约的存储空间,我们也是可以读取的。我们可以直接读取智能合约中的变量值(不管是public还是不是public)从而意识到情况不对。

web3.eth.getStorageAt("0xcEA86636608BaCB632DfD1606A0dC1728b625387", 0, function(x,y){alert(y)});

1
2
3
4
0x00000000000000000000000000000000000000000000000000000000000000d1 //string的长度 string question
0x0000000000000000000000004b2838d9326bd5126f0573d9b5c71c0626ab28f2 //提问者的地址 address questionSender
0x684ff0e88cefc2b7ff23228e02c9a10cc9b5b2e67e12b259a9bca644e19d2b8f //答案hash bytes32 responseHash
0x0000000000000000000000000000000000000000000000000000000000000000

可以发现提问者地址不等于我们所知的调用StartGame钓鱼者的地址

答案hash也与我们的答案hash不符合

小结

  1. Etherscan、BTC.com 比特大陆、Tokenview 上仅涉及ETH转账或 Token 转账的交易,Etherscan不会显示不关乎转账的外部合同的调用。
  2. Etherchain 和 blockchair 可以查看所有调用
  3. 在 Etherscan 查看交易中点击 工具&实用程序 Parity追溯。可以查看交易内部虚拟机层面 智能合约中的调用传递的数据等。(Etherchain中也有这个功能只是包装不到位)

类似合约

从2018年3月份至2018年10月份的都有,最长等待鱼儿上钩的时间有100天

游戏停止,骗币成功:https://etherscan.io/address/0xce6B1AFf0fE66da643D7A9A64d4747293628D667#code

游戏停止,骗币成功:https://etherscan.io/address/0xFf45211eBdfc7EBCC458E584bcEc4EAC19d6A624#code

游戏停止,骗币失败: https://etherscan.io/address/0x4bc53ead2ae82e0c723ee8e3d7bacfb1fafea1ce#code

游戏停止,骗币失败: https://etherscan.io/address/0x3B048ab84ddd61C2FfE89EDe66D68ef27661C0f2

游戏停止,骗币失败: https://etherscan.io/address/0x5ccfcDC1c88134993F48a898AE8E9E35853B2068#code

参考

https://medium.com/quantstamp/exploiting-the-interface-of-etherscan-for-ethereum-attacks-17b72d2897e0
https://paper.seebug.org/671/