跳转至主要内容

如何使用Slither发现智能合约漏洞

solidity智能合约安全性测试静态分析
高级
Trailofbits
构建安全的合约(opens in a new tab)
2020年6月9日
11 分钟阅读 minute read

如何使用Slither

本教程的目的是演示如何使用Slither自动查找智能合约中的漏洞。

  • 安装
  • 命令行使用方法
  • 静态分析简介:静态分析简介
  • API:Python API说明

安装

Slither需要Python3.6及以上版本。 它可以通过pip安装或使用docker。

通过pip安装Slither:

pip3 install --user slither-analyzer

通过docker安装Slither:

docker pull trailofbits/eth-securitytoolbox
docker run-it -v "$PWD":/home/trufflecon trailofbits/eth-securitytoolbox

上文中最后一个命令在docker中运行eth-security-toolbox命令。该eth-security-toolbox命令需要能访问你的当前目录。 你可以从自己的主机更改文件,并在docker中运行针对文件这些工具。

在docker中,运行如下命令:

sol-select 0.5.11
cd /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如图所示:

抽象语法树(AST)

Slither使用由solc工具导出的AST。

虽然构建简单,但AST是一个嵌套结构。 有时,这并不是最直观的代码分析方法。 例如,为了确定表达式a + b <= a所使用的运算,你必须首先分析<=,然后分析+。 一个常见的方法是使用所谓的访问者模式,它以递归方式在树上进行遍历。 Slither在ExpressionVisitor(opens in a new tab)中包含一个通用的访问者程序。

下面的代码使用ExpressionVisitor来检测一个表达式是否包含加法。

1from slither.visitors.expression.expression import ExpressionVisitor
2from slither.core.expressions.binary_operation import BinaryOperationType
3
4class HasAddition(ExpressionVisitor):
5
6 def result(self):
7 return self._result
8
9 def _post_binary_operation(self, expression):
10 if expression.type == BinaryOperationType.ADDITION:
11 self._result = True
12
13visitor = HasAddition(expression) # expression is the expression to be tested
14print(f'The expression {expression} has a addition: {visitor.result()}')
显示全部
复制

控制流图(CFG)

第二种最常见的代码表示是控制流图(CFG)。 顾名思义,它是一种基于图的表示方法,展现了所有的代码执行路径。 每个节点包含一条或多条指令。 图中的边代表控制流操作(if/then/else,循环,等等)。 我们上一个例子的CFG是:

控制流图(CFG)

大多数的代码分析技术都是建立在CFG的基础表示之上。

也存在许多其他的代码表示方法。 根据你想进行的代码分析的不同场景,每种代码表示方法都有其优点和缺点。

分析

你可以在Slither工具中进行的最简单类型的分析是语法分析。

语法分析

Slither可以浏览代码的不同组成部分及其表示方法,使用类似模式匹配的方法找到代码内部不一致的地方和代码缺陷。

例如,以下检测器可以寻找与语法有关的问题。

语义分析

与语法分析相比,语义分析将更深入地分析代码的 "含义"。 这个系列包括一些宽泛的分析类型。 这些分析会产生更强大和有用的分析结果,但编写起来也更复杂。

语义分析常常被用于最先进的代码漏洞检测。

数据依赖性分析

如果变量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 += 1
3}
复制

你的分析将需要知道何时停止。 这里有两种主要策略: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 Slither
2slither = 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):从其名称返回一个状态变量

一个FunctionModifier对象具有:

  • 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日

本教程对你有帮助吗?