本文介绍了 Slither,这是一种静态分析框架,旨在提供有关以太坊智能合约的丰富信息。它通过将 Solidity 智能合约转换为称为 SlithIR 的中间表示来工作。SlithIR 使用静态单赋值 (SSA) 形式和精简的指令集,以便在保留 Solidity 转换为字节码时可能丢失的语义信息的同时,简化分析的实现。Slither 允许应用常见的程序分析技术,如数据流分析和污点跟踪。我们的框架有四个主要用途:(1)自动检测漏洞,(2)自动检测代码优化机会,(3)提高用户对合约的理解,以及(4)辅助代码审查。在本文中,我们概述了 Slither,详细介绍了其中间表示的设计,并评估了其在真实世界合约中的能力。我们展示了 Slither 的漏洞检测速度快、准确性高,并且在查找以太坊智能合约问题方面的性能优于其他静态分析工具,在速度、稳健性以及检测能力和平衡误报率方面表现出色。我们使用了一个包含大量智能合约的数据集进行工具比较,并手动审查了 1000 份最常用的合约的结果。
越来越多的行业正在使用区块链平台执行无需信任的计算,并利用智能合约。区块链技术被广泛应用于金融服务、供应链、物流和医疗保健等领域。其中,以太坊智能合约是最受欢迎的底层技术之一。这些合约通常使用高级编程语言 Solidity 编写,并被编译为以太坊虚拟机 (EVM) 的汇编指令,以部署到区块链上。然而,部署的智能合约代码往往并不安全:软件漏洞经常被发现,并已被恶意攻击者利用,造成数百万美元的损失,并损害了区块链系统的声誉。
在过去几年里,已经开发出多种用于分析和发现以太坊智能合约漏洞的工具和框架,这些工具基于静态分析和动态分析。它们采用了诸如模糊测试、符号执行、污点跟踪和静态分析等流行的程序测试技术。静态分析是检测合约潜在问题最有效的方法之一。通常,静态分析工具通过分析 Solidity 源代码或反汇编后的编译合约版本来工作。然后,它们将代码转换为内部表示,以便更易于分析和检测常见的安全问题。
理想的静态分析框架应具备以下特性:
本文介绍了 Slither,这是一款开源的静态分析框架。我们的工具提供了关于以太坊智能合约的丰富信息,并具备上述关键特性。Slither 采用了自身的中间表示 SlithIR,使 Solidity 代码的静态分析更加直观。它应用了广泛使用的程序分析技术(如数据流分析和污点跟踪)来提取和优化信息。尽管 Slither 主要被设计为面向安全的静态分析框架,但它还可以用于增强用户对智能合约的理解、辅助代码审查以及检测代码优化的缺失情况。
我们的贡献总结如下:
Slither 及我们的数据集均为开源,便于他人验证和改进我们的研究成果。本文的其余部分组织如下:第二节介绍相关工作。第三节概述 Slither 的架构,第四节详细介绍其中间表示语言 SlithIR。在第五节,我们解释了如何评估和比较 Slither 与最先进的工具,并讨论了实验结果。最后,第六节总结了本文内容并讨论了未来工作。
已有多个框架被创建,以便用户和安全研究人员分析以太坊智能合约并检测潜在问题。这些框架基于静态分析或动态分析。
这些工具依赖于对代码的分析,而不执行代码,以检测智能合约中的问题。Securify 是其中之一 [27],由 ETH Zurich 的 SRI Systems Lab 开发。它在字节码级别工作:首先解析和反编译 EVM 字节码,然后使用静态分析将生成的代码转换为语义事实。最后,它将这些事实与预定义模式列表匹配,以检测常见问题。Securify 是开源的,使用 Java 和分层 Datalog 实现。
SmartCheck 是另一个静态分析工具 [20],由 SmartDec 开发。它通过直接从 Solidity 源代码转换为基于 XML 的中间表示来工作。然后,它检查该中间表示是否符合 XPath 模式,以识别潜在的安全性、功能性、操作性和开发性问题。它也是用 Java 实现的。
Solhint 是一个用于 Solidity 代码的 Lint 工具 [18],由 ProtoFire 开发。它旨在提供安全性和风格指南验证。它是开源的,并使用 SolidityJ 解析器 [2] 由 NodeJS 实现。
其他值得注意的静态分析框架包括 Vandal [4] 和 EtherTrust [10]。
GASPER [5] 和 GasReduce [6] 使用静态分析来检测合约中的潜在优化,其中 GASPER 关注高层次优化(例如死代码),而 GasReduce 关注字节码指令模式级别的优化。两者主要关注死代码和循环优化,而不是我们在下文讨论的“可声明为常量”优化,因此这些方法在很大程度上与 Slither 的优化模式是正交的。
这些工具依赖于执行合约,利用符号执行、污点跟踪和模糊测试来发现漏洞。
Oyente 是最早的以太坊智能合约安全问题分析和检测工具之一 [14],由 Melonport 开发,其代码是开源的 [17]。
Manticore 是一个开源的符号执行工具,用于分析以太坊智能合约和二进制文件,由 Trail of Bits 创建 [24]。
Echidna [25] 也是 Trail of Bits 的产品,是一个基于属性的测试工具,专为以太坊智能合约的模糊测试而设计。
Mythril Classic 是 ConsenSys 创建的开源以太坊智能合约安全分析工具 [7],它使用联合符号执行、污点分析和控制流检查来检测各种安全漏洞。
最后,TeEther 是由 Krupp 和 Rossow 创建的自动漏洞利用生成工具,专门用于以太坊智能合约中的某些类型漏洞 [13]。截至本文撰写时,其源代码尚未公开,但作者承诺将在 2018 年 Usenix 会议演示后 90 天内开源。
Slither 是一个静态分析框架,旨在提供对智能合约代码的精细信息,并具备支持多种应用的灵活性。该框架目前用于以下用途:
Slither 通过多阶段静态分析来解析合约。首先,Slither 以 Solidity 编译器从合约源代码生成的抽象语法树(AST)作为输入。在第一阶段,Slither 恢复重要信息,如合约的继承关系图、控制流图(CFG)和表达式列表。接下来,Slither 将整个合约代码转换为其内部表示语言 SlithIR。SlithIR 使用静态单赋值(SSA)以便执行各种代码分析。在第三阶段,即实际代码分析阶段,Slither 计算一组预定义的分析,以提供增强的信息支持其他模块。图 1 总结了所有这些阶段。

Slither 概述
开源版本的 Slither 包含 20 多种漏洞检测器,包括:
此外,Slither 还可以检测其他多种已知的安全问题,如自毁合约、锁定以太币或任意发送以太币 [15]。
闭源检测器扩展了 Slither 的能力,可检测更高级的漏洞,如竞态条件、未受保护的特权函数或不正确的代币操作。
Slither 可以检测导致昂贵代码执行和部署的代码模式,这对智能合约来说尤为重要,因为低效代码会带来直接的经济成本 [5]。该框架包括以下优化检测器:
Slither 提供打印工具,使用户能够快速理解合约的功能和结构。开源打印工具包括:
用户可以使用 Slither 的 Python API3 构建第三方脚本和工具。自定义脚本可以针对特定合约需求。例如,用户可以确保某个变量不会受到特定函数参数的污染,或者某个函数只能通过合法的入口点访问。
第三方工具可以利用 Slither 的内部机制构建更高级的分析,例如基于 SlithIR 进行符号执行,或将 SlithIR 转换为其他中间表示(如 LLVM)。
Slither 由 16K 行 Python 3 代码实现,并在 GitHub4 上开源。它支持持续集成和开发者工具(如 Truffle 和 Remix)。Slither 依赖极少,仅需一个最新版本的 Solidity 编译器来解析合约的 AST。
SlithIR 是 Slither 用于表示 Solidity 代码的混合中间表示(Intermediate Representation,IR)。控制流图(Control Flow Graph,CFG)的每个节点最多可包含一个 Solidity 表达式,该表达式被转换为一组 SlithIR 指令。此表示方式使分析的实现更容易,同时不会丢失 Solidity 源代码中包含的关键语义信息。
SlithIR 使用不到 40 条指令。它没有内部控制流表示,而是依赖于 Slither 的控制流图结构(SlithIR 代码与图中的每个节点相关联)。以下是一些重要指令的高级描述,完整描述可参考 [26]。
符号表示:LV 和 RV 分别表示赋值变量(左值,Left-Value)和被读取变量(右值,Right-Value)。变量可以是 Solidity 变量,也可以是中间表示创建的临时变量。
算术运算:表示为二元或一元运算符:
映射和数组:Solidity 允许通过解引用操作映射和数组。SlithIR 使用一种特定的变量类型,称为 REF(ReferenceVariable),用于存储解引用的结果。索引运算符用于解引用变量:
结构体:访问结构体的成员使用 .
运算符:
调用:Slither 提供了对调用的深入分析,并拥有九种调用指令:
某些调用可能包含额外参数,例如 H_CALL
、L_CALL
、Send
和 Transfer
可包含与调用相关联的 Value
,表示交易的以太币金额。
额外指令:包括 PUSH
(用于数组操作)、CONVERT
(用于类型转换)以及用于操作元组的运算符。
示例:图 3 展示了 SlithIR 对图 2 代码的表示。
静态单赋值(Static Single Assignment,SSA)[19] 是编译和静态分析中常用的表示方式。它要求每个变量仅被赋值一次,并且在使用前必须被定义。例如:
转换为 SSA 形式后变为:
SSA 形式的一个主要优势是可以轻松计算定义-使用链(def-use chains),从而简化数据依赖分析。此外,SSA 形式还能促进更激进的未来分析。例如,由于智能合约的 gas 限制会对合约执行的计算成本施加约束,因此基于 SAT/SMT 的有界模型检查(Bounded Model Checking,BMC)[1] 非常适合合约分析。SlithIR 的 SSA 形式与基于图的控制流表示非常类似于 CBMC [12] 工具用于 C 语言的表示方式。
Slither 存储两种 SlithIR 版本:带 SSA 和 不带 SSA。
状态变量:
在 SSA 形式中,所谓的 phi
函数用于表示变量可能具有多个定义,并且是 SSA 形式的关键元素 [19]。智能合约特别依赖于状态变量,它们充当全局变量。在函数开始时,状态变量的值可能是其初始值,也可能是执行任意函数后的值。此外,外部调用可能会导致重入(reentrancy),从而改变状态变量的值。因此,在每个函数入口处、以及每个外部调用之后,都会为每个被读取的状态变量插入 phi
函数。
别名分析:
Solidity 允许本地变量引用状态变量,如图 4 所示。这些变量称为存储引用(storage references)。对本地变量的写操作可能影响多个状态变量。Slither 通过**别名分析(Alias Analysis)**计算存储引用的所有可能目标,并将这些信息提供给 SSA 引擎。因此,在写入存储引用后,phi
函数会被正确地插入。例如,在图 4 的 ref val += 1;
语句处,将插入两个 phi
函数,以表示 a
和 b
被更新。
已经提出了其他用于智能合约的中间表示:
SlithIR 仍然存在一些限制,并有改进空间。主要的缺陷是它缺乏形式语义(formal semantics),这使得更严格的分析变得困难。另一个限制是,该表示层级过高,无法准确反映低级信息,例如 gas 计算。
我们对 Slither 进行了三个方面的评估:(1) 漏洞检测,(2) 缺失优化检测,以及 (3) 源代码探索。Slither 及其他最新的工具均通过真实世界的合约进行评估。目前,大多数最新工具都专注于安全性问题的检测,因此 (1) 是我们的评估核心。
我们将 Slither(版本 0.5.0)与其他开源静态分析工具进行比较,以检测以太坊智能合约中的漏洞:Securify(修订版 37e2984)、SmartCheck(修订版 4d3367a)和 Solhint(版本 1.1.10)。我们决定将评估重点放在工具的可重入性检测上,因为可重入性是最早出现、被深入研究且最危险的安全问题之一。此外,这些工具均具备可重入性检测功能。
我们未考虑基于动态分析的工具,如 Manticore、Oyente 或 Mythril,因为它们已知存在可扩展性问题,且与静态分析工具并不直接竞争。
我们通过两个实验评估静态分析工具:
文献 [14]、[15]、[20] 中的其他评估通常直接从区块链中抽取合约。然而,我们发现这种选择方式存在不利的偏差,因为部署在以太坊上的大多数合约本质上是测试代码,缺乏复杂性和代码质量。因此,这些合约很可能包含易于检测的漏洞,无法反映真实世界的情况,从而导致结果失真。此前,有研究尝试基于 Zeppelin 审计选择合约进行 Solidity 分析工具的对比(包括静态和动态分析),但最终仅选取了 28 份合约 [8]。相比之下,我们通过限制比较范围而非缩小合约选择范围,从而使用了更大规模的数据集。
工具的评估基于三个指标:性能、健壮性和准确性。
如果智能合约中的函数包含对外部合约的调用,并且该调用可以用于重新进入该函数本身,则该函数被认为是可重入的。可重入漏洞可能导致多种攻击,包括以太币的损失,特别是在外部调用发生后才修改状态变量的情况下。DAO 黑客攻击 [16] 和 SpankChain 事件 [22] 使该问题广为人知。
图 5 展示了一个经典的可重入性示例。由于可重入漏洞的可利用性已经非常明确,大多数以太坊智能合约漏洞检测工具都会在发现类似代码时发出警告。
可重入模式并不少见,但在大多数情况下,其影响并不严重,主要有两个原因:
在我们的评估中,如果工具在合约中标记出了无害的可重入性,则该结果被视为误报。
表 I 的最后两列显示了各工具是否检测到了 DAO 和 SpankChain 漏洞。值得注意的是,这些工具在两个示例中均报告了多个可重入性问题。然而,我们仅在工具能够检测到实际被利用的可重入性漏洞时,才认为结果有效。Slither 是唯一能够找到这两个真实世界可重入性漏洞的工具。
在第二个实验中,我们使用 1000 份合约数据集,在每个合约上运行这些工具,并设置 120 秒的超时,仅使用可重入性检测器。我们手动禁用了其他检测规则,以避免在测量中引入偏差。
由于时间限制,我们仅手动审查了每个工具随机抽取的至少 50 份标记合约,而不是所有可能的无害可重入性问题。
实验表明,Slither 是最准确的工具,误报率最低(10.9%),其次是 Securify(25%)。SmartCheck 和 Solhint 的误报率较高,分别为 73.6% 和 91.3%。
此外,我们统计了至少标记出一个可重入问题的合约数量(标记的合约)以及每个标记合约的平均发现数量。SmartCheck 标记了大量合约,证实了其高误报率,而 Securify 标记的合约数量极少,表明该工具未能发现许多其他工具检测出的真实漏洞。
在这些实验中,Slither 在检测可重入性漏洞方面表现优于其他分析工具,能够发现真实漏洞,同时保持较低的误报率。然而,我们的实验仍然存在许多局限性。
例如,Securify 的误报率基于极少的发现结果(仅 8 个)。此外,我们的数据集中有多个合约共享代码片段,这可能会影响结果的准确性。例如,Slither 的误报主要来自少数几个重复出现在多个合约中的函数。尽管存在这些局限性,实验仍然表明 Slither 比其他工具更准确、更成熟。
我们还发现,由于对 Solidity 语法理解不足,SmartCheck 和 Solhint 产生了大量误报。例如,这些工具误将 Solidity 的 super
关键字(用于调用继承函数)识别为外部调用。

评估结果总结
为了测试 Slither 的优化能力,我们选择了检测器来查找那些本应声明为常量(constant)的变量。如果一个变量被声明为常量,它将不会占用合约的存储空间,并且在使用时需要的指令更少。因此,在可能的情况下使用常量变量可以减少代码大小(从而降低合约的部署成本)以及使用成本。据我们所知,Slither 是唯一能够发现此类代码优化的可用工具。
我们在两个数据集上应用了该检测器:(1)用于实验 2(第 V-A 节)选择的 1000 个合约,以及(2)来自 [28] 的 35,000 个合约。我们发现,在(1)中有 54% 的合约,在(2)中有 56% 的合约包含一个或多个本应声明为常量的变量。其中一些变量从未被访问过,甚至可以完全移除。
这一结果表明,Slither 可以有效地用于检测实际的代码优化。
Slither 提供了多种可视化输出,以增强代码理解。最接近的提供类似功能的工具是 Surya5。Surya 解析合约的 AST,并提供一系列输出。它仅执行语法分析,而不提供语义分析。
表 II 展示了 Slither 输出与 Surya 功能的比较。这两种工具提供了类似的功能,区别在于 Surya 输出 AST 的文本表示和函数调用追踪,而 Slither 提供了变量的读写信息。两者都可以生成报告,但内容有所不同。Surya 显示合约和函数名称,以及可变性和修饰符调用,而 Slither 显示发现的错误数量、代码复杂度(基于圈复杂度),并基于一系列启发式方法(例如 ERC20 代币的铸造限制)提供特定上下文的高级信息。
Slither 包含 Surya 提供的信息,同时还能整合更高级的信息,因为它对代码库有更深入的理解。
最重要的有效性威胁在于,我们的主要实验仅限于在有限的合约集上进行的重入漏洞检测。我们用于确定有意义的合约集的度量标准(交易数量)是合理的 [3],但尚未被社区标准化。对于一些最近提出的智能合约分析框架(例如 SmartAnvil [9]),我们不清楚如何使用它们,因此未将其纳入我们的实验。
为了使我们的结果可重复,并允许检查我们的代码和分析方法,我们已公开了实验设置6。
我们提出了 Slither,这是一个用于智能合约的开源静态分析框架。Slither 快速、稳健、准确,并能提供丰富的智能合约信息。我们的框架利用了 SlithIR,这是一种专为 Solidity 代码的实际静态分析设计的中间表示。Slither 是唯一一个能够同时用于发现错误、建议代码优化、提高合约代码理解的平台。它被设计为易于扩展,并可作为第三方工具的基础。
我们通过将 Slither 在重入漏洞检测方面的表现与其他现有的先进工具进行比较,评估了其错误检测能力。我们发现,Slither 在性能、稳健性和准确性方面均优于其他工具。
我们可以采取多个方向来改进 Slither。由于该工具被日常用于审计,首要的改进方向是集成新的问题检测器。对 SlithIR 进行优化可以生成更高效的代码。在我们的中间表示之上构建符号执行或有界模型检查,可使错误检测器轻松访问形式化验证,并进行基于循环边界的最坏情况 Gas 成本分析。
Slither 分析器最初设计用于 Solidity 代码,但该平台及其中间表示可以适配其他智能合约语言,例如 Vyper。最后,如果能将 SlithIR 转换为 EVM 或 Ewasm 字节码,Slither 还可以用作编译器。

Slither 打印功能与 Surya 的比较
LV
=
RV BINARY RV
LV
=
UNARY RV
LV
=
RV BINARY RV
LV
=
UNARY RV
REF ← Variable[Index]
REF ← Variable.Member
#Solidity示例代码
using
SafeMath
for
uint;
mapping(address => uint) balances;
function transfer(address to, uint val)
public
{
balances[msg.sender] = balances[msg.sender].min(val);
balances[to] = balances[to].add(val);
}
#Solidity示例代码
using
SafeMath
for
uint;
mapping(address => uint) balances;
function transfer(address to, uint val)
public
{
balances[msg.sender] = balances[msg.sender].min(val);
balances[to] = balances[to].add(val);
}
#上面Solidity代码的SlithIR代码示例代码
Function transfer (address , uint256 )
Solidity : balances [msg. sender] = balances [msg. sender ]. sub ( val )
SlithIR :
REF_0( uint256 ) > balances [msg. sender]
REF_1(uint256 ) > balances [msg. sender]
TMP_1( uint256 ) = LIB CALL SafeMath . sub(REF_1 , val )
REF_0 := TMP_1(uint256)
Solidity : balances [ to ] = balances [ to ]. add( val )
SlithIR :
REF_3( uint256 ) > balances [ to ]
REF_4(uint256 ) > balances [to]
TMP_3( uint256 ) = LIB CALL, dest :SafeMath .add(REF_4 , val)
EF_3 := TMP_3( uint256 )
#上面Solidity代码的SlithIR代码示例代码
Function transfer (address , uint256 )
Solidity : balances [msg. sender] = balances [msg. sender ]. sub ( val )
SlithIR :
REF_0( uint256 ) > balances [msg. sender]
REF_1(uint256 ) > balances [msg. sender]
TMP_1( uint256 ) = LIB CALL SafeMath . sub(REF_1 , val )
[培训]科锐逆向工程师培训第53期2025年7月8日开班!