译者:CDra90n@SecQuan

来源:https://mp.weixin.qq.com/s/zKv3wKEXRT8CgOnVHOXi0Q

在过去的几年中一些智能合约被发现易受攻击从而被攻击者利用。然而一种更积极主动的新方法被使用的趋势似乎正在上升,即攻击者不再寻找易受攻击的合约,相反他们试图通过部署脆弱外表下包含隐藏陷阱的合约来诱使受害者落入陷阱。这类合约通常被称为蜜罐(Honeypot)。本文通过调查蜜罐的流行程度、行为以及对以太坊区块链的影响,定义了蜜罐的分类以及构建了工具-蜜獾(HONEY-BADGER),一个使用符号执行和定义明确的启发式算法来检测智能合约蜜罐的工具。

一、以太坊相关知识

1、智能合约

现代区块链如以太坊,旨在通过所谓的智能合约将整个系统的权力去中心化。智能合约是通过以太坊虚拟机(EVM)跨区块链存储和执行的程序。EVM是一个纯粹基于堆栈的虚拟机,它支持图灵完备(Turing-computable)的操作码指令集。智能合约通过事务进行部署、调用和从区块链中删除。EVM的每一次操作都要花费一定数量的燃料(gas)。当超过分配给事务的燃料总量时,程序执行将被终止,其影响将被逆转。与传统程序相比智能合约是不可变的。开发人员通常用高级语言编写智能合约代码,并将其编译成EVM字节码。在撰写本报告时,Solidity是在以太坊中开发智能合约最流行的高级语言。

以太坊曾面临过多次关于智能合约的毁灭性攻击。2016年的DAO黑客攻击和2017年的平价钱包黑客攻击(Parity Wallet Hack),其合计损失超过4亿美元。作为对攻击的回应,学术界提出了大量工具以允许在区块链上部署之前扫描合约中的漏洞。但也因此,攻击者可以采取更积极主动的方法,引诱受害者进入陷阱。换句话说,如果我能让受害者来找我,为什么我要花时间去寻找受害者呢?这种新型欺诈行为被以太坊社区称为“蜜罐”。蜜罐是一种设计上似乎存在明显缺陷的智能合约,它允许任意用户从合约中抽出以太币(Ether,以太坊的密码货币),假定用户事先向合约转让了一定数量的以太币。然而一旦用户试图利用这个明显的漏洞,就会出现第二个尚未被发现的陷阱,从而阻止以太币的成功排放。其想法是用户只关注明显的漏洞,而不考虑第二个漏洞可能隐藏在合约中的可能性。与其他类型的欺诈类似,蜜罐之所以起作用是因为人类往往很容易被操纵,人们并不总是能够用自己的贪婪和假设来量化风险。

2、以太坊虚拟机

以太坊区块链允许用户通过向网络提交事务来创建和调用智能合约。这些交易由所谓的矿工处理。矿工在验证块时使用一个专用虚拟机执行智能合约(表示为以太坊虚拟机)。EVM是一个基于堆栈的、较少寄存器的虚拟机,运行低水平字节代码,由一组操作码指令集表示。为了保证合约终止从而防止矿工陷入无止境的执行循环,引入了燃料的概念。它将成本与每条指令的执行相关联。在发出交易时发件人必须说明他或她愿意支付给矿工的燃料数量,以执行智能合约。

3、以太坊智能合约查询工具

Etherscan 是一个收集和显示区块链特定信息的在线平台。它作为一个区块链查询工具,让用户可以轻松地查找各个区块、事务和智能合约的内容。除了它的查询功能之外,它还提供多种服务。其中一个服务是智能合约创建者可以发布他们的源代码,并确认存储在特定地址下的字节码是编译指定源代码的结果,它还为用户提供了在智能合约上留下评论的可能性。

二、以太坊蜜罐

1、蜜罐

蜜罐是一种智能合约,它假装向任意用户(受害者)泄露其资金,前提是用户向其发送额外的资金。然而用户所提供的资金将被困住,最多只能被蜜罐创建者(攻击者)检索到他们。

蜜罐一般分为三个阶段,如图:

null
null

1.攻击者部署一个看似脆弱的合约,并以资金的形式提供诱饵;

2.受害人试图通过转让至少所需数额的资金来利用合约,但没有做到;

3.攻击者撤回诱饵以及受害人在企图利用漏洞时损失的资金。

攻击者不需要特殊的能力来设置蜜罐。事实上攻击者具有与普通以太坊用户相同的功能。他或她只需要必要的资金来部署智能合约和放置诱饵。

2、蜜罐的分类

共提取了8种不同的蜜罐技术。在分类中组织了不同的技术(见下表),其目的有两个:(I)供使用者参考,以避免普通以太坊间蜜罐;(二)作为研究人员的指南,促进开发欺诈智能合约的检测方法。将不同的技术根据它们的操作分成三个不同的类别:

null
null

A、EVM

(1)余额错乱(Balance Disorder)

null
null

图中的合约描述了一个使用们称之为余额错乱技术的蜜罐的例子。函数multiplicate显示如果此函数的调用者包含大于或等于智能合约的当前余额的值,合约的余额(this.balance)和在事务处理中包含在此函数调用的值(msg.value)将被传送到任意地址 。因此天真用户将相信他或她所需要做的所有事情是用高于或等于当前余额的值来调用该函数,作为回报他或她将获得“投资”价值加上合约中包含的余额。然而如果用户试图这样做,他或她很快就会意识到第5行没有被执行,因为第4行上的状况不存在。这是因为在实际执行智能合约之前,余额已随着交易值的增加而增加。值得注意的是:1)如果合约目前的余额为零,则可以满足第4行的条件,但用户将不会被激励去利用合约;2)第5行的添加this.balance+msg.value仅用于使用户进一步相信在执行之后才更新余额。

B、Solidity编译器

(1)继承错乱(Inheritance Disorder)

null
null

Solidity通过is关键字来进行继承。当一个合约从多个合约继承时,在区块链上只创建一个合约,并且将所有基本合约的代码复制到创建的合约中。上图显示了蜜罐的一个例子,其利用了表示为继承错乱的技术。乍看似乎这个代码没什么特别的,有一个从Ownable合约中继承下来的KingOfTheHill合约。但是注意两件事:1)函数takeAll只允许存储在变量所有者owner中的地址提取合约的余额;2)可以通过使用大于当前jackpot的消息值(第12行)调用回退函数fallback来修改所有者变量。现在如果用户试图调用函数以将自己设置为所有者则事务将成功。但是如果他或她后来试图收回余额,交易会失败。这是因为在第9行声明的变量所有者并不与在第2行声明的变量所有者相同。用户假设第9行的所有者将被第2行的所有者覆盖,但事实并非如此。Solidity编译器将这两个变量视为不同的变量,因此在第9行向所有者写入将不会导致修改在合约Ownable中定义的所有者owner。

(2)跳过空字符串文本(Skip Empty String Literal)

null
null

上图所示的合约允许用户通过向合约的函数invest发送最低金额的以太币来进行投资。投资者可以通过调用divest来撤回他们的投资。现在如果仔细看一看代码,就会意识到似乎没有什么可以阻止投资者将高于原投资金额的金额剥离出去,这样天真的用户就会相信可以利用函数divest。但是此合约包含一个称为跳过空字符串文本的错误。作为函数loggedtransfer(第14行)的参数而给出的空字符串文本将被 Solidity编译器的编码器跳过。这样做的效果是,该参数后面所有参数的编码都向左移动32个字节,因此函数调用参数msg接收目标值,而target被赋予currentowner的值,最后currentowner接收默认值零。因此函数loggedTransfer最终执行向CurentOwner(而不是target)的转移,实质上将所有从合约中divest的尝试都转移给所有者。用户试图使用智能合约的明显漏洞从而有效地将投资转移到合约所有者。

(3)类型推导溢出(Type Deduction Overflow)

null
null

在Solidity中,当将变量声明为var类型时,编译器使用类型推导从分配给变量的第一个表达式中自动推断最小的可能类型。上图中的合约描述了蜜罐的示例,该蜜罐利用了类型推导溢出的技术。起初合约表明用户可以将投资翻一番。但是由于类型仅从第一个赋值中推导出来,第7行的循环将是无限的。变量i的类型为uint-8,此类型的最高值为255,小于2*msg.value。因此永远不会达到循环的停止条件。如果变量multi小于amountToTransfer则仍然可以停止循环。这是可能的,因为amountToTransfer被分配了multi的值,这将小于由于在第8行处发生的整数溢出导致的amountToTransfer,其中i乘以2。一旦循环退出合约将一个值执行转移给调用者,尽管金额最多为255wei(最小的以太币面额,其中1ether=10^18wei),因此远小于最初用户投资的价值。

(4)未初始化结构(Uninitialised Struct)

null
null

Solidity提供以结构形式定义新数据类型的方法。它们将多个命名变量组合在一个变量下,并为Solidity更复杂的数据结构奠定了坚实的基础。上图给出了一个未初始化结构蜜罐的示例。为了收回合约余额,合约要求用户进行最小赌注,并猜出存储在合约中的随机数。但是任何用户都可以轻松地获得随机数的值,因为存储在区块链上的每一个数据都是公开可用的。第一个想法是合约创建者犯了一个常见的错误即假设声明为private的变量是秘密的。无辜的用户只需从区块链读取随机数并通过下注并提供正确的数字来调用函数guessNumber。之后合约创建了一个结构,该结构似乎跟踪用户的参与。然而结构未通过new关键字正确初始化。因此Solidity编译器将包含在结构(Player)中的第一个变量的存储位置映射到包含在合约(randomNumber)中的第一个变量的存储位置。因此用调用者的地址覆盖随机数,从而使第14行的条件失败。值得注意的是蜜罐创建者知道用户可能试图猜测覆盖的值。因此创建者将数字限制在1到10之间(第10行),这大大降低了用户生成满足此条件的地址的机会。

C、Etherscan区块链查询工具

(1)隐藏状态更新(Hidden State Update)

null
null

除了正常的事务外,Etherscan还显示所谓的内部消息(internal messages),这是来自其他合约而不是用户帐户的事务。但是出于可用性的考虑,Etherscan不显示包含空事务值的内部消息。上图中的合约是蜜罐技术的一个例子,表示为隐藏状态更新。在这个例子中余额被转移给那些能够猜出用于计算存储散列正确值的人。天真的用户将假定passHasBeenSet设置为false,并尝试调用不受保护的SetPass函数,该函数允许用已知值重写哈希,前提是至少有一个以太币转到合约。当分析Etherscan上的内部消息时,用户将找不到调用PassHasBeenSet函数的任何证据,因此假设passHasBeenSet被设置为false。但是蜜罐创建者可能会误导Etherscan执行的筛选,以便从另一个合约调用函数PassHasBeenSet并使用空事务值。因此,只查看EtherScan上显示的内部消息,不知情的用户就会认为变量设置为false,并自信地将以太币传输到setpass函数。

(2)隐藏转移(Hidden Transfer)

null
null

Etherscan提供了一个web接口,它显示了经过验证的智能合约的源代码。验证表示提供的源代码已成功编译到关联的字节代码。Etherscan会在HTML文本区域textarea元素中显示源代码,其中较大的代码行仅显示到一定的宽度。因此代码行的其余部分将被隐藏,仅通过水平滚动来单独可见。上图中的合约利用了这个“特性”,在函数withdrawAll的第4行引入了一长串空格,有效地隐藏了下面的代码。如果函数的调用方不是合约所有者,则隐藏代码将抛出从而防止后续的余额转移到函数的任何调用方。还请注意第4行,其中块号必须大于5,040,270。这将确保蜜罐在主网络上部署时会窃取资金。由于测试网络上的块号较小,因此在这样的网络上测试此合约将把所有资金转移给受害者,使他或她认为合约不是一个蜜罐。这种类型的蜜罐标记为隐藏转移。

(3)稻草人合约(Straw Man Contract)

null
null

在上图中提供了一个蜜罐技术示例,将其表示为稻草人合约。乍一看合约第14行的现金提取功能似乎容易受到重入攻击(reentrancy attack)。为了能够使用重入攻击,用户必须首先调用Deposit函数并转移最小数量的以太币。最后用户调用CashOutT函数,该函数执行对TransferLog中存储的合约地址的调用。如图所示名为log的合约应该充当记录器logger。然而蜜罐创建者并没有用包含所示记录器合约字节码的地址初始化合约。相反它是用另一个地址初始化的,该地址指向一个实现相同接口的合约。如果函数AddMessage是用字符串“CashOut”调用的且调用者不是蜜罐创建者,则抛出一个异常。

因此,用户执行的重入攻击总是失败的。另一种选择是在转移余额之前使用委托代理(delegatecall)。委托代理允许被调用方合约修改调用方合约的堆栈。因此攻击者只需将堆栈中包含的用户地址与自己的地址交换,当从委托代理返回时,余额将转移到攻击者而不是用户的地址。

三、蜜獾

1、设计概述

null
null

上图为蜜獾的总体架构和分析管线。蜜獾接受EVM字节码作为输入,并作为输出返回它检测到的不同蜜罐技术的详细报告。蜜獾由三个主要组成部分组成:符号分析、现金流量分析和蜜罐分析。符号分析组件构造控制流图(CFG),并象征性地执行其不同的路径。符号分析的结果随后传播到现金流量分析部分和蜜罐分析部分。现金流量分析使用部分符号分析的结果来检测合约是否能够接收和转移资金。最后利用启发式方法和符号分析的结果对本文研究的蜜罐技术进行了检测。这三个组件中的每一个都使用Z3-SMT求解器检查约束的可满足性。

2、结果

null
null

(1)有效性

上图显示了每个蜜罐技术成功的、中止的和活跃的数量。结果表明,跳过空字串文本技术是最有效的蜜罐技术,成功率约为78%,而隐式转移技术的成功率仅为33%成功率 。蜜罐的总体成功率似乎很低,约为37%,而总体流产率似乎相当高,约为54%。在撰写本报告时,仅10%的分析蜜罐仍处于活跃状态。下图显示了按照蜜罐技术分的蜜罐每月部署的数量。第一个已经部署的蜜罐技术是2017年1月的一次隐藏状态更新。2018年2月是部署蜜罐的高峰期,总共部署了66个蜜罐。每种技术每月部署的蜜罐最多的是隐藏状态更新,2018年6月共有36个。平均每月部署7个蜜罐。在分析中,最快的第一次尝试发生在蜜罐部署后7分钟37秒,而 最长的时间直到部署后142天才发生。蜜罐平均需要9天、中间值16小时才能被利用。有趣的是,大多数蜜罐(约55%)在部署后的前24小时内被利用。

null
null

(2)活跃度

蜜罐的寿命定义为从蜜罐部署到蜜罐中止的那一段时间。本文发现蜜罐的最短寿命是5分钟25秒,最长的寿命约为322天。蜜罐的平均寿命约为28天,而中位数约为3天。然而,在大约32%的情况下,蜜罐的寿命仅为1天。还分析了攻击者将资金存放在蜜罐内的时间,方法是测量受害者第一次企图剥削到攻击者撤走所有资金之间的时间。最短的时间是在受害者掉进蜜罐后的4分28秒。最长的时间大约是100天。平均而言,攻击者会在受害者掉进蜜罐后7天内收回他们所有的资金。然而,在大多数情况下,攻击者将资金存放在蜜罐中最多一天。有趣的是,282个蜜罐中只有37个被销毁,这意味着攻击者在蜜罐内调用一个函数,即调用 SELFDESTRUCT操作码。换句话说,171个蜜罐处于某种“僵尸”状态,它们仍然活着(即没有被摧毁),但没有活动(即它们的余额为零)。通过对37个被毁的蜜罐的分析,发现19个蜜罐在成功后被毁,18个蜜罐在未成功后被毁。

(3)行为

本文的方法将240个地址归类为受害者。在71%的案件中,蜜罐只能诱捕一名受害者。然而,在一个案例中,97名受害者仅被一个蜜罐所困。有趣的是,240个地址中有8个属于多个蜜罐,其中一个地址甚至成了4个不同的蜜罐的牺牲品。还发现53名攻击者至少部署了两个蜜罐,而有一个攻击者则部署了八个不同的蜜罐。值得注意的是,53名攻击者中有42名只是部署了一种特定蜜罐类型的副本,而其余11名部署了不同类型的蜜罐。在282个检测到和人工确认的蜜罐中,有87个(约占31%)包含了对Etherscan的评论。手工分析这些评论发现大多数评论实际上都是警告,说明合约可能是蜜罐。此外,下图显示,“蜜罐”一词是社区用来描述这种智能合约的最流行的术语。令人惊讶的是,在87个被评论的蜜罐中有20个成功了。16个在发表评论前获得成功,4个在发表评论后获得成功。有趣的是,21个蜜罐在发表评论后流产。最快的中止是在评论后33分钟57秒,而最长的中止是在评论37天后进行的。最快的中止仅在评论后33分钟57秒执行,而最长的中止则在评论后37天执行。最后,在用户发表评论后,攻击者平均花了6天的时间和中位数为22小时的时间来中止他们的蜜罐。

null
null

(4)多样性

本文使用标准化的莱文斯坦距离距离(Levenshtein)来度量特定蜜罐技术各个实例之间字节码的相似性。下表概述了每个蜜罐技术在最小、最大、平均和模式方面的相似性。通过观察,除了tdo之外几乎每种技术的字节码相似性都有很大的差异。例如,在隐藏状态更新蜜罐的情况下测量的最小相似度为11%,最大相似度为98%。这表明,即使两个蜜罐拥有相同的技术,他们的字节码可能仍然非常多样化。

null
null

(5)盈利状况

下表列出了每个蜜罐技术的盈利能力。盈利能力计算为收到的金额-(花费的金额+交易费用)。没有为tdo提供任何值,因为对于分析过的单个TP(true positive),攻击者花费的交易费高于攻击者从受害者那里获得的金额。利用隐藏状态更新蜜罐获得的利润有最小也有最大的,最小的是0.00002以太币,最大的是11.96以太币。最赚钱的蜜罐是稻草人合约蜜罐,平均值为1.76以太币,而利润最少的蜜罐是未初始化结构性蜜罐,平均值为0.46以太币。通过蜜罐获得了257.25个以太币的利润,其中171.22个是完全通过隐藏状态更新蜜罐制造的。然而,加密货币的汇率是非常不稳定的,因此它们的美元价值在日常基础上可能有很大的变化。例如,虽然11.96以太币是最大利润,但其实际价值在提取时仅为500美元。因此本文发现以美元计的最大利润实际上是3.10987以太币的蜜罐,因为在撤出时价值为2,609美元。在282个蜜罐中应用这一方法,总利润为90,118美元。

null
null

3、评价

本文具有可用源代码的智能合约的数量相当小,在编写时Etherscan上只有5万份具有源代码的合约。这突出了能够在字节码级别检测蜜罐的必要性。不幸的是在检测某些蜜罐技术时是非常具有挑战性的。例如虽然在源代码级别检测继承错乱非常简单,但是在字节码级别检测它相当困难,因为有关继承的所有信息在编译过程中丢失,在字节码级别上不再可用。某些信息仅在源代码级别而不是字节码级别上可用,着迫使蜜獾利用字节码中其他不太精确的信息来检测蜜罐技术。然而如第5节所示,这种方法降低了检测的精度,并引入了一些判断失误。最后蜜獾的另一个局限性是目前它仅限于本文描述的八种蜜罐技术的检测,没有检测到其他蜜罐技术。然而设计蜜獾时考虑到了模块性,这样就可以轻松地扩展蜜罐分析组件并以此检测更多蜜罐技术。