APP下载

基于模糊测试的智能合约正确性检测

2024-03-12王嘉诚蒋佳佳赵佳豪张玉书王良民

计算机工程与应用 2024年5期
关键词:以太测试用例调用

王嘉诚,蒋佳佳,赵佳豪,张玉书,王良民

1.南京航空航天大学计算机科学与技术学院/人工智能学院/软件学院,南京 211106

2.东南大学网络空间安全学院,南京 211106

伴随着区块链技术[1]的飞速发展,其应用场景越来越广泛,在生活中的普及程度也越来越高。目前,区块链技术已经进入了一个新纪元。作为第二代区块链技术的核心,智能合约[2]在区块链平台上发挥着日益重要的作用,区块链平台上的账户在智能合约的基础上管理着价值越来越庞大的数字资产,也正因如此,黑客也开始将目光瞄准这一新兴的技术领域。由于智能合约和区块链技术的发展时间还较为有限,已经暴露的安全问题和潜在的安全隐患都对以太坊区块链平台上的资产管理造成了严重威胁。

智能合约本质上是一种通过区块链部署并存储在以太坊平台上的具有特定功能的程序,因此上链数据不可篡改也成为智能合约所具备的特点之一,这就导致即使发现已部署的智能合约中存在影响其正确运行的漏洞,也很难对其进行修改。所以,在智能合约被部署之前就对其进行正确性检测成为了亟待解决的问题。

近年来,随着智能合约技术的日渐成熟以及愈加广泛地应用区块链平台之上,越来越多的学者注意到隐藏在智能合约中的漏洞对于用户以及区块链平台安全造成的威胁[3],他们开始探索各种类型的方法对智能合约中存在的安全漏洞进行分析、检测。现有的智能合约安全漏洞检测技术[4]主要有五个类别:基于静态分析的漏洞检测技术、基于测试的漏洞检测技术、基于机器学习的漏洞检测技术、组合技术以及其他。

静态分析[5-10]属于较为传统的漏洞检测方式,其检测合约中隐藏的安全漏洞的方法是在不执行程序的情况下对程序的静态特征进行分析。符号执行技术通过抽象方式模拟执行程序并涵盖程序的多个可能路径。Luu 等人[11]在2016 年提出并构建了一种名为Oyente 的基于符号化执行的漏洞检测工具,用以对智能合约中潜在的安全漏洞进行检测分析,Oyente工具属于目前符号分析领域较为领先的漏洞检测工具,其可以检测堆栈大小限制漏洞、事务顺序依赖漏洞等4种类型的漏洞。形式化方法[12]使用数学和逻辑方法来描述和验证智能合约的设计是否满足其要求。Bartoletti 和Zunino[13]两人在2018年提出了一种在不依赖可信任的中心的去中心化体系内,对涉及比特币交易的合约进行监管的形式化验证语言BitML,交易的参与者可以根据这种语言构建自己的交易并上链,从而执行智能合约。Tsankov等人[14]在2018 年提出了一种名为Securify 的以太坊智能合约自动化安全分析工具,其可以根据漏洞特征分析智能合约是否存在漏洞。Securify分析智能合约源代码编译后生成的字节码。Securify首先根据智能合约的字节码重构依赖图,通过分析依赖图得到智能合约的语义信息。然后Securify根据给定的特征分析智能合约是否符合或违反这些特征,结合从智能合约中获得的语义信息,判断是否存在漏洞。污点分析技术是一种用于跟踪程序中不可信数据或私有数据在一定规则下产生的数据流的程序分析技术。Rodler 等人[15]在2019 年提出了一种使用动态污染跟踪技术保护智能合约免受重入攻击的工具,名为Sereum。当Sereum工作时,它的污染引擎通过动态污染跟踪为预定义源生成的数据分配标签。之后,攻击检测器可以通过观察标记的数据如何影响程序的正常执行来识别程序执行的异常状态。当Sereum确定当前程序中正在发生重入攻击时,Sereum 将与EVM事务管理器连接,并终止正在进行的程序。

在基于测试的漏洞检测技术[16-18]中,模糊测试技术[19-21]通过向目标程序提供大量意外输入来暴露异常结果,从而发现程序漏洞。Liu 等人[22]在2018 年提出了Reguard,这是一种基于模糊测试的智能合约漏洞分析工具,其专门针对智能合约中包含的可重入性漏洞进行自动监测,这种工具是通过迭代的方式生成随机的交易,通过执行这些交易来对智能合约进行模糊测试,并根据对交易执行过程的监控,动态地检测合约中的可重入性漏洞。变异测试会对源程序中的某些语句进行修改,并结合测试用例观察修改后程序的执行结果,从而发现错误。Li等人[23]在2019年提出了MuSC。MuSC首先将每个待测合约的源代码转换为抽象语法树并在抽象语法树中生成变异数据,然后变异数据转换回源代码文件进行编译和执行,从而暴露智能合约的缺陷。MuSC作为首个应用于以太坊智能合约漏洞检测的突变测试工具,实现了突变测试的基本功能,并能在一定程度上暴露合约中的漏洞。

机器学习[24-26]利用过去的数据或经验,以计算机为工具,致力于实时模拟人类的学习。机器学习作为当前的热点技术,也被越来越频繁地应用于智能合约漏洞检测领域。2018年,Tann等人[27]提出了首个应用于智能合约漏洞检测的机器学习方法。该方法利用长短期记忆连续学习智能合约安全漏洞,将智能合约字节码序列映射为矢量序列,作为长短期记忆模型的输入,预测智能合约漏洞。

为了追求更好的漏洞检测效果,弥补单一程序分析技术的局限性,组合技术(将多种类型的程序分析技术结合用于智能合约漏洞检测)从2019 年开始受到业界研究人员的关注。2020 年,Ashouri[28]提出了一种将动态污染跟踪技术与动态符号执行技术成功结合的智能合约漏洞检测工具,名为Etherolic。该工具首先对由输入数据组成的事务进行标记,并在执行期间使用动态污点跟踪技术跟踪事务的传播。当其追踪到受污染的交易后,会检查其相关的敏感数据流,从而识别智能合约中的潜在漏洞。定位漏洞后,Etherolic会对智能合约进行动态符号执行,生成一系列试图触发智能合约漏洞的交易。最后,Etherolic根据受污染数据的跟踪结果和易受攻击代码的触发结果生成漏洞检测报告。

除了上面提到的四类典型的智能合约漏洞检测技术外,还有一些不太常见的漏洞检测技术。例如代码克隆检测、攻击向量检测、在线检测、入侵检测、语义感知和气体感知等。2018 年,Liu 等人[29]提出了一种新的语义感知安全审计技术,称为S-gram。该审计技术将NGram 语言建模与轻量级静态语义标记相结合,通过识别不规则的语义标记序列和优化现有的漏洞分析器来捕获智能合约的高级语义,并预测潜在的漏洞。

本文以基于测试的智能合约漏洞检测技术中的模糊测试技术为主要思路,在智能合约被正式部署之前对其进行动态漏洞检测,判定其正确性。智能合约的正确性一般包括代码正确性和运行正确性。作为一段计算机程序,智能合约的运行正确性在其编译以及部署在区块链平台投入应用时会得到验证,编译不通过以及无法正常运行的智能合约皆不满足其运行正确性。本文重点关注的是智能合约的代码正确性,在某种程度上,代码正确性会对运行正确性造成不可忽视的影响。与一般的计算机程序相同,影响智能合约代码正确性的因素通常包括编程语法错误、逻辑错误等,这一系列问题导致了软件漏洞的引入,使得智能合约无法实现预期的功能或被恶意用户所利用,这都将造成实际的损失。本文着重关注针对七种具有区块链特点的软件漏洞时智能合约的正确性问题。

为实现模糊测试,本文构建了一个针对智能合约的内容以及规范生成模糊输入的框架。该框架通过对智能合约的ABⅠ接口以及字节码进行分析,根据智能合约所调用的函数的参数的不同类型生成不同的模糊输入,并针对不同种类的漏洞构建测试样例。向待测智能合约导入模糊输入后,在执行测试样例时通过以太坊虚拟机来监控智能合约的执行情况,通过对监控获得的若干日志文件进行分析,实现对于智能合约漏洞的检测。经测试,在416个部署在测试网上的智能合约中检测出19个包含漏洞的不正确智能合约,对检测结果进行人工分析后发现,检测的准确率达到了94.7%。

1 区块链、以太坊及智能合约简介

区块链是一个分布式的共享账本,也是一个去中心化的数据库,区块链以分布式存储的方式将比特币交易的信息保存在各个区块中,而每一个区块所包含的数据又直接决定了其子区块的生成,在生成新区块时,需要共识机制来化解分叉问题,同时需要对信息的有效性进行验证。作为支撑比特币的底层技术之一,区块链技术为价值转移提供了新的思路和方法。然而,比特币的功能较为单一,无法在区块链网络上实现较为复杂的应用,其实际应用价值受限。在区块链技术的发展过程中,以太坊的出现很好地解决了这个问题,让区块链的商业应用成为可能。

以太坊(Ethereum)是一个去中心化的应用平台,与比特币等加密货币相同,以太坊同样具备电子支付、数字资产转移等功能。除此之外,任何用户都可以在其上构建去中心化应用,并与其他应用程序进行交互。以太坊上构建的去中心化应用程序便是由智能合约构成的,智能合约管理着以太坊的许多关键功能、操作、用户行为。以太坊支持两类账户:实体用户所有的账户以及智能合约账户。以太坊会在每一笔交易产生之后更新自己的状态,其交易本质是在不同账户(智能合约账户和实体账户)之间发送虚拟货币或二进制数据,当接收方是智能合约账户时,将会把二进制数据作为智能合约的输入数据,执行其代码。

智能合约[30]是第二代区块链核心技术,Nick Szabo在1995 首次提出了“智能合约(smart contract)”的概念,直到2013年以太坊区块链平台出现之后,智能合约的实体才真正诞生。智能合约其本质是由代码组成的程序,也可以理解为是由代码定义的交易规范、准则。智能合约的执行需要达成某些特定的条件或是发生特定的行为事件,其本身具有自己的状态,并且是以区块链平台(如以太坊)为载体存在的。结合区块链不可篡改的特点,智能合约一经部署便不容易被恶意攻击者篡改。当智能合约被部署在区块链平台之后,智能合约自身的状态会和区块链的状态建立起非常紧密的联系,这样一来,智能合约的执行将变得非常高效。

然而,由于智能合约在执行时有可能会涉及到多个不同智能合约之间的协作交互,如果编写智能合约的工作人员未能清晰地掌握智能合约与区块链平台的关系以及智能合约之间的潜在关系,那么开发出的合约就有可能存在安全漏洞。除此之外,因为智能合约和区块链均是近几年新兴的技术,编写智能合约的编程语言以及运行智能合约的区块链平台对于大部分开发者来说接触时间并不长,加之开发工具本身也并不够完善,这就导致开发人员在开发的过程中容易遇到困难和问题,编写出的智能合约极易存在漏洞。这些漏洞都将影响智能合约的正确执行,从而对区块链平台的安全稳定造成威胁。

总的来说,区块链技术作为一项具有重要意义的新兴技术,其快速发展的过程同样也是不断拓展、打破固有思维、消除技术局限性的过程。从比特币、区块链到以太坊、智能合约,从区块链1.0时代到2.0时代,区块链技术得到了越来越广泛的应用,实际应用的推广又进一步推动了技术的发展。发展越是迅猛,安全则越是关键,作为当前技术核心部分的智能合约安全问题则越应受到重视。本文工作即是在以智能合约为核心的二代区块链技术飞速发展的背景下,以模糊测试为主要技术手段,对以太坊智能合约进行漏洞检测,从而对其正确性做出判断。

2 智能合约安全漏洞

本章将对本文中涉及的7 种类型的智能合约漏洞进行介绍。

2.1 不消耗gas的send(Gasless Send)漏洞

不消耗gas 的send 漏洞的产生是基于一种特殊情况:当使用send()函数(相当于一个特殊的call())发送以太币到一个合约时,签名如果匹配不到任何的函数时,将会触发回退函数(fallback())。由于send()函数指定了一个空函数签名,所以当fallback()函数存在时,send()函数将总是会调用回退函数。但和一般的函数不同的是,执行send()所消耗的gas 默认上限是2 300(如果特别指定上限的话,可以大于2 300),因此,如果接收方合约在交易时触发的fallback()函数需要的gas超过其默认上限,以太币的发送方将会遇到一个名为“out-of-gas”的异常。如果这种异常没有被适当地检查并做出提醒,那么恶意的发送者可能会使以太币的发送出现错误。

2.2 异常无序(exception disorder)漏洞

异常无序漏洞是一种由于不同合约对于异常处理的稳定性不一致所导致的漏洞。异常无序漏洞通常在不同合约彼此调用时发生。当一个合约调用另一个合约的函数时,调用行为可能会因为不同类型的异常而失败,当这种异常发生时,处理异常的机制是由合约之间彼此调用的方式确定的。在一个嵌套调用链中,每个调用都是对智能合约中的函数的直接调用,当异常发生时,所有的交易(包括以太币的发送)都会被还原。然而,如果这种异常发生在嵌套调用链中就会产生问题,因为在嵌套调用链中,总是存在一个调用address.call()、address.DelegateCall()、address.send()等函数的调用行为,交易行为的恢复在调用函数处便会停止并返回false,而交易行为对更深层次的调用产生的影响将不会被恢复,抛出的异常也不会传播。异常处理方面的这种不一致性将使调用合约无法获知执行过程中出现的错误。

2.3 可重入性(reentrancy)漏洞

导致可重入性漏洞的原因:有些函数在设计之初并不允许对其进行重入操作,然而,一些恶意代码故意用可重入的方式调用这些函数,例如通过fallback()函数进行调用,这与fallback()函数本身的性质有关。一个智能合约如果没有定义fallback()函数,它在进行以太币交易的时候会抛出异常,会导致以太币无法发送或者退还。于是便给一些不怀好意的攻击者提供了可乘之机。通过fallback()函数调用一些不允许重入的函数可能会导致以太币重复支付的问题出现,从而丢失以太币。著名的Dao[31]攻击便是通过这个方式造成了当时价值六千万美元的以太币丢失。

2.4 时间戳依赖(timestamp dependency)漏洞

很多智能合约的执行逻辑是和当前区块的时间戳有关的,在执行一些类似于发送以太币的关键操作时,如果区块的时间戳是触发该关键操作的条件的一部分,又或是智能合约的代码获取时间戳作为产生随机数的依据变量,就可能会导致合约中存在时间戳依赖漏洞。在区块链这样的分布式系统中,矿工不仅可以决定一个新开采出的区块的时间戳,还可以在900 s 的内自行设置区块的时间戳。如果智能合约在传输以太币时用到了区块的时间戳或时间戳相关的数据,那么可能会存在恶意矿工通过操纵区块的时间戳获取不正当收益。

2.5 区块号依赖(block number dependency)漏洞

区块号依赖漏洞的作用原理与时间戳依赖漏洞类似,当智能合约使用区块号作为触发某些重要步骤的条件的组成部分时或作为生成随机数的依据时,就会造成区块号依赖漏洞。其本质还是因为区块的区块号可以被矿工所操作,如果智能合约基于区块的区块号进行交易,攻击者就可以通过操纵区块的区块号来利用该漏洞。即使不直接使用区块号,将区块号作为某些随机数生成器的参数用以生成随机数,其结果仍然是脆弱的。

2.6 危险的DelegateCall(dangerous Delegate-Call)漏洞

DelegateCall()是以太坊智能合约中较为常见的一种用来实现函数调用的函数,其功能与call()函数较为类似,这二者之间的区别也正是该漏洞产生的原因,call()函数在进行函数调用时,被调用函数的代码是在被调用方本身合约内运行的,但是DelegateCall()调用的目标函数的代码是在DelegateCall()所在的智能合约内运行的,这样的区别便造成了一定的安全隐患。如果DelegateCall()调用的目标函数来自于一个恶意的合约账户,该函数内包含一些危险的操作,当该目标函数在调用者智能合约中运行的时候,将有可能给调用者造成损失。

2.7 冻结以太(freezing Ether)漏洞

存在一些智能合约,其功能是接收以太币,并将收到的以太币转账给其他地址,起到以太币中转的作用。但这些合约本身并不包含发送以太币的函数,无法自行实现转账功能,而只能通过DelegateCall()调用一些其他智能合约中的发送以太币的函数从而实现转账功能。当这些提供以太传输代码的合约执行自毁操作时,调用了这些合约中的转账函数的合约将无法发送以太币,智能合约账户所管理的所有以太币都将被冻结。由于Parity Wallet 漏洞而被第二次攻击的原因便是许多Wallet 合约只能依赖Parity Library 来完成对以太币的操作,然而,当Parity Library被初始化为智能合约,然后被黑客杀死后。依赖Parity Wallet 进行以太币操作的所有Wallet合约管理的以太币都被冻结了。

3 基于模糊测试的安全漏洞检测

本章分为测试用例的构建和模糊测试两个部分,第一部分分别对七种类型的智能合约安全漏洞的测试用例的构建规则进行了描述(测试用例在漏洞检测中的意义是:以太坊虚拟机在监控智能合约执行的过程中会收集一些信息,通过这些信息和测试用例的对照来判断智能合约中是否包含该测试用例对应类型的漏洞);第二部分针对以太坊智能合约面临的安全问题,根据3.1 节描述的测试用例构建方法,本文提出了一种基于模糊测试的漏洞检测方案,对模糊测试的主要功能模块设计思路以及漏洞检测时的流程进行了讲解。

3.1 测试用例的构建

3.1.1 不消耗gas的send漏洞的测试用例

在以太坊虚拟机中,对于send()函数的定义其实是一种特殊类型的call()函数。于是,构建出的不消耗gas的send 的测试用例要确保以太坊虚拟机中的调用确实是send()调用,并且测试用例需要发现send()调用在执行期间返回了ErrOutOfGas的错误信息。

为了检查以太坊虚拟机中的调用是否为send(),需要对调用的输入是否为0 以及调用的gas 限制是否为2 300进行验证。

3.1.2 异常无序漏洞的测试用例

对于异常无序的测试用例的构建原则是:对于一个嵌套调用链(或DelegateCalls()),其总是会起源于一个根调用(或DelegateCall()),在这个嵌套调用链(或DelegateCalls())中,如果至少有一个嵌套调用返回了异常,但根调用却并没有返回异常,这样的情况说明异常没有在嵌套调用链中正确地传播回到根调用,于是认定该嵌套调用链包含异常无序的漏洞。

3.1.3 可重入性漏洞的测试用例

可重入性的测试用例是基于两个子用例定义的:第一个子用例是ReCallTest,用于检测在以调用Call()为根调用的调用链中,函数调用Call()是否不止一次地出现在这个调用链中;第二个子用例是AgentCallTest,其需要对三个条件进行检查:条件一:用于传输的以太币的数量(call()调用值)大于0;条件二:被调用的函数有足够的gas来支付执行复杂代码时的费用;条件三:调用call()的是本文提出的漏洞检测工具所提供的代理合约,而不是测试中待测智能合约所指定的账户。

基于两个子用例构建可重入性的测试用例的具体形式如式(1):

在具体情境中,可重入性漏洞的表现为:当一个函数调用既是某个调用链的根调用,又是该调用链的结尾调用,也就是该调用经过一串调用之后又回到其自身,并且该调用储备了充足的gas,该调用通过call()函数向测试框架所提供的代理合约发送了以太币。例如代理合约是通过对fallback函数的重复调用来体现它的可重入性的,当智能合约的执行触发了测试用例时,说明其存在遭受重入攻击的可能性,便会将该智能合约标记为存在可重入性漏洞的脆弱智能合约。

3.1.4 时间戳依赖漏洞的测试用例时间戳依赖的测试用例是由三个子用例组成的。

三个子用例都是布尔型变量。第一个子用例是TimeStampBool,它代表的是当前待测的智能合约在执行期间是否调用了时间戳的操作码,如果调用,可以初步认定该智能合约的某些操作中用到了时间戳。第二个子用例是SendBool,它代表智能合约中是否包含发送以太币到其他智能合约账户的send()调用。第三个子用例是EtherBool,它代表发送以太币的数量是否大于0。

这三个子用例通过一种特定的关系组合之后获得针对时间戳依赖漏洞的测试用例,该种关系如式(2):

判断某一智能合约是否存在时间戳依赖漏洞的方法:待测智能合约使用了区块的时间戳,并且在智能合约的执行期间进行了以太币的交易。

3.1.5 区块号依赖漏洞的测试用例

区块号依赖的测试用例类似于时间戳依赖,与时间戳依赖不同的是,区块号依赖的测试用例主要检查的是智能合约在执行期间是否调用了区块号的操作码,而不是区块的时间戳的操作码。同样,区块号依赖的测试用例也是由三个子用例组成的。第一个子用例是BlockNumberBool,它代表的是当前待测的智能合约在执行期间是否调用了区块号的操作码,其他两个子用例与时间戳依赖的两个子用例相同,分别是SendBool 和EtherBool。

区块号依赖的测试用例是其三个子用例安装和时间戳依赖相同的方式组合的,其具体表达为式(3):

对于是否存在区块号依赖漏洞的判断原则即为:当前待测智能合约使用了区块的区块号,并且在智能合约的执行期间进行了以太币的传输。

3.1.6 危险的DelegateCall漏洞的测试用例

危险的DelegateCall 的测试用例会对当前执行的智能合约是否包含至少一个DelegateCall()函数以及DelegateCall()调用的函数是否是从初始调用的智能合约的输入中获得的。总的来说就是测试用例会检查被测试的智能合约是否调用了一个DelegateCall(),该DelegateCall()的目标函数是由潜在攻击者所提供的。

3.1.7 冻结以太漏洞的测试用例

冻结以太的测试用例检测的是一个智能合约在本身没有transfer()、send()、call()、suicide()等函数的相关代码来将以太币发送到其他账户地址的情况下,是否可以接收以太币以及是否在执行的过程中使用了DelegateCall()函数。换一种方式来描述就是,如果一个智能合约在执行期间其账户余额大于0,但该智能合约无法自行实现发送以太币的功能,而是需要通过DelegateCall()调用其他智能合约中的可以实现以太币转账功能的函数,那么冻结以太的测试用例就会将该合约标记为有可能存在冻结以太漏洞的脆弱合约。

3.2 模糊测试

3.2.1 模糊测试概述

模糊测试主要包含两个部分,其一是一个离线的以太坊虚拟机插桩工具,其二是一个在线的模糊测试工具,本文将着重就模糊检测工具进行介绍。离线的以太坊虚拟机插桩工具通过对以太坊虚拟机进行插桩,使线上的模糊测试工具能够对智能合约的执行进行监控,分析执行日志文件用于漏洞分析;模糊测试工具通过对智能合约的代码进行分析,生成模糊输入,并根据漏洞的特征定义针对该种漏洞的测试用例,通过提取执行日志文件中的关键信息与测试用例进行比较,从而判断智能合约中是否包含该种类型的漏洞。该模糊测试工具的工作流程如图1所示。

图1 基于模糊测试的漏洞检测概览图Fig.1 Overview of vulnerability detection based on fuzzing

在正式进行本文所涉及的工作之前,先通过网络爬虫的方式从Etherscan网站上爬取了一些已经部署在以太坊平台上的智能合约,爬取的内容包括智能合约创建的代码、ABⅠ接口以及这些智能合约的构造函数参数。在本文所涉及的工作中,已将爬取的智能合约重新部署在自行搭建的以太坊测试网络中。一方面是将这些智能合约作为模糊测试的对象,另一方面是将这些智能合约作为使用合约地址作为参数的智能合约调用的输入。

漏洞检测的过程主要包含以下几个步骤:

(1)ABⅠ及字节码分析。对待测的智能合约的ABⅠ接口以及字节码进行分析,需要对ABⅠ函数的每一个参数的数据类型和ABⅠ函数中所使用到的所有函数的函数签名进行提取。

(2)映射关系建立。对从以太坊平台上爬取下来的所有待测或是待引用地址的智能合约进行ABⅠ签名分析,之后需要建立起智能合约和函数签名的一个映射关系,即根据不同智能合约所包含的不同函数签名进行索引。

(3)生成模糊输入。在这一步模糊测试框架需要根据第一步和第二步的分析结果,根据待测智能合约的ABⅠ规范,生成有效的模糊测试输入以及并未严格按照ABⅠ规范生成的异常输入。

(4)启动模糊测试。以随机函数调用的方式将第三步生成的模糊测试输入以及异常输入并发送给相应的ABⅠ接口,智能合约进行执行。

(5)结果输出及分析。以太坊虚拟机对于模糊测试过程中智能合约的执行情况进行监控,并生成若干执行日志,通过对这些执行日志进行分析,实现智能合约的漏洞检测和正确性分析。

当所有待测智能合约都完成了模糊测试时,整个模糊测试的过程结束。

3.2.2 模糊测试核心步骤

(1)对待测智能合约进行静态分析

模糊测试框架对待测的智能合约进行静态分析,并提取待测的智能合约的公共函数的函数签名。从细节出发进行描述:将每个待测的智能合约的合约文件加上.abi 后缀,导出JSON 格式的ABⅠ,并提取出ABⅠ中声明的所有函数签名,再计算每一个函数签名的前四字节的哈希值(此处哈希的方法是SHA-3)作为其函数选择器,保证不同的函数签名对应不同的函数选择器。最后构建出了一个以函数选择器为键,以包含所有该函数选择器的智能合约的地址向量为值的键值对映射。

接下来将具体介绍模糊测试过程中对智能合约的静态分析。工具通过分析JSON格式的智能合约ABⅠ接口,提取智能合约中每个函数的参数的参数类型以及关于函数的描述,大多数数据类型模糊的输入域是可以确定的,但地址数据类型与其他数据类型不同。在模糊测试框架中,地址数据类型的参数不能随机产生,其具体指代的是外部账户和智能合约账户的地址。当智能合约的地址作为实参被提供给智能合约中包含的函数的时候,函数接收到地址数据类型的参数输入后便会调用call()函数和收到的地址参数对应的智能合约建立联系,进行交互。所以,模糊测试框架必须要使用智能合约的地址来作为带有地址数据类型的参数的ABⅠ函数的输入。

算法1智能合约静态分析算法

在对待测智能合约进行模糊测试时,需要对智能合约的ABⅠ函数签名进行提取,并确定函数签名与智能合约之间的映射关系。当具体给出一个智能合约的ABⅠ函数时,需要确定所有待测智能合约中,哪些可以与这个ABⅠ函数进行交互,也就是说哪些智能合约中还包含了该ABⅠ函数。该确定过程需要调用一个非常重要的函数call,call函数的参数的前四字节对应了模糊测试框架为每一个ABⅠ函数计算的函数选择器(函数签名前四字节的SHA-3哈希)。

算法1 将待检测的二进制形式的智能合约作为输入,输出智能合约的每个ABⅠ函数到智能合约代码中使用的函数选择器集合的映射。首先,算法使用了以太坊虚拟机自带的反汇编工具将智能合约的二进制代码反汇编为汇编代码。接下来,算法会对已经转为汇编代码形式的智能合约中的公共ABⅠ函数进行提取,生成一个函数集合,该集合中存放的就是所有提取出的公共ABⅠ函数。之后算法会通过循环结构对上一步生成的函数集合中的每一个ABⅠ函数进行遍历,在遍历的过程中将会获得该函数用到的函数选择器,并生成一个函数选择器集合。该循环结构在对每个ABⅠ函数进行遍历的时候,首先会获取其函数体,并通过函数体来获取代码段部分,并在每一次循环时为每一个ABⅠ函数创建一个函数选择器集合。在该循环结构中还嵌套了两层循环,二层循环是对代码段中的每一段进行遍历,三层循环是对每一段中的每一行进行遍历,通过每一段的第一行汇编代码内容来判断该段内容是否包含要提取的函数选择器,如果是,就将函数选择器分离出来并添加到函数选择器集合中。在对每一段进行遍历之后,函数选择器集合中可能有不止一个函数选择器,也可能一个都没有,于是先对函数选择器是否为空进行判断,如果不为空,就将该集合与本轮遍历的ABⅠ函数建立映射关系。当所有ABⅠ函数遍历结束后,每一个ABⅠ函数都将建立和它对应的函数选择器集合的映射关系。该算法需要对每一个待测的智能合约执行。

根据该部分第一自然段可知,在本模块功能中除了建立了ABⅠ函数和函数选择器的映射关系以外,还建立了函数选择器和支持该函数选择器对应的函数的智能合约的地址向量的键值对关系。根据这两对关系,不难再建立起ABⅠ函数和ABⅠ函数包含的函数选择器对应的函数所支持的智能合约的关系。于是就根据ABⅠ函数找到所有相应的智能合约,并将它们存放在一个集合之中,再根据ABⅠ函数和智能合约建立起的关系,通过智能合约找到其包含的所有ABⅠ函数,对这些ABⅠ函数进行模糊处理,针对其不同的类型的参数生成不同的模糊输入。在前文提到了关于地址类型输入的问题,如果随机生成地址数据类型的输入,无意义的地址会导致无法在智能合约之间建立联系,所以地址数据类型的参数应该选用智能合约对应的地址。

(2)产生模糊输入

在整个模糊测试框架中,生成模糊输入是非常关键的一个步骤,也是工具实现以模糊测试为基本思路来检测智能合约中的安全漏洞的技术核心,是“模糊”最直接也是最真实的体现。

首先需要明确的是对什么内容进行模糊处理,生成模糊输入。模糊测试框架是对待测智能合约中所包含的函数的参数进行模糊处理,生成模糊化的参数作为函数的输入,并且不同类型的参数会对应不同的模糊处理方式。本文主要采用的模糊处理方法是先获取该函数的所有参数,将其存放在一个集合当中,然后对这个集合中的每一个参数进行遍历,首先对它的类型进行判断,然后再根据参数类型确定不同的模糊处理方式,按照选定的方式为每一个参数生成一个随机的候选值集合,以备为函数提供多次不同的模糊输入,进行漏洞检测。该候选值集合所包含的模糊化参数个数也是根据参数的不同类型在不同的区间范围内随机生成的。

本文采用的参数类型划分方法是按照参数长度来划分不同类型的参数,将函数的所有参数划分为定长参数和不定长参数两个类型。对于定长参数,只需要按照参数的长度,调用相应生成随机数的函数,产生模糊输入集合即可;对于不定长的参数,例如字符串等类型的参数,首先需要随机生成一个数据作为参数的长度,然后按照随机生成的长度再随机生成其参数的具体数据内容。这样的方式可以提高输入的模糊程度,最大程度上覆盖有可能的所有输入。

对于每个函数参数的候选值集合,其候选值并非全部由随机函数按照一定的条件生成,最终的候选值集合由两个部分所组成,一部分是模糊输入,另一部分是在对函数进行静态程序分析的时候确定的该参数常用的一些数据。

在对于智能合约进行模糊测试时,需进行多次测试,次数不固定。在对待测智能合约集合中的每个智能合约进行测试之前会先生成一个确定范围内的随机数作为其模糊测试的次数,以此来达到同时保证测试效果以及测试效率的目的。接着会对该合约循环执行随机次数的模糊测试,在每次模糊测试时,还会在该智能合约包含的所有函数中随机选择一个函数进行模糊测试,从其参数的候选值集合中随机选择作为函数的模糊输入。本文提及的七种智能合约安全漏洞中的大多数漏洞都可以按照常规的针对函数的参数生成模糊输入,然后将模糊输入作为参数传递给函数,执行智能合约这样的方式进行漏洞检测。但这样的方式并不适合可重入性漏洞,或者可以说并不适合所有涉及到不止一个智能合约之间的相互联系的漏洞类型。于是,为了解决这一问题,需要针对可重入性漏洞,根据其漏洞的原理,结合某些智能合约本身的结构特点,设计一个可能触发可重入性漏洞的攻击场景,以此来标记合约中的可重入性漏洞。

(3)收集测试样本

上一部分介绍的模糊输入为整个测试框架提供了输入,输入之后进行模糊测试,模糊测试的本质即在输入了模糊化参数之后,执行待测智能合约。在智能合约的执行过程中,需要提取一些关键信息来构建出本文第三章提到的针对不同安全漏洞类型的测试用例,对这些测试用例进行分析,以此来判断正在执行的智能合约中是否包含某种安全漏洞。

通过观察智能合约的函数调用情况可以发现,智能合约中非常多的规则的定义、交易行为的规定都和call()函数有关,有的会直接使用call()函数,同时send()和DelegateCall()函数也占据了相当的函数调用比例。原因其实也很简单,这几种函数都可以实现不同账户之间的交易转账,而对于智能合约来说,其本身就包含了很多关于交易、转账的行为定义以及相关规则。前文中提到过send()函数其实是一种特殊的call()函数,二者实现了相同的功能,区别是send()函数对gas 的最大支付值有限制(限制为2 300),而call()函数对gas 没有限制;call()函数和DelegateCall()函数的功能也十分类似,二者的主要功能是通过调用其他合约的代码,或是函数来实现转账、交易等行为。区别之处在于当他们在调用其他合约的函数时,程序的运行位置是不同的,当一个合约中的call()函数在调用其他合约的函数时,是在call()函数调用的函数所在的合约中运行的,当DelegateCall()函数调用其他函数时,代码是在DelegateCall()函数本身所在的合约中运行的。由此可知,智能合约中call()函数相关的数据对于分析智能合约执行过程中的交易行为有无异常非常重要。于是便对智能合约中call()函数的一些指标进行了提取,列举在此:发起调用的智能合约账户地址、当前智能合约内的函数、被调用的函数所在的智能合约账户地址、被调用的函数名、被调用的函数的输入参数、传输的以太币的数量、调用行为所需花费的gas、当前调用中用到的操作码。

操作码是这些指标中非常重要的一项,当在构建关于时间戳依赖漏洞和区块号依赖漏洞的测试用例时,将会直接对于智能合约是否包含区块时间戳和区块号的操作码进行检查;除此之外,在前文提到的判断智能合约中的ABⅠ函数的每段代码是否包含函数选择器时也用到了操作码,其判断方法是每个代码段的第一行是否是操作码PUSH4,PUSH4 的含义是将前四字节的数据放入堆栈中(以太坊虚拟机是堆栈虚拟机的一种),与之对应的是函数选择器是由函数签名的前四字节经过SHA-3 哈希后得到的。操作码不仅被直接用在某些测试用例中的,作为支撑起以太坊虚拟机执行指令的重要部分,部分操作码在执行的过程中还会对智能合约的状态产生影响。所以,对操作码相关信息的分析对于安全漏洞检测有着显著的意义。

(4)日志分析与漏洞检测

日志分析与漏洞检测模块通过调用go-ethereum库(https://geth.ethereum.org)的以太坊虚拟机插桩工具对智能合约的执行过程进行监控,在监控的过程中插桩工具会将能够反馈智能合约执行情况的信息以执行日志文件的形式输出。在对相关日志文件进行分析时,该模块会提取日志文件中反映交易相关行为(转账、账户地址等)以及调用信息(Call()、Callnode()、DelegateCall()等)的内容,并将提取的信息记录在栈中。

在日志分析与漏洞检测模块中,栈是核心数据结构,发挥着非常重要的作用。该模块会将交易的发起者、交易的接收者、转账的以太币数量、耗费的gas、账户余额、以太币存储状态等反映智能合约状态以及区块链状态的信息存入栈中。当栈的长度达到一定数值(本文工作设置的阈值为1)时,分析模块便会利用存储在栈中的信息,调用对应的功能函数,结合针对特定类型漏洞构建的测试用例进行检查和判定。对栈中的信息分析完成后便清空栈,之后继续将新提取的信息记录在栈中进行分析,重复以上过程直到当前所有候选待测智能合约执行完毕且全部交易执行完毕。

在分别针对每种类型漏洞进行检测时,会对该类型漏洞的所有候选智能合约执行上述分析过程,逐一检测判定这些智能合约中是否包含相应类型的漏洞,并将经检测可能存在对应漏洞的智能合约名称汇总在一个名单列表文件中,该文件即为漏洞检测的结果,该文件中包含的智能合约即为不满足正确性的智能合约,包含任一类型或若干类型漏洞的智能合约均不满足其正确性。候选智能合约集合中未出现在检测结果名单的智能合约即为针对该种漏洞类型满足正确性的智能合约。

4 漏洞检测实验

本章对于使用本文提出的模糊测试框架对智能合约进行漏洞检测的实验过程进行了具体的阐述。在搭建好实验平台、配置好实验环境之后,将该模糊测试框架部署在以太坊虚拟机中,将416个待检测的智能合约部署在测试网上。针对7种类型的漏洞,对合约进行漏洞检测,检测出19个存在漏洞的不正确智能合约,经人工分析验证,其中18个检测结果正确。

4.1 实验环境/实验设置

在进行智能合约漏洞检测的实验部分时,选用的实验环境是在本机上安装的Linux虚拟机,虚拟机系统选用的CentOS-7-x86_64,分配内存为4 GB,部署4核处理器,硬盘分区40 GB。本机配备Ⅰntel®CoreTMi5-6200 CPU,已安装内存(RAM)8 GB。

搭建实验环境的过程主要分为安装CentOS7 虚拟机以及在虚拟机中安装docker 应用容器引擎两个主要步骤,安装虚拟机时需要注意的是一定要分配足够的内存,原因是需要在虚拟机中搭建一个以太坊虚拟机平台和一个测试网,并且要在以太坊虚拟机中部署上百个智能合约,如果分配的内存不够会导致实验无法正常进行,工具载入到某一步耗尽内存之后便会抛出异常。对于docker 的安装,采用了自动执行的脚本命令,选用的是阿里云的镜像。

由于在整个实验中关注的只是智能合约包含安全漏洞的情况,于是在整个测试网中只设置一个对等节点。Docker应用容器引擎成功安装后,在其中部署以太坊虚拟机平台,并且在docker中安装用于测试的geth客户端并创建一条只包含一个节点的私有链。由于模糊测试框架的主体部分是基于go 语言编写的,该语言与Java 有很多的相似之处,并且关于测试部分是以一个JavaScript项目架构的,于是选择了web3.js(以太坊虚拟机兼容的JavaScript 应用程序接口)与docker 中的geth客户端建立联系。因为创建私有链的原因并不是为了实现真实的以太币交易,而是对部署在测试网的四百余个智能合约进行漏洞检测,所以私有链中对于新区块的开采的虚拟过程设计较为简单,以降低实验复杂度并提高实验效率,从而获得较为理想的实验结果。

4.2 爬取智能合约相关信息

实验部分涉及的待检测智能合约通过Python 爬虫程序从Ethersca网站(https://etherscan.io/)爬取。Etherscan是浏览以太坊区块链平台上所有公共数据的安全工具,可以通过Etherscan 浏览的数据包括交易转账数据、钱包地址、智能合约等。Etherscan 网站支持查看智能合约、验证智能合约、与智能合约进行必要的交互等操作。智能合约能够被公开查看是Etherscan网站的优势之一,也满足了本文工作的相应需求。

由于Etherscan 网站并没有较为完善的反爬虫机制,网站上的智能合约信息也相对固定,于是在爬虫程序的编写部分只要采用较为基础的静态爬取方法即可实现。在爬取过程中,获取的信息主要包括智能合约的源代码(存入.sol文件中)、智能合约的ABⅠ(存入.abi文件中)、智能合约的二进制代码(存入.bin文件中)、智能合约的构造函数参数(存入.txt文件中)等。

然而,并非所有获取的合约都将部署在测试网上进行漏洞检测。首先,获取的智能合约全部是带有源代码的合约,本文所采用的漏洞检测方法是以提取并分析处理智能合约代码中的指定信息为基础的,虽然模糊测试框架对于智能合约的分析主要是针对于字节码的(智能合约以字节码的形式被部署在以太坊平台上),但在手工对漏洞检测的结果进行审计验证时,需要对智能合约的源代码,即高级编程语言进行分析,所以,获取智能合约的源代码对于实验的正常进行有着非常重要的作用。其次,如果获取的智能合约中涉及到其他账户或智能合约地址的调用,那么该地址需是有效地址,这样才能保证部署在测试网上的智能合约可以和其他合约建立正常的交互关系。

4.3 实验过程

实验的具体过程严格遵循了测试框架进行模糊测试、漏洞检测的步骤,实验的具体细节描述如下:

本文采用的方法是以不同类型的漏洞为检测基准,先选定即将投入检测的漏洞类型,然后针对该种漏洞,对部署在测试网上的所有智能合约一一进行检测(提取相关内容并针对该种类型漏洞的测试用例进行分析),检测的内容即该合约中是否存在选定类型的漏洞。最后针对该种类型的漏洞生成一个检测结果文件,即被测的智能合约中有哪些合约包含该类型漏洞,检测结果中将这些合约的名称列出。该种方法相比于前一种方法最明显的改善是去除了很多冗余的检测。最后达到的检测效果便是每一种类型的漏洞对应一个合约名称列表,该列表中包含的便是对所有待测合约进行检测后得到的存在该种类型漏洞的智能合约。这样的方式将会明显提高漏洞检测的效率,获得的检测结果也更加直观。

对于待测智能合约的静态分析是整个漏洞检测过程的开始,同时也是支撑整个检测过程的最基本的步骤。模糊测试框架主要是对待测智能合约的ABⅠ接口以及字节码进行分析,在分析的过程中建立智能合约和ABⅠ函数、ABⅠ函数和其调用的函数两对映射关系(分析过程中会为被调用的函数计算其独一无二的函数选择器)。通过这两对映射,本文为每一个待测智能合约建立一个函数调用集合,针对函数调用集合中的每一个函数,测试框架又会为其建立一个由模糊输入组成的候选值集合,为后续的检测过程做好准备。

在完成对于待测智能合约的静态程序分析之后,便需要根据分析获得的结果生成模糊输入。这一步是整个漏洞检测过程中的核心步骤。模糊测试其实并不是对智能合约本身进行模糊化处理。因为智能合约是由一些定义交易规则规范的代码组成的程序,所以并不能对程序的代码或是程序的执行步骤进行模糊,这将会改变程序的功能或者直接导致程序崩溃,使得交易在执行时出现一系列混乱和错误。测试框架真实的模糊对象是智能合约代码中所包含的函数调用的参数。在对于智能合约的静态分析步骤,可以获得某一智能合约包含的所有函数调用集合,针对该集合中的每一个函数的参数(也可能不包含参数),模糊测试框架会根据参数的不同类型,按照不同的方案生成若干模糊候选值,在执行智能合约进行模糊测试时,将这些候选值作为参数传入函数。

智能合约的执行过程本质上就是不同账户之间根据智能合约所提供的规则(对智能合约所包含的ABⅠ函数的调用)进行交易,这些交易有可能涉及以太币的转移,也有可能是单纯的信息传递。在常规的漏洞检测流程之外,为了保证智能合约的正常执行,还需要构建交易的参与者,即外部账户。除去普通的外部账户之外,针对一些特殊的漏洞类型,例如可重入性漏洞(其特殊之处在于该种漏洞会涉及到不同合约之间的相互调用以及函数的嵌套调用),无法通过普通的交易行为来检测其是否存在于合约之中,于是便需要根据其具体的攻击场景来构建代理账户,代理账户按照会造成可重入性攻击的方式和其他用户进行交易,与此同时标记出存在可重入漏洞的合约。

完成静态分析,生成相关的模糊输入后,便开始针对某一特定类型的漏洞,对已部署的每个智能合约进行漏洞检测。首先通过随机数生成器在某一区间内生成一个整数作为对该智能合约进行漏洞检测的次数,该数字的选取区间需要适中,尽可能达到检测效率以及检测覆盖率的平衡。在每一次漏洞检测时,会随机选择该智能合约对应的函数调用集合中的某个函数进行模糊输入。模糊输入的内容便是在该函数的参数对应的已生成好的若干候选值中随机选取得到的。在一次漏洞检测的过程中涉及了多次随机选择,强调随机性的原因是为了最大限度的保证漏洞检测的覆盖率以及准确性,并通过随机化的方式来模拟智能合约在不同的函数调用顺序下的执行情况。输入完成之后,即监控智能合约的执行情况以及交易的情况。

测试框架通过以太坊虚拟机的HTTP 服务器监控智能合约执行的过程,收集交易过程中产生的一些关键信息,例如交易的发起者和接收者的地址、智能合约在执行过程中的一些特定操作码、交易涉及的以太币金额、gas的耗费以及私有链在交易过程中的状态,账户余额等,将这些信息放入栈中,根据收集到的信息对已经根据漏洞的特点构建好的测试用例进行分析、判断,从而获得最后的检测结果,即某一智能合约中是否包含某一类型的漏洞。

在经过大约40小时的实验过后,针对7种类型智能合约安全漏洞,完成了对部署在测试网上的416个智能合约的漏洞检测。

本文还与业内的其他智能合约漏洞检测工具进行了比较。本文使用开源的符号执行领域的经典工具OYENTE[11]对数据集中的全部416个智能合约进行漏洞检测,并使用同样开源的模糊测试工具ReGuard[17]对已分配给可重入性漏洞的13个智能合约实体进行漏洞检测,对它们的检测结果进行相应的对比分析。

4.4 实验结果及其分析

首先,考虑到对不同类型漏洞进行检测所需时间并不相同,所以根据检测每种类型漏洞的时间耗费,分别为7 种典型的漏洞大致分配了一定数量的智能合约供其检测。总计对416 个智能合约进行了漏洞检测。针对7 种类型的漏洞分别进行漏洞检测的智能合约的数量列举如如表1所示。

表1 漏洞类型与智能合约数量对照Table 1 Vulnerability type versus number of smart contracts

下面,将对漏洞检测实验的结果进行展示,在表2中列举了针对每种类型的漏洞检测出的不正确智能合约的数量、不正确智能合约在所有被测智能合约中的数量占比、检测出的不正确智能合约经人工分析后是否确实包含漏洞,检测正确率是多少。

表2 漏洞检测结果Table 2 Vulnerability detection results

在17 个智能合约中,工具检测出2 个包含不消耗gas的send漏洞的合约(EthronTokenPonzi和BuyerFund),在所有被检测的智能合约中占比约11.76%,根据该种漏洞的特点可以确定,当合约在执行过程中抛出了outof-gas异常,那么该合约包含不消耗gas的send漏洞,在实验的过程中,监控这两个合约的执行时,都抛出了该异常,可以认定并没有出现错报的情况,检测结果是正确的。

在38个智能合约中,本文提出的方案检测出1个智能合约包含异常无序漏洞。在对这个智能合约的代码进行分析之后发现,它们的代码中均包含嵌套调用链,再结合以太坊虚拟机对于合约执行的监控信息,嵌套调用链中抛出异常的位置不是根调用,以此可以判断,对于包含异常无序漏洞的合约的检测结果是正确的。

在13个智能合约中,检测出了1个包含可重入性漏洞的智能合约,在对于该智能合约进行人工审计验证后可以判断,其确实包含可重入性漏洞,检测结果真实可信。

对于时间戳依赖漏洞和区块号依赖漏洞,实验中部署了相对较多的智能合约进行检测。在152 个智能合约中检测出4个包含时间戳依赖漏洞,在88个智能合约中检测出3 个包含区块号依赖漏洞。在经过对于检测结果的人工检查之后发现,对于区块号依赖漏洞的检测出现了误报的情况(检测出包含区块号依赖漏洞的智能合约中,有一个智能合约并未包含相关的漏洞)。结合针对这两种类型的漏洞构建测试用例的方法以及错报的智能合约的实际情况,可以总结出错报的原因是:工具根据测试用例中的判定规则进行判断(规则即为合约代码中是否包含时间戳/区块号的操作码以及是否涉及以太币的传输,如果通过满足条件,即判定包含漏洞),出现错报情况的智能合约确实满足测试用例的判定条件,但测试用例忽略了一种特殊情况,即智能合约中既包含区块号的操作码,又涉及了以太币的交易转账,但区块号在智能合约中的使用并不是作为以太币传输等关键操作的一部分,二者并无关联。这种情况属于少数现象但确实存在,而且在这两种相似的漏洞类型中均可能出现,所以对于时间戳依赖的检测也可能出现错报。这属于对于漏洞的测试用例定义不完善所导致的错报,若要想解决这个问题,则需要在测试用例中加入关于时间戳/区块号是否与以太币转移行为有关的条件的判断,并且需要在监控合约执行的过程中额外提取一些信息,特别是对交易发起方的交易行为的监控信息。

对于危险的DelegateCall 漏洞,工具在28 个智能合约中检测出1个包含该漏洞的合约,为了确认该检测结果是否真实,对这个被检测出的包含漏洞的智能合约进行了人工审查,发现这个合约中包含DelegateCall()函数,并且该DelegateCall()调用的函数确实是从初始调用的智能合约的输入中获得的,由此发现完全符合该漏洞的测试用例条件,得到检测结果正确的结论。

对于冻结以太漏洞,80个智能合约中经过检测发现了7个包含该漏洞的合约。经过对于这7个合约的人工分析后发现,它们确实自身没有包含传输以太币的功能,必须借助于对其他合约的函数的调用才能够实现以太币转账的功能,所以可以证明检测结果真实无误。

对416个智能合约的漏洞检测结果显示,在报告的19 个包含漏洞的不正确合约中,18 个合约检测结果是正确的,1个存在错误的判断,整体上,本文提出的智能合约漏洞检测技术具有较高的检测正确率,达94.7%。

为了更加直观地展示本文提出的方法在检测以太坊智能合约漏洞的性能以及目前仍然存在的问题,本文进行了与智能合约漏洞检测领域部分相关工作的对比实验。本文提出的方法与Oyente、ReGuard两种漏洞检测工具进行对比,针对若干种相同类型的漏洞并针对相同数据集中的智能合约进行漏洞检测实验,并对实验结果的差异性进行对比分析。

Oyente 工具作为符号执行技术领域较为先进且经典的漏洞检测工具,可以对堆栈大小限制漏洞、事务顺序依赖漏洞、时间戳依赖漏洞以及可重入性漏洞四种智能合约漏洞进行检测;ReGuard是一种专门针对可重入性漏洞进行检测的模糊测试工具。表3 对本文提出的方法、Oyente以及ReGuard三者能够检测的漏洞类型进行了统计。根据统计结果,在三种检测方法涉及的9种漏洞类型中,本文提出的方法可检测其中的7种,Oyente工具可以检测其中的4 种,ReGuard 工具可以检测其中的1 种。实质上,在以太坊EⅠP150 硬分叉获得的新链上,堆栈大小限制漏洞作为一种不具备区块链特性的程序漏洞已经得到了解决。将其排除后,对于涉及到的8种漏洞,本文提出的方法可以检测其中的7 种,Oyente工具可以检测其中的3 种,Reguard 工具可以检测其中的1 种。三者中明显本文提出的方法能够检测的漏洞种类最为丰富,并且Oyente工具能够检测的3种漏洞中只有事务顺序依赖漏洞不能被本文提出的方法检测。

表3 参与对照工具可检测漏洞类型Table 3 Types of vulnerabilities detected by tools participating in comparison

本文提出的方法和Oyente 工具二者均可检测的漏洞类型为可重入性漏洞和时间戳依赖漏洞,可重入性漏洞同时也可被ReGuard 工具检测,于是在对比实验中,使用Oyente 工具和ReGuard 工具对数据集中划分给可重入性漏洞的13 个候选智能合约进行检测,并使用Oyente 工具对划分给时间戳依赖漏洞的152 个候选智能合约进行检测,得到的实验结果如表4所示。

表4 对比实验结果Table 4 Results of comparison experiment

在参与对比的三者对可重入漏洞的检测结果中,本文提出的方法检测出1个包含可重入漏洞的智能合约,经人工验证检测结果正确;Oyente工具检测出5个包含可重入漏洞的智能合约,经人工验证,其中3个为误报;ReGuard 工具检测出2 个包含可重入漏洞的智能合约,经人工验证检测结果均正确。

由此不难发现,对比其他两种工具的检测结果,本文提出的方法遗漏了一个存在可重入漏洞的智能合约。对该合约进行人工分析后发现,该合约在传输以太币之前会进行较为复杂的条件判断,在大多数情况下,所有条件判断完成后,智能合约的执行走向不涉及根调用函数经过一连串调用又回到自身的情况,无法触发本文制定的可重入漏洞的测试用例,因此导致漏报。后续还应对文中提出的漏洞检测测试用例进行进一步完善,覆盖尽可能全面的漏洞触发路径的同时确保检测的准确性。

Oyente工具采用了符号执行技术,在面对复杂的程序路径时具备优势,其在13个候选合约中检测出5个可能存在重入威胁的合约,但其中3个属于误报。误报的原因包括未能充分考虑gas不足时无法执行重入调用的情况、未能充分考虑即使存在重入调用,但被调用函数不涉及以太币的转移或涉及以太币转移的函数无法被调用的情况等。虽然Oyente 工具对于可重入漏洞的检测更加全面完善,但其对于可重入漏洞的判定标准划分不够精确,检测准确性明显低于本文提出的方法。

ReGuard工具同样采用了模糊测试技术,其在13个候选合约中检测出2 个脆弱智能合约,检测结果均正确,同时具备了漏洞检测覆盖能力以及检测准确性。ReGuard工具在产生模糊输入方面与本文不同的是,其对合约管理的交易行为进行模糊处理,生成随机的交易,在合约执行这些交易的过程中对不安全因素进行监控。交易行为必然涉及虚拟数字货币的转移,该种模糊处理方式可以对智能合约执行过程中的关键行为进行针对性关注,排除无效信息的干扰。该思路对本文工作的后续延伸及改善具有借鉴意义。相较于本文提出的方法,ReGuard工具对于可重入性漏洞具备较强的检测能力,但其能够检测的漏洞类型较为有限。

在本文提出的方法和Oyente 工具对时间戳依赖漏洞的检测结果中,本文提出的方法检测出4个包含时间戳依赖漏洞的智能合约,经人工验证检测结果正确;Oyente 工具检测出7 个包含时间戳依赖漏洞的智能合约,经人工验证,其中4个为误报。

与检测可重入漏洞时表现出的情况类似,Oyente工具面对同样的候选合约数据集,相较于本文提出的方法检测出了更多的潜在脆弱合约,但同时也存在一定的误报情况,导致误报的原因与本文提出的方法在对区块号依赖漏洞进行检测时出现的一次误报情况的原因类似,即:在检测的过程中发现智能合约中存在时间戳相关的操作,并且进行了以太币的转移,便认为该合约存在时间戳依赖漏洞,但时间戳并未直接参与以太币转移,一些通过操作时间戳进行攻击的恶意行为实质上并不会对以太币的转移造成威胁。与此同时,相较于本文提出方法的检测结果,Oyente工具遗漏了一个存在时间戳依赖漏洞的合约,经人工分析发现,该合约在进行以太币转移操作之前,调用当前时间戳进行涉及密码学加解密的条件判断操作,符号执行技术难以对该种涉及复杂数学函数的代码结构进行分析。

对比实验总结:相较于Oyente 工具,本文提出的方法在针对可重入性漏洞和时间戳依赖漏洞的检测上具备更高的准确性,并且能够检测的漏洞种类更多,在对于可重入性漏洞的检测能力上Oyente工具占优;相较于ReGuard工具,本文提出的方法虽然在针对可重入漏洞的检测能力上略显逊色,但仍具备较高的检测准确性,并在可检测漏洞种类方面明显占优。本文后续工作还应对于选择更有效的模糊种子以及制定更完善的模糊输入生成方案进行进一步探索。

5 结语

本文提出了基于模糊测试的智能合约正确性检测方案,构建了一个较为完整的基于模糊测试的漏洞检测框架,并针对当前典型的7 种具备区块链特点的漏洞,对若干智能合约进行了正确性检测。从实验结果来看,该工具生成的模糊输入有效解决了传统模糊测试方法存在的输入数据量过大影响检测效率的问题,与此同时在检测过程中的随机化过程又能从整体上保证检测的覆盖率。根据漏洞构建出的测试用例也能被监控合约执行时收集到的信息有效触发,对于智能合约是否因包含漏洞而影响正确性做出较为准确的判断。

针对这7 种类型的漏洞,对416 个合约进行了漏洞检测,报告了19个包含漏洞的不满足正确性的合约,经过对于代码的人工分析后发现,这19 个智能合约中18个存在漏洞,1个误报,具有较高的检测正确率。

针对本文进行的工作,其主要的几个技术指标:检测准确率、检测代码覆盖率、可检测的漏洞种类等都还有较大的上升空间。对于检测准确率,需继续对已知漏洞进行全面的分析,构建更加完善的测试用例,与此同时在监控过程中收集更加全面的信息,以此来尽量避免误报情况的出现;对于检测代码覆盖率,可通过统计漏洞检测过程中进行模糊处理的函数占所有代码的百分比获得覆盖率,并通过定义更加合理的随机化处理过程提高检测覆盖率。可检测的漏洞种类越多,检测工具的功能将会越发强大和完善,可以通过分析更多种已知的漏洞,构建更多种漏洞的测试用例,以此来扩展工具的检测范围。目前本文提出的模糊测试框架只能对以太坊平台上的智能合约进行漏洞检测,未来希望可以实现跨平台的智能合约正确性检测。

猜你喜欢

以太测试用例调用
以太极为旗,开启新时代“黄河大合唱”
基于SmartUnit的安全通信系统单元测试用例自动生成
核电项目物项调用管理的应用研究
LabWindows/CVI下基于ActiveX技术的Excel调用
车易链:做汽车业的“以太坊”
基于混合遗传算法的回归测试用例集最小化研究
基于系统调用的恶意软件检测技术研究
基于依赖结构的测试用例优先级技术
百通推出入门级快速工业以太网络交换器系列
以太互联 高效便捷 经济、可靠、易用的小型可编程控制器