2022-08-07星辉娱乐平台赢咖4

在今天的文章中,我将讨论对 星辉娱乐平台赢咖4 内核进行 fuzz 的方法。但首先,请谈谈我是如何走到这一步的。在过去的几年里,我一直在使用 fuzzing 来查找 星辉娱乐平台赢咖4 内核中的漏洞。5 年来,我从事了三个项目:从系统调用端对网络子系统进行模糊测试(并针对发现的错误编写了几个漏洞利用程序),之后我从外部边界对同一个网络进行模糊测试,最后对 USB 进行模糊测试从设备端子系统。在从事内核模糊测试的这几年中,我收集了大量有用的链接和开发。我已经全部订购了,并准备在本文中与您分享。

对于那些不了解 fuzzing 的人,我建议先阅读文章“什么是 Fuzzing 以及如何查找程序中的漏洞”。

星辉娱乐平台赢咖4 内核模糊测试

第一个想法:要生成输入,使用覆盖引导的方法——基于代码覆盖组装。

他是如何工作的?除了从头开始生成随机输入外,我们还维护一组先前生成的“有趣”输入——语料库。有时,我们从语料库中获取一个输入并稍微修改它,而不是随机输入。

然后我们用新的输入执行程序并检查它是否有趣。如果一个输入允许你覆盖之前执行的输入都没有覆盖的一段代码,那么它就会很有趣。如果新的输入允许我们进一步进入程序,那么我们将其添加到语料库中。

于是,我们逐渐深入,越来越深入,越来越多有趣的程序被收集到语料库中。

Linux 内核模糊测试
星辉娱乐平台赢咖4 内核模糊测试

这种方法用于在用户空间中对应用程序进行模糊测试的两个主要工具:AFL和 libFuzzer

覆盖引导方法可以与语法结合使用。如果我们修改一个结构,我们可以根据它的语法来做,而不仅仅是随机丢弃字节。如果输入是一系列 syscol,那么您可以通过添加或删除调用、重新排列它们或更改它们的参数来更改它。

对于内核的覆盖引导模糊测试,我们需要一种收集代码覆盖信息的方法。KCOV 工具就是为此目的而开发的。它需要访问源代码,但对于内核,我们拥有它们。

要启用 KCOV,必须在启用 CONFIG_KCOV 选项的情况下重新构建内核,之后可以通过 / sys / kernel / debug / kcov编译内核代码覆盖率。

KCOV 允许您从当前线程收集内核代码覆盖率,而忽略后台进程。因此,fuzzer 只能收集它播放的 syscols 的相关覆盖率。

我们捕捉错误

现在让我们想一个比内核恐慌更好的方法来检测错误。

恐慌作为错误指示器效果不佳。首先,某些错误不会导致它,例如提到的信息泄漏。其次,在内存损坏的情况下,恐慌可能比故障本身发生的时间晚得多。在这种情况下,该错误很难定位 - 目前尚不清楚模糊器的最后哪个操作导致了它。

为了解决这些问题,发明了动态错误检测器。“动态”一词意味着它们在程序执行期间工作。他们根据他们的算法分析她的行为,并试图抓住发生坏事的时刻。

有几个这样的核探测器。其中最酷的是KASAN。这很酷不是因为我在研究它,而是因为它发现了内存损坏的主要类型:数组越界和释放后使用。要使用它,启用 CONFIG_KASAN 选项就足够了,KASAN 将在后台工作,当检测到错误时将错误报告写入内核日志。

有关动态核心探测器的更多信息,请参阅 Dmitry Vyukov 的报告导师会议:乐趣和利润的动态程序分析幻灯片)

自动化

谈到自动化,您可以想到很多有趣的事情。您可以自动:

  • 监控内核日志以了解崩溃和触发动态检测器;
  • 重启死核的虚拟机;
  • 尝试通过运行崩溃前执行的最后几个输入来重现崩溃;
  • 向内核开发人员报告发现的任何错误。

如何做到这一切?编写代码并将其包含在我们的模糊器中。一个特殊的工程挑战。

一起

让我们采用这三个想法——覆盖引导方法、动态检测器的使用和模糊测试过程的自动化——并将它们包含在我们的模糊器中。我们将有以下图片。

如何运行内核? 在 QEMU 或真实硬件上
输入会是什么? 系统调用
如何将输入传递给内核? 通过运行可执行文件
如何生成输入? API+KCOV知识
如何判断是否存在bug? KASAN和其他探测器
如何自动化? 以上所有项目

如果您再次询问有知识的人哪个内核模糊器使用这些方法,他们会立即回答您:syzkallersyzkaller 目前是领先的 星辉娱乐平台赢咖4 内核 fuzzer。他发现了数千个漏洞,包括可利用的漏洞。几乎所有做过核心模糊测试的人都处理过这个模糊器。

有时您会听到 KASAN 是 syzkaller 不可分割的一部分。这不是真的。KASAN 也可以与 Trinity 一起使用,syzkaller 也可以不使用 KASAN。

fuzz 星辉娱乐平台赢咖4 内核的另一种方法

使用 syzkaller 的思想是内核模糊测试的可靠方法。但是让我们继续讨论如何使我们的模糊器变得更加精美。

将代码拉入用户空间

我们讨论了如何运行内核进行模糊测试的两种选择:使用虚拟机或铁片。但是还有另一种方法:您可以将内核代码拉入用户空间。为此,您需要获取一些独立的子系统并将其编译为库。然后可以使用对普通应用程序进行模糊测试的工具对其进行模糊测试。

对于某些子系统,这很容易做到。如果子系统只是用 kmalloc 分配内存并用 kfree 释放它,这就是内核绑定的结束,那么我们可以用 malloc 替换 kmalloc,用 free 替换 kfree。接下来,我们将代码编译为库并使用相同的libFuzzer进行模糊处理。

对于大多数子系统,这种方法会很困难。所需的子系统可以使用用户空间中根本不可用的 API。例如 RCU。

RCU(Read-Copy-Update)是星辉娱乐平台赢咖4内核中的一种同步机制。

这种方法的另一个缺点是,如果拉入用户空间的代码已更新,则必须再次将其拉出。您可以尝试自动化此过程,但这可能很困难。

这种方法已被用于对eBPFASN.1 解析器和XNU 内核的网络 

模糊外部外部接口

从用户空间到内核的数据可以通过 syscols 传输;我们已经讨论过它们。但由于内核是硬件和用户程序之间的一层,它也有来自设备端的输入。

对 Linux 进行模糊测试
对 星辉娱乐平台赢咖4 进行模糊测试

换句话说,内核处理通过以太网、USB、蓝牙、NFC、移动网络和其他硬件协议传入的数据。

例如,我们向系统发送了一个 TCP 数据包。内核必须解析它以了解它来自哪个端口以及将其传递到哪个应用程序。通过发送随机生成的 TCP 数据包,我们可以从外部对网络子系统进行模糊测试。

问题来了:如何从外部接口向内核传递数据?我们只是简单地从二进制文件中调用 siscols,如果我们想通过 USB 与内核通信,那么这种方法将行不通。

您可以通过真实硬件传递数据:例如,通过网络电缆发送网络数据包或使用USB 接口的Facedancer但是这种方法不能很好地扩展:我希望能够在虚拟机内部进行模糊测试。

这里有两种解决方案。

首先是编写您自己的驱动程序,该驱动程序将插入内核中的正确位置并在那里传递我们的数据。我们将通过 siskols 将数据传输到驱动程序本身。一些接口已经在内核中有这样的驱动程序。

例如,我通过TUN/TAP对网络进行了模糊测试。此接口允许将网络数据包发送到内核,以便数据包通过相同的解析路径,就好像它来自外部一样。反过来,对于 USB fuzzing,我必须编写自己的驱动程序

第二种解决方案是从主机端向 VM 内核提供输入。如果虚拟机模拟网卡,它也可以模拟数据包到达网卡时的情况。

这种方法用于 vUSBf fuzzer它使用 QEMU 和usbredir协议,允许您从主机连接虚拟机内部的 USB 设备。

超越 API 感知的模糊测试

以前,我们将 syscols 视为具有结构化参数的调用序列,其中一个 syscol 的结果可以在下一个中使用。但并非所有的 siscol 都以这种简单的方式工作。

示例: 克隆和  sigaction是的,它们也接受参数,它们也可以返回结果,但同时它们产生另一个执行线程。clone 创建了一个新进程,sigaction 允许您设置一个信号处理程序,该处理程序将在信号到达时进行控制。

这些 syscol 的一个好的 fuzzer 应该考虑到这个特性,例如,来自每个产生的执行线程的 fuzz。

关于复杂子系统

还有 eBPF 和 KVM 子系统。它们不是简单的结构,而是将一系列可执行指令作为输入。生成正确的指令链比生成正确的结构要困难得多。为了模糊这样的子系统,需要开发特殊的模糊器。有点像 fuzzilli 的 JavaScript 解释器fuzzer

构建外部输入

想象一下,我们正在从网络端对 星辉娱乐平台赢咖4 内核进行模糊测试。模糊网络数据包似乎与普通结构的生成和发送相同。但事实上,网络就像一个 API 一样工作,只是从外部开始。

示例:让我们对 TCP 进行模糊处理,我们在主机上有一个要从外部连接的套接字。看起来我们发送 SYN,主机用 SYN / ACK 响应,我们发送 ACK - 就是这样,连接建立。但是我们收到的 SYN/ACK 数据包包含一个确认,我们必须将其插入到 ACK 数据包中。从某种意义上说,这是从核心返回一个值,但从外部返回。

也就是说,与网络的外部交互是一系列调用(发送数据包)以及在接下来的调用中使用它们的返回值(确认号)。我们了解到网络作为 API 工作,并且 API 感知模糊测试的想法适用于它。

关于 USB

USB 是一种不寻常的协议:那里的所有通信都是由主机发起的。因此,即使我们找到了从外部连接 USB 设备的方法,也不能只向主机发送数据。相反,您需要等待来自主机的请求并响应此请求。同时,我们并不总是知道接下来会出现哪个请求。USB fuzzer 必须考虑到这个特性。

超越 KCOV

除了使用 KCOV 之外,您还能如何收集代码覆盖率?

首先,您可以使用模拟器。想象一下,虚拟机逐条模拟内核指令。我们可以渗入仿真循环并从那里收集指令地址。这种方法很好,因为与 KCOV 不同,它不需要内核源代码。因此,此方法可用于可作为二进制文件使用的专有模块。这就是模糊器TriforceAFL和 UnicoreFuzz 所做的。

收集覆盖率的另一种方法是使用处理器的硬件功能。例如,kAFL 使用 Intel PT。

应该注意的是,这些方法的上述实现是实验性的,需要进一步开发以供实际使用。

建立相关覆盖

对于覆盖引导的模糊测试,我们需要从我们正在模糊测试的子系统的代码中收集覆盖率。

到目前为止,我们已经讨论过从当前线程收集的覆盖率并不总是用于此目的:子系统可能会处理其他上下文中的输入。例如,一些 syscols 在内核中创建一个新线程并在那里处理输入。在相同的 USB 数据包的情况下,在加载内核时启动的全局线程中处理,并且不以任何方式绑定到用户空间。

为了解决这个问题,我在 KCOV 中实现了从后台线程和软件中断收集覆盖率的能力。它需要向要从中收集覆盖率的代码部分添加注释。

超越代码覆盖率收集

您不仅可以借助代码覆盖率来指导模糊测试过程。

例如,您可以监视内核的状态:监视内存部分或监视内部对象状态的变化。并向主体添加输入,将核心中的对象引入新状态。

我们在模糊测试期间将内核置于的状态越复杂,我们偶然发现它无法正确处理的情况的可能性就越大。

衬套体的组装

另一种生成输入的方法是根据真实程序的动作来生成。真正的程序已经以非平凡的方式与内核交互并深入到代码内部。即使对于非常智能的模糊器,从头开始生成相同的交互也是不可能的。

我在Moonshine项目中看到了这种方法:作者在 strace 下运行系统实用程序,从它们收集日志并使用生成的 syscol 序列作为使用 syzkaller 进行模糊测试的输入。

捕获更多错误

现有的动态检测器并不完美,可能不会注意到一些错误。如何找到这样的错误?升级探测器。

例如,您可以使用 KASAN(我提醒您它会查找内存损坏)并为一些新的分配器添加注释。默认情况下,KASAN 支持标准内核分配器,例如slab 和 page_alloc。但是有些驱动程序会分配一大块内存,然后自己将其切割成更小的块(你好,Android!)。在这种情况下,KASAN 将无法找到从一个块到另一个块的溢出。您需要手动添加注释。

还有KMSAN——它可以找到信息泄漏。默认情况下,它会在用户空间中查找泄漏。但数据也可能通过网络或 USB 等外部接口泄漏。对于这种情况,可以修改KMSAN 。

您可以从头开始制作自己的错误检测器。最简单的方法是向内核源代码添加断言。如果我们知道某个条件必须始终在某个地方满足,我们添加 BUG_ON 并开始模糊测试。如果 BUG_ON 有效,则错误被发现。我们制作了一个基本的逻辑错误检测器。这种检测器在 BPF fuzzing 的上下文中特别有趣,因为 BPF 错误通常不会导致内存损坏并且不会被注意到。

全部的

让我们总结一下。

有三种全局方法可以对 星辉娱乐平台赢咖4 内核进行模糊测试:

  1. 使用用户空间模糊器。或者使用 AFL 或 libFuzzer 之类的模糊器并重新制作它以调用 siscols 而不是用户空间程序函数。或者将核代码拉到用户空间并在那里进行模糊测试。这些方法非常适合处理结构的子系统,因为基本上用户空间模糊器专注于改变字节数组。示例:模糊文件系统和 Netlink。对于覆盖引导的模糊测试,您必须将覆盖程序集从内核连接到模糊器算法。
  2. 使用 syzcaller。它非常适合 API 感知的模糊测试。它使用一种特殊的语言 syzlang 来描述 syscols 及其返回值和参数。
  3. 从头开始编写自己的模糊器。这是从内部了解模糊测试如何工作的好方法。使用这种方法,您可以对具有不寻常接口的子系统进行模糊测试。

syzkaller 的提示

这里有一些提示可以帮助您获得结果。

  • 不要在具有标准配置的标准内核上使用 syzkaller - 你不会找到任何东西。许多人手动和使用 syzkaller 对内核进行模糊测试。此外,还有syzbot,它对云中的核心进行模糊测试。最好做一些新的事情:编写新的 syscol 描述或采用非标准的内核配置。
  • Syzkaller 可以改进和扩展。当我进行USB fuzzing 时,我在 syzkaller 之上编写了一个附加模块。
  • Syzkaller 可以用作框架。例如,拿一段代码来解析内核日志。Syzkaller 可以识别数百种不同类型的错误,这部分可以在你的 fuzzer 中重用。或者您可以使用管理虚拟机的代码,这样您就不必自己编写了。

你怎么知道你的模糊器是否工作良好?显然,如果他发现了新的错误,那么一切都很好。但是如果找不到怎么办?

检查代码覆盖率。模糊一个特定的子系统?确保你的 fuzzer 到达所有有趣的部分。
将人工错误添加到您正在模糊测试的子系统中。例如,添加断言并检查模糊器是否到达它们。这个技巧与上一个技巧有些相似,但即使你的 fuzzer 没有获得代码覆盖率,它也能工作。

回滚修复错误的补丁并确保模糊器找到它们。

如果 fuzzer 涵盖了您感兴趣的所有代码并找到了以前修复的错误,那么 fuzzer 很可能运行良好。如果没有新的错误,那么要么它们真的不存在,要么模糊器没有让内核进入足够复杂的状态,需要改进。

还有一些提示:

基于代码而不是文档编写模糊器。文档可能不准确。真相的来源永远是代码。我在制作 USB fuzzer 时遇到了这个问题:内核处理的协议子集与文档中描述的不同。

首先让你的模糊器变得聪明,然后让它变得更快。“智能”意味着生成更准确的输入、更好的覆盖范围或类似的东西,而“快速”意味着每秒执行更多。

结论

创建模糊器是一项工程工作。它基于工程技能:设计、编程、测试、调试和基准测试。

因此得出两个结论。首先,要编写一个简单的模糊器,您只需要知道如何编程。第二:要写一个很酷的 fuzzer,你需要成为一个优秀的工程师。syzkaller 之所以如此成功,是因为它投入了大量的工程经验和时间。

我希望我能很快看到一个新的不寻常的模糊器,你会写的