(ASE2018)ContractFuzzer: Fuzzing Smart Contracts for Vulnerability Detection 解析

(ASE2018)ContractFuzzer: Fuzzing Smart Contracts for Vulnerability Detection 解析

  • 前言
  • 一 摘要及主要贡献
      • 摘要
      • 贡献
  • 二 智能合约漏洞及其对应的test oracle[^1]
    • 2.1 Gasless Send
      • 2.1.0 oracle
    • 2.2 Exception Disorder
      • 2.2.0 oracle
    • 2.3 Reentrancy
      • 2.3.0 oracle
    • 2.4 Timestamp Dependency
      • 2.4.0 oracle
    • 2.5 Block Number Dependency
      • 2.5.0 oracle
    • 2.6 Dangerous DelegateCall
      • 2.6.0 oracle
    • 2.7 Freezing Ether
      • 2.7.0 oracle
  • 四 ContractFuzzer工具介绍
    • 4.1 总体概括
    • 4.2 Static Analysis(静态分析阶段)
      • 4.2.1 针对合约池中ABI函数签名的静态分析
      • 4.2.2 针对待测智能合约的静态分析
    • 4.3 Fuzz输入用例的生成
    • 4.4 插桩EVM
      • 4.4.1 Call/DelegateCall/Send
      • 4.4.2 Opcode
    • 4.5 漏洞分析和报告
  • 五 实验结果分析
  • 六 相关工作
  • 总结

前言

这是我发布的第一篇论文解析,这篇论文也是智能合约模糊测试方向最早的一篇顶会论文。我尽量按照自己的理解把它概括出来,如果我的理解或者翻译有问题欢迎读者在评论区批评指正。
原文链接:论文
源码链接:github-ContractFuzzer

一 摘要及主要贡献

摘要

ContractFuzzer基于智能合约的ABI规范来生成模糊测试所需的输入测试用例;定义了test oracle去检测安全漏洞;对EVM进行插装用来读取智能合约的运行时行为。

贡献

  1. 提出了第一个用于在以太坊平台上检测智能合约漏洞的模糊测试框架
  2. 提出了一组能够精确检测真实世界的智能合约漏洞的test oracle
  3. 在6991个真实世界以太坊智能合约上发现了至少459个漏洞,包括DAO和Parity Wallet

二 智能合约漏洞及其对应的test oracle1

test oracle相当于对于一种漏洞类型的具体判别方法。

2.1 Gasless Send

当合约A调用合约B时,合约B的fallback函数会被调用。但该函数能够使用的Gas是固定的,因此当所需的Gas超过限制时,合约A会收到一个ErrOutOfGas异常。如果这个异常没有被捕获或者没有被正确传递,那么恶意的合约A就可以非法占有合约B应得的Ether。

2.1.0 oracle

因为合约调用函数send()本质上是一种特殊类型的call(),所以oracle确保合约确实是被send()调用的且send()确实在执行期间返回一个名为ErrOutOfGas的错误代码。为了检测合约调用是否为send(),只需验证这个调用的输入是否是0且它的Gas限制是否是2300。

2.2 Exception Disorder

由于Solidity语言在合约调用方式不同时具有不同的错误处理方式。因此在一个嵌套的调用链中,如果所有的函数调用都是对合约函数的直接调用,那么当错误发生时,所有的操作都会回滚;但如果至少有一个调用是通过合约地址来进行的低阶调用(address.call(), address.delegatecall(), address.send())。那么当错误发生时,整体的事务回滚会在这个调用处停止,且错误抛出的异常也不会被正确得向外抛出。

2.2.0 oracle

对于一条嵌套的调用链,如果其中至少有一个调用抛出了异常,而这条调用链的根调用没有抛出异常,那么就认为这个合约有Exception Disorder的情况。

2.3 Reentrancy

重入漏洞的核心在于操作的原子性没有得到保证。如“修改Storage变量并转账”这个操作采用了先转账再修改变量的顺序,转账操作被恶意利用反复递归执行。

2.3.0 oracle

这个oracle被分成两个suboracle:

  1. ReentrancyCall:检测A的调用是否在其自己的调用链中出现超过一次
  2. CallAgentWithValue: 检测三个条件:
    2.1 有一个转移Ether超过0的调用
    2.2 有足够的Gas供被调用函数以用作复杂的执行
    2.3 call()的被调用者是自己工具提供的代理合约而不是测试中特别被指定的账户。

当ReentrancyCall和CallAgentWithValue相与为真值时,存在Reentrancy漏洞。

2.4 Timestamp Dependency

合约的设计者使用时间戳来影响或者决定控制流走向,如使用时间戳作为生成随机数的随机种子。但区块链作为一个分布式系统,其中的矿工是有能力在一定取值范围内随意设置时间戳的,这就造成了时间戳依赖漏洞。

2.4.0 oracle

这个oracle被分为三个suboracle:

  1. TimestampOp用来检测当前合约的函数调用中是否引用了TIMESTAMP操作码
  2. SendCall检测这个调用是否是send()并发送Ether到其他合约
  3. EtherTransfer检测call()中Ether的值是否大于0
    当TimestampOp ^ (SendCall v EtherTransfer)为真值时存在时间戳依赖漏洞

2.5 Block Number Dependency

时钟数字依赖原理类似时间戳依赖

2.5.0 oracle

BlockNumOp ^ (SendCall v EtherTransfer)

2.6 Dangerous DelegateCall

代理调用会把被调用函数地址的上下文信息也同步执行,当DelegateCall函数的参数由合约调用者指定时,恶意攻击者便可以利用该漏洞使得该合约执行其他合约的任意代码。

2.6.0 oracle

oracle检测合约中是否存在代理调用,并且代理调用所调用的函数是否从合约的输入中获得。换句话来说代理调用的调用对象是否能够被合约调用者指定。

2.7 Freezing Ether

合约可以接收Ether或者通过代理调用发送Ether,但是没有函数能够直接发送Ether,因此当代理调用的目标合约失效时,当前合约的Ether无法向外发送Ether,资产就被永久冻结了。

2.7.0 oracle

oracle检测合约是否有能力自行转送Ether到其他地址。

四 ContractFuzzer工具介绍

(ASE2018)ContractFuzzer: Fuzzing Smart Contracts for Vulnerability Detection 解析_第1张图片

4.1 总体概括

工作流程为标记0到5。2上面的网络爬虫在以太坊平台上爬取已部署的智能合约——包括合约创建代码(智能合约的二进制文件)、ABI接口、这些合约的构造函数参数;再把这些合约部署到自己的以太坊测试网上,用作模糊测试或作为函数调用的目标地址,其他信息如图所示。

4.2 Static Analysis(静态分析阶段)

4.2.1 针对合约池中ABI函数签名的静态分析

依据每个提供智能合约ABI的json文件,提取出其函数签名,然后把函数签名的前四个字节做Keccak哈希后计算出的函数选择器作为key,具有相同函数选择器的所有智能合约的地址向量作为value构造出一个map。

4.2.2 针对待测智能合约的静态分析

对合约ABI接口的json文件进行解析,抽取函数描述和每个参数的数据类型。其中地址数据类型本质上是代表智能合约的地址或者外部账户,当这种数据类型是函数的参数时,代表这个函数会和这个智能合约进行交互,那么我们在生成fuzz输入测试用例时应选择我们的合约池中合适的合约函数的地址。
在选择能够和当前待测合约进行交互的合约子集时,我们来分析下面这个算法:
(ASE2018)ContractFuzzer: Fuzzing Smart Contracts for Vulnerability Detection 解析_第2张图片
算法的输入是智能合约的二进制文件,输出是一个
——map(key:函数选择器,value:一组智能合约ABI)
算法首先使用EVM工具disam将智能合约字节码分解成汇编码
行4: 从汇编码中抽取公共ABI
行5-16:遍历每个ABI并抽取出其函数体中的代码片段,将其中含有PUSH4操作码的行(函数选择器)分解并存储,最后返回存储的所有的值。
对于一个要模糊的合约函数,我们使用该算法提取出支持该函数选择器作为ABI的所有智能合约的地址,并把它们放到合同池中。在对地址数据类型进行fuzz时,我们直接使用合同池中的地址。

4.3 Fuzz输入用例的生成

首先构造两组值:

  1. 在有效的输入域内随机生成的值
  2. 静态分析得到的在智能合约中频繁被使用的值

然后我们组合这两组值得到每个所需参数类型的预选输入集合。
对于待测合约中的每个函数,都生成一个预选输入集合,并为其中每一个参数设置K个在其输入域内的预选值。最后将这些输入编码成字节码等待被调用。

4.4 插桩EVM

插桩需要收集的信息类型:

  1. 合约中Call和DelegateCall的各种属性。
  2. 在执行中被调用的操作码
  3. 合约在执行中的state

4.4.1 Call/DelegateCall/Send

对于Call/DelegateCall/Send,Send被视为特殊类型的Call,DelegateCall和Call除了上下文和所用的storage之外在本质上相同。因此对这三个操作的数据记录可以共用Call的数据结构。
(ASE2018)ContractFuzzer: Fuzzing Smart Contracts for Vulnerability Detection 解析_第3张图片
为了支持oracle Reentrancy和ExceptionDisorder,我们需要记录合约内部复杂调用链的调用信息。因此我们对EVM.Call()和EVM.Call()进行插桩去收集Call的相关信息,并把CALL操作码置入操作码栈中去记录复杂调用链的调用顺序。

4.4.2 Opcode

一些oracle如TimestampDependency和BlockNumDependency需要检查一些操作码的执行如TIMESTAMP和NUMBER,或者有些操作码会改变待测智能合约的状态,对于这些操作码我们都需要进行记录。
因此对interpreter.Run()函数进行插桩,在运行时把它们压入待测合约的栈中,本文在129个EVM指令中选择了34个会被直接使用的指令进行插桩,以用作安全分析。

4.5 漏洞分析和报告

在实际模糊测试中,当调用栈为空时,EVM的插桩模块会对收集到的插桩信息相关的sub-oracle进行检查。并把这些sub-oracles发送到专门的处理接口执行最终的检查,生成最终发现的漏洞信息。

五 实验结果分析

六 相关工作

总结

模糊测试在智能合约测试上有着非常优异的效果,本文作为这个方向的第一篇顶会为后人开创了先河。实验结果和相关工作暂时省略,后期如果有必要阅读本文源码的话我会顺带补全。对论文原文或者源码感兴趣的读者可以查看文章开头的链接。
欢迎在评论区讨论


  1. 把原文的2和3合到一起了 ↩︎

你可能感兴趣的:(论文解析,智能合约模糊测试,区块链,软件测试,智能合约)