NEUZZ: Efficient Fuzzing with Neural Program Smoothing

1.摘要

  摘要模糊识别已成为软件漏洞查找的事实上的标准技术。然而,即使是最先进的模糊器在发现难以触发的软件bug方面也不是很有效。大多数流行的模糊器使用进化指导来生成可以触发不同bug的输入。这样的进化算法,虽然实现起来快速、简单,但经常会陷入无结果的随机突变序列中。梯度引导优化提供了一个有前途的替代进化引导。通过有效地利用梯度或底层函数的高阶导数,在解决机器学习等领域的高维结构优化问题时,梯度引导技术已被证明显著优于进化算法。然而,梯度引导的方法并不直接适用于模糊处理,因为现实世界的程序行为包含许多不连续点、高原和山脊,这些都是基于梯度的方法经常被卡住的地方。我们观察到这个问题可以通过创建一个平滑的代理函数来近似目标程序的离散分支行为来解决。在本文中,我们提出了一种新的程序平滑技术,使用代理神经网络模型,可以增量地学习一个复杂的,真实世界程序的分支行为的平滑近似。我们进一步证明,这种神经网络模型可以与梯度引导的输入生成方案一起使用,以显著提高模糊过程的效率。我们广泛的评估表明,NEUZZ在10个流行的真实世界的程序上,在发现新错误和实现更高的边缘覆盖率方面显著优于10个最先进的灰盒模糊器。NEUZZ发现了31个以前未知的错误(包括两个cve),其他的模糊器在10个真实世界的程序中未能发现,并且在24小时的运行中实现了比所有测试的灰盒模糊器多3倍的边缘覆盖。此外,NEUZZ在LAVA-M和DARPA CGC bug数据集上的性能也优于现有的fuzzers。

2. 介绍

  Fuzzing已经成为发现软件漏洞的事实上的标准技术[88],[25]。fuzzing过程包括生成随机测试输入,并使用这些输入执行目标程序,以触发潜在的安全漏洞[59]。由于它的简单性和低的性能开销,fuzzing在许多真实世界的程序[3],[1],[30],[70],[11],[78]中已经非常成功地发现了不同类型的安全漏洞。尽管它们有着巨大的潜力,但流行的模糊器,特别是对于大型程序来说,往往会在尝试冗余的测试输入时陷入困境,难以找到程序逻辑中隐藏的安全漏洞[82],[36],[68]。从概念上讲,fuzzing是一个优化问题,其目标是找到程序输入,使给定的测试时间[60]内发现的漏洞数量最大化。
  然而,由于安全漏洞在整个程序中是稀疏且不规则分布的,大多数fuzzers的目标是通过最大化某种形式的代码覆盖(例如,边缘覆盖)来测试尽可能多的程序代码,以增加他们发现安全漏洞的机会。最流行的模糊器使用进化算法来解决潜在的优化问题,生成新的输入,最大化代码覆盖率[88],[11],[78],[45]。进化优化从一组种子输入开始,对种子应用随机突变来生成新的测试输入,为这些输入执行目标程序,并且只保留有希望的新输入(例如,那些实现新代码覆盖的输入)作为进一步突变的语料库的一部分。然而,随着输入语料库的增大,进化过程在到达新代码位置时变得越来越低效。
  进化优化算法的一个主要限制是,它们不能利用底层优化问题的结构(即梯度或其他高阶导数)。梯度引导优化(如梯度下降)是一种很有前途的替代方法,在解决包括气动计算和机器学习在内的不同领域的高维结构优化问题时,已被证明显著优于进化算法[89],[46],[38]。
  然而,梯度引导的优化算法不能直接应用于现实世界的模糊程序,因为它们通常包含大量的不连续行为(在这种情况下,梯度不能精确计算),这是由于在不同的程序分支上有很大不同的行为 [67],[21],[43],[20],[22]。我们发现这个问题可以通过创建一个平滑的(即可微的)代理函数来克服,这个代理函数近似于目标程序的分支行为。不幸的是,现有的程序平滑技术[21],[20]导致了令人难以承受的性能开销,因为它们严重依赖于符号分析,而由于一些基本的限制,如路径爆炸、不完整的环境建模和符号内存建模的巨大开销,这些符号分析无法扩展到大型程序。[16][15][35][49]。
  在本文中,我们引入了一种新颖的、高效的、可扩展的程序平滑技术,它使用前馈神经网络(NNs),可以增量地学习复杂的、真实世界的程序分支行为的平滑近似,即,预测由特定给定输入执行的目标程序的控制流边缘。我们进一步提出了一种梯度引导搜索策略,该策略计算并利用平滑逼近(即神经网络模型)的梯度来识别目标突变位置,从而使目标程序中检测到的bug数量最大化。我们演示了神经网络模型是如何通过对错误预测的程序行为递增地重新训练模型来改进的。我们发现前馈神经网络很自然地适合我们的任务,因为(i)它们具有近似复杂非线性函数的能力,正如普遍逼近定理[33]所暗示的那样,(ii)它们支持高效、准确地计算梯度/高阶导数[38]。
  我们设计并实现了我们的技术作为NEUZZ的一部分,一个新的学习型模糊器。我们将NEUZZ与10个最先进的fuzzers在10个真实世界的程序上进行比较,这些程序覆盖了6种不同的文件格式(例如,ELF, PDF, XML, ZIP, TTF和JPEG),平均有47546行代码,LAVA-M错误数据集[28]和CGC数据集[26]。我们的结果表明,NEUZZ在检测错误和实现边缘覆盖方面始终比其他所有的模糊器都有很大的优势。NEUZZ在测试的程序中发现了31个以前未知的bug(包括CVE-2018-19931和CVE-2018-19932),这些bug是其他fuzzer无法发现的。我们在DARPA CGC数据集上的测试也证实,NEUZZ在发现不同的bug方面比Driller[82]等最先进的模糊器表现更好。

我们在这篇论文中的主要贡献如下:

  • 我们是第一个识别程序平滑的意义,采用有效的梯度引导技术进行模糊。
  • 我们引入了第一种高效和可扩展的程序平滑技术,使用代理神经网络来有效地模拟目标程序的分支行为。我们进一步提出了一种增量学习技术,以迭代地细化代理模型,因为更多的训练数据变得可用。
  • 我们证明了代理神经网络模型的梯度可以用来有效地生成程序输入,使目标程序中发现的错误数量最大化。
  • 作为NEUZZ的一部分,我们设计、实现和评估我们的技术,并证明它在广泛的真实世界的程序和策划的bug数据集上显著优于10个最先进的fuzzers。

  本文的其余部分组织如下。第二节总结了关于优化和梯度引导技术的必要背景资料。第三节概述了我们的技术以及一个激励人心的例子。第四节和第五节详细描述了我们的方法和实现。我们在第VI节中介绍了我们的实验结果,并描述了NEUZZ在第VII节中发现的一些示例bug。第八节总结了相关工作,第九节总结了全文。

3. 优化基础

  在本节中,我们首先描述优化的基础知识,以及梯度引导优化相对于进化引导的平滑函数的好处。最后,我们演示了如何将模糊化投射为一个优化问题。
  一个优化问题通常由三个不同的部分组成:一个参数 x x x的向量,一个要最小化或最大化的目标函数 F ( x ) F (x) F(x),以及一组约束函数 C i ( x ) C_i(x) Ci(x),每一个都涉及必须满足的不等式或等式。优化过程的目标是找到参数向量 x x x的一个具体值,使 F ( x ) F(x) F(x)最大化/最小化,同时满足所有约束函数 C i ( x ) C_i(x) Ci(x),如下图所示。
max ⁡ x ∈ R n min ⁡ F ( x )  subject to  { C i ( x ) ≥ 0 , i ∈ N C i ( x ) = 0 , i ∈ Q \max _{x \in R^{n}} \min F(x) \text { subject to }\left\{\begin{array}{l} C_{i}(x) \geq 0, i \in N \\ C_{i}(x)=0, i \in Q \end{array}\right. xRnmaxminF(x) subject to {Ci(x)0,iNCi(x)=0,iQ
  这里R, N和Q表示实数集合,分别是不等式约束的指标,和等式约束的指标。

3.1 函数平滑和优化

  优化算法通常在一个循环中运行,从参数向量x的初始猜测开始,然后逐渐迭代以找到更好的解决方案。任何优化算法的关键组成部分是它使用的从一个x值移动到下一个x值的策略。大多数策略都利用 目 标 函 数 F 、 约 束 函 数 C i 目标函数F、约束函数C_i FCi的值,如果可用的话,还利用梯度/高阶导数。
  不同优化算法收敛到最优解的能力和效率在很大程度上取决于目标函数和约束函数 F 和 C i F和C_i FCi的性质。一般来说,更平滑的函数(即那些具有明确定义和可计算导数的函数)比具有许多不连续的函数(例如,脊或平台)更有效地优化。直观地说,目标函数/约束函数越平滑,优化算法就越容易准确计算梯度或高阶导数,并利用它们系统地搜索整个参数空间。
  在本文的其余部分,我们特别关注没有任何约束函数的无约束优化问题,即,C =φ,因为它们非常接近于我们的目标域fuzzing。对于无约束光滑优化问题,梯度引导方法在求解高维结构优化问题时明显优于进化策略[89],[46],[38]。这是因为梯度引导技术有效地利用了梯度/高阶导数,从而有效地收敛到最优解,如图1所示。
在这里插入图片描述

####3.2 凸性和梯度引导优化

  对于称为凸函数的常见函数类,梯度引导技术是非常有效的,并且总是能够收敛到全局最优解[86]。直观地说,如果函数图上任意两点之间有一条直线完全位于该图上或该图上,则该函数为凸函数。更正式地说,如果函数f的定义域内所有点x和y都满足以下性质,则函数f称为凸函数: f ( t x + ( 1 − t ) y ) ≤ t f ( x ) + ( 1 − t ) f ( y ) , ∀ t ∈ [ 0 , 1 ] 。 f (tx +(1−t)y)≤tf (x) +(1−t)f (y),∀t∈[0,1]。 f(tx+(1t)y)tf(x)+(1t)f(y)t[0,1]
  然而,在非凸函数中,梯度引导方法可能会陷入局部最优解,其中目标函数大于(假设目标是最大化)所有附近的可行点,但在整个范围内的其他地方存在其他更大的值 的可行参数值。 然而,即使在这种情况下,简单的启发式方法(如从新的随机选择的起点重新启动梯度引导方法)在实践中也被证明是非常有效的 [38]、[86]。

3.3 ,模糊测试无约束优化

  Fuzzing可以表示为一个无约束优化问题,其中目标是在固定的测试输入数量下,最大限度地增加在测试程序中发现的bug /漏洞的数量。因此,目标函数可以被认为是Fp (x)返回1如果输入x触发一个bug /弱点当目标程序执行p与输入x。然而,这样的一个函数太捣乱(例如,大多containingflat高原和一些非常急剧转换)来有效地进行优化。因此,大多数灰盒模糊器反而试图最大化测试代码的数量(例如,最大化边缘覆盖),作为一个替代的代理度量[88],[11],[73],[55],[22]。这样的目标函数可以表示为f0p (x),其中f0返回程序P的输入x所覆盖的新控制流边的数量。注意,f0比原始函数F更容易优化,因为执行新控制流边的所有可能的程序输入的数量往往比触发bug /安全漏洞的输入要高得多。
  大多数现有的灰盒模糊器使用进化技术[88],[11],[73],[55],[22]以及其他领域特定的启发式算法作为主要的优化策略。选择这种算法而不是梯度引导优化的关键原因是,大多数真实世界的程序包含许多不连续点,这是由于沿着不同程序路径[19]的显著不同的行为造成的。这样的不连续性可能导致梯度引导的优化在非最优解卡住。在本文中,我们提出了一种使用神经网络来平滑目标程序的新技术,使其适合于梯度引导的优化,并演示了模糊器如何利用这种策略来显著提高其有效性。

4. 方法概述

  图2给出了我们方法的高级概述。我们将在下面详细描述关键组件:
在这里插入图片描述

4.1 神经程序平滑

  平滑地逼近程序的不连续分支行为对于精确计算梯度或梯度导向优化所需的高阶导数至关重要。如果没有这样的平滑,梯度引导的优化过程可能会在不同的不连续点/平台上卡住。平滑过程的目标是创建一个平滑的函数,它可以模拟程序的分支行为,而不会引入大的错误(即,它最小程度地偏离了原始程序的行为)。为此,我们使用前馈神经网络(NN)。正如普遍近似定理[33]所暗示的那样,神经网络非常适合近似任意复杂(潜在的非线性和非凸)的程序行为。此外,通过设计,神经网络还支持有效的梯度计算,这对我们的目的至关重要。我们要么使用现有的测试输入,要么使用现有进化模糊器生成的测试输入语料库来训练NN,如图2所示。

4.2 梯度引导优化

  光滑的神经网络模型,一旦训练,可以用来有效地计算梯度和高阶导数,然后可以利用更快的收敛到最优解。梯度引导算法的不同变体,如梯度下降、牛顿方法或准牛顿方法,如L-BFGS算法,使用梯度或高阶导数来实现更快的收敛[10],[13],[65]。平滑神经网络使模糊输入生成过程可能使用所有这些技术。在本文中,我们设计、实现和评估了一个简单的梯度引导输入生成方案,该方案是为基于覆盖的fuzzing而设计的,具体描述见章节IV-C。

4.3 增量学习

  任何类型的现有测试输入(只要它们在目标程序中暴露不同的行为)都可以潜在地用于训练神经网络模型和引导模糊输入生成过程。在本文中,我们通过收集一组测试输入和相应的边缘覆盖信息,运行进化模糊器(如AFL)来训练神经网络。
  但是,由于用于训练神经网络模型的初始训练数据可能只覆盖了一小部分程序空间,在模糊过程中观察到新的程序行为,我们通过增量训练进一步细化模型。增量训练的关键挑战是,如果只对新数据进行训练,神经网络可能会完全忘记从旧数据[57]中学习到的规则。我们通过设计一种新的基于覆盖的过滤方案来避免这个问题,该方案创建一个新旧数据的压缩摘要,允许神经网络对它们进行有效的训练。

4.4 一个规范的例子

  我们在图3中展示了一个简单的激励示例,以演示我们方法背后的关键洞察力。图3中所示的简单C代码片段演示了在许多实际程序中常见的通用开关式代码模式。特别地,示例代码计算输入的非线性指数函数(即pow(3,a+b))。它根据被计算函数的输出范围返回不同的值。我们还假设,如果函数的输出范围在(1,2),则执行了一个bug代码块(用红色标记)。
  考虑这样一种情况,像AFL这样的进化模糊器已经设法探索了第2行和第9行中的分支,但未能探索第5行中的分支。这里的关键挑战是找到将在第5行触发分支的a和b的值。进化的模糊者经常与这样的代码斗争,因为通过随机变异找到解决方案的几率非常低。例如,图3a显示了代码片段所表示的原始函数。函数曲面从 a + b = 0  到  a + b − ϵ = 0 ( ϵ → + 0 ) a+b=0 \text { 到 } a+b-\epsilon=0(\epsilon \rightarrow+0) a+b=0  a+bϵ=0(ϵ+0)为了在模糊过程中最大化边缘覆盖,进化模糊器只能对输入进行随机突变,这类技术没有考虑函数曲面的形状。相比之下,我们的神经网络平滑和梯度引导突变被设计来利用由梯度测量的函数表面形状。
  我们用另外两个分支的程序行为训练一个神经网络模型。NN模型平滑地逼近程序行为,如图3b和3c所示。然后,我们使用NN模型进行更有效的梯度引导优化,以找到a和b的期望值,并逐渐细化模型,直到找到期望的分支,从而锻炼目标bug。

5. 方法

我们将在下面详细描述我们方案的不同组成部分.

5.1 程序平滑性

  程序平滑是使梯度引导优化技术适用于具有离散行为的真实世界程序的一个基本步骤。如果没有平滑处理,梯度引导优化技术对于优化非光滑函数并不十分有效,因为它们往往会在不同的不连续点上卡住[67]。平滑过程使这种不规则性最小化,从而使梯度导向优化在不连续函数上更加有效。
  一般来说,对于不连续函数f的平滑处理可以认为是f和平滑掩模函数g之间的卷积运算,从而产生如下图所示的新的平滑输出函数。一些流行的平滑掩模的例子包括不同的高斯函数和Sigmoid函数。
f ′ ( x ) = ∫ − ∞ + ∞ f ( a ) g ( x − a ) d a f^{\prime}(x)=\int_{-\infty}^{+\infty} f(a) g(x-a) d a f(x)=+f(a)g(xa)da
  然而,对于许多实际问题,不连续函数f可能没有封闭的表示形式,因此无法解析计算上述积分。在这种情况下,使用离散版本f 0(x) =P a f (a)g(x - a),卷积是数值计算的。例如,在图像平滑中,通常使用固定大小的二维卷积核来执行这种计算。然而,在我们的设置中,f是一个计算机程序,因此相应的卷积不能解析计算。
  然而,对于许多实际问题,不连续函数f可能没有封闭的表示形式,因此无法解析计算上述积分。在这种情况下,使用离散版本 f ′ ( x ) = ∑ a f ( a ) g ( x − a ) f^{'}(x) = {\textstyle \sum_{a}^{}}f(a)g(x-a) f(x)=af(a)g(xa),卷积是数值计算的。例如,在图像平滑中,通常使用固定大小的二维卷积核来执行这种计算。然而,在我们的设置中,f是一个计算机程序,因此相应的卷积不能解析计算。
  程序平滑技术可以分为两大类:黑盒平滑和白盒平滑。黑盒方法从f的输入空间中选取离散样本,并使用这些样本数值计算卷积。相比之下,白盒方法研究程序语句/指令,并尝试使用符号分析和抽象解释[21],[20]来总结它们的效果。黑盒方法可能会引入很大的近似错误,而白盒方法则会导致令人望而却步的性能开销,这使得它们在现实世界的程序中是不可行的。为了避免这样的问题,我们使用神经网络以灰盒方式(例如,通过收集边缘覆盖数据)学习程序行为的平滑近似,如下所述。

5.2 神经网络平滑性

  本文提出了一种新的程序平滑方法,通过使用代理神经网络模型来学习并根据观察到的程序行为迭代改进目标程序的平滑逼近。代理神经网络可以平滑地推广到观察到的程序行为,同时也能准确地建模潜在的非线性和非凸行为。经过训练的神经网络可以有效地计算梯度和高级导数,指导模糊输入生成过程,如图3所示。
在这里插入图片描述

  • 为什么是NNs?

  正如普遍逼近定理[33]所暗示的那样,神经网络非常适合逼近复杂的(潜在的非线性和非凸)程序行为。使用神经网络学习平滑程序近似的优点如下:(i)神经网络能够准确地模拟复杂的非线性程序行为,并能有效地进行训练。之前的基于模型的优化工作使用了简单的线性和二次模型[24],[23],[71],[52]。然而,这种模型并不适合建模具有高度非线性和非凸行为的真实世界软件;(ii)神经网络支持其梯度和高阶导数的高效计算。因此,梯度制导算法可以在模糊过程中计算和使用这些信息,而不需要额外的开销;(iii)神经网络可以根据程序对类似输入的行为,归纳并学会预测程序对不可见输入的行为。因此,神经网络可以根据程序对少量输入样本的行为,潜在地学习整个程序的平滑近似。

  • NN模型训练

  虽然神经网络可以用来建模程序行为的不同方面,但在本文中,我们专门使用它们来建模目标程序的分支行为(即,预测给定程序输入所执行的控制流边缘)。使用神经网络建模分支行为的挑战之一是需要接受可变大小的输入。前馈神经网络与现实世界的程序不同,通常接受固定大小的输入。因此,我们设置了一个最大的输入大小阈值,并在训练期间用null字节填充任何较小的输入。请注意,支持更大的输入并不是主要的问题,因为现代网络可以很容易地扩展到数百万个参数。因此,对于较大的程序,如果需要,我们可以简单地增加阈值大小。然而,我们的经验发现,相对适度的阈值产生最好的结果,而较大的输入并不会显著提高建模精度.
  形式上,设 f : { 0 × 00 , 0 × 01 , … , 0 × f f } m → { 0 , 1 } n f:\{0 \times 00,0 \times 01, \ldots, 0 \times f f\}^{m} \rightarrow\{0,1\}^{n} f:{0×00,0×01,,0×ff}m{0,1}n表示接受程序输入为大小为m的字节序列的NN,并输出大小为n的边缘位图。设θ表示f的可训练权值参数。给定一组训练样本 ( X , Y ) (X, Y) (X,Y), X 和 Y X和Y XY是一组输入字节代表相应的边缘覆盖图,的培训任务参数函数 f ( X , θ ) = Y f (X,θ)= Y f(X,θ)=Y是获取参数 θ ^ \hat{\theta} θ^, θ ^ \hat{\theta} θ^参数最小值θP∈X, Y∈Y L (Y, f (X,θ)),L (Y,f (x,θ))定义了NN的输出与训练集中地面真值标签y∈y之间的损失函数。训练任务是找到NN f的权重参数θ,以使损失最小化,该损失是用距离度量定义的。特别地,我们使用二元交叉熵来计算预测位图和真实覆盖位图之间的距离。特别地,设yi和fi(x,θ)分别表示ground真值和f预测输出位图中的第i位。那么,两者之间的二元交叉熵定义为:
− 1 n ∑ i = 1 n [ y i ⋅ log ⁡ ( f i ( x , θ ) + ( 1 − y i ) ⋅ log ⁡ ( 1 − f i ( x , θ ) ] -\frac{1}{n} \sum_{i=1}^{n}\left[y _ { i } \cdot \operatorname { l o g } \left(f_{i}(x, \theta)+\left(1-y_{i}\right) \cdot \log \left(1-f_{i}(x, \theta)\right]\right.\right. n1i=1n[yilog(fi(x,θ)+(1yi)log(1fi(x,θ)]
  在本文中,我们使用前馈全连接网络来模拟目标程序的分支行为。前馈结构允许高效计算梯度和快速训练[53]。我们的平滑技术对训练数据的来源是不可知的,因此神经网络可以在从现有输入语料库收集的任何边缘覆盖数据上进行训练。对于我们的原型实现,我们使用现有的进化模糊器(如AFL)生成的输入语料库来训练我们的初始模型。

  • 数据预处理

  训练数据的边缘覆盖通常是有偏差的,因为它只包含程序中所有边缘的一小部分的标签。例如,一些边可能总是由训练数据中的所有输入一起执行。在机器学习中,一组标签之间的这种类型的相关性被称为多重共线性,这通常会阻止模型收敛到一个小的损失值[34]。为了避免这种情况,我们遵循机器学习的常见做法降维,将训练数据中总是出现在一起的边合并成一条边。此外,我们只考虑在训练数据中至少被激活一次的边缘。这些步骤大大减少了标签的数量,从平均大约65,536个减少到大约4,000个。注意,在增量学习的每一次迭代中,我们都会重新运行数据预处理步骤,因此一些合并的标签可能会被分割,因为它们的相关性可能会随着模糊过程中发现的新边缘数据而降低。

5.3 梯度引导优化

  不同的梯度引导优化技术,如梯度下降、牛顿方法或准牛顿方法,如L-BFGS,都可以使用梯度或高阶导数来实现更快的收敛[10],[13],[65]。平滑神经网络通过支持梯度和高阶导数的高效计算,使模糊输入生成过程能够潜在地使用这些技术中的任何一种。在本文中,我们专门设计了一个简单的梯度引导搜索方案,该方案对较小的预测误差具有鲁棒性,以证明我们的方法的有效性。我们把对更复杂技术的探索留在未来的工作中。
  在描述我们基于神经网络梯度的突变策略之前,我们首先给出了梯度的形式化定义,梯度表示每个输入字节需要改变多少才能影响神经网络中最后一层神经元的输出(表示程序中边缘覆盖的改变)f[80]。在这里,每个输出神经元对应一个特定的边,并计算一个0到1之间的值,总结给定输入字节在特定边上的影响。输入的神经网络输出神经元的梯度f w.r.t已被广泛用于对抗输入生成[39],[66]和可视化/理解dnn[87],[80],[56]。直观地说,在我们的设置中,基于梯度的引导的目标是找到将改变最后一层神经元的输出的输入,这些神经元对应于从0到1的不同边缘。
  给定参数神经网络 y = f ( θ , x ) y = f(θ,x) y=f(θ,x)中定义的部分ivb,让易建联表示的输出i f的神经元在最后一层,也可以写成 f i ( θ , x ) f_i(θ,x) fi(θ,x)梯度G (fi(θ,x)对输入 x x x可以被定义为 G = ∇ x f i ( θ , x ) = ∂ / ∂ x G =∇xfi(θ,x) =∂/∂x G=xfi(θ,x)=/x。注意, f 的 梯 度 w . r . t 到 θ f的梯度w.r.t到θ fw.r.tθ很容易计算,因为神经网络训练过程需要迭代计算这个值来更新 θ θ θ。因此,G也可以通过简单地将θ的梯度计算替换为x的梯度计算来轻松地计算出来。注意梯度G的维数与输入x的维数相同,在我们的例子中, x x x是一个字节序列。
在这里插入图片描述

  • 1.梯度引导优化

  算法1展示了我们的梯度引导输入生成过程的轮廓。其关键思想是识别具有最高梯度值的输入字节并对其进行变异,因为它们表明对NN的重要性更高,因此有更高的机会导致程序行为的重大变化(例如,翻转分支)。从种子开始,我们迭代地生成新的测试输入。正如算法1所示,在每次迭代中,我们首先利用梯度的绝对值来识别将导致输出神经元中对应于未取边的最大变化的输入字节。接下来,我们检查每个字节的梯度符号,以决定突变的方向(例如,增加或减少它们的值),以最大化/最小化目标函数。从概念上讲,我们对梯度符号的使用类似于[39]中引入的对抗输入生成方法。我们还将每个字节的变异限制在其合法范围(0-255)内。第6行和第10行表示使用剪辑函数来实现这样的边界。我们从一个小的变异目标(算法1中的k)开始输入生成过程,然后指数级增长目标字节的变异数量,以有效地覆盖大的输入空间。

5.4 使用增量学习进行细化

  梯度引导的输入生成过程的效率很大程度上取决于代理神经网络对目标程序的分支行为建模的准确性。为了获得更高的精度,我们在模糊过程中观察到发散的程序行为(即目标程序行为与预测行为不匹配)时,对神经网络模型进行增量优化。我们使用增量学习技术来保持神经网络模型的更新,通过学习新的数据时,新的边缘被触发。神经网络细化背后的主要挑战是防止神经网络模型在训练新数据时突然忘记它以前从旧数据中学到的信息。这种遗忘现象在深度学习文献中广为人知,被认为是稳定性-可塑性困境[58],[8]的结果。为了避免这种遗忘问题,神经网络必须改变权重以学习新的任务,但又不能太大以至于忘记之前学习过的表示。改进神经网络最简单的方法是将新的训练数据(即程序分支行为)与旧的数据一起添加,然后从头开始训练模型。然而,随着数据点数量的增长,这种再培训变得更难规模化。之前的研究主要使用两种广泛的方法来解决这一问题[44],[51],[31],[75],[29],[40],[76]。第一种方法是使用分布式模型、正则化或创建多个模型的集成,为新模型和旧模型保留单独的表示,以最大程度地减少遗忘。第二种方法维护旧数据的摘要,并根据新数据和已总结的旧数据对模型进行再训练,因此比完全再训练更有效。我们建议有兴趣的读者参阅Kemker et al.[48]的调查以获得更多细节。在本文中,我们使用基于边缘覆盖的过滤,只保留触发新分支的旧数据进行再训练。当新的训练数据可用时,我们识别那些达到新的边缘覆盖的数据,将它们与经过过滤的旧训练数据放在一起,然后再训练神经网络。这种方法有效地防止了训练数据样本的数量随着再训练迭代次数的增加而急剧增加。我们发现我们的过滤方案可以很容易地支持50次重复再训练,同时仍然保持训练时间在几分钟以内。

6. 应用与实现

训练数据收集。

  对于测试的每个程序,我们在单个核机器上运行AFL-2.5.2[88]一个小时,收集NN模型的训练数据。为10个项目收集的培训输入的平均数量约为2K。得到的语料库以5:1的比例进一步分解为训练数据和测试数据,其中使用测试数据以确保模型不会过拟合。我们使用10KB作为阈值文件大小,从AFL输入语料库中选择我们的训练数据(平均90%的AFL生成的文件在阈值以下)。

变异和重新训练

  如图2所示,NEUZZ迭代运行以生成1M突变,并增量地重新训练NN模型。我们首先使用算法1中描述的突变算法生成1M突变。我们将参数i设置为10,这将为seed输入生成5,120个突变输入。接下来,我们随机选择100个输出神经元代表目标程序中100个未探索的边,从两个种子中产生10240个突变输入。最后,我们使用AFL的分叉服务器技术[54],使用1M突变输入执行目标程序,并使用任何覆盖新边缘的输入进行增量再训练。

模型参数的选择。

  NEUZZ的成功取决于训练模型和产生突变时不同参数的选择。在这里,我们通过经验探索了确保四个程序(readelf、libjpeg、libxml和mupdf)上的最大边缘覆盖的最佳参数。表一总结了结果。
  首先,我们评估有多少关键字节需要突变/初始种子(1号线算法1参数ki)。我们选择k = 2节中描述IV-C并展示了三所覆盖的迭代(i = 7, 10, 11,算法1行1)每迭代1 m突变。对于所有四个程序,较小的变异(每次变异所改变的字节更少)可能导致更高的代码覆盖率,如表Ia所示。i = 11的最大值使所有四个程序的代码覆盖率最小。这个结果可能是由于算法1中的第4和第8行在没有尝试其他种子的情况下,在单个种子上浪费了太多的突变(在1M的突变预算中)。但是,四个程序的最佳变异字节数各不相同。对于readelf和libxml, i的最佳值是10,而对于libjpeg和mupdf, i的最佳值是7。由于在i = 7和i = 10之间实现的代码覆盖率差异不大,所以我们选择i = 10作为剩余的实验。
  然后,我们通过改变神经网络模型的层数和每个隐层的神经元数量来评估神经网络模型中超参数的选择。特别地,我们比较了隐层1和隐层3以及每层4096和8192个神经元的神经网络结构。对于每个目标程序,我们使用相同的训练数据来训练四个不同的神经网络模型,并产生1M的突变来测试实现的边缘覆盖。对于所有四个程序,我们发现隐藏层为1的模型比隐藏层为3的模型性能更好。我们认为这是因为1层隐藏层模型足够复杂,足以模拟目标程序的分支行为,而较大的模型(即3层隐藏层)相对较难训练,也容易过度拟合。

7. 评价

  在本节中,我们将评估NEUZZ的bug发现性能,并相对于其他最先进的模糊器实现边缘覆盖。具体来说,我们回答了以下四个研究问题:

  • neuzzer能比现有的fuzzer找到更多的bug吗?
  • NEUZZ能比现有的模糊器实现更高的边缘覆盖吗?
  • NEUZZ能比现有的基于rnn的模糊器更好的执行吗?
  • 不同的模型选择如何影响NEUZZ的性能?

   我们首先描述我们的研究对象和实验设置。

A. 研究对象

  我们在三种不同类型的数据集上评估NEUZZ (i) 10个真实世界的程序,如表IIb, (ii) LAVA-M[28],和(iii) DARPA CGC数据集[26]。为了演示NEUZZ的性能,我们将NEUZZ检测到的缺陷的边缘覆盖率和数量与10个最先进的模糊器进行了比较,如表IIa所示。

B.实验装置

  我们的实验设置包括以下两个步骤:首先,我们运行一个小时的AFL生成初始种子体。然后,我们在相同的初始种子体下运行每个模糊器,并比较它们实现的边缘覆盖率和发现的bug数量。具体来说,10个真实世界的程序,LAVA-M数据集和CGC数据集的时间预算分别是24小时,5小时和6小时。
  对于进化模糊器,种子语料库用于初始化模糊测试过程。对于基于学习的模糊器(即 NEUZZ 和基于 RNN 的模糊器),相同的种子语料库用于生成训练数据集。 至于由 Klee 和 AFL 组成的混合工具 KleeFL,我们额外运行 Klee 一个小时以生成额外的种子,然后将它们添加到原始种子语料库中,以进行接下来的 24 小时模糊测试过程。 请注意,我们只报告每个模糊器的变异输入所覆盖的额外代码,而不包括来自初始种子语料库的覆盖信息。
  在RQ3中,我们评估并比较了NEUZZ与基于rnn的模糊器的性能。基于rnn的fuzzers可能需要比NEUZZ多20倍的训练时间。然而,为了关注这两种突变算法的有效性,我们评估了固定数量突变的边缘覆盖率,以排除这些不同的训练时间的影响。我们还执行一个独立的评估,比较这两个模型的训练时间成本。在RQ4中,我们还评估了固定数量突变的边缘覆盖率,以排除不同模型间不同训练时间成本的影响。

本文的评价可以在论文的最后面看到

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值