代码审计循序渐进:第一部分

本文是作者分享的进行代码审计的步骤,主要分为理解代码和发现漏洞两个阶段。第一部分主要讲解如何高效地理解代码,包括建立基础知识、绘制代码流程图、阅读代码、并做笔记记录审计过程中的疑问、猜想和关键点。

Image

审计一步一步来:第一部分

  • 我觉得我花了比必要的更多的时间和精力来执行我的审计

  • 我觉得我可能因为我错误的审计方法而遗漏了漏洞

  • 我觉得我在审计中达到了一个瓶颈,即使可能还有漏洞,我也找不到任何东西

这些都是我刚开始审计时困扰我的问题。

如果你对这些感觉中的任何一个感同身受,请继续阅读。在本文中,我将解释我用来执行我自己的审计的逐步过程,我对其进行了优化,以解决这些问题。

这不是审计的“正确方式”,而是对我有用的“一种方式”。

该方法包括 2 个非常简单的步骤:

  1. 详细了解代码

  2. 寻找破坏它的方法

编辑:这篇文章原本应该涵盖这两个步骤,但是第一步太长了,所以我将其分为第 1 部分(如下)和第 2 部分(稍后发布)。

1. 了解代码

阅读代码应该是你在审计中所做的主要事情。这是我为了最有效地阅读代码所做的事情:

1.1 打好基础

1.2 绘制代码流程图

1.3 阅读代码

1.4 做笔记

1.1 打好基础

我总是通过获取我即将阅读的协议应该做什么的基本、高级的想法来开始我的审计。

我花在这上面的时间取决于代码库的复杂程度以及我对它实现的理念的熟悉程度。它可以是 15 分钟到 1-2 小时之间的任何时间。

我做什么:

  • 阅读 README

  • 浏览文档

  • 阅读比赛描述(如果代码是用于比赛的)

我不做什么:

  • 观看代码演练(稍后会详细介绍)

👉 如果我不能用人类的语言向自己解释协议,我就无法理解代码。

1.2 绘制代码流程图

在我开始阅读代码之前,我会构建它的心理地图,因为对我来说理想的审计方式是跟随用户与协议交互的流程:

  1. 首先,了解合约设置

  2. 然后,查看入口函数

  3. 然后,了解用户可以采取哪些替代路径

  4. 最后,查看出口函数

但是,当我打开一个代码库时,我不知道什么是入口函数或什么是出口函数。所以我浏览一下范围内的代码:

  • 列出所有 external/public 函数

  • 记下每个函数写入和读取哪些状态变量

  • 快速浏览一下注释或 NatSpec 以获取提示

有时函数名称或注释会告诉我从哪里开始。例如,在借贷协议中,deposit() 是一个入口点,这非常明显。

但有时情况并非如此。

发生这种情况时,我依赖于对状态变量的分析:如果一个函数写入一个状态变量,但不从中读取任何变量(或仅从合约设置中设置的变量读取),那么这可能是一个入口函数。

👉 如果你了解写入特定变量的函数如何工作,那么了解读取该变量的函数如何工作也会更容易。

1.3 阅读代码

现在我知道了协议的作用以及用户将以什么顺序与之交互,我开始阅读代码。

协议通常是模块化的,因此相同的内部函数会在多个外部函数中重复使用。这通常是复杂性的主要来源。

复杂性令人麻痹。因此,我不是一次性面对复杂性,而是分层解决它:

1. 起初,我以一种肤浅的方式阅读函数。

Image

complexFunction()

假设正在审计的外部函数具有一个 calculatedVariable,该变量是通过 complexFunction() 计算的。

在第一次尝试时,我会记下这一点,并完全跳过 complexFunction()。

我以这种方式浏览所有外部函数,表面上涵盖合约。

关键是不要陷入复杂性。

原因是起初,我需要很长时间才能理解 complexFunction()。但是通过更多关于整个协议如何工作的上下文,我可以更快更容易地理解它。

👉 在这一步中,我花大量时间在代码的简单部分,而几乎不花时间在代码的复杂部分。

2. 然后,我更进一步

在我阅读了所有外部函数之后,我对主要的交互有了很好的了解:

  • 哪些函数写入或读取哪些状态变量

  • 值如何在协议内流动

  • 协议内有哪些可能的路径

考虑到所有这些,以前看起来复杂的部分现在看起来简单得多。

所以我再次阅读代码。现在我已经了解了代码中比较表面的部分,我可以跳过它。所以我将注意力转移到更复杂的部分。

但我并不一定准备好一次性处理所有的代码复杂性:

Image

evenMoreComplexFunction()

有时逻辑太复杂,有许多条件路径或深层调用追踪(我已经审计过深度超过 10 层的协议)。

当发生这种情况时,我无法一次性理解它。所以我和第 1 步中做的事情一样:专注于我目前可以理解的内容,跳过其余部分。

👉 在这一步中,我几乎不花时间在代码的简单部分(因为我已经理解了它),而花大量时间在代码的复杂部分。

3. 然后,我再次更进一步(一次又一次)

现在我再次重复上面的第 2 步,直到我对整个协议有一个清晰的理解。

👉 在每一轮中,我花在早期关注的部分的时间越来越少,而花在之前跳过的部分的时间越来越多。

奖励。代码演练

在上面步骤 1-3 之间的某个时间点,当我至少对代码如何工作有了基本的了解时,我观看了代码演练。

当我对代码如何工作一无所知时,观看代码演练对我来说完全没有用。

1.4 做笔记

做笔记是根本。

我记下 3 件事:

  • 可能是错误但我不理解它们如何工作的疑问

  • 可能是错误的假设,但我还没有足够的深度来确认/否认

  • 合约中不太直接或我以后可能会忘记的方面

👉 这些笔记允许我从我的记忆中卸载假设。这样,我可以继续我的工作流程而不会中断,因为我知道我不会丢失任何线索。

我使用这些笔记的确切方式是本文第二部分的主题之一。

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

0 条评论

请先 登录 后评论
philbugcatcher
philbugcatcher
江湖只有他的大名,没有他的介绍。