如何使用Slither发现智能合约漏洞
如何使用Slither
本教程的目的是演示如何使用Slither自动查找智能合约中的漏洞。
安装
Slither需要Python3.6及以上版本。 它可以通过pip安装或使用docker。
通过pip安装Slither:
pip3 install --user slither-analyzer
通过docker安装Slither:
docker pull trailofbits/eth-securitytoolboxdocker run-it -v "$PWD":/home/trufflecon trailofbits/eth-securitytoolbox
上文中最后一个命令在docker中运行eth-security-toolbox命令。该eth-security-toolbox命令需要能访问你的当前目录。 你可以从自己的主机更改文件,并在docker中运行针对文件这些工具。
在docker中,运行如下命令:
sol-select 0.5.11cd /home/trufflecon/
运行脚本
使用python 3运行如下python脚本:
python3 script.py
命令行
命令行与用户定义的脚本。Slither带有一组预定义的探测器,可以找到许多常见的漏洞。 从命令行调用Slither将运行所有的检测器,不需要使用者具有详细的静态分析知识。
slither project_paths
除了检测器之外,Slither还通过其打印机(opens in a new tab)和工具(opens in a new tab)提供代码审查功能。
使用 crytic.io(opens in a new tab) 以获得对私有探测器和 GitHub 集成功能的访问权限。
静态分析
Slither静态分析框架的功能和设计已在博客文章(1(opens in a new tab),2(opens in a new tab))和一篇学术论文(opens in a new tab)中进行了描述。
静态分析有不同的风格。 你很可能意识到,像clang(opens in a new tab)和gcc(opens in a new tab)这样的编译器依赖于上述这些研究技术,但它也是(Infer(opens in a new tab)、CodeClimate(opens in a new tab)、FindBugs(opens in a new tab)和基于形式化方法的一些工具,比如Frama-C(opens in a new tab)和Polyspace(opens in a new tab)。
我们不会在这里详尽地回顾静态分析技术和研究人员。 相反,我们将专注于了解Slither是如何工作的,以便你能更有效地利用它来发现漏洞和理解代码。
代码表示
与对单一的执行路径进行推理的动态分析相比,静态分析会对所有的执行路径进行一次性的推理。 为此,它依赖于一个不同的代码表示方式。 最常见的两个是抽象语法树(AST)和控制流图(CFG)。
抽象语法树(AST)
每次编译器解析代码时都会用到AST技术。 它可能是进行静态分析的最基本结构。
简而言之,AST是一棵结构化的树,通常每片叶子包含一个变量或一个常量,而树的内部节点是操作符或控制流操作。 考虑以下代码:
1function safeAdd(uint a, uint b) pure internal returns(uint){2 if(a + b <= a){3 revert();4 }5 return a + b;6}复制
相应的AST如图所示:
Slither使用由solc工具导出的AST。
虽然构建简单,但AST是一个嵌套结构。 有时,这并不是最直观的代码分析方法。 例如,为了确定表达式a + b <= a
所使用的运算,你必须首先分析<=
,然后分析+
。 一个常见的方法是使用所谓的访问者模式,它以递归方式在树上进行遍历。 Slither在ExpressionVisitor
(opens in a new tab)中包含一个通用的访问者程序。
下面的代码使用ExpressionVisitor
来检测一个表达式是否包含加法。
1from slither.visitors.expression.expression import ExpressionVisitor2from slither.core.expressions.binary_operation import BinaryOperationType34class HasAddition(ExpressionVisitor):56 def result(self):7 return self._result89 def _post_binary_operation(self, expression):10 if expression.type == BinaryOperationType.ADDITION:11 self._result = True1213visitor = HasAddition(expression) # expression is the expression to be tested14print(f'The expression {expression} has a addition: {visitor.result()}')显示全部复制
控制流图(CFG)
第二种最常见的代码表示是控制流图(CFG)。 顾名思义,它是一种基于图的表示方法,展现了所有的代码执行路径。 每个节点包含一条或多条指令。 图中的边代表控制流操作(if/then/else,循环,等等)。 我们上一个例子的CFG是:
大多数的代码分析技术都是建立在CFG的基础表示之上。
也存在许多其他的代码表示方法。 根据你想进行的代码分析的不同场景,每种代码表示方法都有其优点和缺点。
分析
你可以在Slither工具中进行的最简单类型的分析是语法分析。
语法分析
Slither可以浏览代码的不同组成部分及其表示方法,使用类似模式匹配的方法找到代码内部不一致的地方和代码缺陷。
例如,以下检测器可以寻找与语法有关的问题。
状态变量映射(opens in a new tab):遍历所有的状态变量,并检查是否有任何变量映射来自于继承的合约(state.py#L51-L62(opens in a new tab))的变量。
不正确的ERC20接口(opens in a new tab):寻找不正确的ERC20函数签名(incorrect_erc20_interface.py#L34-L55(opens in a new tab))。
语义分析
与语法分析相比,语义分析将更深入地分析代码的 "含义"。 这个系列包括一些宽泛的分析类型。 这些分析会产生更强大和有用的分析结果,但编写起来也更复杂。
语义分析常常被用于最先进的代码漏洞检测。
数据依赖性分析
如果变量variable_a
的值受到变量variable_b
的影响,那么就说变量variable_a
是数据依赖于变量variable_b
的。
在下面的代码中,变量variable_a
是依赖于变量variable_b
的。
1// ...2variable_a = variable_b + 1;复制
Slither内置了数据依赖(opens in a new tab)分析功能,这要归功于它的中间表示法(在后面的部分讨论)。
在严格平等危险的检测器(opens in a new tab)中可以找到一个数据依赖的使用例子。 这里Slither将对危险值进行严格平等比较(incorrect_strict_equality.py#L86-L87(opens in a new tab)),同时,将通知用户应该使用>=
或<=
(而不是 ==
)去阻止攻击者使合约进入代码陷阱。 另外,这个检测器会认为balanceOf(address)
的调用结果值是一个潜在的危险(incorrect_strict_equality.py#L63-L64(opens in a new tab)),同时,会使用数据依赖引擎来跟踪其使用情况。
定点计算
如果你的分析在CFG的节点中遍历并沿着CFG的边进行,你可能会看到一些已经访问过的节点。 例如,出现如下所示的循环代码:
1for(uint i; i < range; ++){2 variable_a += 13}复制
你的分析将需要知道何时停止。 这里有两种主要策略:1)在每个节点上迭代有限次数,2)通过计算所谓的定点。 一个定点基本上意味着分析此节点不会提供任何有意义的信息。
在代码可重入检测器中可以找到使用的定点的示例:Slither探索这些节点,寻找外部调用、写入和读取存储。 一旦到达某个定点 (reentrancy.py#L125-L131(opens in a new tab)),分析器就会停止代码遍历,并通过不同的重入模式来分析结果,了解是否存在代码重入现象。(reentrancy_benign.py(opens in a new tab)、reentrancy_read_before_write.py(opens in a new tab)、reentrancy_eth.py(opens in a new tab))。
使用高效的定点计算方法编写分析,需要很好地理解分析是如何传播其信息的。
代码的中间表示
中间表示(IR)是一种比原始语言更适合进行静态分析的语言。 Slither将Solidity转换为它自己的IR:SlithIR(opens in a new tab)。
如果你只是想编写基本的代码检查,那么理解SlithIR不是必须的。 但是,如果你打算编写更高级的语义分析,对SlithIR的理解将派上用场。 SlithIR(opens in a new tab)和SSA(opens in a new tab)打印器工具将帮助你理解代码是如何被翻译的。
API基础知识
Slither有一套API,可让你探索合约的基本属性及其功能。
加载代码库:
1from slither import Slither2slither = Slither('/path/to/project')3复制
探索合约和相关的函数
一个Slither
对象具有:
contracts (list(Contract)
:合约列表contracts_derived (list(Contract)
:未被其他合约继承的合约列表(合约的子集)get_contract_from_name (str)
:从合约名称返回一个合约实例
一个Contract
对象具有:
name (str)
:合约名称functions (list(Function))
:函数列表modifiers (list(Modifier))
:函数列表all_functions_called (list(Function/Modifier))
:合约可访问的所有内部函数列表inheritance (list(Contract))
:继承的合约的列表get_function_from_signature (str)
:通过函数签名返回一个函数get_modifier_from_signature (str)
:从函数签名返回一个修改器get_state_variable_from_name (str)
:从其名称返回一个状态变量
一个Function
或Modifier
对象具有:
name (str)
:函数的名称contract (contract)
:声明该函数的合约nodes (list(Node))
:组成该函数/修改器的CFG的节点列表entry_point (Node)
:CFG的入口点variables_read (list(Variable))
:所读取变量的列表variables_written (list(Variable))
:所写入变量的列表state_variables_read (list(StateVariable))
:所读取状态变量的列表(所读取变量的子集)state_variables_written (list(StateVariable))
:所写入状态变量的列表(所写入变量的子集)
上次修改时间: @nhsz(opens in a new tab), 2023年8月15日