摘要

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

1.简介

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

  • 我们首先确定了程序平滑对于采用有效的梯度引导技术进行模糊化的重要性。
  • 我们引入了第一种有效且可扩展的程序平滑技术,使用代理神经网络来有效地建模目标程序的分支行为。我们进一步提出了一种增量学习技术,当更多的训练数据可用时,迭代地改进代理模型。
  • 我们证明了代理神经网络模型的梯度可以用来有效地生成程序输入,从而最大化目标程序中发现的错误数。
  • 作为NEUZZ的一部分,我们设计、实现和评估我们的技术,并证明它在广泛的实际程序和已保存的bug数据集上显著优于10个最新的模糊器。

论文的其余部分安排如下。第二节总结了优化和梯度引导技术的必要背景信息。第三节概述了我们的技术,并给出了一个激励性的例子。第四节和第五节详细描述了我们的方法和实现。我们在第六节中介绍了我们的实验结果,并在第七节中描述了NEUZZ发现的一些样本错误。第八节对相关工作进行了总结,第九节对论文进行了总结。

2.优化基础知识

在这一节中,我们首先描述了优化的基本原理,以及梯度引导优化相对于光滑函数的进化引导的好处。最后,我们将演示如何将模糊化转化为一个优化问题。
一个优化问题通常由三个不同的部分组成:参数x的向量、要最小化或最大化的目标函数F(x)和一组约束函数C i(x),每个约束函数都涉及必须满足的不等式或等式。优化过程的目标是在满足所有约束函数C i(x)的情况下,找到使F(x)最大/最小的参数向量x的具体值,如下所示。
image.png
这里R、N和Q分别表示实数集、不等式约束的索引和等式约束的索引。
函数平滑与优化
优化算法通常在一个循环中运行,从对参数向量x的初始猜测开始,然后逐步迭代以找到更好的解。任何优化算法的关键部分都是它用来从一个x值移动到下一个x值的策略。大多数策略利用目标函数F、约束函数C i以及梯度/高阶导数(如果有的话)的值。
不同优化算法收敛到最优解的能力和效率在很大程度上取决于目标函数和约束函数F和C i的性质。一般来说,较平滑的函数(即具有定义明确且可计算的导数的函数)可以比具有许多不连续性(例如脊或高原)的函数更有效地优化。直观地说,目标/约束函数越平滑,优化算法就越容易准确地计算梯度或高阶导数,并利用它们系统地搜索整个参数空间。
在本文的其余部分中,我们特别关注没有任何约束函数的无约束优化问题,即C=φ,因为它们紧密地模拟了我们的目标域fuzzing。对于无约束的光滑优化问题,梯度引导方法在求解高维结构优化问题时明显优于进化策略[89]、[46]、[38]。这是因为梯度引导技术有效地利用梯度/高阶导数有效地收敛到图1所示的最优解。
凸性和梯度导向优化
对于一类称为凸函数的公共函数,梯度引导技术是高效的,并且总是能够收敛到全局最优解[86]。直观地说,如果函数图上任意两点之间的直线完全位于图的上方或图的上方,则函数是凸的。更正式地说,如果域中的所有点x和y对满足以下性质,则函数f称为凸函数:f(tx+(1-t)y)≤tf(x)+(1-t)f(y),∀t∈[0,1]。
然而,在非凸函数中,当目标函数大于(假设目标是最大化)所有附近的可行点时,梯度导引法可能陷入局部最优解,但在整个可行参数值范围内的其他地方存在其他更大的值。然而,即使在这种情况下,简单的启发式方法,如从新的随机选择的起点重新启动梯度引导方法,在实践中也被证明是非常有效的[38],[86]。
image.png
模糊无约束优化
模糊化可以表示为一个无约束优化问题,其目标是在固定数量的测试输入下,最大化测试程序中发现的错误/漏洞的数量。因此,目标函数可以被认为是F p(x),当目标程序p使用输入x执行时,如果输入x触发错误/漏洞,则返回1。然而,这样一个函数的性能太差(即大多数包含平坦的高原和一些非常尖锐的跃迁),无法有效地进行优化。
因此,大多数灰盒模糊器试图最大化测试代码的数量(例如,最大化边缘覆盖率),作为代理度量[88]、[11]、[73]、[55]、[22]。这样的目标函数可以表示为F0 p(x),其中F0返回由程序p的输入x覆盖的新控制流边的数目。注意,与原始函数F相比,F 0相对容易优化,因为执行新控制流边缘的所有可能程序输入的数量往往明显高于触发错误/安全漏洞的输入。
大多数现有的灰盒模糊器使用进化技术[88]、[11]、[73]、[55]、[22]以及其他领域特定的启发式作为它们的主要优化策略。在梯度引导优化中选择这样的算法的关键原因是,由于在不同程序路径上的行为明显不同,大多数实际程序包含许多不连续性[19]。这种不连续性可能导致梯度导向优化陷入非最优解。本文提出了一种利用神经网络对目标程序进行平滑的新方法,使其适合于梯度引导优化,并演示了模糊算子如何利用这些策略来显著提高其效率。

3.方法概述

图2给出了我们方法的一个高级概述。我们将在下面详细描述关键组件。
image.png
Neural program smoothing
光滑地逼近程序的不连续分支行为对于精确计算梯度或高阶导数是梯度引导优化所必需的。如果没有这种平滑,梯度导向优化过程可能会在不同的不连续性/高原上陷入困境。平滑过程的目标是创建一个平滑函数,该函数可以模拟程序的分支行为,而不会引入大错误(即,它与原始程序行为的偏差最小)。为此,我们使用了一个前馈神经网络(NN)。正如普遍逼近定理[33]所暗示的,NN非常适合于逼近任意复杂(潜在非线性和非凸)程序行为。此外,NNs,通过设计,也支持高效的梯度计算,这对我们的目的是至关重要的。我们通过使用现有的测试输入或者使用现有的进化模糊器生成的测试输入语料库来训练神经网络,如图2所示。
梯度导向优化
光滑神经网络模型,一旦训练,可以用来有效地计算梯度和高阶导数,然后可以利用它们更快地收敛到最优解。梯度引导算法的不同变体,如梯度下降法、牛顿法或准牛顿法,如L-BFGS算法,使用梯度或高阶导数来加快收敛速度[10]、[13]、[65]。平滑NNs使得模糊输入生成过程能够潜在地使用所有这些技术。在本文中,我们设计、实现和评估了一个简单的梯度引导输入生成方案,该方案是为基于覆盖的模糊化而定制的,详细描述见第IV-C节.
渐进式学习。任何类型的现有测试输入(只要它们在目标程序中公开不同的行为)都可能被用来训练神经网络模型和引导模糊输入生成过程。在本文中,我们通过收集一组测试输入和相应的边缘覆盖信息来训练神经网络。然而,由于神经网络模型用于训练的初始训练数据可能只覆盖程序空间的一小部分,因此在模糊化过程中观察到新的程序行为时,我们通过增量训练进一步细化模型。增量训练的关键挑战在于,如果神经网络只接受新数据的训练,它可能会完全忘记从旧数据中学习到的规则[57]。我们通过设计一种新的基于覆盖的过滤方案来避免这个问题,该方案创建了新旧数据的简明摘要,从而允许神经网络在这些数据上得到有效的训练。
image.png
一个激励的例子。我们在图3中展示了一个简单的激励示例,以展示我们方法背后的关键洞察力。图3所示的简单C代码片段演示了在许多实际程序中常见的通用开关式代码模式。具体来说,示例代码计算输入的非线性指数函数(即pow(3,a+b))。它根据计算函数的输出范围返回不同的值。我们还假设,如果函数输出范围在(1,2)中,则执行错误代码块(标记为红色)。
考虑这样一种情况,进化模糊学家,如AFL,已经设法探索了第2和第9行的分支,但未能探索第5行的分支。这里的关键挑战是找到a和b的值,它们将触发第5行的分支。进化模糊者经常与这样的代码斗争,因为通过随机变异找到解决方案的几率非常低。例如,图3a显示了代码段表示的原始函数。函数曲面从a+b=0到a+b-?=0(?)??+0)。为了使模糊过程中的边缘覆盖最大化,进化模糊器只能对输入进行随机变异,因为这种方法不考虑函数曲面的形状。相比之下,我们的神经网络平滑和梯度引导突变被设计用来利用梯度测量的函数曲面形状。
我们从另外两个分支训练一个关于程序行为的神经网络模型。神经网络模型平滑地逼近程序行为,如图3b和3c所示。然后,我们使用神经网络模型执行更有效的梯度引导优化,以找到所需的a和b值,并逐步地优化模型,直到找到执行目标错误的所需分支。

4.方法

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

A.程序平滑

程序平滑是使梯度引导优化技术适用于具有离散行为的实际程序模糊化的重要步骤。没有平滑,梯度引导优化技术对于优化非光滑函数不是很有效,因为它们往往会在不同的不连续处卡住[67]。平滑过程最小化了这种不规则性,因此使得梯度引导优化在不连续函数上更有效。
一般来说,不连续函数f的平滑可以被认为是f与平滑掩模函数g之间的卷积运算,以产生如下所示的新的平滑输出函数。一些常用的平滑掩模包括不同的高斯函数和sigmod函数。
image.png
然而,对于许多实际问题,不连续函数f可能不具有闭式表示,因此解析计算上述积分是不可能的。在这种情况下,使用离散型f 0(x)=P a f(a)g(x-a),并用数值计算卷积。例如,在图像平滑中,通常使用固定大小的二维卷积核来执行这种计算。然而,在我们的设置中,f是一个计算机程序,因此相应的卷积不能用解析的方法计算。
程序平滑技术可以分为两大类:黑盒平滑和白盒平滑。blackbox方法从f的输入空间中提取离散样本,利用这些样本进行数值计算卷积。相比之下,白盒方法关注程序语句/指令,并尝试使用符号分析和抽象解释来总结它们的效果[21],[20]。blackbox方法可能会引入较大的近似误差,而whitebox方法会导致令人望而却步的性能,这使得它们不适用于实际程序。
为了避免这样的问题,我们使用NNs以灰度框的方式(例如,通过收集边缘覆盖数据)学习程序行为的平滑近似,如下所述。

B.神经程序平滑

本文提出了一种新的程序平滑方法,利用代理神经网络模型学习并迭代地根据观察到的程序行为优化目标程序的平滑逼近。代理神经网络可以平滑地推广到观察到的程序行为,同时也可以精确地建模潜在的非线性和非凸行为。神经网络一旦训练好,就可以用来有效地计算梯度和更高层次的导数,以指导模糊输入生成过程,如图3所示。
为什么是NNs?正如普遍逼近定理[33]所暗示的,NN非常适合于逼近复杂(潜在的非线性和非凸)程序行为。使用NNs学习光滑程序逼近的优点是:(i)NNs能够精确地建模复杂的非线性程序行为,并且能够有效地训练。以前的基于模型的优化工作使用简单的线性和二次模型[24]、[23]、[71]、[52]。然而,这种模型不适合于具有高度非线性和非凸性的软件建模;(ii)NNs支持梯度和高阶导数的有效计算。因此,梯度引导算法可以在模糊化过程中计算和使用这些信息,而不需要额外的开销;并且(iii)NNs可以根据程序在相似输入上的行为来概括和学习预测程序在未知输入下的行为。因此,NNs可以根据程序对少量输入样本的行为来学习整个程序的平滑近似。
神经网络训练。虽然NNs可以用于对程序行为的不同方面进行建模,但在本文中,我们专门使用它们来建模目标程序的分支行为(即预测由给定程序输入执行的控制流边缘)。使用神经网络来建模分支行为的挑战之一是需要接受可变大小的输入。前馈NNs与实际程序不同,通常接受固定大小的输入。因此,我们设置了最大输入大小阈值,并在训练期间用空字节填充任何较小大小的输入。请注意,支持更大的输入并不是一个主要问题,因为现代NNs可以轻松地扩展到数百万个参数。因此,对于较大的程序,如果需要,我们可以简单地增加阈值大小。然而,我们的经验发现,相对适度的阈值产生最好的结果,更大的投入并没有显著提高建模精度。
image.png
在本文中,我们使用前馈全连接NNs来模拟目标程序的分支行为。前馈结构允许高效的梯度计算和快速训练[53]。
我们的平滑技术对训练数据的来源是不可知的,因此神经网络可以在从现有输入语料库收集的任何边缘覆盖数据上进行训练。对于我们的原型实现,我们使用由现有的进化模糊器(如AFL)生成的输入语料库来训练我们的初始模型。
训练数据预处理。由训练数据执行的边缘覆盖通常倾向于有偏差,因为它只包含程序中所有边缘的一小部分的标签。例如,某些边可能总是由训练数据中的所有输入一起执行。在机器学习中,一组标签之间的这种相关性被称为多重共线性,这通常会阻止模型收敛到一个较小的损失值[34]。为了避免这种情况,我们遵循常见的降维机器学习实践,将训练数据中总是一起出现的边合并成一条边。此外,我们只考虑在训练数据中至少激活一次的边。这些步骤显著地将标签数量从平均约65536个减少到约4000个。需要注意的是,我们在增量学习的每次迭代中都会重新运行数据预处理步骤,因此在模糊化过程中发现新的边缘数据时,合并的标签可能会因为相关性降低而被分割。

C.梯度导向优化

不同的梯度引导优化技术,如梯度下降法,牛顿法,或准牛顿法,如L-BFGS,可以使用梯度或高阶导数,以加快收敛速度[10],[13],[65]。光滑NNs通过支持梯度和高阶导数的有效计算,使得模糊输入生成过程能够潜在地使用这些技术中的任何一种。在本文中,我们特别设计了一个简单的梯度引导搜索方案,该方案对较小的预测误差具有鲁棒性,以证明我们的方法的有效性。我们把探索更复杂的技术作为未来的工作。
在描述基于神经网络梯度的变异策略之前,我们首先给出了梯度的形式化定义,该定义指示每个输入字节应改变多少以影响神经网络中最后一层神经元的输出(指示程序中改变的边缘覆盖率)f[80]。这里,每个输出神经元对应于一个特定的边,并计算一个0到1之间的值,总结给定输入字节对特定边的影响。神经网络f w.r.t.输出神经元的梯度。这些输入广泛用于对抗性输入生成[39]、[66]和可视化/理解DNNs[87]、[80]、[56]。直观地说,在我们的设置中,基于梯度的制导的目标是找到将改变对应于不同边的最后一层神经元输出的输入,从0变为1。
给定一个参数NN y=f(θ,x),如第IV-B节所定义,让y i表示f的最后一层第i个神经元的输出,也可以写成f i(θ,x)。f i(θ,x)相对于输入x的梯度G可以定义为G=∇x f i(θ,x)=∂y i/∂x。注意,f的梯度w.r.t到θ很容易计算,因为神经网络训练过程需要迭代计算这个值来更新θ。因此,只要把θ梯度的计算换成x梯度的计算,就可以很容易地计算出G。注意梯度G的维数与输入x的维数相同,在我们的例子中,输入x是一个字节序列。
image.png
梯度导向优化。算法1显示了梯度引导输入生成过程的轮廓。关键思想是识别具有最高梯度值的输入字节并对其进行变异,因为它们对NN表示更高的重要性,从而有更高的机会导致程序行为的重大变化(例如,翻转分支)。
从种子开始,我们迭代地生成新的测试输入。如算法1所示,在每次迭代中,我们首先利用梯度的绝对值来识别输入字节,这将导致对应于未确定边的输出神经元的最大变化。接下来,我们检查每个字节的梯度符号,以确定变异的方向(例如,增加或减少它们的值),从而最大化/最小化目标函数。在概念上,我们使用的梯度符号类似于[39]中引入的对抗性输入生成方法。我们还将每个字节的变异限制在其合法范围内(0-255)。第6行和第10行表示使用clip函数来实现这种边界。
我们从一个小的变异目标(算法1中的k)开始输入生成过程,并指数增长要变异的目标字节数,以有效地覆盖大的输入空间。

D.改进与增量学习

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

5.实现

在本节中,我们将讨论我们的实现以及如何微调NEUZZ以获得最佳性能。我们已经在http://GitHub.com/dongdongshe/neuzz发布了我的实现。我们所有的测量都是在运行Arch Linux 4.9.48的系统上进行的,该系统带有nvidiagtx1080tigpu。
NN架构。我们的神经网络模型在Keras-2.1.3[5]中实现,Tensorflow-1.4.1[6]作为后端。神经网络模型由三个完全连接的层组成。隐藏层使用ReLU作为其激活函数。我们使用sigmoid作为输出层的激活函数来预测控制流边缘是否被覆盖。神经网络模型训练了50个阶段(即整个数据集的50次完整通过),以达到较高的测试精度(平均约95%)。由于我们使用一个简单的前馈网络,因此所有10个程序的训练时间都不到2分钟。即使在3.6GHz的英特尔i7-7700上进行纯CPU计算,训练时间也不到20分钟。
image.png
收集训练数据。对于每个测试的程序,我们在一台单核机器上运行AFL-2.52b[88]一小时,以收集神经网络模型的训练数据。为10个项目收集的平均培训投入约为2K。得到的语料库进一步分成训练和测试数据,比例为5:1,测试数据用于确保模型不过度拟合。我们使用10KB作为阈值文件大小,从AFL输入语料库中选择我们的训练数据(平均90%的AFL生成的文件低于阈值)。
突变和再训练。如图2所示,NEUZZ迭代运行以生成1M个突变并递增地重新训练NN模型。我们首先使用算法1中描述的变异算法生成1M个变异。我们将参数i设置为10,这将为种子输入生成5120个变异输入。接下来,我们随机选择100个输出神经元代表目标程序中100个未探索的边缘,并从两个种子中产生10240个突变输入。最后,我们使用AFL的fork-server技术执行目标程序,并使用覆盖新边的任何输入进行增量再训练。
模型参数选择。NEUZZ的成功与否取决于模型训练和突变发生中不同参数的选择。在这里,我们经验地探索了确保四个程序(readelf、libjpeg、libxml和mupdf)的最大边缘覆盖率的最佳参数。结果汇总在表一中。
首先,我们评估每个初始种子需要变异多少个关键字节(算法1第1行的参数k i)。我们选择第IV-C节中所述的k=2,并显示通过三次迭代(算法1第1行中i=7、10、11)实现的覆盖率,每次迭代有1m个突变。对于所有四个程序,较小的变异(每次变异更改的字节数较少)可能导致较高的代码覆盖率,如表Ia所示。i=11的最大值实现了所有四个程序的最小代码覆盖率。这个结果可能是由于算法1中的第4行和第8行在一个种子上浪费了太多的突变(超出了100万个突变预算),而没有尝试其他种子。然而,这四个程序的最佳变异字节数各不相同。对于readelf和libxml,i的最佳值是10,而对于libjpeg和mupdf,i的最佳值是7。由于i=7和i=10在实现代码覆盖率方面的差异不大,因此我们选择i=10作为剩余的实验。
其次,通过改变隐层的层数和神经元数目来评估神经网络模型中超参数的选择。特别是,我们比较了神经网络结构与1和3个隐藏层和每层4096和8192个神经元。对于每个目标程序,我们使用相同的训练数据来训练四个不同的神经网络模型,并生成1M个突变来测试所获得的边缘覆盖率。对于这四个程序,我们发现只有一个隐藏层的模型比只有三个隐藏层的模型表现得更好。我们认为这是因为1个隐藏层模型足够复杂,可以对目标程序的分支行为进行建模,而较大的模型(即有3个隐藏层)则相对较难训练,而且也容易过度拟合。

6.评估

在这一部分中,我们评估了NEUZZ的错误查找性能,并相对于其他最新的模糊器实现了边缘覆盖。具体来说,我们回答了以下四个研究问题:
RQ1:NEUZZ能比现有的fuzzer找到更多的bug吗?
RQ2:NEUZZ能比现有的fuzzer获得更高的边缘覆盖率吗?
RQ3:NEUZZ能比现有的基于RNN的fuzzer性能更好吗?
RQ4:不同的选择如何影响NEUZZ的表现?
我们首先描述我们的研究对象和实验环境。

A.研究对象

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

B.实验装置

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

C.结果

RQ1:NEUZZ能比现有的fuzzer找到更多的bug吗?
为了回答这个问题,我们评估了NEUZZ w.r.t.三种设置中的其他模糊器:(i)检测真实世界的错误。(ii)在LAVA-M数据集中检测注入的缺陷[28]。(iii)检测CGC错误。我们详细描述了结果。
(i) 检测真实世界的错误。我们比较了NEUZZ和其他fuzzer在24小时运行时发现的错误和崩溃的总数。NEUZZ和其他fuzzer发现了五种不同类型的错误:内存不足、内存泄漏、断言崩溃、整数溢出和堆溢出。为了检测不一定会导致崩溃的内存错误,我们使用AddressSanitizer[4]编译程序二进制文件。我们通过比较AddressSanitizer报告的堆栈跟踪来测量发现的唯一内存错误。对于不会导致AddressSanitizer生成错误报告的崩溃,我们将检查执行跟踪。通过手动分析触发无限循环的输入,可以发现整数溢出错误。我们使用未定义的行为消毒剂[7]进一步验证整数溢出错误。结果总结见表三。
NEUZZ在6个程序中发现所有5种类型的bug。AFL、AFLFast和AFL laf英特尔发现3种类型的错误,它们没有发现任何整数溢出错误。其他的fuzzer只发现两种类型的错误(即内存泄漏和断言崩溃)。AFL可以在程序大小上发现堆溢出错误,而NEUZZ可以在程序nm上发现相同的错误和另一个堆溢出错误。总的来说,NEUZZ发现的bug比第二好的fuzzer多2倍。此外,只有NEUZZ发现的strip中的整数溢出错误和nm中的堆溢出错误被分配到了CVE-2018-19932和CVE-2018-19931,稍后由开发人员修复。
image.png
(ii)在LAVA-M数据集中检测注入的漏洞。创建LAVA数据集是为了通过提供一组注入大量bug的真实程序来评估fuzzers的效率[28]。LAVA-M是LAVA数据集的一个子集,由4个GNU coreutil程序base64、md5sum、uniq组成,分别注入44、57、28和2136个bug。所有的错误都由四字节的幻数比较来保护。只有在满足条件时才会触发错误。我们将NEUZZ在发现这些错误方面的性能与其他最先进的模糊器进行了比较,如表4所示。按照传统做法[22],[28],我们使用模糊器运行时的5小时时间预算。
在LAVA数据集中触发幻数条件对于覆盖率引导的fuzzer来说是一项困难的任务,因为fuzzer必须在2564个可能的情况中生成4个连续字节的精确组合。为了解决这个问题,我们使用了一个定制的LLVM pass来检测魔法字节检查,比如Steelix[55]。但与Steelix不同的是,我们利用NN的梯度来指导输入生成过程,以找到满足魔术检查的输入。我们运行一个小时的AFL来生成训练数据,并使用它来训练一个NN,其梯度识别可能触发幻数字节条件的第一个字节比较的关键字节。接下来,我们对第一个关键字节相邻的每个字节执行局部穷举搜索,以256次尝试解决其余三个字节的比较。因此,我们需要一个NN梯度计算来找到影响魔术检查的字节位置,并通过4×256=1024次尝试来触发每个bug。对于程序md5sum,根据LAVA-M的作者的最新建议[27],我们进一步将种子简化为一个行,这显著提高了模糊性能。
如表4所示,NEUZZ在程序base64、md5sum和uniq中找到所有的bug,并为程序who找到最多的bug。请注意,LAVA-M的作者在所有4个程序中都留下了一些未列出的错误,因此NEUZZ发现的错误总数实际上高于列出的错误数,如结果所示。
NEUZZ有两个关键的优势,比其他的模糊测试方法。首先,NEUZZ将搜索空间分成多个可管理的步骤:NEUZZ在AFL生成的数据上训练底层NN,使用计算出的梯度到达第一个关键字节,并围绕找到的关键区域执行局部搜索。其次,与VUzzer利用目标二进制中硬编码的幻数构造程序输入不同,NEUZZ基于梯度的搜索策略不依赖任何硬编码的幻数。因此,它可以找到程序md5sum中的所有错误,该程序在幻数检查导致VUzzer失败之前对输入字节执行一些计算。与目前最先进的LAVA-M数据集fuzzer Angora相比,NEUZZ在md5sum中又发现了3个bug。与安哥拉不同,N EUZZ使用NN梯度来更有效地触发复杂的幻数条件。
image.png
(iii)检测CGC错误。DARPA CGC数据集[2]由DARPA Cyber Grand Challenge中使用的易受攻击程序组成。这些程序作为执行各种任务的网络服务来实现,目的是镜像具有已知漏洞的真实应用程序。程序中的每一个错误都由对输入的一些健全性检查来保护。数据集附带了一组输入作为漏洞的证据。
我们对50个随机选择的CGC双星进行NEUZZ、Driller和AFL评估。由于为每个fuzzer运行每个测试二进制文件需要在CPU/GPU上运行6小时,而且我们有限的GPU资源不允许并行执行多个实例,所以我们随机选择了50个程序,以使总的实验时间保持在合理的范围内。类似于LAVA-M,这里我们还运行一个小时的AFL来生成训练数据,并使用它来训练NN。我们提供相同的随机种子给所有三个模糊器,让他们运行六个小时。NEUZZ使用LAVA-M数据集使用的自定义LLVM pass在CGC二进制文件中插入魔术检查。
结果(表五)显示,N EUZZ发现了50个二进制文件中的31个二进制文件,而AFL和Driller分别发现了21个和25个。NEUZZ发现的马车双星包括了Driller和AFL发现的所有双星。NEUZZ进一步在6个新的二进制文件中发现了AFL和Driller都无法检测到的错误。
image.png
我们分析了一个示例程序CROMU_(如清单1所示)。这是一个ASCII内容服务器,它从客户端获取查询并提供相应的ASCII代码。当用户试图将命令设置为VISUALIZE时,将触发空指针取消引用错误。AFL在6小时的时间预算内未能检测到这个漏洞,因为它在猜测魔法弦方面效率低下。尽管Driller试图通过混合执行来满足这种复杂的魔术字符串检查,但在这种情况下,它找不到满足检查的输入。相比之下,NEUZZ可以很容易地使用NN梯度来定位程序输入中影响magic比较的关键字节,并找到满足magic检查的输入。
结果1:NEUZZ在6个不同的程序中发现了31个以前未知的错误,而其他的fuzzer找不到。在发现LAVA-M和CGC漏洞方面,NEUZZ也优于最先进的fuzzer。
RQ2:NEUZZ能比现有的fuzzer获得更高的边缘覆盖率吗?
为了研究这个问题,我们比较了24小时固定运行时间预算的模糊器。该评估不仅显示了模糊器发现的新边的总数,而且还显示了新边覆盖的速度随时间的变化。
image.png
我们从AFL的边缘覆盖报告中收集边缘覆盖信息。结果总结在表六中。对于所有10个实际程序,NEUZZ在边缘覆盖方面明显优于其他fuzzer。如图4所示,在第一个小时内,NEUZZ可以实现比其他模糊器明显更多的新边缘覆盖。在程序strip、harfbuz和readelf上,NEUZZ可以在一小时内实现1000多个新的边缘覆盖。对于readelf和objdump程序,NEUZZ运行1小时后的新边缘覆盖率甚至超过了所有其他fuzzer运行24小时后的新边缘覆盖率。这显示了NEUZZ的优越的边缘覆盖能力。在所有的9个项目中,NEUZZ的边缘覆盖率分别比基线AFL高6×、1.5×、9×、1.8×、3.7×、1.9×、10×、1.3×和3×个,比第二高的6个模糊数高4.2×、1.3×、7×、1.2×、2.5×、1.5×、1.3×和3×个。对于最小的程序zlib,它只有不到2k行代码,NEUZZ实现了与其他fuzzer相似的边缘覆盖。我们相信,当这样一个小程序的大多数可能的边经过24小时的模糊处理后已经被发现时,它就会达到饱和点。该算法的显著性能证明了N EUZZ算法在利用梯度覆盖新边的方法有效地定位和变异关键字节方面的有效性。NEUZZ在大型系统中也有很好的扩展性。事实上,对于超过10K行的程序(如readelf、harfbuzz、mupdf和libxml),NEUZZ实现了最高的边缘覆盖率,其中污点辅助模糊器(即VUzzer)和符号执行辅助模糊器(即KleeFL)要么表现糟糕,要么无法扩展。
梯度引导变异策略允许NEUZZ探索不同的边缘,而其他基于进化的fuzz常常陷入困境,重复检查相同的分支条件。此外,神经网络平滑技术的最小执行开销有助于N EUZZ很好地扩展到更大的程序,而其他高级进化模糊器由于使用重磅程序分析技术(如污点跟踪或符号执行)而产生高执行开销。
在进化模糊器中,AFLFast采用了一种优化的种子选择策略,该策略更关注稀有边缘,从而在8个程序上实现了比AFL更高的覆盖率,特别是在libjpeg、size和harfbuzz中。另一方面,VUzzer在小程序(如zlib、nm、objdump、size和strip)的第一个小时内实现了比AFL、AFLFast和AFL laf intel更高的覆盖率,但它的领先地位迅速停滞,并最终被其他模糊器超越。同时,VUzzer在更大的程序(如readelf、harfbuzz、libxml和mupdf)上的性能会下降。我们怀疑VUzzer的污点跟踪器引入的不精确性会导致它在大型程序上表现不佳。KleeFL使用符号执行引擎Klee生成的附加种子来指导AFL的探索。与VUzzer类似,对于小程序(nm、objdump和strip),KleeFL在开始时有很好的性能,但是它的优势是在几个小时后Klee的附加种子会消失。此外,KleeFL基于Klee,无法扩展到具有复杂库代码的大型程序,这是众所周知的符号执行限制。因此,KleeFL没有libxml、mupdf和harfbuzz程序的结果。与VUzzer和KleeFL不同,NEUZZ不依赖于任何繁重的程序分析技术;NEUZZ使用从NNs计算出的梯度来生成有希望的突变,即使对于更大的程序也是如此。有效的神经网络梯度计算过程允许NEUZZ比VUzzer和KleeFL在识别影响不同未显示程序分支的关键字节方面具有更好的伸缩性,从而实现显著的边缘覆盖。
AFL laf intel使用LLVM过程将复杂的幻数比较转换为嵌套的字节比较,然后在转换后的二进制文件上运行AFL。它在程序条上实现了第二高的新边缘覆盖率。但是,比较转换会将其他指令添加到常见的比较操作中,从而导致潜在的边爆炸问题。边缘爆炸极大地提高了边缘冲突的发生率,影响了进化模糊算法的性能。此外,这些附加指令还会导致额外的执行开销。因此,像libjpeg这样经常进行比较操作的程序会明显减速(例如libjpeg),而AFL laf intel则难以触发新的边缘。
结果2:NEUZZ的边缘覆盖率明显高于其他灰度盒模糊器(比AFL高4倍,比第二好的24小时运行的模糊器高2.5倍)。
image.png
RQ3:NEUZZ能比现有的基于RNN的fuzzer性能更好吗?
现有的基于递归神经网络(RNN)的模糊器从过去的模糊经验中学习突变模式,以指导未来的突变[72]。这些模型首先从AFL产生的大量变异输入中学习变异模式(由关键字节组成)。接下来,他们使用突变模式构建一个AFL过滤器,只允许关键字节上的突变通过,否决所有其他非关键字节的突变。我们选择4个先前研究过的程序来评估NEUZZ相对于基于RNN的fuzzer在100万个突变中的性能。我们使用相同的训练数据训练两个神经网络模型,然后让这两个基于神经网络的模糊器产生100万个变异,并比较两种方法获得的新代码覆盖率。如表七所示,我们报告了实现的边缘覆盖和训练时间。
image.png
在所有四个程序中,NEUZZ在一百万个突变上明显优于基于RNN的fuzzer。NEUZZ在四个程序中分别比基于RNN的fuzzer实现了8.4×4.2×6.7×和3.7×的边缘覆盖。此外,基于RNN的模糊器的训练开销平均比NEUZZ高20倍,因为RNN模型比前馈网络模型要复杂得多。
将基于RNN的fuzzer与AFL进行了比较,结果表明前者在1小时的语料库中比基于libxml和mupdf的AFL平均实现了2倍的边缘覆盖率。我们还观察到,基于RNN的fuzzer否决了约50%由AFL产生的突变。因此,基于RNN的fuzzer的1M突变的新边缘覆盖可以实现香草AFL中2M突变的边缘覆盖。这就解释了为什么基于RNN的fuzzer在一些程序中发现了大约2倍于AFL的新边。如果AFL在2百万个突变后卡住,基于RNN的fuzzer也会在1百万个筛选突变后卡住。与基于RNN的模糊器相比,NEUZZ的关键优势在于,NEUZZ使用基于神经网络的梯度引导搜索获得关键位置,而RNN模糊器则尝试以端到端的方式对任务建模。实验结果表明,该模型能够区分RNN模型可能遗漏的关键字节的不同影响因素。对于突变的产生,我们对由相应的影响因素决定的关键字节进行穷举搜索,而基于RNN的模糊器仍然依赖于AFL的均匀随机突变。
结果3:NEUZZ是一种基于简单前向网络的模糊器,在不同的项目中,其边缘覆盖率提高了3.7~8.4倍,明显优于RNN模糊器。
RQ4:不同的选择如何影响NEUZZ的表现?
NEUZZ的模糊性能在很大程度上取决于训练神经网络的精度。如第五节所述,我们通过经验发现,一个具有1个隐藏层的神经网络模型足以描述真实程序的复杂分支行为。在本节中,我们通过探索1隐层结构的不同模型设置(即线性模型、无求精的NN模型和具有增量求精的NN模型)来进行烧蚀研究。我们评估了这些模型对NEUZZ性能的影响。
为了比较模糊性能,我们在4个程序上为每个版本的NEUZZ生成了1百万个突变。我们通过去除隐藏层中的非线性激活函数来实现线性模型,从而使整个前馈网络完全线性化。神经网络模型是由AFL中的同一种子库训练而成。接下来,我们从被动学习模型中产生1百万个突变,并测量这些1百万个突变所达到的边缘覆盖率。最后,我们从100万个变异中筛选出行使未观察边缘的变异输入,并将这些选择的输入添加到原始种子语料库中,以增量方式重新训练另一个神经网络模型,并使用它生成进一步的变异。结果总结见表八。我们可以看到,这两种神经网络模型(有或没有增量学习)在所有4个测试程序中都优于线性模型。这说明非线性神经网络模型比简单的线性模型更能逼近程序行为。我们还观察到,增量学习有助于NNs获得更高的精度,从而获得更高的边缘覆盖率。
image.png
结果4:神经网络模型优于线性模型,增量学习使神经网络更精确。

7.bug案例研究

在本节中,我们将提供NEUZZ发现的三种不同类型的错误的示例并进行分析:整数溢出、内存不足和导致崩溃的错误。
我们注意到,大量的程序错误是由于错误处理变量的极值造成的。由于NEUZZ可以枚举从0x00到0xff的所有关键字节(请参见算法1第3行),因此我们设法找到由错误处理的极值导致的大量错误。例如,NEUZZ通过将影响内存分配大小的输入字节设置为非常大的值,可以在libjpeg、objdump、nm和strip中找到许多内存不足的错误。
strip的整数溢出。NEUZZ发现了一个整数溢出错误,可以在strip上引发无限循环。清单2显示了strip程序中的一个函数,该函数解析输入ELF文件的程序头表中的每个部分,并将所有部分分配给输出ELF文件中的新程序头表。整数溢出发生在清单2第11行的if条件下,因为NEUZZ将段大小设置为一个非常大的值。因此,程序陷入无限循环。我们发现这个错误存在于最新版本的Binutils 2.30和旧版本的2.26和2.29中。
libjpeg内存不足。在JPEG压缩过程中,为了减小文件的大小,每一个颜色空间的数据都被相应的采样因子降采样。根据JPEG标准,采样因子必须是1到4之间的整数。这个值在解压过程中用于确定需要分配多少内存,如清单4所示。NEUZZ设置一个大值,该值会导致为图像数据分配过多内存,从而导致内存不足错误。这些错误可能会被利用来对使用libjpeg显示图像的服务器发起拒绝服务攻击。
image.png
readelf的崩溃。ELF文件由文件头、程序头、节头和节数据组成。根据ELF规范,ELF头包含64位二进制文件的第60字节处的字段e_shnum,该字段指定ELF文件中的节数。NEUZZ将输入文件的节数设置为0。如清单3所示,如果节数等于0,则实现返回一个空指针,该指针被后续代码取消引用,从而触发崩溃。

8.相关工作

程序平滑。帕纳斯等人。[67]注意到不连续性是开发安全可靠软件的基本挑战之一。乔杜里等人。[21]、[18]、[19]提出了程序平滑的思想以便于程序分析,并提出了一种使用抽象解释和符号执行的严格平滑算法。不幸的是,这样的算法会产生令人望而却步的性能开销,特别是对于大型程序。相比之下,我们的平滑技术利用NNs的学习能力来实现更好的可伸缩性。
基于学习的模糊。最近,人们对使用机器学习技术改进模糊器越来越感兴趣[37]、[72]、[84]、[9]、[81]、[12]、[64]。然而,现有的基于学习的模糊器将模糊建模作为一个端到端的ML问题,即学习ML模型直接预测能够获得更高代码覆盖率的输入模式。相比之下,我们首先使用NNs平滑地逼近程序分支行为,然后利用梯度引导输入生成技术来获得更高的覆盖率。因此,我们的方法比端到端的方法更能容忍通过ML模型来学习错误。在这篇文章中,我们的经验证明,我们的策略在发现错误和实现更高的边缘覆盖方面都优于端到端建模[72]。
基于污点的模糊。一些进化模糊者试图利用污染信息来识别有希望的突变位置[85],[42],[63],[73],[55],[22]。例如,TaintScope[85]被设计用来识别影响系统/库调用的输入字节,并专注于改变这些字节。类似地,Dowser[42]和BORG[63]专门使用污染信息来分别检测缓冲区边界违规和缓冲区过度读取漏洞。相比之下,Vuzzer[73]通过静态分析捕获魔法常数,并将现有值变异为这些常数。Steelix[55]使用二进制文件收集有关比较指令的其他污染信息。最后,安哥拉[22]使用动态污染跟踪来识别有希望的突变位置,并执行坐标下降来指导这些位置上的突变。
然而,所有这些基于污染跟踪的方法从根本上受到限制,因为动态污染分析会产生很高的开销,而静态污染分析则会出现很高的误报率。我们的实验结果表明,通过使用神经网络来识别有希望的突变位置,NEUZZ很容易优于现有的基于污染的模糊器。
一些fuzzer和测试输入生成器[43]、[83]、[22]尝试在目标程序上直接使用不同形式的梯度引导优化算法。然而,如果没有程序平滑,这样的技术往往会在不连续处挣扎和卡住。
象征性/混合性处决。符号和共形执行[50]、[14]、[77]、[61]、[36]使用可满足性模理论(SMT)求解器来解决路径约束并找到有趣的测试输入。一些项目还尝试将模糊化与这种方法结合起来[17]、[32]、[82]。不幸的是,由于符号分析的一些基本限制,这些方法在实践中难以扩展,包括路径爆炸、不完全环境建模、符号记忆建模的大量开销等。

与我们的工作同时,NEUEX[79]使用NNs学习程序中间变量之间的依赖关系,并与传统的SMT求解器一起使用梯度引导神经约束求解,使得符号执行更加有效。相比之下,在本文中,我们着重于使用NNs来提高模糊化的效率,因为它是迄今为止在大型真实程序中发现安全关键错误的最流行技术。
神经程序。神经程序本质上是学习目标程序逻辑的潜在表示的神经网络。最近的一些工作已经从一个程序的输入输出样本中合成了这样的神经程序,以精确地预测新输入的程序输出[41]、[74]、[62]。相比之下,我们使用NNs来学习程序分支行为的光滑近似。

9.结论

我们提出了一种有效的学习型模糊器NEUZZ,它使用代理神经网络平滑地逼近目标程序的分支行为。我们进一步演示了如何使用梯度引导技术生成新的测试输入,以发现目标程序中的不同错误。我们的广泛评估表明,NEUZZ在检测到的错误数量和实现的边缘覆盖方面都显著优于其他10个最先进的模糊器。我们的结果显示了利用不同的梯度引导输入生成技术和神经平滑技术来显著提高模糊过程的有效性的巨大潜力。
**确认我们感谢我们的牧羊人马修希克斯和匿名评论员的建设性和宝贵的反馈意见。这项工作部分由NSF资助CNS-18-42456、CNS-18-01426、CNS-16-17670、CNS-16-18771、CCF-16-19123、CNS-15-63843和CNS-15-64055;ONR资助N00014-17-1-2010、N00014-16-1-2263和N00014-17-1-2788;ARL Young调查员(YIP)奖;谷歌教师奖学金;亚马逊网络服务奖。本文中表达的任何意见、发现、结论或建议都是作者的意见、发现、结论或建议,并不一定反映美国政府、ONR、ARL、NSF、Google或Amazon的意见、发现、结论或建议。