阅读笔记——《A survey of protocol fuzzing》
- 【参考文献】Zhang X, Zhang C, Li X, et al. A survey of protocol fuzzing[J]. ACM Computing Surveys, 2024, 57(2): 1-36.
- 【注】本文仅为作者个人学习笔记,如有冒犯,请联系作者删除。
目录
1、Introduction
2、Background
2.1、Communication Protocols
2.2、Types of Protocols
3、Protocol Fuzzing Overview
3.1、Differences between protocol fuzzing and traditional fuzzing
3.1.1、High communication complexity
3.1.2、Constrained Testing Environment
3.2、Summary of Existing Protocol Fuzzers
4、Input Generator
4.1、Communication Model Construction
4.1.1、Top-Down Approaches
4.1.1、Bottom-Up Approaches
4.2、Task Scheduling
4.3、Testcase Construction
5、Executor
5.1、Efficient Execution
5.1.1、Scalability Improvement
5.1.2、Execution Cost Reduction
5.2、Runtime Information Extraction
6、Bug Collector
6.1、Memory-Safety Bug Oracles
6.1.1、Fatal Signals 致命信号
6.1.2、Crash Logs and Debug Information 崩溃日志和调试信息
6.1.3、Error-Signaling Messages(标记为“ESM”)错误信号消息
6.1.4、Abnormal Physical Behaviors(标记为“APB”) 异常物理行为
6.1.5、Timeout and Liveness Checks 超时和存活性检查
6.2、Non-Memory-Safety Bug Oracles
6.2.1 不正确的消息内容与状态转换
6.2.2 传输中的消息不一致性
6.2.3 异常性能指标
6.2.4 执行差异(表5第6列标注为“DE”)
7、Directions of Future Research
7.1、Towards Perfect Communication Model Construction
7.2、Towards Multi-Dimension Testing Perspectives
7.3、Fuzzing Characterized Protocol Targets
7.4、Combining with Other Vulnerability-Finding Techniques
7.5、Shift-Left Protocol Fuzzing
1、Introduction
- 本调查旨在概述特定于协议的挑战、相应的解决方案和未来的发展方向。
- 检查传统的模糊和协议模糊之间的区别。(第3节)
- 回顾现有的工作如何解决协议模糊化中的挑战。(第4-6节)
- 探索该领域潜在的未来发展方向。(第7节)
2、Background
2.1、Communication Protocols
- 通信协议是一组规则,它能够利用任何形式的物理量变化,实现通信系统中的两个或多个实体之间的信息交换。通信协议的实现通常涉及多个阶段。
- 首先,协议是概念上设计的,包括定义基于协议需求执行的规则、行为和功能,并考虑到效率、可靠性、可伸缩性和安全性等因素。设计阶段的结果是一组规范。
- 然后,在开发阶段,将协议的设计转化为具体的实现。这可以是软件、硬件的形式,或者是两者的结合。一旦开发出来,该协议将经过严格的测试,以确认其符合协议规范,并满足性能和可靠性要求。
- 其中,本文主要关注的模糊化是一种常用的测试协议实现的技术。最终,协议实现将部署在现实环境中。
2.2、Types of Protocols
- 协议可以从多种角度进行分类,例如功能性、规范的可访问性以及与OSI网络参考模型各层的对应关系,如图3所示。
- Based on functionalities:从功能的角度来看,协议展示了广泛的品种,每一种都是为实现独特的操作目标而定制的。例如,安全协议的设计主要是为了确保数据的完整性和机密性,例如TLS和DTLS(数据报传输层安全性)。路由协议,如BGP(边界网关协议),致力于有效地管理穿越网络的数据包的路由。此外,应用程序协议,如用于web服务的HTTP(超文本传输协议)和用于电子邮件的SMTP(简单邮件传输协议),专门在应用程序层启用特定功能。
-
Based on availability of specification:当考虑协议规范的可访问性时,在 开放协议和专有协议之间存在区别。开放协议,如TCP,具有公开可访问的规范,允许广泛的审查和实现。相比之下,专有协议,如微软的RDP(远程桌面协议),都是由单个实体管理的,其规范并不是完全公开的。协议规范的可用性对于模糊化的各个阶段至关重要,例如精心构建输入、构造状态机和检测bug。重要的是要澄清,对开放和专有的分类只与规范的可用性有关,并且独立于协议实现的源代码的可访问性有关。
- Based on statefulness:关于有状态性,协议被分为有状态类别和无状态类别。有状态协议,如TLS和TCP,需要多轮交互。无状态协议,如UDP和HTTP,不跨请求维护状态信息。
- Based on OSI reference model:基于OSI网络参考模型,协议可以分为七个不同的层:物理、数据链路、网络、传输、会话、表示和应用程序。每个协议层都解决了一类不同的通信问题。其中,低层协议与物理硬件具有较高的耦合。值得注意的是,并不是每个协议都与OSI模型中的单个层精确对齐。例如,TLS/DTLS包含会话层和表示层的功能;Wi-Fi协议包含的主要功能物理链路层和数据链路层。鉴于在许多来源中对协议分层的不同解释,我们根据它们的主要功能对这些协议进行了分类。
3、Protocol Fuzzing Overview
3.1、Differences between protocol fuzzing and traditional fuzzing
- 在本小节中,我们将讨论文献中确定的与协议模糊相关的独特挑战。协议实现与传统的模糊目标在两个关键方面有所不同:第一,它们表现出更高的通信复杂性;其次,它们的测试环境相对更受限。这些差异不仅突出了方案模糊的特殊性,而且也对应了一系列固有的挑战。
3.1.1、High communication complexity
- 通信的高复杂性可以从以下两个方面进行讨论。
- 尊重沟通中的语义约束。协议通过为消息交换提供一组标准化的规则,作为促进不同系统之间通信的主干。这种通信本质上是复杂的,通常涉及一个多轮过程,其中必须依次执行多个步骤才能使交换成功。这样的协议本身就需要有状态的实现,通信阶段的每个阶段都建立在前一个之上。在测试场景中,这意味着在早期的约束得到令人满意的满足之前,不能测试协议实现的更深层次——这些是通信协议中固有的严格语义约束。语义约束有两种主要形式:消息内部约束和消息间约束。消息内约束与单个消息的结构和内容有关,确保数据字段在该消息的上下文中语法正确和语义有意义。以TCP为例,在一个TCP段中,有几个关键字段,如源端口、目标端口等。
- 测试通信过程的不同特性。除了基本的消息交换功能外,协议还需要保证一系列额外的特性,这些特性可以形成更安全或更可靠的通信,如时间要求、身份验证、机密性和并发性。在实现中有效地测试这些属性需要一种更复杂的测试形式,这种形式超越了典型的应用程序模糊,后者主要侧重于改变结构化输入以发现问题。每个属性可能需要对模糊框架进行重大修改甚至重新设计,包括开发专门的输入生成器、反馈机制和神谕,以促进有效的测试。例如,在构建一个旨在检测协议实现中的流量放大攻击的模糊器时,需要一个预测器来识别不成比例的请求-响应数据量比,这表明了一个放大因子。目前,输入生成器需要巧妙地重新设计,以生成特定的协议消息的变化,以最大限度地提高潜在的放大因素。此外,放大因子可以作为一种反馈,指导模糊器更有效地探索输入空间。
3.1.2、Constrained Testing Environment
- 由于协议与硬件之间的紧密耦合,协议模糊通常面临着一个受限的测试环境。
- 首先,许多协议被设计用于低级物理设备之间的通信或发生在专门部门的通信,例如,位于OSI参考模型下层的协议,即物理和数据链路层,或为汽车、工业控制系统(ICS)、电网和航空系统等特定部门设计的协议。在这些情况下,测试吞吐量将受到硬件依赖性的限制,例如缺乏警告、可伸缩的模糊的瓶颈等。
- 此外,这些物理依赖关系也限制了先进的模糊技术的应用。这是因为许多先进的模糊技术需要来自测试目标的灰盒或白盒测试信息,但由于在这些特定的硬件上缺乏程序分析框架,因此无法得到满足。
3.2、Summary of Existing Protocol Fuzzers
- 一般的模糊器由三个基本组件组成,即输入生成器、执行器和bug收集器。在一次模糊的迭代中,输入生成器首先向执行器生成一个测试输入。然后,执行器用给定的输入执行PUT,并收集其他两个组件的运行时信息。最后,bug收集器检查运行时信息,以确定输入是否触发了bug。当前研究工作如图4所示。
- Input generator:理想情况下,该组件负责生成输入,以尽可能有效地暴露内部的功能。为了实现这一点,协议模糊器通常会实现有三个主要子阶段,包括通信模型构建、调度和测试案例构建。
- Executor:为了追求协议模糊的理想执行者,当代的研究集中在两个关键方面:高效执行和运行时信息提取。前者探索了一个高效、自动化和可扩展的测试环境的开发,增强了协议测试的执行。后者侧重于创建一个分析环境,提取基本的运行时信息,从而通知和改进输入生成和bug检测过程。
- Bug collector:bug收集器组件的主要目标是两个:为了增加它可以检测到的bug类型的多样性,并提高这些检测的准确性。该组件经过了精心的调整,以精心识别广泛的漏洞,从缓冲区溢出到更微妙的非内存安全漏洞,如逻辑错误和规范违反。
4、Input Generator
- 在本节中,将详细介绍现有的工作如何改进输入生成器,以解决协议模糊过程中的独特挑战。如表2所示,总结了在设计输入生成器的三个关键阶段的现有工作中使用的技术。
4.1、Communication Model Construction
- 为了增强具有语义约束的传统模糊器,必须开发一个协议的通信模型来指导模糊过程。该通信模型同时包括状态模型和数据模型。状态模型详细说明了不同协议状态之间的转换,而数据模型则指定了驱动这些状态转换的消息的格式和结构。
- 通常,现有的工作将协议的通信模型表示为状态机或其变体。状态机是一种描述协议实现的内部状态转换的数据结构。状态机可以表示为有向图,如确定性有限自动机(DFA)或Mealy机。在这些图中,节点表示实体的内部状态,而边表示由于接收或发送某些类型的消息而引起的状态转换。通过参考通信模型,协议模糊器可以感知当前目标状态,并根据当前状态下可接受的消息类型的数据模型生成测试例,从而提高测试例的有效性。请注意,一个协议实现可能有多个通信模型,因为它的行为可能因其工作模式或配置而不同。
- 例如,Wi-Fi设备可以被配置为运行在AP模式、STA模式或P2P模式[23,178]中,而SIP实现可以被配置为客户端、服务器或代理[117],它们各自对请求的反应不同,从而具有不同的通信模型。大多数现有的工作将这些在不同配置中运行的实现视为不同的目标:它们为一个给定的配置构造一个通信模型。
- 本节中将提供一个基于其通信模型构造方法的现有工作的分类法。如图5所示,它们被分为两类: 自上而向下的方法和自底向上的方法。
4.1.1、Top-Down Approaches
- 自顶向下的方法通过学习协议的文本描述,如规范或文档,来构建协议通信模型。自顶向下的方法需要协议规范作为输入,因此主要针对开放协议。得益于对规范的全局协议知识和精确定义的状态和转换,由自上而下的方法构建的通信模型相对完整和准确。值得注意的是,所构建的通信模型可能仍然与实现的不同。这是因为开发人员可以根据实际情况来定制或扩展规范中描述的设计。这种差异可能会影响最终的模糊性能。在方法上,有一些手动和自动的方法可以从协议文档中构建一个通信模型。
- 手动构建:
- 大多数现有的工作都使用相当多的领域专业知识手动构建一个通信模型。例如,Garbelini等人[56]和GREYHOUND[55]通过参考协议规范的核心设计,构建了一个低能量蓝牙(BLE)和Wi-Fi的整体状态机。
- 尽管手动构建状态机是一项容易出错且劳动密集的任务,但其好处在于专家可以灵活定制(调整或扩展)状态机,以最大化其在工作目标中的效果。例如,为了检测由不同协议模式之间的不正确复用(如不同的协议版本、扩展、认证模式或密钥交换方法)引起的状态机错误,Beurdouche 等人[18]手动构建了一个复合状态机,包含跨协议模式的所有有效状态转换。然后,那个复合状态机用于生成偏差轨迹作为测试用例,以发现无效的状态转换。
- 类似地,FuzzPD[77]经过精心设计,以适应USB电力传输协议(USBPD)中固有的独特双重角色特性,在该协议中,每个设备同时充当电源和电源接收端。通过整合这两种角色的状态机,FuzzPD能够在模糊测试过程中实时无缝切换电源角色。与上述研究不同,有些工作选择学习状态机的部分信息作为指导。
- Zou等人提出了TCP-Fuzz,这是一种新颖的方法,结合了从RFC文档中手动提取的15条依赖规则。这些规则涵盖了多种依赖关系,包括数据包间、系统调用与数据包之间、以及系统调用间的交互。利用这些规则,TCP-Fuzz巧妙地通过同时生成相互依赖的数据包和系统调用来生成测试用例。
- 另一个例子是L2Fuzz[120]。作者构建了一个映射,描述了协议中19个状态相关的有效命令。这个映射有助于生成特定的测试用例,以产生在当前状态下可接受的命令,从而提高测试过程的相关性和有效性。
- 一些工作还解决了规范与实现之间通信模型不完全相等的问题。以异构的单点登录(SSO)平台为例,MoSSOT[149]首先构建了一个常规SSO过程的状态机,然后分析不同SSO平台的实际SSO网络流量,学习实现细节,如每个操作中的关键参数。这些实现细节细化了不同SSO平台中状态机的状态转换条件。
- 自动构建:
- 为了自动化手动构建通信模型的易错且劳动密集的过程,一些研究通过自动从协议规范中检索语义约束来实现这一目标。
- 例如,RESTler[13]基于Swagger规范中的返回类型学习消息依赖关系,Swagger是一种描述RESTful API端点、方法、参数和返回类型的结构化规范格式。
- Pacheco等人提出使用自然语言处理(NLP)从协议规范中提取有限状态机(FSM)[118]。需要注意的是,这两篇论文未列入表2,因为这些工作并不是在构建有状态协议的模糊测试工具。
- 随着大语言模型(LLM)技术的进步,越来越多的研究开始使用LLM技术自动化学习和理解协议规范,例如mGPTFuzz[94]、CHATAFL[104]和LLMIF[172]。这些方法旨在在模糊测试过程中提供指导,特别是在推断当前协议状态和生成适当的测试消息时。LLM的集成为提高协议模糊测试的效率和有效性提供了有前景的途径,通过利用其复杂的语言理解能力来解读复杂的协议规范,并相应地指导测试策略。
4.1.1、Bottom-Up Approaches
- 自下而上的方法为通信模型重建提供了另一种解决方案。这些方法利用协议实现的可观察信息来重建通信模型。由于它们不依赖于文本文档或规范,因此适用于专有协议。与自上而下的方法不同,自上而下方法在文档中有明确的协议状态定义,而自下而上的方法中的状态定义是针对特定目的的,并且可能在不同的用例、方法和实现之间有所不同。例如,AFLNET[123]根据PUT响应的状态码确定协议状态。另一个例子是StateAFL[110],它将长期存在的内存的内存布局分组为不同的状态。从学习来源的角度来看,这些方法可以分为两类,即基于流量分析的方法和程序分析辅助的方法。
- 基于流量分析的方法:侧重于纯粹从观察到的网络流量轨迹中重构协议通信模型。这种方法易于操作,并且在无法跟踪程序执行的情况下工作良好,例如,无法获得包含目标程序的固件。基于交通分析的通信模型构建方法可以是被动的,也可以是主动的。
- 被动学习(在表2第4列中标记为“TAPL”)方法主要依赖于一组预先收集的PUT与其他实体的网络跟踪数据来推断通信模型。现有研究提出的学习算法可以分为两类:基于统计的算法和基于神经网络的算法。
- 对于前者,Pulsar[57]通过计算网络跟踪库中相邻消息发生的概率来构建二阶马尔可夫模型,然后将该马尔可夫模型最小化为DFA(确定性有限自动机)。在接收到消息后,Pulsar将其与推断出的DFA中的某个状态进行匹配,从而选择有效的响应模板来构建新的测试用例。
- 对于后者,Fan等人[44]和SeqFuzzer[194]使用LSTM学习有状态协议的语法和时序特征。具体来说,他们采用长短期记忆(LSTM)作为序列到序列(seq2seq)模型[165]的编码器和解码器。Seq2seq模型是一种编码器-解码器结构,能够处理不同长度的输入和输出序列。编码器LSTM模型通过捕获的网络跟踪学习协议的特征,而解码器LSTM模型则用于生成模糊测试输入。
- 基于被动网络跟踪的状态机学习方法操作简便、运行速度快。然而,所构建的状态机的质量依赖于捕获流量的覆盖范围。实际上,很难捕获全面的消息类型和序列,这导致构建的通信模型缺少一些未捕获的状态或状态转换。
- 主动学习(在表2第4列中标记为“TAAL”)方法涉及在模糊测试过程中学习通信模型。这些方法可以根据是否预先定义全局状态集进行分类。
- 第一类方法不提前定义全局状态集,即不预设状态机中可能的状态数量和性质。这种方法采用自动机主动学习算法来辨识目标的状态机。学习算法基于用户定义的输入/输出字母表和字母表与具体消息之间的映射器。开始时从空状态机出发,这些算法通过与目标协议实现交互,反复提出和完善模型,直到不再发现与学习的状态机相反的例子。该类别的大多数工作利用Angluin的𝐿∗算法,基于协议规范定义输入字母表,并通过消息模板将其转化为实际消息。DYNPRE[88]结合了一种自适应消息重写技术,以管理会话特定的标识符,同时与服务器交互。它使用字节级变异和服务器反馈来推测消息的含义和格式,识别不同的消息类型,并根据观察到的消息模式创建精确且最小的协议状态机。
- 相反,第二类方法通过基于规则的方法预定义状态集,以绕过自动机学习算法的复杂性,并通过变异已知的消息来学习状态之间的转换。例如,AFLNET[123]使用响应消息的状态码推断当前协议状态,变异真实消息序列以揭示状态转换。Bleem[90]利用Scapy库解析消息,并通过保留所有枚举类型字段将消息抽象为各种消息类型。这一策略基于从超过50个Scapy支持的协议中获得的经验观察,其中不同的枚举字段值通常表示不同的数据包或帧类型。然后,Bleem使用这些抽象的消息轨迹来构建一个指导图进行模糊测试。另一个例子是Braktooth[53],它定义了八条规则,根据消息特征将消息映射到状态。它作为PUT与标准协议栈之间的代理,变异通信以探索额外的状态转换。类似地,Garbelini等人[54]建立了映射规则,利用捕获的跟踪(即pcap文件)识别状态并学习状态机。
- 被动学习(在表2第4列中标记为“TAPL”)方法主要依赖于一组预先收集的PUT与其他实体的网络跟踪数据来推断通信模型。现有研究提出的学习算法可以分为两类:基于统计的算法和基于神经网络的算法。
- 程序分析辅助的方法(在表2第4列中标记为“PAL”):与基于流量分析的方法相比,程序分析辅助方法还使用内部执行信息来构建通信模型。通常,内部执行信息包括静态和动态程序分析的结果,这不仅需要访问程序,还需要程序分析框架,例如程序插桩工具。根据使用的内部执行信息类型,现有工作可以分为基于执行轨迹和基于状态变量的方法。
- 基于执行轨迹的方法根据目标的执行轨迹识别不同的内部执行状态。例如,ICS3Fuzzer[45]动态插桩目标监督软件以收集执行轨迹。通过比较执行轨迹的身份,ICS3Fuzzer能够区分PUT是否处于不同的状态。
- 基于状态变量的方法通过跟踪输入处理过程中状态变量的值变化来检测协议状态转换。这些方法基于一个简单的观察,即大多数协议实现使用某些变量来存储当前状态。因此,它们将这些变量识别为状态变量,并通过其值来区分不同的状态。例如,StateAFL[110]通过识别内存快照中的长寿命数据结构来识别可能的状态变量。类似地,STATEINSPECTOR[102]通过定位堆内存中在每个消息序列执行过程中保持相同值的内存区域来识别状态变量。不同的是,SGFuzz[15]通过正则表达式识别状态变量,自动提取所有至少被赋值一次的枚举类型变量。这一方法的背后洞察基于调查发现,大多数协议实现使用枚举类型的状态变量。STATELIFTER[148]将协议解析器中的循环视为状态机的核心。通过静态分析,它遍历代码中的循环结构,并将每次循环迭代可能执行的不同路径映射到不同的状态。循环迭代之间的依赖关系被视为状态机中的状态转换。ParDiff[195]使用静态符号分析,通过识别并将相关的消息解析约束转化为有序的状态转换,从协议实现中提取有限状态机(FSM)。
- 一些研究也使用程序分析辅助方法来构建协议实现的数据模型。Polyglot[22]利用动态二进制分析通过密切监控程序如何处理网络数据来提取协议信息,揭示复杂的协议语义和结构,而不依赖源代码。近期的研究Netlifter[147]使用静态分析直接从源代码中推导出精确的协议规范,采用抽象格式图(AFG)高精度地捕捉和可视化复杂的数据结构关系和依赖性。Spenny[163]将动态分析与符号执行相结合,精确地逆向工程工业控制系统协议。
4.2、Task Scheduling
- 在最近的协议模糊测试研究中,调度阶段已根据处理与状态相关的复杂性的方法论进行明确分类。这一分类主要分为两大类:层次化方法和单一方法。
- 层次化方法将调度过程分解为两个独立的阶段:1)状态间调度(Inter-state scheduling):此阶段涉及使用基于状态优先级或相关性的状态调度算法来选择一个状态进行模糊测试;2)状态内调度(Intra-state scheduling):一旦选择了目标状态,便应用一般的调度算法来优化该状态内的模糊测试。
- 通过将这两个阶段分开,层次化方法可以对模糊测试过程进行更加细致的控制。例如,如果我们想在握手完成后测试协议,状态间调度阶段将首先优先选择这个状态。然后,状态内调度阶段将使用传统的调度方法(如种子调度、字节调度、变异策略调度)生成特定的测试用例,在握手后的状态中执行。在这一范式中,调度过程中使用的启发式方法主要分为三类,分别是:
- 稀有优先(Rarity-preferred,表2第5列的SRHS)。稀有优先启发式方法将更多资源分配给较少执行的状态,假设这些状态可能隐藏着更多尚未发现的相邻状态或代码逻辑。
- 性能优先(Performance-preferred,表2第5列的SPHS)。性能优先启发式方法优先选择那些显示出较高代码覆盖率或缺陷发现率的状态。
- 复杂度优先(Complexity-preferred,表2第5列的SCHS),如表3所示。复杂度优先启发式方法则偏好选择复杂度较高的状态(即连接更多基本块的状态)或较深的状态(即距离初始状态较远的状态)。
- 例如,ICS3Fuzzer[45]倾向于选择更深的状态和那些会触发更多基本块的状态。作为生成型模糊测试工具,Pulsar[57]计算从当前状态可以到达的所有状态的权重,然后选择具有最大权重的状态进行下一次测试。具体来说,状态的权重是通过计算固定数量转换中所有可变字段的总和来得出的。
- 然而,由于这些状态选择算法在不同的平台和目标上分别实现和评估,因此很难进行公平的比较并得出结论性的结果。Liu等[85]评估了AFLNet[123]的三种现有状态选择算法,包括稀有优先算法、随机选择状态的算法和顺序状态选择算法。他们发现这些算法在代码覆盖率方面取得了非常相似的结果,并将其原因归因于AFLNET的粗粒度状态抽象和对状态生产力的不准确估计。因此,他们提出了AFLNETLEGION算法[85],以解决这些问题,该算法基于一种蒙特卡罗树搜索算法的变种[84]。
- 通过将这两个阶段分开,层次化方法可以对模糊测试过程进行更加细致的控制。例如,如果我们想在握手完成后测试协议,状态间调度阶段将首先优先选择这个状态。然后,状态内调度阶段将使用传统的调度方法(如种子调度、字节调度、变异策略调度)生成特定的测试用例,在握手后的状态中执行。在这一范式中,调度过程中使用的启发式方法主要分为三类,分别是:
- 单一方法采用一个统一的调度阶段,其中状态相关的信息直接集成到调度算法中。这意味着调度器将种子在不同状态下的表现作为决策过程的一部分,而不是将状态转换和测试用例生成视为独立的步骤。例如,SGFuzz[15]根据状态的执行次数将状态分为稀有状态和普通状态。在分配种子能量时,它计算每个种子执行的稀有状态的比例,并将此比例作为原始功率调度算法的参数之一。类似地,SGFuzz将更多能量分配给包含与预期协议行为相对应的状态转换的种子。这是因为SGFuzz预计这些有效的状态转换更容易被变异为其他无效的状态转换,从而引发错误处理逻辑。类似地,LTL-Fuzzer[103]也调度整个种子,优先选择在执行过程中更接近目标代码位置的种子。
- 层次化方法将调度过程分解为两个独立的阶段:1)状态间调度(Inter-state scheduling):此阶段涉及使用基于状态优先级或相关性的状态调度算法来选择一个状态进行模糊测试;2)状态内调度(Intra-state scheduling):一旦选择了目标状态,便应用一般的调度算法来优化该状态内的模糊测试。
4.3、Testcase Construction
- 协议模糊测试中使用的构造策略可以分为包级和序列级两类。
- 包级构造策略(在表2第6列标注为“Packet”)。协议模糊测试中的包级构造策略基本上继承了通用模糊测试工具的常见策略。例如,Peach Fuzzer [41] 中的元素,如关联、修正和变换,通常用于描述协议字段之间的关系,如长度、校验和和编码转换等。像位翻转和设置为零等通用变异方法也常用于生成包级测试用例。本文中更多的关注点是利用协议特定特性来减少输入空间或提高触发漏洞的有效性的构造策略。对于前者(即减少输入空间),SPIDER [81] 利用领域特定的洞察,即大多数Openflow消息会触发现有SDN控制器中的新系统事件,进而影响状态计算和资源足迹。基于这一洞察,SPIDER 可以直接生成事件序列,而不是生成Openflow消息,从而显著减少输入空间。此外,L2Fuzz [120] 将L2CAP包格式分为可以变异的字段,并保持其他字段不变,以生成更不容易被拒绝的测试用例。IPSpex [197] 将网络流量和网络包构建的执行跟踪结合起来,提取ICS协议的消息字段语义。针对后者(即提高触发漏洞的有效性)的策略主要是从实践中总结出的启发式方法。例如,EmNetTest [8] 系统地生成有效构造的包,其中包含无效的头部字段或截断的头部。该策略背后的洞察来源于对61个嵌入式网络堆栈(ENS)报告漏洞的全面研究。类似的策略在许多行业会议中也有提及。BadMesher [128] 采用几种特定领域的策略,如将长度字段设置为边界值,随机删除一些字段,以提高在Wi-Fi Mesh设备中触发漏洞的有效性。Yen 等人 [183] 发现一些策略,如将ID字段变为不存在的ID,改变端口号或长度字段为边界值(例如0xFF/0x00),以及将IP地址改为随机地址,在模糊测试Data Distribution Service(DDS)协议时非常有效。同样,BrokenMesh [179] 在对蓝牙Mesh协议进行模糊测试时,采用了一些策略,如变异包计数或长度字段。TaintBFuzz [133] 使用静态污点分析来识别Zigbee协议字段与Zigbee实现中用于做路径决策的变量之间的关系,从而优先考虑预计能揭示更多代码路径并提高测试效果的变异。MPFuzz [89] 使用全局同步机制共享不同模糊测试实例之间的关键字段信息,并基于这些字段的语义特征进行有针对性的变异,从而更容易发现潜在的漏洞。
- 序列级构造策略(在表2第6列标注为“Sequence”)。协议模糊测试工具可能采用一些序列级构造策略。这些策略主动构造偏离常规协议状态机的消息序列,期望触发更多的非内存安全类漏洞。基于生成的模糊测试工具和基于变异的模糊测试工具在序列级构造方面的操作方式有所不同。
- 基于生成的模糊测试工具:这些模糊测试工具利用已知的协议知识构造消息序列,如标准状态机和消息间依赖关系。一些显著的例子包括使用添加或删除随机协议消息的策略,生成偏离标准状态机的异常消息序列的工作。如Sweyntooth [56]、Greyhound [55]和Braktooth [53]等项目,通过细致监控目标协议实现(PUT)的状态转移,并根据状态机模型有策略地在错误的状态下注入有效数据包,以触发异常。Fiterau-Brostean等人的最新研究[49]提出了一种新方法,通过输入表示某些类型状态机漏洞的有限自动机目录,以及PUT的模型来检测状态机漏洞。该方法分析这些模型,并生成暴露漏洞的测试用例。
- 基于变异的模糊测试工具:这些模糊测试工具主要采用简单但有效的策略来变异种子消息序列,通常包括数据包打乱、随机插入或删除等技术。例如,AFLNET [123]通过维护来自网络流量的消息池,构建消息序列,并可以将这些消息集成或替换为现有的种子。AFLNET还结合了字节级和序列级操作符,如替换、插入、复制和删除消息,以构造消息序列。类似地,DYFuzzing [8]变异种子并应用Dolev-Yao(DY)攻击者策略。Frankenstein [138]重新组织已知的消息序列,以增强代码覆盖率。He等人[63]提出了一种针对5G非接入层(NAS)协议的独特模糊测试工具,该工具从抓包中提取数据包并转化为结构化的消息表。该模糊测试工具根据协议消息中的关键信息字段的类型应用不同的变异技术,显著提高了消息变异过程的智能性和精确度。例如,长度字段结合边界值和中间值(包括0、最大值、最小值和随机中间值)进行变异。需要注意的是,基于变异的模糊测试工具必须谨慎地管理消息序列中具体字段之间的关联性,如会话号、计数器或时间戳。在这些字段上进行不加选择的变异可能会导致输入无效并导致早期拒绝。为了解决这个挑战,AFLNET [123]修改了PUT的代码,使用固定的会话号1,从而确保了模糊测试过程的有效性。
5、Executor
- 这一部分将详细介绍协议模糊测试工具在执行器方面的关键改进。如图6所示,协议模糊测试中的执行器通常包括四个关键过程。首先,执行器需要为PUT(协议实现)准备一个可执行的执行环境(① 执行环境准备),然后通过输入馈送机制向PUT发送输入(② 输入馈送),在输入处理过程中提取运行时信息(③ 信息提取),并在当前迭代完成后将执行状态和环境状态重置为特定状态(④ 执行重置)。
- 在表4中总结了现有协议模糊测试工作在高效执行(包括①、②、④)和运行时信息提取(③)方面的关键技术和改进。表中的工作从文献中选取,因为它们与执行器直接相关。
5.1、Efficient Execution
- 在协议模糊测试中,通常有两个方向来提高模糊测试的效率:建立一个支持并行测试的执行环境,以提高可扩展性(图6中的①);减少每次测试迭代的执行成本(图6中的②和④)。
5.1.1、Scalability Improvement
- 在此上下文中,可扩展性模糊测试指的是能够创建多个测试环境进行并行模糊测试的能力。在协议模糊测试中,这一能力尤为重要,因为许多模糊测试目标与硬件紧密相关。传统的并行测试方法通常需要购买多台物理设备,这在经济上是有负担且低效的。对于协议模糊测试,由于许多模糊测试目标依赖于特定的执行环境,因此对这些目标的并行测试只能通过购买多台物理设备来实现,这样会导致高昂的经济成本和资源浪费。
- 仿真成为可扩展性模糊测试的关键解决方案。它为协议实现(PUT)提供了一个虚拟执行环境,减少了对专用硬件的依赖,并促进了多个并行测试实例的创建。这种能力显著提升了可扩展性,允许在多个环境中进行广泛的模糊测试操作。一些协议模糊测试工具利用现有的仿真解决方案来扩展模糊测试过程(在表4的第4列中标记为“CUVM”)[69, 122, 142, 149, 162]。
- 然而,两个困难阻碍了在协议模糊测试中使用仿真。首先是协议实现二进制文件的可用性,因为许多固件镜像并不公开。其次,与硬件的多样性相比,现有的仿真器只能支持其中的一小部分。这些困难导致许多工作仍然采用硬件循环(HIL)方式进行模糊测试(在表4的第4列中标记为“HIL”)。
- 一些工作根据不同设备的特点解决了这些问题(在表4的第4列中标记为“SE”)。针对第一个挑战,现有工作通过拦截空中下载(OTA)固件更新,或使用厂商特定的命令或调试端口来提取目标二进制文件。例如,Frankenstein [138] 利用Patchram机制,这是一种Broadcom厂商特定命令,可以用于将断点临时补丁到ROM中,以拍摄物理蓝牙芯片的内存快照并在未经修改的QEMU版本中进行仿真。为了应对第二个挑战,现有的工作通常使用一种名为重新托管的方法,部分仿真物理硬件的功能 [87, 96]。例如,BaseSafe [96] 使用Unicorn引擎(一种流行的CPU仿真器)选择性地重新托管多个信令消息解析函数。
5.1.2、Execution Cost Reduction
- 另一个提高模糊测试效率的方向是优化每次迭代中的中间执行步骤。接下来,我们将介绍现有工作在这一方向上的进展,主要集中在两个子过程:输入馈送(图6中的②)和执行重置(图6中的④)。
- 输入馈送(Input Feeding)。输入馈送机制作为输入生成器和PUT之间的管道,将测试用例传递给PUT进行解析和执行。根据Fuzzer和PUT之间依赖的进程间通信(IPC)机制,现有的方法大致可以分为四类:OTA(空中下载)基的、基于套接字的、基于共享内存的和基于文件的。OTA基和基于套接字的方法主要用于Fuzzer和PUT不能部署在同一物理设备上的情况。基于共享内存的和基于文件的方法则可以在Fuzzer和PUT可以部署在同一设备上时加速输入馈送过程。
- OTA-Based 输入馈送(OTA-Based Input Feeding)
- OTA-based输入馈送机制通常用于模糊测试那些通常是封闭性质、与硬件组件紧密集成的协议实现,如Wi-Fi [55, 152, 169]、蓝牙(包括经典蓝牙和BLE)[53, 56, 120]、LTE [96]、Zigbee [95]、4G/5G [54, 63]和SMS/MMS协议[58]等。
- 在这种方法中,PUT和Fuzzer需要部署在相邻的物理空间内,并通过特定的频段进行通信。因此,OTA-based的模糊测试需要使用具备接收和发送功能的射频收发设备,如软件定义无线电(SDR),以处理宽调谐范围内的信号。OTA-based的模糊测试能够测试整个协议栈,包括物理层。然而,OTA-based方法是上述方法中最慢的。因此,许多无线协议模糊测试工具尝试使用其他输入馈送机制,以期获得更好的性能,接下来会介绍这些方法。
- 基于套接字的输入馈送(Socket-Based Input Feeding)
- 套接字输入馈送机制通常用于基于TCP/IP基础设施的协议实现。在这些方法中,Fuzzer和PUT通过IP地址进行通信,使用包括TCP套接字和UDP套接字在内的套接字机制。套接字方法包括两种部署模式,一种是Fuzzer和PUT之间的点对点(P2P)通信,Fuzzer可以根据PUT的角色充当客户端或服务器。另一种部署模式是中间人攻击(MiTM),在这种模式下,Fuzzer充当两个通信方之间的代理,进行突变或注入正常的通信流量。
- MiTM模式的输入馈送主要用于协议涉及某些上下文信息(如校验和、数据包顺序等)且通过突变静态种子无法保持有效的场景。然而,这两种模式都需要解决两个挑战。首先,套接字通信开销较大,涉及大量的上下文切换。现有工作通过避免使用这些昂贵的网络功能来提高套接字输入馈送机制的效率。例如,SnapFuzz[9]将原始的互联网套接字替换为UNIX域套接字,后者是一种轻量级的IPC机制,不像IP套接字那样需要进行路由、校验和计算等操作。
- 其次,Fuzzer很难确定PUT是否已经完成处理前一个消息,并准备好接收下一个消息。如果PUT未准备好接收消息而过早接收到消息,它可能会拒绝消息,从而导致Fuzzer与其状态机不同步。为了解决这个问题,Fiterau-Brostean等[47]和AFLNET[123]设置了静态时间间隔来等待PUT初始化、处理请求和发送响应。然而,静态定时器过于粗粒度,可能会浪费大量时间等待超时,从而减慢模糊测试过程。SnapFuzz[9]和AMPFuzz[79]开发了一种更细粒度的方法来检查套接字的状态。具体来说,它们使用与网络系统调用相关的函数,如
recv()
、recvfrom()
等,作为准备接收下一个消息的标志。它们通过二进制重写和编译时代码插桩监控所有这些函数调用,并通知Fuzzer发送下一轮输入。
- 基于文件的输入馈送(File-Based Input Feeding)
- 基于文件的输入馈送利用静态或动态插桩技术,通过将重网络操作替换为文件操作,以提高性能。例如,Yurong等[30]在PUT源代码不可用的情况下,通过预加载定制的库[189]将套接字通信转换为文件操作。类似地,Nyx-net[142]将一个库注入到目标中,挂钩目标连接的网络功能,获取其相关文件描述符,并将模糊输入注入到正确的位置。
- 基于共享内存的输入馈送(Shared-Memory-Based Input Feeding)
- 基于共享内存的输入馈送将模糊输入写入共享内存的地址,并挂钩相关函数从共享内存读取测试用例[17, 51, 96, 138]。例如,BaseSafe[96]在目标进程的分叉副本中执行每个生成的测试用例,每次运行的输入被复制到相应子进程中的适当地址。类似地,Frankenstein[138]创建一个虚拟调制解调器来注入自定义数据包。模糊输入被写入RAM中的接收缓冲区,该缓冲区通过直接内存访问(DMA)映射到硬件接收缓冲区。此外,HNPFuzzer[51]基于共享内存模拟网络功能,以减少Fuzzer和PUT之间消息传输的时间消耗。
- 其他
- 还有一些工作依赖于专用通信通道来传递模糊输入。例如,为了对远程桌面协议(RDP)的客户端进行模糊测试,Park等通过虚拟通道(RDP中的一种抽象层,用于传输数据)主动从服务器向客户端发送模糊输入[119]。Song等使用媒体转换器将汽车以太网与标准千兆以太网之间的流量进行转换,并对电子控制单元(ECU)的SOME/IP协议栈进行模糊测试[156],SOME/IP是ECU之间的控制通信协议。
- OTA-Based 输入馈送(OTA-Based Input Feeding)
- 执行重置(Execution Reset)。在每次执行迭代之后,有必要将PUT重置为指定的状态,并等待下一轮模糊测试的到来。这是因为每个测试用例可能会影响PUT的内部执行状态(例如,全局变量),或者影响执行环境(例如,文件系统、数据库)。如果没有重置执行状态,PUT的行为会变得更加非确定性,这使得重现漏洞变得更加困难。例如,在对FTP服务器进行模糊测试时,某个测试用例可能会导致在共享文件夹下创建一个文件。如果该共享文件夹没有被重置,后续的测试用例尝试创建一个同名文件时,FTP服务器会报告错误,这意味着相同的测试用例会导致PUT的行为不同。根据对现有工作中执行重置方法的分析,执行重置过程主要包括三个关键子阶段:重置时间选择:首先,执行器需要判断当前迭代是否已经完成,这是进行任何重置操作之前的前提条件;执行状态重置:一旦确认当前执行周期完成,执行器会重置PUT的运行时状态;执行环境重置:随后,执行器会重置与PUT相关的外部执行环境。
- 重置策略选择(Reset Strategy Selection)
- 重置策略主要用于确定何时或在哪个执行点进行重置,这对模糊测试的性能有重要影响。过早的重置可能会导致目标在仍在执行某些可能存在漏洞的任务时就被终止,而过晚的重置则可能会影响测试效率。常见的方法是设定固定的时间间隔来重置执行。例如,AFLNET [123] 允许用户手动配置重启PUT之前的时间延迟。然而,这种方法相对粗粒度,且很难确定一个合适的时间间隔。
- 为了精确控制何时重置执行,一些工作通过程序分析来寻找表示迭代结束的代码位置,并在这些代码位置注入程序终止的指令。例如,AMPFuzz [79] 通过静态分析,在不包含消息发送API的代码分支中注入终止调用。此外,一些工作选择不在每次模糊测试迭代后进行执行重置,以提高性能。例如,Charon [201] 利用程序状态推断模块推测PUT完成处理数据包的时刻,从而检测特定输入的覆盖情况,避免重复重启PUT以收集反馈。类似地,SGFuzz [15] 不在每次迭代时重启PUT,而是进行后期分析,确定输入与目标程序行为之间的关系。具体而言,它收集所有已执行过的输入,并最小化输入列表,以找出最小的消息序列来触发漏洞。
- 执行状态重置(Execution State Reset)。执行状态重置负责将正在运行的PUT进程的上下文恢复到指定的状态,包括寄存器和内存中的数据等。现有的执行状态重置机制可以分为三类:基于消息的重置、进程重启和快照恢复。
- 基于消息的重置(标记为“MR”)通过发送特定类型的消息强制PUT终止当前会话并恢复到初始状态。例如,在对Wi-Fi接入点(AP)进行模糊测试时,WiFuzz使用去认证消息来重置状态 [169]。这种方法易于使用,但仅支持有限的协议,因为并非所有协议都设计有重置消息。此外,尽管它可以重置PUT的显式协议状态,但不能重置测试目标的隐式状态,例如全局变量和已分配但未释放的内存。
- 进程重启(标记为“ProcR”)是一种相对较重的操作,因为程序的重启涉及多个开销较大的预处理步骤,例如将程序加载到内存、动态链接等,导致效率较低。
- 快照与恢复机制已经集成到模糊测试中。这种方法涉及在特定的运行时状态下创建PUT的检查点,并在每次模糊测试迭代后将其恢复到该检查点。这种方法有效地绕过了资源密集型初始化操作的重复执行,从而提高了模糊测试的效率。特别是在协议模糊测试中,快照技术带来了显著的好处。协议通常是有状态的,这意味着输入通常由多个前置消息组成,这些消息将PUT引导到指定状态,然后再引入精心构造的消息。测试用例通常共享相同的前置消息序列,特别是在需要反复探索某个特定状态时。在协议模糊测试过程中实现快照技术,消除了与解析这些共享数据包序列相关的冗余执行,从而显著提高了模糊测试的效率。当前协议模糊测试研究中使用的快照方法大致可以分为两种类型:进程级快照和虚拟机级快照。
- 进程级快照机制(标记为“PSR”)依赖于操作系统提供的系统调用功能来实现其功能。通常,根据所使用的API,现有的方法可以分为两种类型:基于fork的快照和基于ptrace的快照。
- 基于fork的快照机制被广泛应用于一些著名的通用模糊测试工具中,包括AFL [188]。具体而言,AFL将一段fork-server代码插入到PUT程序二进制文件中,这段代码在
main()
函数之前执行。在接收到来自AFL模糊测试端的信号后,fork-server通过fork()
函数生成一个子进程,并且该子进程继续执行main()
函数。由于fork-server已经加载了所有资源,因此每个子进程只需要执行main()
函数的代码,从而绕过了开销较大的预处理步骤,提高了效率。许多协议模糊测试工具也采用了这种机制来进行状态重置。此外,一些工作扩展了AFL中的原始fork-server机制,允许在不同的代码点进行有条件的多次初始化,使得模糊测试工具能够方便地在协议的各种状态之间切换,从而加速模糊测试过程 [9, 30]。 - 基于ptrace的快照机制,如CRIU和DMTCP,利用调试API
ptrace()
收集所有进程上下文信息,并将其保存为映像文件 [82]。在恢复过程中,这些快照机制读取已转储的映像文件,并通过fork()
或clone()
等系统调用重新创建进程。与基于fork的快照不同,基于ptrace的快照可以在运行时的任何状态进行检查点,不需要在执行前预设快照条件(即fork-server调用的位置)。
- 基于fork的快照机制被广泛应用于一些著名的通用模糊测试工具中,包括AFL [188]。具体而言,AFL将一段fork-server代码插入到PUT程序二进制文件中,这段代码在
- 虚拟机级快照机制(标记为“VMSR”)利用虚拟机监控程序的功能,在特定时间点捕获整个虚拟机的快照,通常通过超调用(hypercall)来实现。当触发超调用时,运行在虚拟机中的程序会退出虚拟机上下文,并将控制权转交给虚拟机监控程序。尽管基于虚拟机监控程序的方法用户友好,且不需要插桩,但由于其粒度较大,这种方法的效率较低且空间消耗较大。为了提高虚拟机级快照在协议模糊测试中的实用性,Nyx-net [142] 实现了一种增量快照方法,以减少创建和删除快照的开销。具体而言,Nyx-net在初始状态下建立一个根快照,每次执行迭代时都从这个根快照开始。在随后的模糊测试迭代中,Nyx-net在执行输入消息后,根据根快照生成增量快照。因此,Nyx-net在测试共享相同前缀消息序列的测试用例时,显著提高了性能。
- 进程级快照机制(标记为“PSR”)依赖于操作系统提供的系统调用功能来实现其功能。通常,根据所使用的API,现有的方法可以分为两种类型:基于fork的快照和基于ptrace的快照。
- 重置策略选择(Reset Strategy Selection)
- 执行环境重置。执行环境的重置主要涉及重置可能被PUT影响的文件系统或数据库。
- 许多模糊测试工具要求用户提供清理脚本以恢复所有更改,这需要大量人工努力来分析PUT对外部环境的潜在影响。为了解决这个问题,Snapfuzz [9] 利用自定义的内存文件系统,在模糊测试迭代完成后,自动丢弃修改。此外,基于虚拟机监控程序的快照机制(VMSR,表4第7列)[142],通过捕获整个虚拟机的状态,可以同时重置执行状态和执行环境。
5.2、Runtime Information Extraction
- 一般来说,现有工作中使用的运行时信息提取方法可以根据其通用性分为三类。
- 硬件辅助的方法(标记为“HA”在表4第9列)利用某些专用硬件设备固有的独特能力来提取运行时信息。一个典型的例子是Nyx-net [142],该方法利用了Intel处理器追踪(Intel PT)。这是某些高端Intel CPU特有的功能,能够详细记录软件执行的各个方面,例如控制流路径,从而实现对深入的覆盖信息的全面收集。
- 基于软件的方法利用软件执行环境的能力(例如编译器、操作系统、虚拟机管理程序等)来获取运行时信息。插桩是实现运行时信息提取的最常用方法,它在程序的特定代码点插入信息收集的函数调用。程序插桩可以是静态的(标记为“SSI”在表4第9列)或动态的(标记为“SDI”在表4第9列)。前者发生在PUT运行之前,可以在编译时进行,或者通过直接重写二进制文件[9]。后者发生在PUT运行时,利用像DynamoRIO [119]或Frida [64]这样的工具,在特定代码点注入钩子函数以收集运行时信息。
- 基于外部可观察行为的方法是最通用的一类方法,因为它不依赖于执行环境的任何支持,可以以黑盒方式使用。外部可观察行为有多种形式,例如程序的输出(在表4第9列标记为“Resp”)和侧信道信息,如功耗和响应时间。这些可观察行为的启发式方法背后的原理是,这些行为的差异可以表示PUT处于不同的状态,或者经历了不同的执行路径。具体来说,AFLNET [123]和Fieldfuzz [21]通过响应消息中的状态码来识别不同的协议状态。Snipuzz [46]和FUME [121]采用了不同的响应消息意味着不同执行路径的启发式方法。因此,它们将能引发不同响应的输入作为后续变异测试的种子,以期增加覆盖率。Aafer等人[3]和Logos [175]利用执行日志作为反馈,来优化输入生成语法,因为开发人员通常会在日志中添加详细的输入验证信息。通过观察侧信道信息,如系统状态、功耗和响应时间,Flowfuzz [151]确定硬件开关是否经历了不同的执行路径。
6、Bug Collector
- 为了应对协议模糊测试中的挑战,现有的研究根据不同的信息来源设计了内存安全性错误(memory-safety bug)和非内存安全性错误(non-memory safety bug)预警机制,如图7所示。
6.1、Memory-Safety Bug Oracles
6.1.1、Fatal Signals 致命信号
- 致命信号(由程序或Sanitizers引发)已被广泛应用于许多当代研究中,作为检测错误的关键机制。内存安全性错误通常通过用无效值覆盖数据或代码指针的方式表现出来,导致诸如段错误(segmentation fault)或进程终止等严重的进程中断,从而生成像SIGSEGV、SIGABRT等致命信号。模糊测试工具可以通过检查目标程序(PUT)是否因这些信号而崩溃来检测错误。为了应对那些不会立即导致程序崩溃的内存安全性错误,模糊测试工具使用消毒器。消毒器是一种专门用于识别和突出不安全或不良内存访问模式的错误检测工具。一旦识别出这些异常,消毒器会终止目标程序,从而表明可能存在错误。消毒器可以在编译时启用,也可以在运行时动态启用。
6.1.2、Crash Logs and Debug Information 崩溃日志和调试信息
- 一些研究通过分析系统日志或调试信息来判断 PUT 是否崩溃 [21, 45, 53, 56, 120, 128, 174]。这些系统日志和调试信息可以通过多种渠道获取。具体来说,ICS3 Fuzzer 使用 Windows EventLog 服务来检测 Windows 系统中的崩溃事件 [45]。Swentooth [56] 和 Braktooth [53] 提出通过各自的蓝牙开发板暴露的调试端口收集启动消息或崩溃消息。启动消息是程序崩溃的一个指示,因为蓝牙设备在发现设备无响应时,会通过看门狗程序重置蓝牙 SoC。Wang 等人 [174] 利用自然语言处理(NLP)技术处理日志,检测 PUT 的异常行为。与其他方法不同,L2Fuzz [120] 和 FieldFuzz [21] 通过检查是否生成了崩溃转储来识别崩溃。
6.1.3、Error-Signaling Messages(标记为“ESM”)错误信号消息
- 许多协议使用特殊的响应或状态码来指示内部错误,因此可以用于 bug 检测。例如,L2Fuzz 通过检查接收到的数据包是否包含错误信号消息(如连接失败、连接中止、连接重置和连接拒绝)来检测蓝牙 L2CAP 协议的漏洞 [120]。这些错误消息表明 PUT 可能已经崩溃。OWFuzz 使用 Deauth / Disassoc 帧,即 Wi-Fi 协议的管理帧,来终止通信,作为在 Wi-Fi 协议栈模糊测试中发现异常的指示 [23]。
6.1.4、Abnormal Physical Behaviors(标记为“APB”) 异常物理行为
- 目标设备的异常物理行为,例如启动声音,也可以作为 bug 预警器。例如,在对蓝牙音响设备进行模糊测试时,Braktooth 使用重复启动声音事件作为 bug 预警器 [53]。这是因为当蓝牙设备发生错误时,设备会被看门狗程序重启,并且在启动过程中会播放启动声音。不同的是,PCFuzzer [86] 使用示波器收集输出模块的物理信号,以监控目标的状态。
6.1.5、Timeout and Liveness Checks 超时和存活性检查
- 超时和存活性检查通过检查目标的无响应来识别崩溃或无限循环。检查目标无响应的常见方法是为响应设置静态超时。如果在规定时间内未收到目标的响应消息,则认为目标进程已死或进入了无限循环。这种方法适用于调试技术有限的环境,例如无法获取进程信号或调试日志。然而,设置固定超时是一种相对粗粒度的方法,可能由于网络波动或目标负载过大而引入误报。一些工作提出了几种主动的存活性检查,以缓解误报问题。例如,Snipuzz [46] 多次重新发送输入序列以减少误报。IoTFuzzer [28]、OWFuzz [23] 和 BadMesher [128] 使用心跳消息(例如 ICMP 消息)来推断 PUT 的状态。
6.2、Non-Memory-Safety Bug Oracles
- 非内存安全漏洞是由非内存访问原因引起的漏洞,违反了某些预期属性,例如逻辑错误、RFC 违反或影响性能的错误。非内存安全漏洞的识别比较具有挑战性,因为它们没有统一的可观察行为。检测非内存安全漏洞通常需要用户根据目标破坏的属性来定义预警机制。根据检查的属性,这些预警机制大致可以分为四类:不正确的消息内容与状态转换、传输不一致、异常性能指标和执行差异。我们将在以下小节中详细描述这些技术。需要注意的是,尽管有多种方式来识别可能的非内存安全漏洞,但大多数方法只能报告目标程序的可疑行为,仍然需要专家进行手动验证,以确定漏洞的影响和可利用性。
6.2.1 不正确的消息内容与状态转换
- 不正确的消息内容检查响应的内容是否违反了某些语义约束。不正确的状态转换验证状态转换是否有效或被允许。在大多数情况下,这些规则是从协议规范中提取的,或者由专家知识设计的。这些规则可以采用不同的形式,例如标准状态机、线性时间性质、响应消息的约束等。例如,Beurdouche 等人[18]手动构建了一个标准状态机,并将其用作基准来识别目标程序的偏差行为。利用这种方法,在TLS实现JSSE中发现了一个逻辑漏洞[18]。该漏洞允许攻击者绕过所有与密钥交换和认证相关的消息,从而使他们能够启动未加密的通信。
- 给定协议实现需要满足的线性时间时序逻辑(LTL)性质,LTL-Fuzzer[103]利用定向灰盒模糊测试,将模糊测试引导至可能影响该属性的特定位置,并检查在每次执行迭代过程中该属性是否成立。此外,Sweyntooth[56]和Greyhound[55]检查接收到的响应数据包是否属于当前协议状态的预期类型集。任何不匹配的消息类型都会被标记为异常。Loki[93]从PBFT共识协议论文[25]中提取规则,作为预警机制检测区块链实现中的非内存安全漏洞。例如,Loki在Hyperledger Fabric[10]中发现了一个漏洞,攻击者可以利用该漏洞确认非法交易。
6.2.2 传输中的消息不一致性
- 一些研究工作检查是否存在非内存安全漏洞,可能导致协议的完整性破坏。具体来说,由于正确的数据传输是TCP协议的基本属性之一,TCP-Fuzz[200]在发送方和接收方两端设计了数据检查器,用于检查是否违反了这一属性。每当消息被发送或接收时,数据检查器会检查发送的消息与接收到的消息是否一致。
6.2.3 异常性能指标
- 一些研究旨在找到可能影响目标程序(PUT)性能的网络攻击策略,这些研究通过监控PUT的一些性能指标是否超出正常范围来判断攻击策略的有效性。例如,为了找到UDP服务中的放大型DDoS攻击策略,AMPFuzz[79]使用带宽放大因子(BAF)[137]作为指标,BAF是指所有响应消息的长度之和与攻击请求的长度之比,通过该指标找出可以最大化吞吐量消耗的消息。TCPWN[69]和ABBrate[122]旨在通过模型引导的方式找到针对TCP拥塞控制实现的攻击策略,这些策略可以增加或减少拥塞窗口。为了检测输入是否确实影响了拥塞控制机制,TCPWN从系统日志中获取窗口大小,并与预期的基准值进行比较。
6.2.4 执行差异(表5第6列标注为“DE”)
- 差异化测试通过比较同一协议不同实现的执行行为来调查潜在的安全影响。这种方法具有可扩展性,因为它不依赖于代码插桩。例如,TCP-Fuzz[200]通过比较多个TCP实现的输出,识别不一致性。Yang等人[180]利用差异化测试揭示以太坊中的共识漏洞,这些漏洞可能导致分叉攻击。他们生成一系列交易作为输入,并观察两个以太坊客户端(分别实现于Golang和Rust)的响应。类似地,IcyChecker[182]通过生成变异的DApp交易序列,并验证最终状态的一致性,识别区块链状态不一致的漏洞。ParDiff[195]利用双重模拟算法比较不同协议实现的有限状态机(FSM),并通过分析状态转换条件的差异来识别不一致性。然而,该领域面临的一个重大挑战是确认哪个实现偏离了协议的预期行为,并确定观察到的行为差异是由于错误还是协议RFC中的规格不全。因此,大多数采用差异化测试的研究都会整合后续的人工检查阶段,以区分实际的漏洞和无害的差异。
- 为了提高漏洞发现效率,一些研究将目标程序(PUT)与已经经过充分测试或正式验证的实现进行比较,这些实现被称为“参考堆栈”[200]。例如,TCP-Fuzz[200]利用经典且经过广泛测试的内核级TCP堆栈(如Linux TCP或FreeBSD TCP)作为参考,来测试较新的TCP堆栈。在这种情况下,如果报告了不一致性,通常意味着新协议实现中存在漏洞。这种方法不仅能识别差异,还为评估各种协议实现的正确性提供了框架。
7、Directions of Future Research
7.1、Towards Perfect Communication Model Construction
- 当前的通信模型构建方法远未完善,往往导致知识获取不完整或不准确,或者需要大量的人工努力。具体而言,如第4节所介绍,现有的通信模型构建方法可以大致分为自下而上和自上而下两种方法。自下而上的方法旨在学习特定协议实现的通信模型,而不是协议本身的规范通信模型。然而,对于自上而下的方法,大多数现有工作仍然严重依赖于人工过程,从协议规范中构建状态机。这种人工构建不仅劳动强度大,而且容易出错。
- 现有的研究[71, 118]已经开始探索利用自然语言处理(NLP)技术从协议规范中自动提取部分有限状态机(FSM)。这一探索初步验证了自动提取协议通信模型的可行性。然而,由于规范中的歧义和未指定的行为,这种方法目前仍无法从协议规范中提取规范的通信模型,因此无法实现文本与通信模型之间的完全一对一转换。
- 为了解决这一问题,可以探索基于机器学习模型的方法,以便更好地构建通信模型。考虑到近期大型语言模型(LLM)[75, 111, 172, 192]的显著进展,一个有前景的方向是开发基于LLM的解决方案,以实现更精确的模型构建。另一个可能的方向是结合其他信息源(如协议实现的代码、开发过程中的代码提交或注释信息、程序分析结果等)来帮助更好地理解规范内容。
7.2、Towards Multi-Dimension Testing Perspectives
- 现有的研究更多地集中在改变数据包的内容或数据包顺序上。尽管这种方法在某些程度上有效,但它忽视了协议存在多维度的测试视角,例如消息延迟[68]、缓存状态[73]、配置[37, 193]和并发级别[76]等变量,这些在第3.1节中有提到。这些属性在决定目标系统行为时起着至关重要的作用。
- 为了有效地在协议实现中测试这些属性,需要创建详细的模型,准确地表示每个属性,包括消息延迟、缓存状态、配置参数和并发级别等。此外,可以设计特定的预言机和变异器来评估协议在涵盖这些多维度方面的不同场景下的行为正确性。这个方向非常有趣,并且有助于建立对协议韧性和稳健性的更全面评估。
7.3、Fuzzing Characterized Protocol Targets
- 一个重要且尚未充分探索的未来研究方向是面向特征化协议目标的模糊测试。当前的研究尚未全面覆盖各种协议,尤其是那些具有独特特征和重要性的协议。以下三个领域尤其值得关注:
- 领域特定协议。如卫星通信[164]、无人机通信(UAV) [61]和机器人操作系统(ROS) [114]等专有领域协议,通常具有较高的知识门槛和相对封闭的性质。这些协议在许多基础设施中扮演着至关重要的角色,使其安全性研究至关重要。目前,针对这些协议的模糊测试研究相对稀缺,为学术界提供了通过开发新的模糊测试技术和工具,提高测试有效性和安全性的机会。
- 硬件实现协议。另一个方向是为在硬件上实现的协议设计模糊测试工具,如FPGA[167]。这些硬件实现通常与软件层面的协议在错误特性上有所不同,因此需要开发新的方法,以更有效地识别和利用潜在的漏洞。
- 多方协议。另一个可能的协议模糊测试方向是支持多方协议。一般来说,协议有许多通信模式,如点对点模式[69, 122, 170]、客户端-服务器(主从)模式[28, 45, 169]和多方模式[159]。现有的协议模糊测试工具更多关注前两种模式,通过扮演客户端/服务器来测试对方[28, 45, 169],或作为对等节点来测试目标协议[69, 122, 170]。多方协议尚未得到充分研究。例如,在区块链网络中,一个节点可以充当计算节点、共识节点或管理节点[10],每个节点负责不同的任务。智能合约协议的正确执行需要这些角色的协作。如何有效地测试这些多方协议是一个有趣但具有挑战性的问题。
7.4、Combining with Other Vulnerability-Finding Techniques
- 除了模糊测试,还有许多漏洞发现技术,如符号执行和模型检查。虽然这些技术与模糊测试的结合在一般上下文中已有所探索,但它们在协议模糊测试中的应用仍然相对不足。这一点为未来的研究提供了有前景的方向,尤其是考虑到组合方法仍面临协议定义的复杂通信测试挑战。直观地说,未来的研究可以改进现有的漏洞发现技术,以更好地解决协议特有的挑战。
- 此外,许多协议都伴随着高质量的学习资源,如详细的规范。未来的研究可以探索如何有效地利用这些宝贵的资源,以指导和增强组合方法。
7.5、Shift-Left Protocol Fuzzing
- 虽然已有一定的研究关注将通用模糊测试技术集成到开发周期中——例如使用libFuzzer、OSS-Fuzz工具,以及在CI/CD集成测试中的模糊测试研究[130]——但专门致力于填补协议模糊测试与开发过程之间的空白的研究仍然较少。协议模糊测试不同于通用软件模糊测试,它涉及严格测试各种协议,这些协议允许不同软件系统和组件之间的通信与数据交换。协议目标通常具有比一般软件目标更复杂的开发工作流,这种复杂性源于协议需要精确遵循既定标准和规范,以确保在不同系统之间的互操作性,从而带来了在集成和测试中的独特挑战。这些挑战需要一种量身定制的模糊测试方法,能够理解并适应协议开发的复杂性。因此,需要一种左移的协议模糊测试方法,将协议特定的模糊测试技术更早地集成到软件开发生命周期中。这可能包括从开发者的角度来设计技术,并且在必要时,也可以考虑人机交互(HCI)[24]技术。通过这种方式,可以在更早的阶段发现漏洞和问题,从而更容易且更具成本效益地解决这些问题,确保协议实现的更强大和安全的软件生态系统。