利用机器学习和Slither-simil实现高效审计

本文介绍Trail of Bits如何利用机器学习工具Slither-simil分析Solidity代码相似性,自动化检测智能合约漏洞,提升审计效率并减少人工错误,涵盖技术实现和挑战。

利用机器学习和Slither-simil实现高效审计

Trail of Bits手动积累了丰富的数据——多年的安全评估报告——现在我们正在探索如何利用这些数据,通过Slither-simil使智能合约审计过程更高效。

基于先前审计中嵌入的累积知识,我们着手检测新客户代码库中类似的易受攻击代码片段。具体来说,我们探索了机器学习(ML)方法,以自动改进Slither(我们的Solidity静态分析器)的性能,并使审计员和客户的生活更轻松。

目前,具有Solidity及其安全细微差别专家知识的人工审计员扫描和评估Solidity源代码,以在不同粒度级别发现漏洞和潜在威胁。在我们的实验中,我们探索了如何自动化安全评估,以:

  • 最小化重复人为错误的风险,即忽略已知、记录的漏洞的机会。
  • 帮助审计员更快、更轻松地筛选潜在漏洞,同时降低误报率。

Slither-simil

Slither-simil是Slither的统计补充,是一个代码相似性测量工具,使用最先进的机器学习来检测类似的Solidity函数。当它去年作为实验开始时,代号为crytic-pred,它用于向量化Solidity源代码片段并测量它们之间的相似性。今年,我们将其提升到一个新水平,并直接应用于易受攻击的代码。

Slither-simil目前使用自己的Solidity代码表示SlithIR(Slither中间表示),在函数粒度级别编码Solidity片段。我们认为函数级分析是开始研究的好地方,因为它既不太粗糙(如文件级别),也不太详细(如语句或行级别)。

图1:Slither-simil过程工作流的高级视图。

在Slither-simil的过程工作流中,我们首先从先前的存档安全评估中手动收集漏洞,并将它们转移到漏洞数据库。请注意,这些是审计员在没有自动化的情况下必须找到的漏洞。

之后,我们编译了先前客户的代码库,并通过自动化函数提取和规范化脚本将它们包含的函数与我们的漏洞数据库匹配。在此过程结束时,我们的漏洞被规范化为SlithIR令牌,作为我们ML系统的输入。

以下是我们如何使用Slither将Solidity函数转换为中间表示SlithIR,然后进一步令牌化和规范化它作为Slither-simil的输入:

1
2
3
4
5
6
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_value <= allowance[_from][msg.sender]);     // Check allowance
        allowance[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }

图2:来自合约TurtleToken.sol的完整Solidity函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Function TurtleToken.transferFrom(address,address,uint256) (*)

Solidity Expression: require(bool)(_value <= allowance[_from][msg.sender])
SlithIR: 
         REF_10(mapping(address => uint256)) ->    allowance[_from]
         REF_11(uint256) -> REF_10[msg.sender]
         TMP_16(bool) = _value <= REF_11
         TMP_17 = SOLIDITY_CALL require(bool)(TMP_16)

Solidity Expression: allowance[_from][msg.sender] -= _value
SlithIR: 
         REF_12(mapping(address => uint256)) -> allowance[_from]
         REF_13(uint256) -> REF_12[msg.sender]
         REF_13(-> allowance) = REF_13 - _value

Solidity Expression: _transfer(_from,_to,_value)
SlithIR: 
         INTERNAL_CALL,      TurtleToken._transfer(address,address,uint256)(_from,_to,_value)

Solidity Expression: true
SlithIR: 
         RETURN True

图3:同一函数及其SlithIR表达式打印输出。

首先,我们将每个语句或表达式转换为其SlithIR对应项,然后对SlithIR子表达式进行令牌化,并进一步规范化它们,以便尽管此函数的令牌与漏洞数据库之间存在表面差异,但会出现更多相似匹配。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type_conversion(uint256)

binary(**)

binary(*)

(state_solc_variable(uint256)):=(temporary_variable(uint256))

index(uint256)

(reference(uint256)):=(state_solc_variable(uint256))

(state_solc_variable(string)):=(local_solc_variable(memory, string))

(state_solc_variable(string)):=(local_solc_variable(memory, string))

...

图4:先前表达式的规范化SlithIR令牌。

在获得此函数的最终形式令牌表示后,我们将其结构与漏洞数据库中易受攻击函数的结构进行比较。由于Slither-simil的模块化,我们使用各种ML架构来测量任意数量函数之间的相似性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ slither-simil test etherscan_verified_contracts.bin --filename TurtleToken.sol --fname TurtleToken.transferFrom --input cache.npz --ntop 5

Output:
Reviewed 825062 functions, listing the 5 most similar ones:

filename            contract      function     score
...
TokenERC20.sol      TokenERC20    freeze       0.991
...
ETQuality.sol       StandardToken transferFrom 0.936
...
NHST.sol            NHST          approve      0.889

图5:使用Slither-simil测试智能合约函数与其他Solidity合约数组。

让我们看看ETQuality.sol智能合约中的函数transferFrom,看看它的结构如何类似于我们的查询函数:

1
2
3
4
5
6
7
8
9
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
    if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
        balances[_to] += _value;
        balances[_from] -= _value;
        allowed[_from][msg.sender] -= _value;
        Transfer(_from, _to, _value);
        return true;
    } else { return false; }
}

图6:来自ETQuality.sol智能合约的函数transferFrom。

比较两个函数中的语句,我们可以很容易地看到它们都包含,按相同顺序,二进制比较操作(>=和<=),相同类型的操作数比较,以及另一个类似的赋值操作,带有内部调用语句和返回“true”值的实例。

随着相似性得分向0降低,这些结构相似性被观察到的频率降低,而在另一个方向上;两个函数变得更相同,因此相似性得分为1.0的两个函数彼此相同。

相关研究

过去两年,Solidity中自动漏洞发现的研究已经起飞,像Vulcan和SmartEmbed这样的工具,使用ML方法发现智能合约中的漏洞,正在显示有希望的结果。

然而,所有当前相关方法都专注于像Slither和Mythril这样的静态分析器已经可检测的漏洞,而我们的实验专注于这些工具无法识别的漏洞——具体来说,那些未被Slither检测到的漏洞。

过去五年的许多学术研究都专注于将ML概念(通常来自自然语言处理领域)并在开发或代码分析上下文中使用它们,通常称为代码智能。基于此研究领域先前相关的工作,我们旨在弥合人类审计员和ML检测系统在发现漏洞方面性能之间的语义差距,从而用自动化方法(即机器编程或MP)补充Trail of Bits人类审计员的工作。

挑战

我们仍然面临数据稀缺的挑战,关于可用于分析的智能合约规模以及其中有趣漏洞出现的频率。我们可以专注于ML模型,因为它很性感,但在Solidity的情况下,它对我们没有多大好处,因为即使语言本身也非常年轻,我们需要谨慎对待我们可用的数据量。

归档先前客户数据本身就是一项工作,因为我们必须处理不同的solc版本来单独编译每个项目。对于在该领域经验有限的人来说,这是一个挑战,我在此过程中学到了很多。(我暑期实习的最重要收获是,如果你在做机器学习,除非你必须做数据收集和清理阶段,否则你不会意识到它们是多大的瓶颈。)

图7:10次安全评估中发现的89个漏洞的分布。

饼图显示了我们调查的10次客户安全评估中89个漏洞的分布情况。我们记录了显著漏洞和那些Slither无法发现的漏洞。

Slither-simil的未来之路

去年夏天,我们恢复了Slither-simil和SlithIR的开发,有两个目标:

  • 研究目的,即开发端到端相似性系统,缺乏特征工程。
  • 实际目的,即增加特异性以提高精确度和召回率。

我们实现了基于文本的基线模型与FastText,以与改进模型进行比较,结果有显著差异;例如,一个不基于软件复杂性指标,但专注于基于图的模型,因为它们目前是最有前途的。

为此,我们提出了一系列技术在最高抽象级别(即源代码)上尝试Solidity语言。

为了开发ML模型,我们考虑了监督和无监督学习方法。首先,我们开发了一个基于令牌化源代码函数并将它们嵌入欧几里得空间(图8)的基线无监督模型,以测量和量化不同令牌之间的距离(即不相似性)。由于函数由令牌构成,我们只是添加差异以获得任何大小的任何两个不同片段之间的(不)相似性。

下图显示了一组训练Solidity数据中的SlithIR令牌在三维欧几里得空间中球化,相似令牌在向量距离上更接近。每个紫色点显示一个令牌。

图8:包含一组训练Solidity数据中的SlithIR令牌的嵌入空间

我们目前正在开发一个专有数据库,包括我们以前的客户及其公开可用的易受攻击智能合约,以及论文和其他审计中的参考文献。它们将共同形成一个统一的全面Solidity漏洞数据库,用于查询、后期训练和测试新模型。

我们还在开发其他无监督和监督模型,使用由Slither和Mythril等静态分析器标记的数据。我们正在检查具有更多表达性的深度学习模型,我们可以用它们建模源代码——具体来说,基于图的模型,利用抽象语法树和控制流图。

我们期待检查Slither-simil在新审计任务上的性能,看看它如何提高我们保证团队的生产力(例如,在分类和更快地找到低 hanging fruit)。当它更成熟和自动可扩展时,我们还将测试它在Mainnet上。

您可以在此Github PR上尝试Slither-simil。对于最终用户,它是最简单的CLI工具可用:

  • 输入一个或多个智能合约文件(目录、.zip文件或单个.sol)。
  • 识别预训练模型,或在合理数量的智能合约上单独训练模型。
  • 让魔法发生,并检查相似性结果。
1
$ slither-simil test etherscan_verified_contracts.bin --filename MetaCoin.sol --fname MetaCoin.sendCoin --input cache.npz

结论

Slither-simil是一个强大的工具,有潜力测量用Solidity编写的任何大小的函数片段之间的相似性。我们正在继续开发它,基于当前结果和最近相关研究,我们希望在今年年底前看到有影响力的真实世界结果。

最后,我要感谢我的主管Gustavo、Michael、Josselin、Stefan、Dan和Trail of Bits的其他人,他们使这成为我经历过的最非凡的实习经历。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计