ContractFuzzer: Fuzzing Smart Contracts for Vulnerability Detection
软工顶会 ASE18
基于智能合约的ABI生成fuzz的输入,针对不同漏洞类型定义了新的测试oracle,利用EVM记录智能合约的运行时行为,分析日志来报告安全漏洞。实验对6991份智能合约标记出了459个漏洞。
INTRODUCTION
截止 2018.3, 以太坊智能合约和DApps的数量超过2百万,以太坊账户管理的ether超过9800万,约为5800 USD。管理如此多财富的智能合约成为了黑客的攻击目标。
智能合约容易受攻击的原因:
- 智能合约的执行依赖于区块链和其他智能合约,开发者可能忽视合约间的隐含关系
- 编程语言和运行时环境比较新颖,开发者对缺陷理解不到位
- 智能合约的不可篡改性使得难以在部署后更新,漏洞修复可能需要较长时间
其他解决方案存在的问题:
- 不准确,误报率较高
- 符号验证所有的可能路径存在路径爆炸问题,如果只验证部分路径,也可能导致假阴性。
贡献
- 第一个以太坊智能合约fuzzing的框架
- 提供了一系列新的测试oracle
- 系统的测试了6991个真实的合约
A REVIEW OF SMART CONTRACTS
区块链的状态 $s$ 是从地址到账户的映射,账户可以由人类和程序拥有。以太坊可以看做是基于交易的状态机,状态由每个交易更新,交易的有效性由共识协议验证。交易实际上是一个账户向另一个账户发送的消息,可以包括二进制数据(输入)和ether
安全威胁:blockchain level, EVM level, and smart contract level.
Moreover, even using the block.blockhash() function with block.number as parameters for random number generation is still vulnerable either due the execution mechanism of EVM or due to the transparency of the blockchain.
DEF1N1NG TEST1NG ORACLES FOR VULNERAB1L1T1ES OF SMART CONTRACTS
- Gasless Send:
send()
是由call()
实现的,检查call()
的输入为0且gas limit是2300,检查send()
是否返回ErrOutOfGas\ - Exception Disorder:根调用没有返回异常但是嵌套的调用返回异常,即一场没有正确地传播到根调用
- Reentrancy
- 检查函数调用 A 是否在源自调用 A 的调用链中出现多次
- 检查是否有
call()
调用并且值大于0;是否有足够的gas来执行复杂操作;被call()
调用的是工具自身的代理合约 - ContractFuzzer 只有在能够成功对目标合约发起重入攻击时才会标记重入漏洞
- Timestamp/Block Number Dependency
- 检查是否调用了TIMESTAMP操作码
- 检查是否
call()
是send()
,发送了ether - 检查
call()
的值是否大于0
- DelegateCall
- 检查是否存在deletegatecall的调用
- 调用的函数从输入中获取
- FreezingEther
- 检查是否能收到ether
- 是否使用delegatecall,不存在transfer/send/call/suicide等将ether转移到其他地址的调用
- 标记出余额>0,无法采用自身合约转出ether
THE SMART CONTRACT FUZZER
Overview
0-5 共6步
包括离线EVM检测工具和在线模糊测试工具,EVM进行合约的运行,模糊测试工具提取信息
采用爬虫将Ehterscan网站的合约的二进制码 ABI 构造参数等爬下来,部署在测试网上
合约用来作为模糊测试的对象和被其他合约调用
步骤2 用于测试合约间的交互
工具将生成符合ABI规范的有效模糊测试输入,以及在步骤3中跨越有效性边界的突变输入。
Static Analysis of Smart Contracts
extract the signatures of the public functions supported by those contracts.
该工具基于每个智能合约的 ]SON 格式导出的 ABI,提取 ABI 中声明的所有函数签名,计算函数签名前4字节的哈希值作为function selector并构建key-value(selector-address vector)的映射
将ABI的JSON解析,提取函数描述和参数的数据类型,address类型必须采用能够提供相应函数的合约的地址
For a given ABI function of a smart contract under test, how can we effectively determine subset of smart contracts that it can interact with? function selector,call的参数和函数签名的前4个字节对应
是否正确
首先反汇编,提取出ABI,提取出函数体,定位函数代码段,如果行以PUSH4开头,当前行加入到selector当中,最后输出Map,搜索Map查询所有的支持selector作为ABI的合约地址放进一个私有的智能合约池中用来进行交互
Fuzzing Input Generation
生成固定长度和非固定长度的输入的算法不同
- 固定长度 INT\
, UINT\ , BYTES\ 和数组 \ [M] - 首先在有效输入域上随机生成值
- 然后我们还构建了另一组种子输入,这些种子输入在基于静态分析的智能合约中经常使用
- 两类结合作为候选值
- 非固定长度的值 bytes string \
[] - 随机生成一个正整数作为长度,随机从输入域上选
为每个函数都生成候选输入的集合,因此循环执行生成k个实例作为候选集,编码成字节码供调用
为重入漏洞生成输入需要在两个合约间交互式调用
BountyHunt send ether with a call at line 5 before setting the value of the corresponding bounryAccount to 0 at line 7.
Instrumenting EVM to Collect Test Oracles
需要收集的三类信息:
- call和delegatecall的属性
- 调用的操作码
- 执行时合约的状态
为了支持重入漏洞和异常中断漏洞的检测,记录了Call的链
EVM.Call() and EVM.DelegateCall() function 来记录call的信息
interpreter.Run() 来记录opCode
Vulnerability Analysis and Report
当初始调用的调用堆栈变为空时,EVM中的检测模块将首先根据收集到的检测信息检查这些子oracle。然后,检测模型将这些子oracle发送到侦听本地主机端口8888的HTTP服务器。服务器将收集子oracle的结果,然后对每个测试oracle的复合条件执行最终检查。
EXPERIMENT AND RESULTS ANALYSIS
PC中配置了两个dockers来帮助设置测试客户端和以太坊测试网络。在docker运行测试客户端中,在node.js运行时中使用以太坊javascript API (web3.js库)与testnet docker中的geth客户端进行交互。测试网络docker安装geth客户端1.7.0版本,然后还在该docker中创建了一个以太坊私有区块链,其中一个对等节点作为测试网络。
当时Etherscan有9960个合约,全部爬下来去掉了无法部署的合约,剩下6991
contract creation code (bytecode), the ABI interfaces, and the constructor argument of those contracts作为输入
首先进行静态分析,得到每个ABI的私有的合约池并提取出ABI函数用于生成输入
对于每个合约采用3类账户进行调用:创建者账户;外部账户;AttackAgent(重入)
每个账户2种调用模式:带有ehter和无ether的
对于每个 ABI 函数,如果包含参数,ContractFuzzer 将生成 k 个输入来调用它。否则,我们将简单地对其执行一次调用。结合 3 种账户类型和 call.value() 的 2 种选择,我们将为带参数的函数生成 6k 次调用,为不带参数的函数生成 6 (3\2) 次调用。用一个较大的值初始化 k,以便 ContractFuzzer 可以有广泛的候选调用可供选择。当模糊特定的智能合约时,我们会将为其每个 ABI 函数生成的所有调用合并到智能合约的调用池中。然后ContractFuzzer启动HTTP服务器来收集和分析测试预言机。对于每个智能合约,ContractFuzzer 从其调用池中随机选择调用来执行模糊测试,模拟智能合约函数的不同调用顺序。最后,服务器收集并分析结果以形成报告。经过大约 80 个小时的模糊测试后,我们停止了实验,直到结果逐渐收敛。
和Oyente verification tool进行实验结果对比
除了时间戳和区块号依赖有假阳率,其他均没有,手动进行了核查
误报情况的原因是,对这两种类型的漏洞的测试预言机定义不精确。事实上,在这两种情况的测试预言机定义中,检查了操作码(TIMESTAMP 和 NUMBER)的使用以及测试的 ABI 函数中ether转移调用的使用。还没有检查在读取 TIMESTAMP 和 NUMBER 和使用它们来计算以太币转移的条件之间是否存在数据流定义使用链。但是,记录此类信息可能涉及通过检测所测试的智能合约进行昂贵的数据流跟踪。考虑到实际较高的真阳性率,作者认为ContractFuzzer的解决方案是一种具有成本效益的权衡。
Oyentle支持4种漏洞检测,调用深度漏洞已经在EIP150 hardfork中修复了,交易顺序依赖攻击contractFuzzer不支持,只比较了时间戳依赖和重入攻击
对于时间戳依赖漏洞,contractFuzzer假阴(未检测出)的原因:
- 时间被硬编码,并将区块时间戳与之进行比较,但是不涉及以太币转移 Table.7
- 在有限的测试时间内,某些条件很难触发。例如,一个合约在触发时间戳相关的以太币传输之前需要函数的特定调用模式。我们可以使用不同的函数调用串行执行更广泛的模糊测试来改善这种情况。comprehensive input generation schemes
对于重入漏洞,contractFuzzer假阴的原因是一些智能合约的漏洞在转移以太之前必须执行复杂的条件检查。然而,这些条件很难由ContracFuzzer触发。
Oyente假阳的三类:
- uses send() and transfer() operation with a limited gas,被调用者没有足够的gas进行重入
- 严格检查caller是否为合约的拥有者
- 只能将ehter转给硬编码的地址
未来研究方向:
- 降低假阳率
- 增加支持的漏洞类型
- 扩展到其他区块链平台
配置实验环境
首先下载姜博老师在github.com提供的container,结果居然在百度龟速盘,卒
容器大小约为3g,考虑到用网络从windows传给虚拟机比较慢,设置了vmware的共享文件夹,不是很好用
参考samba配置,在Ubuntu配置了samba服务器,可以在windows访问,实际传输文件约为80Mbps
1 | $ docker load<contractfuzzer.tar |
启动docker后,运行命令
1 | $ ./run.sh --contracts_dir ./examples/exception_disorder |
大概运行了2h
查看reporter目录下的文件
contract_fuzzer
is one part of ContractFuzzer, which generates contract call messages based on contract’s ABI definition;contract_tester
is one part of ContractFuzzer, which sends the contract call messages to our instrumented Geth client.
1 | /ContractFuzzer/examples/exception_disorder/fuzzer/reporter # cat tester_run.log |
“The existing private blockchain has slowed down over time and now the mining speed is too low. truffle-contract is timing out while trying to push the transaction”
实际更换在windows的wsl上运行docker后不再存在这个问题(依然存在
和xls交流后记录
- 学习智能合约的开发:https://github.com/smartcontractkit/full-blockchain-solidity-course-js
- 测试网领取测试币
- chainlink:Get Sepolia Testnet LINK Tokens | Chainlink Faucets 20 test LINK, 0.1测试币
-
- Sepolia Faucet,每天0.5ETH
- Goerli Faucet,每天0.02ETH,Goerli需要在主网上充值0.001eth,测试网领取测试币
继续学习contractFuzzer的实现,尝试将链部署在Ganache上
- 以后写合约考虑使用Hardhat网络