智能合约测试新方法:手动引导模糊测试 - Ackee Blockchain

  • Ackee
  • 发布于 2024-09-13 19:55
  • 阅读 36

本文介绍了智能合约模糊测试中的一种新型方法:手动引导模糊测试 (Manually Guided Fuzzing),它结合了状态模糊测试和白盒模糊测试的优点,通过定义“流”来指导测试过程,从而更有效地发现漏洞。文章还对比了手动引导模糊测试与其他模糊测试技术,如黑盒模糊测试、属性模糊测试和差异模糊测试,并提供了实际案例和资源。

Fuzzing 是一种广为人知的软件测试技术,尤其适用于智能合约的测试。传统上,作为 Fuzzing,测试人员主要理解的是 黑盒 Fuzzing,或者更高级的 基于属性的 Fuzzing。然而,一种新的创新方法 手动引导的 Fuzzing 提供了增强的效率和有效性,填补了传统 Fuzzing 技术留下的空白。本文将探讨引导式 Fuzzing 与黑盒 Fuzzing、基于属性的 Fuzzing、差异 Fuzzing 和其他技术的不同之处。

理解基础知识:有状态 vs. 无状态,以及黑盒 vs. 灰盒 vs. 白盒 Fuzzing

Fuzzing 技术可以根据其状态性和提供的指导级别进行分类:

有状态 vs. 无状态 Fuzzing

无状态 Fuzzing:在无状态 Fuzzing 中,每个测试用例都是独立的。Fuzzer 不会维护任何关于先前状态或输入的知识,这限制了它发现与操作顺序相关的 Bug 的能力。

有状态 Fuzzing:此方法会考虑系统的状态,这意味着一个测试用例的结果会影响后续的测试用例。有状态 Fuzzing 对于测试像智能合约这样复杂的系统更有效,在这些系统中,状态转换至关重要。请参阅 此代码片段 中的差异,该代码片段用 Wake 框架 编写。

黑盒 vs. 灰盒 vs. 白盒

黑盒 Fuzzing:Fuzzer 在不知道被测系统内部工作原理的情况下运行。它必须知道接口,但它依赖于随机输入和启发式技术来探索状态空间,以识别意外行为或崩溃。虽然在某些情况下有效,但黑盒 Fuzzing 非常耗时且耗费资源,通常导致不完整的覆盖率。

灰盒 Fuzzing:Fuzzer 具有关于被测系统内部工作原理的一些信息。这个定义非常广泛,一个例子是定义的 不变量(参见基于属性的 Fuzzing)。

白盒:相比之下,Fuzzer 被提供了关于被测系统的所有信息。这种方法允许更有针对性和效率的 Fuzzing,专注于关键代码路径和特定场景,从而更快、更可预测地检测到 Bug(参见手动引导的 Fuzzing)。

获得更多:高级 Fuzzing 技术

有更多高级技术允许在更多极端情况的场景中进行测试:

差异 Fuzzing:Fuzzer 将被测系统的输出与参考系统的输出进行比较。参考可以是系统的模型,用高级语言(如 Python)实现。测试的主题也可以是与实现相同功能的现有库进行比较的单个函数。差异 Fuzzing 是一种优雅的技术,主要用于数学函数,可以揭示 Solidity 的许多舍入和精度错误,并且在与有状态 Fuzzing 结合使用时变得非常强大。差异 Fuzz 测试的一个例子是 IPOR 审计,其中 Ackee Blockchain Security 将合约中连续复利计算的实现与量身定制的 Python 模型进行了比较,从而产生了一个严重和两个高严重性问题。

Fork 测试:用于测试具有复杂依赖项或集成(Aave、Chainlink)的项目。Fork 测试涉及在从实时网络 Fork 的开发链(如 Anvil)上运行测试。这允许系统与真实世界的数据和状态进行交互,而无需重新部署和模拟合约存储。使用真实世界的数据可确保比数据模拟(形式验证中使用的一种技术)更准确和全面的测试。Fork Fuzz 测试的一个例子是 Lido Stonks 审计,其中 Ackee Blockchain Security Fork 了以太坊主网,以在真实条件下测试协议。使用 Fork 的 USDT 合同导致发现了一个中等严重性的集成问题,请参阅完整的源代码 此处

探索基于属性的 Fuzzing

基于属性的 Fuzzing,或 不变量测试,介于黑盒和白盒 Fuzzing 之间。在这里,测试人员(对被测系统有一定的了解)定义了系统必须始终满足的特定属性,称为 不变量,而不管输入如何。然后,Fuzzer 尝试生成违反这些属性的输入。通过测试人员引入不变量,Fuzzer 在了解系统属性的情况下运行,这赋予了它“灰盒”的称号。虽然比黑盒 Fuzzing 更受控制,但基于属性的 Fuzzing 仍然难以完全探索复杂的状态空间,尤其是在像复杂智能合约这样状态丰富的系统中。

不变量是在每次状态更改(交易)后执行的测试。

@invariant()
def invariant_balance(self) -> None:
    assert self.token.balanceOf(self.admin) == self.balances[self.admin]

介绍手动引导的 Fuzzing

手动引导的 Fuzzing 结合了有状态 Fuzzing 和白盒 Fuzzing 的优势,添加了 的概念,以提供结构化的测试方法。此方法要求测试人员完全了解被测系统并指导 Fuzzing 过程,确保彻底检查代码的关键部分。

是系统内的一系列操作或交易。例如,一个流可能涉及将代币从一个帐户转移到另一个帐户。测试人员定义这些流以确保 Fuzzer 以重要代码路径为目标。

流是在测试序列中执行的单个测试步骤。流是使用 @flow 装饰器定义的:

@flow(precondition=lambda self: self.count > 0)
def flow_decrement(self) -> None:
    self.counter.decrement(from_=random_account())
    self.count -= 1

手动引导的 Fuzzing 的工作原理

使用 Wake 框架,手动引导的 Fuzzing 涉及以下几个步骤:

  1. 定义不变量:与基于属性的 Fuzzing 类似,手动引导的 Fuzzing 需要定义在执行流后应保持为真的属性或不变量。例如,在代币转账之后,不变量可以是代币的总供应量保持不变,并且余额已正确更新。
  2. 定义流:现在测试人员指示 Fuzzer 应如何与合约交互。如果们坚持之前的例子,它将启动代币转账。
  3. 结合流和不变量:手动引导的 Fuzzing 通过结合随机流来创建更高效的 Fuzzing 过程,每个流都伴随着所有不变量检查。与随机探索状态空间不同,此方法允许测试人员专注于代码的特定区域,从而显著减少所需的时间和计算资源。

手动引导的 Fuzzing 的(缺)优点

  • 效率:通过使用特定流和属性指导 Fuzzer,手动引导的 Fuzzing 显著减少了探索的状态空间,从而减少了找到 Bug 所需的时间。
  • 灵活性:测试人员可以完全控制 Fuzzing 过程,使他们能够测试特定场景,包括有状态的交互、跨链交易和复杂的合约依赖项,这些场景通常难以用黑盒 Fuzzing 或形式验证覆盖。
  • 能力越大,责任越大:测试人员必须识别代码中可能的危险信号,并决定使用 Fuzz 测试覆盖它们。通常,测试人员为所有公共状态更改函数定义流,并选择正确的函数的输入值。如果测试人员错过了这些危险信号或没有攻击向量的想法,则 Fuzzing 活动将不会发现漏洞。

结论

手动引导的 Fuzzing 代表了一种责任转移,从使用暴力或启发式算法的测试人员转移回审计师和安全研究人员手中,他们必须引导 Fuzz 测试走向攻击向量以发现漏洞。手动引导的 Fuzzing 为测试像高度集成的智能合约这样的复杂系统提供了一种更高效、更精确和更可扩展的方法。使用支持手动引导的 Fuzzing、Fork 测试和差异测试的 Wake 框架探索上述技术。

额外资源

以下示例展示了不同类型 Fuzz 测试的真实世界用法:

  • 原文链接: ackee.xyz/blog/introduci...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ackee
Ackee
Cybersecurity experts | We audit Ethereum and Solana | Creators of @WakeFramework , Solidity (Wake) & @TridentSolana | Educational partner of Solana Foundation