近年来以太坊获得了极大的欢迎,从2016年1月的平均每日交易1万增加到2020年1月的平均50万。同样,智能合约开始发挥更大的价值,使它们成为攻击者的目标,结果用户成为攻击的受害者,损失了数百万美元。为了应对这些攻击,学术界和工业界都提出了许多工具来扫描智能合约中的漏洞,然后再将其部署到区块链上。但是,大多数这些工具仅专注于检测漏洞而不是攻击,更不用说量化或追踪被盗资产的数量。在本文中介绍了Horus,该工具具有基于逻辑驱动和图驱动的交易分析功能对智能合约攻击自动检测和分析。 Horus提供了一种快速的方法来量化和追踪以太坊区块链中被盗资产的流量。本研究对直到2020年5月部署在以太坊上的所有智能合约进行大规模分析,在野发现了1,888个受攻击的智能合约和8,095个易受攻击的交易。最后,还通过对Uniswap和Lendf.me攻击进行深入分析,证明了工具的实用性。
0x01 Introduction
以太坊率先在区块链上引入了图灵完备智能合约的概念,从而彻底改变了数字资产的交易方式,这些是跨区块链执行和存储的程序。但是由于区块链的防篡改性质,一旦部署智能合约就无法再对其进行修改。在撰写本文时,以太坊的市值超过420亿美元,使其成为市场上第二有价值的加密货币,最有价值的以太坊智能合约WETH持有价值超过20亿美元的ether。此外,以太坊在过去的四年中从2016年1月的每日平均交易1万增长到2020年1月的平均50万。价值和知名度的增长会引起滥用,并且缺乏管理机构也导致了一些攻击者开始利用易受攻击的智能合约窃取资金。因此,在过去的几年中,已经出现了许多研究工作和工具来识别智能合约漏洞,但是大多数这些工具仅专注于分析智能合约的字节码,而不是其交易或活动。只有少数人利用事务来检测攻击,而大多数要么需要修改以太坊客户端,要么需要编写大型而复杂的攻击检测脚本。此外,所有这些工具都不允许在检测到被盗资产后直接追踪。
在这项工作中介绍了Horus,这是一个能够根据历史区块链数据自动检测和分析智能合约攻击的框架。除了检测攻击之外,该框架还提供了量化和追踪以太坊账户中被盗资产流的方法。该框架在不修改以太坊客户端的情况下重放交易,并将其执行编码为逻辑fact。然后使用Datalog查询来检测攻击,从而使该框架易于扩展以检测新攻击。通过将检测到的交易加载到图形数据库中,并执行交易图形分析来追踪被盗资金。使用Horus进行了一次纵向研究,涵盖了从2015年8月到2020年5月的整个以太坊区块链历史,包括超过300万个智能合约。正在研究的基本研究问题之一是,这些年来的努力是否减少了在野的攻击。为了量化该问题的答案,首先调查攻击是否持续发生或是否偶尔出现。尽管大多数众所周知的攻击都具有可观的货币价值,但想知道规模较小但持续不断的攻击是否会更频繁地发生并且仍然未被发现。工具的代码和数据可从https://github.com/christoftorres/Horus 公开获得。
0x02 The Horus Framework
在本节中提供有关Horus框架的设计和实现的详细信息,Horus使对以太坊智能合约的攻击进行纵向研究的过程自动化。该框架具有从历史数据中检测和分析智能合约攻击的功能。而且,该框架还提供了追踪以太坊账户中被盗资产流的方法。后者对于研究攻击者的行为特别有用。下图概述了Horus的体系结构。该框架组织为EAT(提取,分析和追踪)模式的,包括三个不同阶段:
(1)提取:提取阶段将交易的列表作为输入,从中提取与执行相关的信息并将其存储为Datalog。
(2)分析:分析阶段将一组Datalog关系查询作为输入,这些查询共同识别对提取的Datalog的攻击。
(3)追踪:追踪阶段检索通过分析获得的攻击者帐户列表,并获取与这些帐户相关的所有交易(包括正常交易,内部交易和代币转移)。之后,创建一个图形数据库,该数据库捕获了来自这些账户的资金流(以太币和代币)。此外,可以用标记帐户列表来扩充数据库,以增强对被盗资产的追踪。
A.提取
提取器的作用是向以太坊客户端请求交易清单的执行追踪,并将其转换为反映其执行语义的逻辑关系。执行追踪由已执行的EVM指令的有序列表组成。该列表中的每个记录都包含诸如已执行的操作码,程序计数器,调用堆栈深度和当前堆栈值之类的信息。不幸的是,执行追踪无法直接从历史区块链数据中获取,它们只能在合约执行期间记录下来。
基于Go的以太坊客户端(Geth)通过debug_traceTransaction和debug_traceBlockByNumber函数提供了调试功能,这能够重播任何给定的过去事务或块的执行,并检索其执行追踪。通过远程程序调用(RPC, Remote Procedure Call)请求执行追踪。修改Geth以加快检索执行追踪的过程的局限性在于,用户不能使用Geth的默认版本,而必须使用修改后的版本,并且每次发布新版本的Geth时,都需要进行更改。本研究决定不修改Geth,而是决定提高通过RPC检索执行追踪的速度。注意到执行追踪包含许多与分析无关的信息,Geth允许注入用JavaScript编写的执行追踪程序。通过这种机制,可以减小执行追踪的大小并提高执行速度,而无需实际修改Geth。例如,JavaScript代码从执行追踪中删除了当前程序计数器、剩余gas和指令的gas成本。而且,代码并没有返回与每个执行指令有关的整个堆栈和内存的完整快照,而是仅返回与该执行指令相关的堆栈元素和内存切片。
上面代码显示了提取器通过迭代执行追踪的每个记录并对相关信息进行编码而生成的Datalog fact列表。尽管大多数fact与低级EVM操作(例如调用)有关,但其他fact与高级别操作有关。例如,erc20转移fact是指在转移代币时发出的ERC-20代币事件“ Transfer”,其中contract表示代币合约的地址,而from和to分别表示代币的发送者和接收者。重要的是要注意,通过修改提取器,分析器和追踪器,可以轻松地修改或扩展此列表以支持与本文提出的方法不同的研究。除了使用默认类型数字和符号外,还定义了自己的三种新类型:160位值的Address,EVM操作码集的Opcode 和256位堆栈值的Value。
动态污点分析:提取器利用动态污点分析来追踪指令之间的数据流。然后,安全专家可以使用数据流fact来检查数据是否从一条指令流向另一条指令。污点是通过源引入的,然后在整个执行过程中传播,最后检查它是否流入接收器。源表示可能引入不可信数据的指令(例如CALLDATALOAD或CALLDATACOPY),而接收器表示敏感位置的指令(例如CALL或SSTORE)。本研究实现了自己的动态污点分析引擎。引擎循环遍历每条执行的指令并检查执行的指令是否为源,然后引擎根据定义的语义通过标记受影响的堆栈值,内存区域或存储位置来引入污点,使用遵循LIFO逻辑的数组结构实现了堆栈。内存存储使用Python字典实现,该字典将内存和存储地址映射到值。污点传播在字节级别执行(请参见上图中的示例)。
执行命令:诸如Parity钱包破解之类的攻击是由两个按特定顺序执行的交易组成的。为了检测这种多事务攻击,框架通过三元组o =(b,t,s)对多事务进行总编码,其中b是块数,t是事务索引,s是执行步骤。执行步骤是一个简单计数器,在事务执行开始时将其重置,并且它的值在每条已执行的指令之后增加。执行步骤与交易索引绑定,而交易索引与区块编号绑定,因此,框架能够准确识别跨多个交易以及整个区块链历史记录的任何指令的执行顺序。
B.分析
工具的第二阶段使用Datalog引擎来分析给定的Datalog关系和查询列表是否与任何先前提取的Datalog fact相匹配。这些Datalog查询可识别恶意交易,这些交易通过利用给定漏洞成功对智能合约进行了具体攻击。框架使用Souffleé作为其Datalog引擎。 Soufflé将Datalog关系和查询编译为高度优化的C ++可执行文件。在下文中,提供了数据日志查询,用于检测重入、Parity钱包破解、整数溢出、未处理的异常和短地址攻击。尽管存在许多智能合约漏洞,但在这项工作中,重点关注被NCC Group列为前十名的智能合约漏洞,并且可以提取被盗的以太币或代币数量或锁定。
重入(Reentrancy):每当合约调用另一个合约,并且在适当更新原始合约中的状态之前,被调用合约都会回调原始合约(即重入调用),就会发生重入。通过识别源自同一调用者并调用同一被调用者的循环调用来检测重入(参见上面代码)。检查两个成功的调用(即结果为1)是否共享相同的交易哈希,调用者、被调用者、ID和分支,其中第二个调用的调用深度比第一个调用的调用深度高。然后,检查是否有两个存储操作具有与第一个调用相同的调用深度,其中第一个操作是SLOAD并发生在第一个调用之前,而第二个操作是SSTORE并发生在第二个调用之后。
Parity钱包破解(Parity wallet hacks):在本文中,专注于检测两个Parity钱包破解。这两种破解都是由于错误的访问控制实施而导致攻击者将自己设置为所有者,从而使他们能够执行关键行动,例如资金转移或合约破坏。通过检查是否存在两个事务t1和t2都包含相同的发送者和接收者来检测第一种Parity钱包破解,其中t1输入的前4个字节与initWallet函数的函数签名(即e46dcfeb)匹配,以及前4个t2输入的字节与execute函数的函数签名(即b61d27f6)匹配(请参见上面代码)。然后,检查是否存在一个call,该调用是t2的一部分,并且在t之后执行t2(即block1 <block2; block1 = block2,index1 <index2)。
以与第一个相似的方式检测第二种Parity钱包破解,除了在这种情况下,检查t2的输入是否匹配kill函数的函数签名(即cbf0b0c0),并且t2包含selfdestruct(参见清上面代码)。
整数溢出(Integer Overflflows):通过检查来自CALLDATALOAD或CALLDATACOPY opcode的数据是否流入arithmetic运算来检测整数溢出,该算术结果与EVM返回的结果不匹配。之后,检查算术运算的结果是否流过SSTORE storage操作和发生erc20_transfer,其中的amount是算术计算中使用的两个操作数之一(请参见上面代码)。请注意,在此工作中仅着重于检测与ERC-20 代币相关的整数溢出,因为已识别出代币智能合约过去常常是整数溢出的受害者。
未处理异常(Unhandled Exception):默认情况下,智能合约执行的内部调用可能仅会取消回退由那些失败的调用引起的状态更改。开发人员有责任检查每个调用的结果并执行适当的异常处理。但是,许多开发人员忘记或决定忽略此类例外的处理,导致资金没有转移给合法所有者。通过检查操作码为“ CALL”的调用是否失败(即结果为0)且mount大于零且结果未在某种条件下使用的情况(参见上面代码)来检测未处理的异常。
短地址(Short Address):ERC-20的transfer和transferFrom函数将目标地址和给定数量的代币作为输入。在执行期间,如果未将事务参数正确编码为32个字节的块,则EVM将在交易输入的末尾添加尾随零,从而将输入字节向左移几个零,因此不希望增加代币数被转移。但是,攻击者可以通过生成以结尾的零结尾的地址并忽略这些零来利用这一fact,然后让另一方(例如,Web服务)发出调用,以调用transfer / transferFrom包含攻击者格式错误的地址。通过首先检查事务输入的前4个字节是否匹配transfer的函数签名(即a9059cbb)或transferFrom的函数签名(即23b872dd)来检测短地址攻击。然后,对于函数transfer,检查输入小于68(即4个字节的函数签名,32个字节的目标地址和32个字节的数量),并进行函数传递检查输入的长度是否小于100(即4个字节的函数签名,32个字节的地址 ,32个字节的目标地址和32个字节的数量),最后检查是否发生了erc20传输(请参见上面代码)。
C.追踪
最后阶段是追踪从攻击者帐户到带有标签的帐户(例如交易所)等被盗资产(例如以太币)。追踪通过从已经通过数据日志分析确定的恶意交易中提取发件人地址和时间戳开始。发件人地址被推定为属于攻击者的帐户。之后,追踪器使用Etherscan的API为每个发件人地址检索其所有正常交易,内部交易和代币转移,并将其加载到Neo4j图形数据库中。依靠Etherscan等第三方服务来检索正常交易,内部交易和代币转移,因为默认的以太坊节点不提供现成的此功能。帐户被编码为顶点,而交易则被编码为这些顶点之间的有向边。区分三种类型的帐户:攻击者帐户,未标记的帐户和标记的帐户。每种帐户类型都包含一个地址。带标签的帐户包含一个类别(例如,交易所)和标签(例如,Kraken 1)。从Etherscan大量的带标签帐户5中获取类别和标签,总共下载了5,437个标签,属于204个类别。区分三种不同类型的交易:普通交易,内部交易和代币交易。每种交易类型都包含交易值,交易哈希和交易日期。代币交易包含代币名称,代币符号和小数位数。事务可以向后或向前加载。向前加载交易能够追踪攻击者将其赃款发送至何处,而向后加载交易能够追踪攻击者从何处接收其资金。
从加载交易者时的攻击者帐户开始,然后递归加载属于同一交易的相邻帐户的交易,最多允许给定的跃点数,不会为交易量超过1,000的帐户加载交易。这是为了避免通过混合服务,交换或游戏智能合约的交易使图形数据库膨胀。此外,当向后加载事务时,仅加载发生在攻击时间戳之前的事务,而当向前加载事务时,仅加载发生在攻击时间戳之后的事务。最后,在完成所有交易后,安全专家可以使用Neo4j自己的图形查询语言Cypher查询图形数据库,以追踪被盗资金的流向。显然追踪仅在一定程度上有效,因为混合服务和交换阻止了进一步的追踪。但是,追踪对于研究攻击者是否将其资金发送到混合器或交易所以及确定正在使用的服务以及扩展的范围仍然很有用。
0x03 Evaluation
数据集:使用以太坊ETL框架来检索每个已部署至区块10 M的智能合约的交易清单。总共收集了697,373,206笔交易和3,362,876份合约。收集到的合约的部署时间戳记为2015年8月7日至2020年5月4日。过滤掉了没有交易的合约,并删除了fas限制为21,000(即不执行代码)的交易。此外,跳过了2016年拒绝服务攻击中的所有事务,因为它们会导致执行时间延长。应用这些过滤器后,最终获得了1,234,197个智能合约的最终数据集,其中包括371,419,070笔交易。在提取阶段,Horus在最终数据集中生成了大约700GB的Datalog fact。
实验设置:所有实验都是使用一台具有64 GB内存的计算机和一个Intel®Core™i7-8700 CPU(具有12个主频为3.2 GHz的内核)运行的,运行64位Ubuntu 18.04.5 LTS。此外使用了Geth 1.9.9版,Soufflé1.7.1版和Neo4j 4.0.3版。
上表总结了本研究结果:发现了1,888个被攻击的合约和8,095个对抗性交易。从这些合约中,有46次使用了重入攻击,Parity钱包被黑客破解有600例,整数溢出攻击有125例,未处理的异常攻击有1,068例,短地址攻击的受害者有55例。对于Parity钱包破解,发现大多数人是在第一种破解中遭到攻击的。还观察到,大多数容易受到整数溢出影响的合约都受到整数下溢的攻击。
0x04 Analysis
在本节中,将通过对评估结果的分析以及对最近的Uniswap和Lendf.me事件的案例研究,来证明Horus在检测和分析现实世界中的智能合约攻击中的实用性。
A.攻击的数量和频率
上图描述了每日攻击的每周平均数与每日部署的每周平均数相比。每周部署合约的高峰期是在2017年底,并且在此高峰期之前发生的每周攻击量最大。而且,大多数攻击似乎发生在同一天的群集中。怀疑攻击者会在区块链中扫描类似的易受攻击合约,并同时利用它们。攻击中的前三个峰值对应于DAO和Parity钱包破解,而最后一个峰值对应于最近的Uniswap / Lendf.me黑入。
上图描述了在评估过程中衡量的每种漏洞类型的恶意交易的发生。虽然重入攻击似乎偶尔会发生,但其他类型的漏洞(如未处理的异常)却被相当连续地触发。总体而言,随着时间的推移,发现越来越少的合约成为短地址攻击和整数溢出的受害者,这表明智能合约在过去几年中变得更加安全,但是,也看到智能合约仍然容易受到众所周知的漏洞(例如重入)的攻击和未处理的异常,尽管可以使用自动化安全工具。上图还说明了每笔对抗交易中被盗(重入和Parity钱包破解 1)或被锁定(未处理的异常和Parity钱包破解 2)的美元金额。通过在攻击时将一个以太币的价格乘以通过Datalog查询提取的以太币来计算USDamount。不提供用于短地址攻击和整数溢出的USD数量,因为这些攻击涉及被盗的ERC-20 代币,并且无法获得这些代币的历史价格。可以看到,就以太坊被盗而言,DAO hack和第一个Parity钱包破解仍然是破坏力最大的两种攻击,分别价值94,812,885美元和107,773,036美元。为了方便读者,标记了DAO攻击或两个Parity钱包破解等著名事件,以证明Horus能够检测到它们。
B.Uniswap和Lendf.me事件的取证分析
Uniswap:2020年4月18日,攻击者能够从Uniswap的ETH-imBTC流动资金池中抽出大量以太币。他们有意选择imBTC代币,因为它实现了ERC777标准,这将使他们能够注册回调函数,从而对Uniswap进行重入攻击。攻击者将从购买用于ETH的imBTC代币开始。之后,他们将在同一笔交易中将购买的imBTC代币的一半交换回ETH。但是,后者将触发攻击者在攻击之前注册的回调函数,从而使他们能够控制并回调Uniswap合约,以在更新转换率之前将imBTC令牌的剩余半数交换为ETH。因此,攻击者可以以更高的转换率交易第二批imBTC代币。有趣的是,Uniswap知道此漏洞,并且在攻击发生的前一年就已公开披露。
使用Horus提取并分析了当天进行的所有交易,确定了总共525个对Uniswap进行再入攻击的交易,累计利润为1,278 ETH(232,239.46 USD)。攻击开始于世界标准时间00:58:19,大约在3.5小时后世界标准时间04:22:58结束。上图描绘了攻击的时间表,显示了攻击者投资的以太币数量以及他们每笔交易的净利润,看到净利润会随着时间下降。单笔交易的最高利润约为9.79 ETH(1,778.72 USD),而最低利润为0.01 ETH(2.73 USD)。攻击者通过购买大约80 ETH的代币来开始攻击,然后逐渐下降到1 ETH。此外,看到利润主要与攻击者正在投资(即用于购买imBTC代币的)以太币数量有关。还看到有时会出现一些波动,攻击者在投资相同数量的以太时会获得更多利润。这可能是由于在攻击过程中其他参与者在Uniswap上交易了imBTC,因此影响了汇率。在最后一步中,使用Horus的追踪功能追踪了攻击者帐户的整个以太流,最多5跳。交易图分析显示,攻击者在不同的交易所上交换了大约702 ETH(被盗资金的55%)的代币:Uniswap上的589 ETH上的WETH,DAI,USDC,BAT和MKR,31 ETH上的Compound和82 ETH在1inch.exchange。后者对于取证特别有用,因为1inch.exchange可以追踪在其平台上执行的交易的IP地址,这在使攻击者匿名时很有用。
Lendf.me:2020年4月19日,攻击者耗尽了Lendf.me的所有流动资金池。与Uniswap黑客类似,攻击者利用Lendf.me交易imBTC的fact,并可以注册一个回调函数以执行重入攻击。攻击者首先将x个数量的imBTC令牌存入Lendf.me的流动资金池。接下来,仍然在同一事务中,他们将存入另一个金额y,但是,这一次触发了攻击者注册的回调函数,该函数将从Lendf.me撤回先前存放的x代币。交易结束时,攻击者在imBTC代币合约上的imBTC余额将为x-y,但Lendf.me合约上的imBTC余额将为x + y,从而将其在Lendf.me上的imBTC余额增加x,而无需实际存款。与Uniswap相似,这里的问题是用户的余额仅在代币传输后才更新,因此更新基于传输前的数据,因此忽略了两者之间的任何更新。
使用Horus提取并分析了当天收集的所有交易,确定了总共46条针对Lendf.me进行重入攻击的交易,以及19条使用被盗的imBTC代币借用其他代币的交易。上图左侧显示了攻击者在攻击过程中存放的imBTC代币数量,以及攻击者通过借入其他代币获得的USD数量。右侧显示了攻击者从Lendf.me借入的USD代币数量。攻击者从12个不同的代币中借入,价值25,244,120.74美元,其中1031万美元仅来自借入WETH。攻击者在世界标准时间00:58:43发起了攻击,并于2小时后在世界标准时间02:12:11停止了攻击。他们开始存储少量的imBTC,并随着时间的推移将其数量增加到291.35 imBTC。借阅开始于世界标准时间01:22:27,结束于世界标准时间03:30:42。最后,使用Horus跟踪攻击者帐户中最多3个跃点的代币流。发现攻击者最初在ParaSwap,Compound,Aave和1inch.exchange上用部分被盗代币交换了其他代币。但是,大约在10小时后的UTC时间14:16:52,攻击者开始将所有窃取的代币发送回Lendf.me的管理员帐户(0xa6a6783828ab3e4a9db54302bc01c4ca73f17efb)。然后,Lendf.me将所有代币移动到恢复帐户(0xc88fcc12f400a0a2cebe87110dcde0dafd2 9f148)中,然后用户可以在其中恢复其代币。
0x07 Conclusion
在过去的几年中,业界已经提出了许多用于以太坊智能合约的自动漏洞检测工具。这就导致了一个问题,即智能合约的安全性是否已经提高。在本文中介绍了可扩展工具Horus的设计和实现,该框架用于执行有关智能合约攻击的检测,分析和追踪的纵向研究。分析了2015年8月至2020年5月的交易,确定了8,095起攻击以及1,888个易受攻击的合约。分析表明,尽管整数溢出之类的攻击的攻击次数似乎有所减少,但尽管有大量新的智能合约安全工具,但仍存在未处理的异常和可重入攻击。最后,还对最近的Uniswap和Lendf.me事件进行了深入分析。