智能合约蜜罐-区块链浏览器
一篇以前写的文,现在修改补充了些许,发了上来
前言
智能合约蜜罐相对于互联网蜜罐的目的有着本质的区别:后者着重在于诱导攻击,然后做检测分析,来收集攻击手法与漏洞;而前者更像是一场赌博的骗局,利用种种方法,诱导目标转账进入合约,完成韭菜收割。但是这个蜜罐的名词也是挺恰当的,就也这么叫了。
有趣的是智能合约蜜罐其目标锁定在智能合约开发者,智能合约代码审计人员,略懂区块链技术的信息安全人员(emmmm)
通常而言智能合约蜜罐的欺骗性在于区块链漏洞,逻辑漏洞;又或是赌博合约。
此处介绍的是利用第三方组件导致的智能合约蜜罐。
智能合约蜜罐的奇特组件——区块链浏览器
蜜罐合约地址:0xcEA86636608BaCB632DfD1606A0dC1728b625387
我们可以通过Etherscan浏览器看到该合约的外部交易,内部调用,代码,代码abi等信息
智能合约代码分析
先来看关键的智能合约代码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
68pragma 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符合就给所有钱
这是明显存在漏洞的智能合约:
- 在区块链上的交易调用都是可见的,我们可以在区块链浏览器中看到(相当于是默认允许中间人攻击的监听)。在Etherscan中可以直接解密。而在函数的StartGame函数中,response是直接明文传入然后再进行hash存储,即问题设置的答案完全可以被知道
- 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个内部交易
在2020.02.14去看Etherscan,会发现交易不会直接帮我们解析出调用过程的参数,但是可以用下面介绍了另一种方法得到解析。
按照时间线排序分析
外部交易0xf9f25d… 0x8F1F6FEb78BA90ad003E1B7408caA164aD90830d地址创建合约(创建合约)
外部交易0x41365… 创建者使用交易调用合约函数Startgame(),带上1.03Ether(部署问题和答案,从结果来看,这应该是一个抛饵行为)
外部交易0xcb589e… 受害者使用交易调用合约函数Play(),没有value,即没有带钱(受害者试探,由于没有带钱是不会通过if判断的)
外部交易0x8486f4… 受害者使用交易调用合约函数Play(),带上1.05Ether的钱(受害者上钩,提交了1Ether以上的钱,讲道理按照逻辑这里应该获得合约返回的亲,但是并没有这笔交易)
内部调用0xb68f60… 合约把所有钱转账给了另一个合约0x4B2838d9326bD5126F0573D9b5c71C0626Ab28f2(创建者收网,暴露出了一个合约地址)
我们得到两个地址,我们给他取个别名:
钓鱼者:0x8F1F6FEb78BA90ad003E1B7408caA164aD90830d
还引出了一个奇怪的创建者收网用的智能合约:
钓鱼中间合约:0x4B2838d9326bD5126F0573D9b5c71C0626Ab28f2
总体流程总结如下:
尝试分析中间智能合约
尝试在Etherscan中查看0x4B2838d9326bD5126F0573D9b5c71C0626Ab28f2钓鱼中间合约
发现该智能合约源码不公开,由0x78d39cDf39e80498237BC330e752DaBd8f90AC2f(从转钱结果推断,取名为钓鱼者小号)进行创建,并且该地址对钓鱼中间合约进行了几次调用。就触发了0xcEA86636608BaCB632DfD1606A0dC1728b625387(钓鱼合约)给该中间智能合约转钱。
但是拥有钓鱼合约源码的我们可以知道,只有对合约的Stopgame()函数产生调用,该智能合约才会对外转钱。中间合约肯定调用了钓鱼合约,才导致钓鱼合约会给中间合约转钱
其中一定有一些我们通过区块链浏览器看不见的调用在发生。
而钓鱼合约运行不在我们预期之内,就是因为这些看不见的调用。
寻找缺失的版图
四处寻找有没有能显示这些预计之外的调用的区块链浏览器。
- 与Etherscan结果类似的Tokenview:https://tokenview.com/cn/eth/address/0xcea86636608bacb632dfd1606a0dc1728b625387
4个外部交易,2个call调用(多了一个外部call调用,但是只是把合约创建又分作外部调用,又分作内部调用)
然后就找到了完全暴露的智能合约内部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…对于中间合约的调用。不管。
第二个调用:
第三个调用:
都对钓鱼合约进行了调用。
关注其中input数据到底调用了什么,利用ethereum-input-decoder解密
- 从Etherscan中钓鱼智能合约的code中获取abi填入,填入input
第二个调用:调用StartGame,传入问题,答案
第三个调用:调用NewQuestion,传入问题,答案hash
可以看出,其实真正的答案是先用了StartGame设定,再NewQuestion修改。
之后我们看到的钓鱼者的StartGame调用,由于if(responseHash==0x0)
验证不通过,不会对智能合约答案造成影响,只是一个烟雾弹。
同理看一下多出来的上面的那个call调用(在受害者上钩之后):
交易:https://etherscan.io/tx/0xb86f60ff9a075a30aa4008c1cd70ed15f424d141c4de5b3afbadd9d7a18f97b4
函数调用情况:https://etherscan.io/vmtrace?txhash=0xb86f60ff9a075a30aa4008c1cd70ed15f424d141c4de5b3afbadd9d7a18f97b4&type=parity
利用中间钓鱼合约完成收网。
真实交易时间线:
- block:5806406 | 钓鱼小号部署中间合约(钓鱼准备)
- block:5873826 | 钓鱼者创建钓鱼合约(准备出发钓鱼)
- block:5873890 | 钓鱼小号控制中间合约调用钓鱼合约StartGame()函数,传入问题,sZs答案(钓鱼准备ing)
- block:5873890 | 钓鱼小号控制中间合约调用钓鱼合约newQuestion()函数,传入问题,一个答案hash(钓鱼准备ing)
- block:5873943 | 钓鱼者使用交易调用钓鱼合约Startgame(),传入问题,带上1.03Ether(烟雾弹+抛饵)
- block:5881051 | 受害者使用交易调用合约函数Play(),没有带钱(鱼儿试探)
- block:5881054 | 受害者使用交易调用合约函数Play(),带上1.05Ether的钱(鱼儿上钩)
- block:5881321 | 钓鱼小号控制中间合约调用钓鱼合约StopGame()函数,撤回钱
至此完美完成了一次智能合约蜜罐攻击,一次利用以太坊最流行区块链浏览器Ethersacn的缺陷,打一个信息差的蜜罐钓鱼
流程图如下:
再读智能合约代码
再回过头去去看看似乎萌新弱鸡的代码,处处用心险恶
之前疑点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 | 0x00000000000000000000000000000000000000000000000000000000000000d1 //string的长度 string question |
可以发现提问者地址不等于我们所知的调用StartGame钓鱼者的地址
答案hash也与我们的答案hash不符合
小结
- Etherscan、BTC.com 比特大陆、Tokenview 上仅涉及ETH转账或 Token 转账的交易,Etherscan不会显示不关乎转账的外部合同的调用。
- Etherchain 和 blockchair 可以查看所有调用
- 在 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/