HFL: Hybrid Fuzzing on the Linux Kernel

一、背景相关

1.1 会议背景

NDSS 会议(The Network and Distributed System Security Symposium)是和CCS,USENIX SECURITY及IEEE S&P并称的计算机系统安全领域的四大顶级会议。网络和分布式系统安全研讨会(NDSS)促进了网络和分布式系统安全的研究人员和从业人员之间的信息交换。目标受众包括对网络和分布式系统安全性的实际方面感兴趣的人员,重点是实际的系统设计和实现。一个主要目标是鼓励和使Internet社区能够应用,部署和提高可用安全技术的状态。

NDSS 2020会议于2020年2月23日至26日在加利福尼亚州圣地亚哥的双体船度假酒店及水疗中心举行。关于模糊测试,本次会议主要收录了四篇论文,分别是:

\1. HYPER-CUBE: High-Dimensional Hypervisor Fuzzing

\2. HFL: Hybrid Fuzzing on the Linux Kernel

\3. HotFuzz: Discovering Algorithmic Denial-of-Service Vulnerabilities Through Guided Micro-Fuzzing

\4. Not All Coverage Measurements Are Equal: Fuzzing by Coverage Accounting for Input Prioritization

1.2 作者信息

本篇论文是由六位作者合作完成的,分别是:Kyungtae Kim、 Dae R. Jeong、 Chung Hwan Kim、 Yeongjin Jang、Insik Shin*、*Byoungyoung Lee。其中一作为Kyungtae Kim,来自普渡大学,其主要研究方向为系统和软件安全、程序分析、深度学习安全等。

在这里插入图片描述

图 作者信息

1.3 论文摘要

Abstract—Hybrid fuzzing, combining symbolic execution and fuzzing, is a promising approach for vulnerability discovery because each approach can complement the other. However, we observe that applying hybrid fuzzing to kernel testing is challenging because the following unique characteristics of the kernel make a naive adoption of hybrid fuzzing inefficient: 1) having indirect control transfers determined by system call arguments, 2) controlling and matching internal system state via system calls, and 3) inferring nested argument type for invoking system calls. Failure to handling such challenges will render both fuzzing and symbolic execution inefficient, and thereby, will result in an inefficient hybrid fuzzing. Although these challenges are essential to both fuzzing and symbolic execution, to the best of our knowledge, existing kernel testing approaches either naively use each technique separately without handling such challenges or imprecisely handle a part of challenges only by static analysis.

To this end, this paper proposes HFL, which not only combines fuzzing with symbolic execution for hybrid fuzzing but also addresses kernel-specific fuzzing challenges via three distinct features: 1) converting indirect control transfers to direct transfers, 2) inferring system call sequence to build a consistent system state, and 3) identifying nested arguments types of system calls. As a result, HFL found 24 previously unknown vulnerabilities in recent Linux kernels. Additionally, HFL achieves 15% and 26% higher code coverage than Moonshine and Syzkaller, respectively, and over kAFL/S2E/TriforceAFL, achieving even four times better coverage, using the same amount of resources (CPU, time, etc.). Regarding vulnerability discovery performance, HFL found 13 known vulnerabilities more than three times faster than Syzkaller.

摘要:混合模糊测试技术,结合了符号执行和模糊测试,是一种很有前途的漏洞发现方法,因为每种方法都可以互补。 然而,我们观察到,将混合模糊应用于内核测试是具有挑战性的,因为内核的以下独特特性使得混合模糊效率低下:1)具有由系统调用参数决定的间接控制传输,2)通过系统调用控制和匹配内部系统状态,3)推断调用系统调用的嵌套参数类型。 如果不处理这些问题,将会导致模糊和符号执行效率低下,从而导致低效的混合模糊。 虽然这些挑战对于模糊和符号执行都是必不可少的,但据我们所知,现有的内核测试方法要么天真地单独使用每种技术,而不处理这些挑战,要么只通过静态分析而不精确地处理部分挑战。

为此,本文提出了HFL,它不仅将模糊与混合模糊的符号执行相结合,而且还通过三个不同的特性来解决内核特定的模糊挑战:1)将间接控制传输转换为直接传输;2)推断系统调用序列以建立一致的系统状态;3)识别系统调用的嵌套参数类型。 因此,HFL在最近的Linux内核中发现了24个以前未知的漏洞。 此外,HFL的代码覆盖率分别比Moonshine和Syzkaller高15%和26%,超过kAFL/S2E/TriforceAFL,使用相同数量的资源(CPU、时间等),实现了更好的四倍。)。 关于漏洞发现性能,HFL发现13个已知漏洞比Syzkaller快三倍多。

二、问题的提出

2.1模糊测试本身的问题

普通随机测试仅限于处理紧密的分支条件,因为生成一个输入来探索这样的分支需要从巨大的搜索空间中猜测一个值。使用随机生成的输入进行测试不能探索超出严格分支条件的程序路径,例如,if(I==0xdeadbeef),因为它需要从一个巨大的(32)空间中猜测一个值。

2.2符号执行本身的问题

符号执行是一种程序测试技术,它可以生成一个输入,将目标程序的执行驱动到特定的程序路径。 为此,符号执行将目标程序的所有输入作为符号,并继续跟踪程序的运行时上下文作为符号表达式。 例如,当符号执行满足条件分支时,它将跟踪分支条件作为相对于符号的路径约束。当执行到达一个感兴趣的程序路径,符号执行可以生成一个输入,通过解决符号上的符号约束来将程序驱动到该路径。符号执行的一个关键限制是它受到状态爆炸问题的影响。每当执行满足条件分支时,就会发生这种情况。 更具体地说,由于符号执行满足条件分支,它必须探索分支的两侧,在通过这样一个分支后,要探索的路径(即状态)的数量增加一倍。 典型的程序包含大量的条件分支,更糟糕的是,处理输入的循环将使待探索路径的数量增长得更快。

2.3混合模糊执行遇到的问题

鉴于随机模糊测试和符号执行的优点和缺点,提出了混合模糊[32,34]可以是这两种方法的一般扩展。 混合模糊测试将模糊测试和符号执行结合起来,以补充上述每种方法的局限性。 换句话说,当随机模糊测试被紧密的分支条件阻塞时,符号执行就会出现救援;当符号执行受到状态爆炸问题的影响时,随机模糊测试可以帮助精确一条特定的探索路径,从而避免状态爆炸。但是,这样还是对于内核来说,还是有问题,因为内核的主要三点特性带来的挑战:

1)首先,Linux内核使用许多间接控制传输来支持多态,这使得传统的测试效率低下

2)很难推断系统调用的正确序列(和依赖关系),以便构建触发漏洞所需的系统状态

3)系统调用参数中的嵌套结构使接口模板化变得困难.

2.3.1 间接控制传输

**示例:RDMA/AUTOFS***中的间接控制流。**图1所示的示例说明了在驱动程序中使用函数指针表引起间接控制传输的情况,其中远程直接内存访问(RDMA)网络上的数据通信是管理/操作的。 在本示例中,ucma_write()使用函数指针表,即ucma_table,通过用数据的头信息索引表,由用户级输入char__userbuf控制。 函数表ucma_table保存一组函数指针(第1-8行),其中每个函数指针实现网络通信的特定功能,如连接和绑定。 数组中分配的函数在第16行间接调用。 特别是,来自userspace的值hdr.cmd(从buf复制)作为函数表的索引。 根据索引值,将从表中执行不同的函数。 Autofs是另一个例子,一个自动安装各种文件系统的服务程序。在图2中,autofs_ioctl充当调度器,它使用函数指针表_ioctls调用各种底层控制函数。 以类似的方式,从用户空间派生的cmd通过间接函数调用隐式地影响下面的控制流传输。

图1-2-1图1-2-2

2.3.2 系统调用依赖

Linux为了提供文件系统访问,内核维护每个文件的文件描述符,其状态可以定义为{open、close}。 具体来说,为了响应open 系统调用,返回一个新的文件描述符,其中内核内部状态初始化为open。 然后,只有在打开文件描述符的状态(在读/写系统调用中指定)时,下面的读/写系统调用才能工作。 因此,只应在打开的系统调用之后调用读/写系统调用。 否则,读/写系统将立即返回一个错误,从而限制了进一步的代码覆盖探索。

例: DRM中的复杂Syscall序列。 直接渲染管理器(DRM)负责管理Linux系统中的图形设备和内存。 图3展示了DRM驱动程序中ioctl syscall依赖的简化示例。 在示例中,如果cmd的值是DRM_ALLOC ,则内核执行drm_alloc。 它初始化ID,然后通过ID字段(第24行)返回给用户程序)。 此初始化的ID值将是下一个访问DRM设备的syscall的锚,其中ID值引用先前DRM_ALLOC的DRM设备。 这种后续用例之一是使用cmd值作为DRM_BIND。 在这种情况下,内核执行drm_bind并传递ID值以检查一致性(第32行)。

要增强示例中的覆盖率,我们应该考虑两个基本条件。 首先,我们维护syscalls的调用顺序(即调用两个ioctl,其中第一个使用cmd DRM_ALLOC第二个使用cmd DRM_BIND)。 接下来,当调用这两个syscall时,也应该保留syscall参数依赖项。 换句话说,通过arg参数指定的ID字段应该被传播-当调用cmd DRM_BIND时,它的ID字段应该使用从cmd DRM_ALLOC返回的ID值。

在这里插入图片描述

图3

2.3.3 嵌套结构参数

**例: USBMon中的嵌套Syscall参数。**4显示了一个简化的功能proc_control,它基本上控制连接的USB设备。 从ioctl syscall派生的函数需要多层内存缓冲区,这些缓冲区最初由其第二个参数。 在第9行,首先将外部内存缓冲区复制到分配的内核缓冲区ctrl中。 在没有错误的情况下返回,由先前复制的缓冲区(即ctrl.data)的字段成员指向的后续内存副本发生在第11行的一定大小(即ctrl.len。请注意,没有内置的知识,这样的嵌套和可变大小的形式的syscall参数,执行可能会停止(在第9行或第11行),因为内存访问无效,然后才能到达其主要功能(第14行)。

在这里插入图片描述

图4

三、问题的解决方法

3.1 转换控制-从间接到直接的流

HFL设计了一个基于内核源代码的离线翻译器,将间接控制流转换为直接控制流.当面对间接控制流时,执行以下过程:1)翻译器确保函数指针表的索引变量来源于syscall参数。 2)给定函数表及其可行索引值,HFL执行分支转换(类似于循环展开)-对于每个索引值,HFL插入一个条件分支跳转到相应的函数指针。 下图总结了代码转换的简化结果。

在这里插入图片描述

图6

3.2 通过系统调用序列推断连贯的系统状态

论文提出了通过系统调用序列推断连贯的系统状态。主要分为三个步骤,:1.静态分析以找到候选依赖对。2.运行时验证以确定真正的依赖,3.符号检查以检测参数相关性。

  1. 静态分析以找到候选依赖对。作为第一步,HFL执行静态分析以捕获候选依赖对。 特别是,HFL执行过程间点-对目标内核进行分析,收集一对读/写操作,即一条指令执行读指令,另一条指令执行写,其中两条指令都从同一个内存位置读取和写入。 我们将这些读/写操作对称为候选依赖对。 请注意,此分析在执行混合模糊之前是脱机执行的,下一阶段是混合模糊的一部分,它以这样的候选依赖对作为输入。

  2. 运行时验证以确定真正的依赖。给定一组候选依赖对,HFL现在开始具体执行内核。 为了过滤掉由于点到分析固有的假阳性问题而产生的假依赖,HFL在这个阶段执行基本的验证。 更具体地说,当HFL象征性地执行任何候选依赖对的两个指令时,HFL检查这些是否访问相同的地址。 如果是这样,则表示此依赖项对中的指令确实相互依赖,从而产生真正的依赖项对。 值得注意的是,一旦识别了这个真正的依赖项,HFL就能够推断系统调用调用顺序-也就是说,执行写操作的系统调用必须在执行读操作的系统调用之前调用,因为写操作可以初始化读操作所依赖的值。 如果没有事先执行写,包含读的系统调用可能只是返回一个错误。

  3. 符号检查以检测参数相关性。除了确定系统调用的顺序外,依赖对还确定跨系统调用的多个参数(如§III-C所示)。 为了了解这一点,HFL利用符号约束信息,来自符号化的系统调用参数。 具体来说,HFL跟踪依赖对象引起的值的流,并分别计算其读/写操作的输出/绑定点。 此外,这允许从系统调用中的符号化参数内存中识别相关内存位置(偏移量)及其大小。

由于HFL的模糊测试不断地与符号分析器交互,HFL立即将由Concolic执行产生的排序集的输出反馈给模糊测试,并在以后的突变中反映最新的系统排序信息。 以这种方式,HFL继续识别新的系统关系并更新结果,直到其终止。

示例:将系统调用依赖于重构系统状态。 图7使用图3中的示例描述了我们的系统调用序列推理的工作流。 给定一个用户程序和(候选)指令依赖项,HFL开始具体执行程序(➊)。 同时,属于对的指令被放置在观察中。 一旦HFL在给定对中(按任何顺序)(➋)命中两个指令,它将检查该对以进行运行时验证。 特别是,HFL从两个指令中获取依赖关系中的两个对象(即操作数),然后查看它们的内存地址是否相等(➌)。 如果满足,这对结果是一个真正的依赖(➍)。

由于系统调用s中的参数内存块在初始阶段(§IV-A)被符号化,因此传播的符号化内存区域的访问允许定位依赖项的符号内存的偏移量(➎)。 执行完成后,它将真正的依赖关系合并在一起(➏)、HFL设置并建立新的系统调用调用规则(➐)。 作为最后一步,这样一组新的调用命令将被反馈,并将用于更改新的输入程序(➑)。

在这里插入图片描述

图7

3.3 嵌套系统调用参数检索

以下两条信息是重新构造嵌套系统调用参数的关键:

  1. 连接到嵌套输入结构的内存位置;
    
  2. 内存缓冲区参数的长度。
    

示例:检索嵌套的系统调用参数。 图8步骤详细通过参数检索。 系统调用到达内部核函数proc_control,以及它的参数值(➊)。 在第4行(➋)的传输函数的第一次调用时,我们将arg缓冲区置于控制之下,并从其第三个参数值(➌)中获得缓冲区大小(0x14)。 接下来,在第6行(➍)的后续调用中,我们确保ctrl.data是符号污染的,并且两个缓冲区都是嵌套关系(➎)。 使用缓冲区的符号状态,我们学习相应的偏移值,其中指针变量稍后将驻留在上层缓冲区中。 执行结束后,我们为调用的系统调用(➏-➐)定义一个新的参数规则,然后将其传递给Fuzzer(➑)。

在这里插入图片描述

图8

四、实现与评估

4.1 HFL的实施

总的来说,HFL的设计遵循了以前的用户级混合模糊技术,它结合了传统模糊测试和符号执行。 HFL的一般模糊特征可以用以下三个特征来描述:1)内核系统模糊,2)覆盖引导模糊,3)符号分析。

HFL是在现有的模糊测试技术Syzkaller (syzkaller是Google团队开发的针对Linux内核进行模糊测试的开源工具,目前还在不断的维护之中。)和符号执行引擎S2E之上实现的。 我们基本上利用这两种工具的核心特性进行基本模糊和符号执行特性,并对它们进行重大调整,以实现HFL的设计。例如,HFL利用Syzkaller中配备的输入生成算法以及S2E中的符号引擎(例如约束求解器。至于系统参数符号化,我们利用来自S2EAPI的函数s2e_make_symbolic,并将其调整为在许多位置测量目标核。为了处理间接控制流,我们增加了gcc3,以使翻译功能朝着直接控制流发展。为了在Linux内核源代码上执行过程间静态分析,我们使用SVF静态分析器。 由于它的分析是在LLVMIR上进行的,我们使用llvmlinux预先将内核源代码转换为适当的LLVM位码。 表X总结了我们修改HFL中使用的工具的努力。 我们开源了我们的参考实现,这样安全分析人员和研究人员就能使受益。

在这里插入图片描述图 HFL实现

4.2 实验结果评估

4.2.1 漏洞发现

HFL发现了51个漏洞。 我们手动分析并找出根本原因,区分新的漏洞。 总之,24个漏洞原来是未知的。包括UAF,整数溢出等各个方面的漏洞 我们报告了所有新发现的漏洞——其中17个已经被确认(其中4个已经被尊敬的内核开发人员修补)

在这里插入图片描述
图 发现的漏洞

4.2.2 执行效率

图总结了性能比较的结果。 正如所看到的,HFL在较早的时间(约15小时)检测到了所有这些漏洞,而Syzkaller(超过50小时)则是如此,这反映了HFL在发现错误方面表现出更好的能力。

图9

4.2.3 代码覆盖率

总之,HFL的总体覆盖改善率分别为15%和26%左右。 与kAFL、S2E和TriforceAFL相比,我们观察到HFL的覆盖率提高了4倍以上。

在这里插入图片描述

图 代码覆盖率

五、总结与评价

5.1 总结

论文基于Linux内核的三种特性对于传统混合模糊的测试的挑战,提出了解决方案,本文的主要贡献有:

·第一个混合内核****Fuzzer。 我们提出了一种混合内核模糊,HFL,综合利用随机模糊和符号测试技术的好处。

·处理内核特定的挑战。 我们确定了三个关键的内核特定的挑战,应用混合模糊,并设计HFL来解决这些挑战。 特别是,我们将间接控制流转换为直接控制流,使混合模糊更有效。 我们还通过在HFL过程中推断系统序列来保持内核内部状态的一致性。 此外,我们还有效地解释了系统参数的接口,这些参数通常以复杂和多层结构的形式存在。

·错误发现和代码覆盖的实验结果。 在我们的评估中,HFL在最近的Linux内核中发现了24个以前未知的漏洞。 此外,HFL在Moonshine和Syzkaller上实现了大约15%和26%的更高代码覆盖率,在kAFL/S2E/TriforceAFL上,它使用相同数量的资源(CPU、时间等)实现了更好的覆盖率。 在漏洞发现性能方面,HFL发现已知的13个漏洞比Syzkaller快三倍多。

5.2 评价

这是一篇发表在顶会上关于linux内核测试的论文,对于刚刚着手学习模糊测试的新手来说,属实有一定的难度,其中主要问题有以下几点:1.对于传统的程序分析技术,比如模糊测试、符号执行没有非常清楚的概念,不知道对于代码来说他们具体的执行过程;2.对于linux内核了解的不足,不太清楚文献中所提出的linux内核中的三点特性的原理;3.没有具体的实现HFL,不清楚传统模糊测试为什么不能解决linux内核的这三个方面的挑战,以及HFL是如何解决这三个挑战的具体流程和代码实现等。对于这些问题,在以后的学习中要重视一些基础知识的掌握,不能急于求成啊!不过还是有不少的收获,通过对本篇文献的汇报,不仅是对模糊测试还是对linux内核,都有了更加深入的理解,在论文汇报的选题翻译、PPT的制作、总结报告的攥写中等工作中也收获良多。最后,十分感谢老师和同学们的帮助。

参考链接

[1] https://www.jianshu.com/p/ffe2d48ba6a8

[2] https://blog.csdn.net/wcventure/article/details/105281874

[3] https://blog.csdn.net/trustbo/article/details/36010701

[4] https://blog.csdn.net/omnispace/article/details/80248252

[5] https://www.cnblogs.com/lsdb/p/10026081.html

[6] https://kt0755.github.io/

[7] https://xz.aliyun.com/t/5079

details/36010701

[4] https://blog.csdn.net/omnispace/article/details/80248252

[5] https://www.cnblogs.com/lsdb/p/10026081.html

[6] https://kt0755.github.io/

[7] https://xz.aliyun.com/t/5079

[8] https://www.oschina.net/p/s2e?hmsr=aladdin1e1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值