ContractFuzzer:Fuzzing Smart Contracts for Vulnerability Detection

Posted by 周东宜 on November 1, 2018

Recently by the same author:


智能合约安全

You may find interesting:


2018.1.18区块链论文讨论班通知


Nothing at stake problem of PoS

Paper Name

Introduction

Category

  • Problem Pattern: Less studied problem / Well studied problems

Motivation

Background

文章的研究问题是智能合约模糊测试工具的开发。

由于现在有数以百万计的智能合约部署在各种分布式应用程序中。这些智能合约的安全漏洞已经给用户造成了巨大的财产损失。

文章开发出了一种新的模糊测试工具,用来测试以太坊智能合约的漏洞。使用智能合约的ABI规范生成模糊测试的输入,使用EVM来记录智能合约运行时的行为,并且分析这些行为日志来生成测试报告。该工具能够成功检测出导致了6000万美元损失的 “The DAO”漏洞和导致了3000万美元损失以及1.5亿美元的以太币冻结的parity钱包的漏洞。

Literature Review

智能合约漏洞

  • Delmolino等人【4】指出,即使是很小的智能合同,也可能会有很多逻辑问题。他们列出了一些常见的逻辑问题,如合同从未向其发送方和非加密数据泄露隐私。米勒10审计了智能合同的源代码,并报告了以太坊的呼叫堆栈溢出错误。在2016年经历了持续的攻击之后,这个问题通过以太坊的硬分叉解决了。Atzei
  • 系统地调查了Ethereum智能合同的安全攻击。他们根据自己的特点提供了智能合同漏洞的分类。

智能合约安全

  • Fillâtre【6】提供了一个用于程序验证的工具Why3,它现在可以在可靠的Web IDE【 9】中作为正式的验证后端。当在IDE中编写智能合同时,该工具可以帮助检查整数数组的排列、数学运算的溢出和零错误的划分。
  • Bhargavan 【3】成功地研究了智能合同的安全性和可靠性编译器的可靠性。他们设计了一个工具套件,可以分别将可靠源代码和EVM字节码转换为F*语言编写的程序。
  • Luu et al 【9】设计了一款名为“Oyente”的智能合同的形式化验证工具。Oyente构建了智能合同的控制流图,然后在控制流图上执行符号执行,同时检查是否存在任何易受攻击的模式。
  • Nikolic等人 【12】设计了MAIAN,这是一个符号执行工具,用于推理追踪属性以发现脆弱的智能合同。它指定了基于跟踪属性的三个典型的智能合同漏洞。通过符号执行,MAIAN可以有效地检测出贪婪、挥霍和自杀的合同。与他们的工作不同的是,contract模糊工具执行了模糊测试和运行时的监视来检测在执行过程中发生的漏洞,这可能会产生更少的误报。
  • Hirai【8】使用了一个名为“Isablelle/HOL”的工具 来验证一个名为“Dee”的智能契约,它是Ethereum名称服务实现的一部分。具体地说,这项工作验证了一个只有Deed的所有者才能减少它的余额的oracle。此外,他们还发现,在验证过程中,EVM的实现没怎么配测试过。
  • Echidna【 21】是一种智能合约模糊测试工具,在测试人员的单元测试中定义了测试神谕。它还可以为智能合约的模糊测试生成输入。然而,它没有提供支持智能合同安全性测试的直接API。

漏洞检测的模糊测试技术

任何黑盒的模糊者都是基于语法的,如SPIKE

【2】和Peach【5】。Hanford【 7】和Purdom 【13】在20世纪70年代开始研究基于语法的测试用例。他们展示了基于语法的模糊测试工具,能够有效地检测出被测的应用程序中的漏洞。

Research Niche

研究动机:智能合约的安全漏洞给人们带来巨大的财产损失,而缺少关于智能合约的模糊测试工具的研究。

Work

Research Objectives

工作目标是开发出一个模糊测试工具,为此,需要定义漏洞类型,并对每种漏洞给出测试神谕(Test Oracle)的定义,即定义检测漏洞的方法

Challenge

研究难点是测试神谕(Test Oracle)的定义和实施

Insight

Atzei系统地调查了Ethereum智能合同的安全攻击。他们提供了智能合同漏洞的分类。

Research Summary

定义了7种漏洞类型,以及这7种漏洞的检测方式方法。提出了模糊测试工具ContractFuzzer。

Evaluation

填写内容

Evaluation Summary

作者对测试工具检测出来的漏洞进行了人工确认,计算漏洞检测的准确率。并将contractFuzzer和现有的模糊测试工具oyente进行了对比,分析了两个工具的真假性和假真性及其形成的原因。

Implications

实验告诉我们在使用这个工具的时候,时间戳漏洞和块编号依赖漏洞的检测可能不太准确,一些安全的智能合约可能会被错误地判定为有漏洞的智能合约。

Novelty

Contributions

提出了一个新的智能合约模糊测试工具。

Limitations

工具检测出来的漏洞类型还不全面。

对于某些漏洞的检测准确率还不够高。

Technical Content

图片1

图1描述了ContractFuzzer的合同模糊测试的工作流程,其中主要步骤是标号为0~5的那几个粉红色圆圈。

工具

contractFuzzer工具包括一个离线的EVM工具和一个在线的模糊测试工具。

他们还在以太坊测试链上部署了智能合约。部署的智能合约有两个目的:一个是作为模糊测试的主题,另一个是作为智能合约的输入,即使用合约地址作为参数。

他们还建立了一个web爬虫程序,从EtherScan网站上提取部署在以太坊上的智能合同。他们的爬虫会提取出合约创建代码(智能契约的二进制代码)、ABI接口和这些合约的构造函数参数。

流程

  1. 步骤0:离线EVM基础设施负责EVM的运行,让模糊测试工具能够监控智能合同的执行,从而为漏洞分析提取信息。在线的模糊测试过程从第1步开始
  2. 步骤1:ContractFuzzer工具分析ABI接口和测试中的智能合同的字节码。提取ABI函数的每一个参数的数据类型,以及每个ABI函数中使用的函数的签名。
  3. 步骤2:该工具将执行ABI签名分析,对从Ethereum抓取的所有智能合同进行分析,然后通过他们支持的函数签名来索引智能合同。
  4. 步骤3:根据第1步和第2步的分析结果,该工具将生成符合ABI规范的有效的模糊测试输入,以及步骤3中有效边界的突变输入。注意,ABI不仅可以指定常见的数据类型作为参数还可以用来指定地址和函数选择器。
  5. 从第2步返回的索引智能合约用于为ABIs生成以合约地址作为参数的输入。
  6. 步骤4:该工具将启动模糊测试处理过程,用随机函数调用对ABI接口的生成输入进行轰炸。
  7. 步骤5:工具开始通过分析在模糊测试过程中生成的执行日志来检测安全漏洞。
  8. 模糊测试的过程一直持续到可用的测试时间被消耗完。
  9. 当这个工具完成了所有的智能合同的测试时,整个的模糊测试的活动就结束了
表1:7种漏洞类型及其测试神谕(test Oracle,即检测方式)
漏洞类型 漏洞说明 检测方式 特征(可静态分析) 现象(需要动态分析) 该漏洞可能导致的(不好的)后果 个人理解
1 交易发送不消耗gas(gasless Send()在发送的时候,接收者的fallback()函数也会被唤醒,如果接收者的fallback函数的回退金额很高,那么发送者会得到一个gas耗尽的异常。如果这样的异常没有被检查和适当的传播,一个恶意的发送者可以发送交易而又看起来很无辜的保有他的以太币 在EVM中,send()是作为call()的一个特殊类型实现的。GaslessSend确保EVM中的调用确实是一个send()调用并且send()调用在执行中返回一个ErrOutOfGas错误代码。检查输入是否是0,gaslimit是否是2300就可以验证一个调用是否为send()。 有Send()调用并且 有ErrOutOfGas的错误代码返回。 不用花费gas也可以发送交易 就是说这个gas耗尽的异常没有被检测到
2 异常无序(
Exception Disorder
异常无序是由于
solidity
在异常处理方面是不一致的,依赖于契约之间的调用方式。当一个合约调用另一个合约的函数的时候,它可能会因为不同类型的异常而失败。当这种异常发生时,处理机制是由调用的方式决定的。给定一连串的嵌套调用,其中每个调用都是对合同函数的直接调用,当异常发生时,所有事务将被恢复(包括以太币交易)。

然而,对于一连串的嵌套调用,如果其中至少有一个调用是通过地址上的低级调用方法(
address.call
()、
address.
delegatecall
()或
address.send
())发出的,事务回滚只会在调用函数中停止,并返回
false
。这样,没有任何其他的副作用可以被恢复,也不会传播任何
throw
。在异常处理方面的这种不一致将使调用合约不知道在执行过程中发生的错误。
对于由根调用(或委托)发起的一连串嵌套调用(或委托),如果根调用不抛出异常,而至少有一个嵌套调用抛出异常,则考虑调用链出现了异常无序漏洞。也就是说,异常没有正常的传回到根调用 根调用没有抛出异常但是嵌套调用中至少有抛出一个异常 执行过程中出事了但是根调用无法得知 异常没有被根调用收到
3 重入(
reentrancy
重入bug即开发人员在设计有些函数的时候本来是不打算将它们设计成可重入的类型的。然而,恶意的合约故意以可重入的方式调用这些函数(例如,通过回退(fallback)函数),这样被调用的合约可能会丢失ether。著名的DAO攻击就是利用了这样的漏洞导致了6000万美元的损失。 重入测试命令的定义基于两个子命令。第一个子命令是ReentrancyCall,检查A函数的调用在起源于A的调用链中是否出现超过了1次。第二个子命令是CallAgentWithValue,检查三个条件:(1)有一个call()调用使用超过0的价值(发送ether的数目)(2)调用者有足够的气体津贴来执行更复杂代码的执行(例如:不是发送或者转出)(3)调用call()的调用者是我们的工具提供的代理合约,而不是被测试的合同指定的账户。重入测试命令这样定义ReentrancyCall∧CallAgentWithValueReentrancy漏洞这种时候会出现:有一个会通过一连串的调用回调自己的调用,且该调用通过有足够的gas的Call(),发送以太币给我们的代理合约,这样我们的代理合约通过这种fallback函数可以再次运行重入调用。换句话说,只有当它能够成功地对目标合约进行重入攻击时,ContractFuzzer标记重入漏洞。 调用者是攻击代理。调用者有足够的气体津贴来执行不是发送或转出的更复杂的代码执行。 a调用在起源于a调用的调用链中超过1次 可能被恶意盗取以太币 恶意的攻击者可以以某种可重入的方式来调用某些函数,可能导致被调用的函数丢失以太币。
4 时间戳依赖(
Timestamp Dependency
当一个智能合约使用区块时间戳作为运行一个重要的操作的条件的一部分(例如发送以太币)或者作为生产随机数的传入参数时,就产生时间戳依赖漏洞。在像区块链这样的分布式系统中,矿工可以自由地在短时间内设置一个块的时间戳,少于900秒24。然而,如果一个智能的契约基于时间戳来传输以太,那么攻击者就可以操纵块时间戳来利用这个漏洞。 TimestampDependency的测试命令定义了三个子命令。第一个是TimestampOp,检查当前合约的调用是否在执行期间调用时间戳操作码(opcode)。第二个子命令是SendCall,检查一个调用是否是一个给其他合约发送以太币的send()调用。第三子命令是EtherTransfer,检查call()的值(转账的ether数量)是否大于0.TimestampDependency被定义为TimestampOp∧(SendCall∨EtherTransfer)也就是说,TimestampDependency发生在:当前合约使用块时间戳而且在执行期间转移了以太币。 使用了时间戳的操作码。调用了send()。给其他账号转了钱。【但是这样是不准确的,因为要时间戳和转钱有关联才是时间戳依赖】 可能被恶意利用 使用时间戳作为运行的重要操作的条件的一部分,比如转账多少,转给谁,转账条件啥的。
5 块编号依赖(
Block Number Dependency
块编号依赖像时间戳依赖一样,当一个智能合约使用block.number作为执行一个重要操作的条件的一部分的时候(例如发送以太币)或者作为生产随机数的传入参数时就会发生。区块编号、时间戳是矿工可以操纵的变量,所以它们不能被用作熵的来源,因为矿工们可能会有恶意的动机。此外,由于EVM的执行机制、或者由于区块链的透明性,甚至使用将block.num作为参数来生成随机数的block.blockhash()函数也是容易被攻击的。 BlockNumDependency的测试命令和TimestampDependency测试命令一样定义了三个子命令。第一个是BlockNumOp,检查当前合约的调用是否在执行期间调用区块编号操作码(opcode)。第二个子命令是SendCall,检查一个调用是否是一个给其他合约发送以太币的send()调用。第三子命令是EtherTransfer,检查call()的值(转账的ether数量)是否大于0.BlockNumDependency被定义为BlockNumOp∧(SendCall 使用了区块编码的操作码。调用了send()。给其他账号转了钱。【但是这样是不准确的,因为要区块编码和转钱有关联才是时间戳依赖】 可能被恶意利用 使用区块编号作为运行的重要操作的条件的一部分,比如转账多少,转给谁,转账条件啥的。
6 危险的委托调用(
Dangerous DelegateCall
委托调用(
delegatecall
)和信息调用(
call
)完全相同,只是目标地址的代码是在调用合约的上下文中运行的。这意味着合约可以在运行时动态地从不同的地址加载代码,而存储仍然只关联调用合约的。这是在
solidity
中实现重用代码库的方法。然而,当委托调用的参数被设置为
msg.
data

,
攻击者可以使用一个函数的签名来制作
msg.data
,这样攻击者可以指使受害合约调用它所提供的任何功能。比如说
parity wallet
所遭受的第一轮攻击就是这种类型,损失了
30
百万美元。
DangerDelegateCall检查在当前合约的执行中是否有委托调用被调用,而且被委托调用调用的函数是从初始调用的输入(比如msg.data)中获取的。换句话说,该测试命令检查的是是否被测试的合约会呼叫一个委托调用,其目标函数是被潜在的攻击者提供的。 执行中有委托调用的函数,被委托调用的函数式从初始调用的输入中获取的。 损失以太币。 委托调用的时候,调用的函数是根据传入的值来确定的,那么调用者就可以调用被调用的函数的所有函数,即可以控制这个合约,这很危险,被调用的合约的钱可能就这样全被转走。
7 冻结以太币(
Freezing Ether
这些合约可以接收以太币和通过委托调用发送以太币到其他地址,但是他们自己没有任何发送以太币到其他地址的函数。也就是说,他们单纯的依赖于其他合约的代码(通过委托调用)来发送以太币。当提供以太币操作码的合约执行自杀或自毁操作时,调用合约没有办法发送以太币,所有的以太币都被冻结。
Parity wallet
遭受到的的第二轮攻击就是因为许多钱包合约只依赖于
parity
库来操纵他们的
ether
。当
parity
库通过初始化被改成一个合约,然后黑客杀死了这个合约,所有的依赖于
parity
库的以太币都被冻结了
FrezingEther
的测试命令检查合约是否可以接受以太币,并且在执行的过程中使用委托代理,但是在合约中没有
transfer
/send/call/suici
de
的代码可以让他转移自己的以太币到其他地址。换句话说,
FrezingEther
会将在执行过程中余额大于
0
但是没有办法用自己的代码来传输比特币(例如:
call

transfer

suicide
)的合约标记为脆弱的。
合约可以接收以太币,可以使用委托代理,但是合约中没有
transfer
/send/call/suici
de
的代码可以让他转移自己的以太币到其他地址
冻结以太币。 这种合约
可以接收以太币,可以通过委托调用发送以太币到其他地址,但是自己没有任何发送个以太币到其他地址的函数

Future Work

  1. 研究对其他类型的漏洞的test oracle,比如oyente工具可以检测的事务性依赖。
  2. 完善漏洞的测试神谕,比如时间戳漏洞和块依赖漏洞。