SYMTCP: Eluding Stateful Deep Packet Inspection with Automated Discrepancy Discovery论文翻译

《SymTCP:基于自动发现差异的有状态深度包检测技术》

Zhongjie Wang∗, Shitong Zhu∗, Yue Cao∗, Zhiyun Qian∗, Chengyu Song∗,
Srikanth V. Krishnamurthy∗, Kevin S. Chany, and Tracy D. Brauny
∗Department of Computer Science and Engineering, University of California, Riverside,
fzwang048, szhu014, [email protected], fzhiyunq, csong, [email protected]
yU.S. Army Research Lab, fkevin.s.chan.civ, [email protected]

摘要
通常部署的深度包检测(DPI)系统的一个关键特征是,它们实现了网络堆栈的简化状态机,这通常与终端主机的状态机不同。这两种状态机之间的差异被用来绕过这种基于DPI的中间盒。然而,大多数先前的方法依赖于手工制作的对抗性数据包,这些数据包不仅是劳动密集型的,而且在多个基于DPI的中间包中可能无法很好地工作。我们在这项工作中的目标是开发一种自动的方法来制作候选的对抗性数据包,特别是针对TCP实现。我们实现这一目标的方法取决于关键的洞察力,即虽然DPI实现的TCP状态机不清楚,但那些终端主机已经很好地建立起来了。因此,在我们的系统SYMTCP中,使用符号执行,我们系统地探索了终端主机的TCP实现,识别可以到达代码中关键点的候选包(例如,导致包被接受或丢弃/忽略);然后,这些自动识别的包通过DPI中间盒馈送,以确定是否引起差异并且可以避开中间盒。我们发现我们的方法非常有效。它可以在不到一个小时内生成数以万计的候选对抗包。当评估多个最先进的DPI系统,如Zeek和Snort,以及国家级审查系统,即。在中国的防火墙中,我们不仅发现了以前已知的规避策略,而且还发现了以前从未报道过的新策略(例如,涉及紧急指针)。该系统可以很容易地扩展到操作系统和DPI中间盒的其他组合,并作为一个有价值的工具来测试未来DPI对规避尝试的稳健性。

1 简介

深度包检测(Deep packet inspection,DPI)已成为现代网络安全基础设施中普遍采用的技术。通过组装和检查应用程序层内容,DPI启用了传统防火墙中没有的强大功能。其中包括恶意软件检测[10]、远程攻击预防[41]、钓鱼攻击检测[16]、数据泄漏预防[46]、政府网络监视[7]、[6]、目标广告[28]、[3]和分层服务的流量区分[51]、[32]、[20]。

不幸的是,要从有状态协议(如TCP)中组装应用程序层内容,DPI需要设计协议的相应状态机。这引入了DPI的一个基本限制,即易受协议歧义的影响。简而言之,大多数网络协议规范(例如,TCP的rfc[36])都是用自然语言(英语)编写的,这使得它们具有内在的模糊性。更糟糕的是,规范的某些部分故意未指定,从而导致特定于供应商的实现。因此,不同的网络堆栈实现(例如,Windows和Linux)通常在其状态机中具有固有的差异[42]、[13]、[38]。事实上,即使是同一网络堆栈实现的不同版本,也可能存在差异。为了确保低开销和与大多数实现的兼容性,DPI中间框通常实现自己的简化状态机,这与终端主机上的状态机必然不同。

正如前面的工作[37]、[48]、[29]所指出的,这种差异导致某些网络包被“DPI中间盒”或终端主机接受/丢弃。利用此特性,可以使用插入数据包(即DPI中间框接受并作用的数据包)来更改其状态,而远程主机丢弃/忽略它)和规避数据包(即DPI中间框忽略但远程主机接受并作用于它的数据包)[37]来误导DPI的协议状态机器。具体地说,这样的数据包导致DPI进入与终端主机上的状态不同的状态。因此,DPI无法再像最终主机那样忠实地组装相同的应用程序层内容,无法捕获任何恶意或敏感的负载。

到目前为止,对插入和规避数据包的研究是基于针对特定DPI中间盒手工制作这样的数据包[37]、[48]、[29]。不幸的是,分析每一个中间包的实现,并为这种对抗性的包生成提出相应的策略,是一项繁重的任务。人们可以通过搜索所有可能的包序列来识别插入和规避包,从而潜在地自动化该过程。不幸的是,搜索空间是指数级的大,也就是说,有2160种可能性可以覆盖甚至是单个包的20字节TCP头,更不用说测试一系列包了。

“我们能否开发自动方法来构造数据包,使DPI中间盒的状态与(终端)服务器的状态成功地解除同步?“这个问题正是我们在本文中所针对的工作的关键,回答这个问题不仅有助于测试未来几代的新闻部,而且有助于在反对未来审查技术的军备竞赛中保持领先地位。我们这里的重点是TCP,因为它是构建最流行的应用层协议的基础。我们开发了一种方法,这种方法是由这样一种观点驱动的:尽管DPI中间盒的TCP状态机很模糊,但在终端主机上的TCP实现已经很好地建立起来(例如,很大一部分服务器运行Linux操作系统)。鉴于此,我们探索了终端主机的TCP状态机(使用符号执行),并基于它们可以达到的临界点和状态(即由于各种原因接受或丢弃/忽略数据包的状态)生成候选数据包组。接下来,我们通过DPI中间盒提供这些数据包来执行差异测试,并观察它们是否导致任何差异,即DPI中间盒是否仍然能够执行其识别包含恶意/敏感有效负载的连接的预期功能。
这项工作的主要贡献如下:

  • 我们通过在终端主机上探索TCP状态机,并对blackbox DPIs进行差分测试,提出了自动识别插入和逃逸包的问题。
  • 我们开发了SYMTCP,这是一个完整的端到端的方法,可以自动发现任何TCP实现(当前是Linux)和blackbox DPI之间的差异。并发布了SYMTCP数据集和源代码。https://github.com/seclab-ucr/sym-tcp
  • 我们针对三个DPI中间包Zeek,Snort和Great Firewall of China(GFW)评估我们的方法,并自动发现大量的规避机会(文献中从未报道过)。该系统可以很容易地扩展到其他DPI,并作为针对DPI未来实现的有用测试工具。

2 背景

在本节中,我们首先提供一个简单的背景,说明为什么可以避开针对DPI的攻击。随后,我们将提供一些关于符号执行和相关技术的背景知识,因为这些是构建SYMTCP不可或缺的。

A. 对深度包检测的规避攻击
DPI是专门设计用来检查与更高层相关的内容,例如应用层(例如HTTP、IMAP)。为了检查应用层的有效负载,DPI首先从从接口捕获的网络数据包(TCP数据包)重建数据流。然后它自动分配一个合适的协议解析器来解析原始数据流。最后,它对解析的输出执行“模式匹配”。举例来说,考虑基于关键字过滤HTTP请求的常见情况(例如,部署在审查防火墙上)。当DPI模块(为了便于说明而简称为DPI)检测到HTTP URI中的特定关键字时,它可能会采取后续操作(例如,阻止连接或静默记录行为)。有时模式匹配签名可能更复杂,其中DPI检查来自多个层的字段和来自两个方向(到服务器和从服务器)的数据在序列中的组合[44]。例如,一个终端主机首先向端口443发送“HELLO”消息,然后另一方用“OLLEH”消息响应。

然而,由于协议的模糊性,DPI的TCP实现与终端主机(例如服务器)的TCP实现之间存在差异,因此DPI存在固有的规避漏洞[37],[23]。一个例子是Snort接受一个TCP RST包,只要它的序列号在接收窗口内(这太宽松了),而最新的Linux实现将确保RST包的序列号与下一个预期的序列号(rcv_next)完全匹配。这使得攻击者能够发送一个插入RST数据包,该数据包的inwindow序列号故意标记为“bad”,这将终止Snort上的连接,而远程主机实际上会丢弃/忽略该数据包。这种差异为攻击者通过发送精心编制的数据包来躲避DPI打开了一个缺口。

除了由于协议实现的差异之外,缺乏网络拓扑结构的知识也可能导致额外的歧义。例如,DPI很难推断包是否会到达目的地。因此,攻击者可以发送具有较小TTL的数据包,使其无法到达远程主机,但是,这样的数据包会影响DPI。

以往的研究工作[48]、[29]利用网络的模糊性和协议执行的差异性,设计了针对现实世界的DPI系统的规避策略,如中国和伊朗的国家审查系统,以及isp针对分层服务的流量区分系统。这些规避策略被证明在使DPI无效方面有很高的成功率。然而,大多数常见的差异可以通过DPI设备来弥补,从而导致军备竞赛。相比之下,我们的系统向自动化规避战略迈出了重要的一步,这不仅可以作为一个有价值的测试工具来对付未来几代的DPI,而且还可以跟上在DPI规避背景下不断升级的军备竞赛的步伐。

B. 符号执行vs.混合执行vs.选择性符号执行
符号执行(Symbolic execution)[26]是一种功能强大、精确的软件分析/测试技术,由于它能够突破复杂而严密的分支条件,并沿着执行路径深入,因此被广泛采用,与其他不太精确的技术(如模糊化)相比,这是一个明显的优势。在符号执行中,变量被赋予符号值以探索目标程序的执行空间,而不是使用具体的值。符号执行引擎通过解释每个指令(在中间表示级别,如LLVM-IR[11]或VEX[40]或二进制级别[49])来模拟程序执行,并为每个程序变量维护符号表达式。在途中,引擎以符号表达式的形式收集路径约束。每当遇到带有符号谓词的分支时,引擎将检查相应的真/假路径是否可满足(借助于SMT解算器);如果可以,则将执行路径分叉为两条,并根据分支条件(真或假)添加新的路径约束。然而,符号执行的缺点在于其效率或可伸缩性。模拟执行和约束求解都可能非常慢,即使使用了诸如缓存和增量求解之类的优化[11]。而且,在一个通用规模的现代软件中,可行的执行路径的总数是巨大的,这导致了臭名昭著的路径爆炸问题。

混合(共同)执行(Concolic execution)[12]是一种实用的测试技术,它通过具体的执行来增强符号执行。其基本思想是将一个具体的值绑定到每个符号表达式,从而可以随时在符号执行和具体执行之间切换模式。当遇到具有符号谓词的分支时,混合执行引擎首先使用具体值来确定要走的路径;然后,它还尝试为相反的分支生成新的具体值。当代码或函数的某个特定部分可能导致路径爆炸,或者如果约束求解器无法求解或求解效率低下,则可以切换到具体的执行,以防止分叉和约束求解,并在以后切换回。然而,这可能会造成完整性和稳健性方面的损失,作为一种权衡[4]。大多数最先进的符号执行引擎,如Angr[40]和S2E[17]都支持混合执行。

选择性符号执行(Selective symbolic execution)[17]进一步扩展了混合执行的思想,使其在测试大型复杂软件(如操作系统内核)时更加灵活和实用。特别是,选择性符号执行引擎只允许测试程序的子系统(例如,TCP实现)。这是通过在具体模式(其中大多数符号变量已经具有具体值)和符号模式之间的转换来实现的,如下所示:

  • 从具体到符号的转换:引擎象征作用域的输入(进入作用域的数据),如函数参数,以提供以欠约束为代价探索作用域内所有执行路径的可能性,即失去对外部组件输入的附加约束。
  • 从符号到具体的转换:引擎具体化符号变量,这可能导致过度约束,因为我们任意选择一个可能的值来分配给任何符号变量,这可能会损害完整性。
    S2E[17]是一个具有代表性的系统,它将有选择的符号执行与整个系统的仿真相结合来测试Linux内核。它的符号执行性能是通过在符号模式下有选择地运行感兴趣代码的一部分(例如,特定函数)来控制的,同时保持大多数其他部分和外部系统在具体模式下运行。S2E提供了不同级别的执行一致性,允许在性能、完整性和分析的可靠性之间进行权衡。
    在我们的解决方案中,为了解决实际TCP实现的复杂性,我们使用了S2E中的选择性符号执行特性来有效地探索Linux内核中的TCP实现。

3 威胁模型和问题定义

在本节中,我们首先描述我们的威胁模型。随后,我们将设计SYMTCP时要解决的问题形式化。
A. 威胁模型
我们考虑的威胁模型如图1所示。我们假设DPI引擎位于客户机和服务器之间,并且能够读取客户机和服务器之间交换的所有数据包。在这项工作中,我们只关注TCP协议,因为它可以说是最流行的传输层协议。通过从TCP层避开DPI,我们可以中断DPI的TCP包重组,因此可以允许上层协议避开DPI(例如HTTP、HTTPS)。
在这里插入图片描述
我们假设DPI引擎有自己的TCP实现,可以重新组合捕获的IP包并将其转换为TCP数据流。然后,它根据中间盒的功能(如审查、网络入侵检测等)对重新组装的数据流执行所需的检查,其行为是确定的。我们还假设,如果触发警报,检查将导致可观察的效果,例如阻塞或重置连接;否则,我们无法判断躲避攻击是否成功。

主机(例如,客户端)的目标是在发送敏感内容之前,通过发送精心编制的数据包,利用DPI的TCP实现与另一端主机(例如,服务器)的TCP实现之间的差异,避开对DPI引擎的检查。为了便于讨论,在本文的其余部分,我们认为除非另有明确说明,否则客户是试图逃避检查的人。我们认为DPI的TCP实现是一个黑盒,因此,客户端只能发送探测数据包。对探测包的响应(或缺少响应)允许客户端推断DPI的TCP状态机的状态。我们假设服务器使用公共可用的TCP堆栈实现(例如,Linux),因此,客户端可以作为白盒执行分析。这些假设还意味着服务器不会通过使用专门的或自定义的TCP堆栈与客户端串通,否则可以建立任意隐蔽通道[33]。

B.问题定义
从概念上讲,规避包是一个被服务器接受但被DPI引擎丢弃/忽略的TCP包。类似地,插入数据包是由服务器丢弃但被DPI引擎接受的TCP数据包。然而,这样的定义是不精确的。在这一节中,我们的目标是提供一个更精确的定义,我们在这项工作中使用的概念,以及SYMTCP解决的问题。首先,我们定义什么是与包相关联的accept和drop属性。
TCP状态机。从概念上讲,每个TCP实现都可以建模为一个确定性Mealy机器,M = (Q; q0; Σ; Λ; T; G),其中
在这里插入图片描述
在这里插入图片描述
是一组状态;是初始状态;是输入字母表,即TCP数据包;是输出字母表,即TCP数据有效载荷;是状态转移函数;是输出函数。
与传统的确定性有限状态机相比,Mealy机的输出由其电流状态和电流输入决定。注意,在这项工作中,我们将TCP状态机M的输出定义为存储应用层将使用的数据(即有效负载)的缓冲区的输出,而不是响应包。原因在于:(1)DPI对敏感关键字的检测严格依赖于应用层的有效负载;(2)DPI引擎的TCP层不会生成任何类似ACK包的TCP级输出。这个模型允许我们统一DPI和终端主机的状态机定义。我们还将输出行为简化为:只要数据有效负载将被输出到应用层,即使是以延迟的方式,我们认为包会生成非空输出。

**定义1:下降。**给定一个TCP状态机M,如果一个包P属于Σ既不引起状态改变也不产生任何输出,则它被丢弃。在这里,状态可以是高级TCP状态(例如,侦听、建立)或低级/实现级状态(例如,已发送的质询ACK的数量[13])。

相应地,我们将丢弃路径定义为TCP实现的程序路径,该程序路径在不改变TCP会话的当前状态或生成任何输出的情况下释放传入的TCP数据包。为了在实际中识别丢弃路径,我们还将丢弃点定义为程序点或语句,其中任何经过它的路径都将成为丢弃路径。在我们分析的Linux内核中,我们手动标记了总共38个唯一的放置点(更多细节请参见第8节)。注意,单个丢弃点可以对应于许多不同的分组实例。例如,具有“错误校验和”的数据包可以有任意的SEQ或ACK号,以及任意的TCP报头。
定义2:接受
定义3:同步
定义4:错误的关键字和警报。
定义5:规避包。
定义6:插入包。
**目标。**给定上述定义,SYMTCP的目标是自动查找分组序列P1:::n,其中最后一个分组Pn是规避/插入分组。

4 SYMTCP的工作流程

SYMTCP的工作流概述如图2所示。工作流分为离线选择性混合执行阶段和在线测试阶段。离线阶段的输入包括一组初始种子TCP包(例如,初始SYN),该初始种子TCP包可以驱动混合执行引擎,以及一个Linux-TCP实现(如前所定义)的接受点和丢弃点的手动管理列表。
在这里插入图片描述
在离线阶段,通过在服务器的TCP实现上运行混合执行( concolic execution),我们试图收集在不同TCP状态下到达接受点(accept)或丢弃点(drop)(定义见第III-B节)的所有执行路径(如果可能),并收集相应的路径约束。每个路径对应于分组序列P1…n并且所收集的路径约束稍后用于生成用于差分测试的具体测试分组,即用作候选插入/规避分组。

图3说明了一些到达丢弃点的示例数据包(定义1:这些数据包没有任何效果,只是被丢弃并可选地被确认)和一些到达接受点的示例数据包(定义2:推进TCP状态机或导致数据被接受)。请注意,我们的分析将始终从TCP侦听状态开始,并以TCP已建立状态结束,因为它表示注入插入/规避数据包的完整机会窗口。例如,在[48]中有报道称,如果客户端向处于侦听状态的服务器发送SYN-ACK,服务器将丢弃数据包(并发送RST),而中国防火墙(Great Firewall of China,GFW)将被混淆为认为客户端就是服务器。这样的SYN-ACK包实际上是一个插入包,它允许客户机接着进行正常的三方握手并开始未经检查地发送数据(定义6)。另一个例子是包含数据有效载荷的SYN包,这是TCP标准允许的(有效载荷将被缓冲,直到三方握手完成),但是DPI可能错误地忽略它[37],从而使该包成为逃避包(定义5)。我们不希望将服务器的状态提升到已建立的状态之外(例如,时间等待),因为这样我们就无法再传递数据。
在这里插入图片描述
离线阶段:简而言之,离线的混合执行引擎首先引导一个正在运行的Linux内核,其中一个TCP套接字处于listen状态。然后,我们给它提供多个符号化数据包,以尽可能详尽地探索服务器的TCP状态机。此阶段的主要输出是以符号公式和符号约束形式的候选插入/规避数据包序列,这些约束描述TCP头字段应采用的可能值(包括描述数据包之间相互关系的约束)。注意,每个包序列将最多包含一个到达丢弃点的包。这是因为每一个这样的“丢弃数据包”本身并不影响TCP状态机;因此,具有两个(连续的)“丢弃数据包”的序列相当于具有单个“丢弃数据包”的两个序列(即,分割原始序列)。使用符号执行引擎首先发现较短的序列我们使用类似于广度优先搜索的策略来发现数据包序列,并限制符号数据包总数的实用性(更多细节见第V节);因此,包含多个丢弃数据包的较长序列是不必要和冗余的。相反,到达同一接受/丢弃点的不同路径不是多余的,并且可以表示不同的事件。例如,如图3所示,如果当前的TCP状态是SYN_RECV,则可以发送两种类型的ACK数据包,以将TCP状态提前到已建立状态(两者都会导致相同的接受点):(1)一个有效负载为0字节的ACK数据包(其中SEQ和ACK数字与预期完全匹配),或(2)具有窗口内有效载荷的ACK分组(只要结束序列大于预期序列号)。它们对应于两个不同的接受路径,表示向前移动TCP状态的两种不同方式。发现这些不同的路径是至关重要的,因为并非所有的路径都由DPI同等地处理(从而导致可能的规避机会)。

离线符号执行引擎(如图2所示)的另一个输出是,对于每个候选数据包序列,都有一个对应的TCP连接状态,服务器将在数据包序列被消耗后结束。记录此信息有助于生成后续探测数据包。例如,如果候选数据包的序列是具有错误校验和的单个TCP SYN,则我们知道服务器将保持侦听状态;因此,在发送数据包以检查DPI是否被初始候选插入数据包混淆之前,需要进行适当的三方握手。

在线阶段:在在线阶段,我们试图通过添加额外的约束来具体化这些候选插入/规避数据包(更多细节见第6节)。其中一个约束是服务器的初始序列号(每次探测服务器时随机生成)。一旦约束解算器生成具体候选插入/规避包的序列,它们将被馈送到DPI prober(连同后续包)。
在这里插入图片描述
我们在图4中演示了这个过程。对于每个包序列,我们从第一个包开始,根据当前包执行探测。如果当前数据包到达一个丢弃点,我们将把它作为一个候选插入数据包,并探测DPI,以查看它是否导致DPI稍后忽略具有已知坏负载的数据包(定义6)。例如,校验和错误的SYN包将被视为候选插入包(当服务器处于侦听状态时)。如果当前数据包到达一个接受点,例如带有数据的SYN数据包(如前面提到的示例),我们将把它馈送给DPI,并观察它是否符合逃避数据包的条件(定义5)。如果DPI像服务器一样接受数据包(这是DPI通常在接受数据包时比较宽松的常见情况[37]),那么我们将转到下一个数据包并重复该过程。注意,对于共享公共前缀包的不同序列的包,我们只需要评估公共包一次(作为候选插入或规避包)。

5 离线阶段:TCP实现的实际混合执行

我们的解决方案建立在流行的能够分析操作系统内核的混合执行引擎S2E[17]之上。挑战在于,全尺寸TCP实现具有相当复杂的有限状态机(尤其是低层状态机)。因此,在同一个系统上应用混合执行是非常具有挑战性的。我们将在本节中描述如何应对更详细的挑战。具体来说,在V-A中,我们描述了如何使用选择性的共有执行来限制符号执行空间。在V-B中,我们描述了如何表示输入,即TCP报头和选项中的字段。在V-C中,我们讨论了如何在TCP中抽象校验和函数。最后,在V-D中,我们讨论如何处理服务器端的输入(特别是服务器使用的序列号),这些输入是先验不知道的。

A. 有选择地混合执行有利于完整性
因为它是重量级的,我们希望只在TCP代码基上运行符号执行;对于系统的其余部分,我们希望使用具体的执行来降低复杂性。为了实现这个愿景,我们需要定义符号执行和具体执行之间的边界。实现这一点的一种方法是执行细粒度的函数级分析,以识别与TCP逻辑相关的那些函数,但这将需要昂贵的手动操作。为了解决这个问题,我们使用了一个更保守、更粗粒度的边界,即Linux中的整个net/ipv4编译单元(对象文件)。当我们在net/ipv4编译单元的地址空间内时,我们使用符号执行来运行代码并启用forking。当我们在这个地址空间之外时,我们在禁用forking的情况下具体地运行代码,但仍然保留原始约束(如S2E所支持的那样)。这样做的好处是,当从具体模式切换到符号模式时,我们不会丢失符号表达式。S2E还为每个符号变量维护一个具体值,这些值将在具体执行期间使用。具体值在具体执行中第一次访问时由约束求解生成。我们强调,这不同于从一开始就应用纯具体执行;从符号模式切换到具体模式仍然保留符号变量并在具体执行期间传播它们。

默认情况下,即使在具体模式下运行,S2E也会在采取具体分支时收集路径约束(conlic执行中的标准[4])。这样做的原因是,在具体执行期间,只执行一个分支,结果绑定到该分支。然而,这将导致先前讨论过的“过度约束”问题(在第二节中),即强制执行某些分支(因为切换到具体执行时具体化)。更重要的是,我们只关注TCP代码库,而我们范围之外的执行是不相关的(无论采用了哪种路径)。因此,我们放弃在具体执行模式期间收集的任何约束。例如,TCP代码库之外的netfilter模块将读取符号TCP头字段并引入约束。然而,netfilter的执行结果完全不影响主TCP逻辑,因此我们可以安全地忽略这些约束。具体地说,netfilter ConnTrack模块被动地跟踪TCP连接,并独立于主TCP逻辑维护连接状态。因此,它的执行是无关紧要的——即使我们忽略了它的约束并强制使用不同的执行路径,它也不会对我们感兴趣的主要TCP状态产生任何影响。

B. 表示TCP头和选项
由于我们将范围限制为TCP级别的插入和规避数据包,所以我们只表示数据包的TCP头(而不是IP头或应用程序负载)(见图5)。除源端口号和目标端口号外,我们象征所有TCP头字段。符号化字段包括序列号、确认号、数据偏移量、标志、窗口大小、校验和和紧急指针。此外,我们希望象征TCP选项,它指的是TCP报头的最后一部分,并且具有相关的可变长度。
在这里插入图片描述
表示TCP选项字段本质上是困难的,因为它由嵌套的TLV(类型值-长度)结构列表组成。目前,IANA已经分配了35个与tCp选项相关的号码[24],其中包括标准号码和其他过时号码,而且这个号码还在增长。有些选项具有固定长度,有些选项具有可变长度(例如SACK)。有些选项具有相关的子类型(例如,MPTCP)。尽管tCp选项字段的最大长度为40字节,但是所有顶级选项类型的组合数量仍然很大。如果我们还包括非法案例(例如,一个选项出现了多次),或者还想考虑选项的顺序,那么问题就会恶化。

Linux仅使用解析循环实现10个TCP选项,这仍然很容易导致路径爆炸问题。理论上,即使只执行一次循环,也至少有210=1024个执行路径。在实践中,当它被编译成二进制形式时,会引入额外的分支;因此,可能的路径数量要大得多。考虑到TCP逻辑中已经存在大量的路径,这个问题会呈指数级恶化。由于这些原因,我们需要通过限制TCP选项的可能组合来限制搜索空间。

当我们试图限制循环执行时间和每个TCP选项的出现次数时,我们发现即使我们只执行一次循环,并且允许每个选项最多出现一次,路径的数量仍然非常大。因此,作为缓解此问题的实用方法,除了限制执行时间外,我们还将TCP选项的特定组合作为种子(从在Internet上观察到的通信量)馈送到我们的混合执行引擎;执行首先探索我们的种子值,然后是其他值。

C. 提取校验和函数
TCP校验和是基于一个伪报头来计算的,这个伪报头包括IP地址、整个TCP报头和有效负载。如前所述,我们不想象征IP报头或有效负载,因为这可能会损害符号执行性能。因此,我们将校验和验证函数抽象如下:
在这里插入图片描述
其中f表示校验和验证函数,pkt是所考虑的网络分组。如果TCP头中的校验和字段等于1,则认为它是有效的校验和;如果等于0,则认为它是无效的校验和。因此,约束解算器为有效的校验和情况生成1的校验和,为无效的校验和情况生成0的校验和。当我们探测DPI(稍后讨论)时,我们相应地用正确有效或无效的校验和填充校验和字段。通过对校验和函数的抽象,避免了对TCP头字段的复杂约束,从而提高了性能。

D. 表示服务器的初始序列号
在TCP的3路握手过程中,服务器的初始序列号(ISN)是在SYN/ACK数据包中生成并发送到客户端的随机数。当客户端接收到SYN/ACK包时,它需要通过发送一个ACK包来回显服务器的ISN,该ACK包的确认号等于服务器的ISN加1。因为服务器是为每个TCP连接随机生成的,所以我们需要表示服务器处于离线符号执行阶段,并收集表示服务器的is和客户端确认号之间关系的路径约束。然后在联机探测阶段(§VI),我们限制服务器使用从SYN/ACK包获得的具体值,并动态生成客户端包的具体值。

E. 多轮符号执行
如前文第四节所述,我们从监听状态开始符号执行。为了更深入地探索状态机(直到建立的状态),我们象征着多个包。具体来说,我们选择象征共3包有几个原因。首先,3个数据包应该提供对TCP状态机的合理覆盖,因为只需要2个数据包就可以将TCP状态从listen提升到established(三方握手中的SYN和ACK);第三个数据包可以进一步探索established中的其他次要状态。第二,我们更喜欢较短的插入和规避分组序列,因为较长的序列在实际中可能不可靠(例如,由于分组丢失)。

为了探索不同的包序列,我们开发了一个自定义的路径搜索/调度程序来指导S2E首先探索1和2的包序列(达到一定的阈值),然后允许第三个包到达。

正如第八章后面所讨论的,尽管TCP中没有太多的接受点和丢弃点,但是在我们的实验中,可能的接受和丢弃路径的数量是指数型的,不可能用尽,这促使我们的搜索策略平衡了对不同长度序列的探索。

6 生成在线规避攻击

通过第5节中描述的离线共音执行阶段,SymTCP获得可用于生成插入/规避分组候选者的路径约束。在本节中,我们将描述SYMTCP的差异测试阶段,以探测DPI,以确定DPI的TCP实现与服务器的TCP实现之间的行为差异。

A、 构造插入/规避包候选
利用与符号执行期间收集的每个执行路径相关的约束(如第5节所述)以及一些附加约束,我们可以将这些约束馈送到约束解算器以生成TCP头字段的具体值。使用这些值,SYMTCP构造一个包序列Pi…n(n<3),以探测DPI。

还有两个附加约束。第一个是服务器的初始序列号(is),如V-D所述。第二个包括对TCP标志、序列号和ACK号的附加约束。当数据包提前到达一个丢弃点时(实际上大多数字段是无约束的),这些对于候选插入数据包尤其必要。例如,如果由于未经请求的MD5 TCP选项而丢弃数据包,则它对TCP标志、SEQ或ACK号没有约束。由于希望DPI忽略错误(不检查MD5 TCP选项),这些其他字段将直接影响DPI处理数据包的方式。在这种情况下,我们的解决方案是生成这些约束,使包尽可能合法(即,使用正确的SEQ和ACK号)。对于TCP标志,我们只列举最常见的,DPI更可能接受的标志(SYN、SYN/ACK、ACK、RST、RST/ACK、FIN、FIN/ACK)。例如,我们可以生成一个带有未经请求的MD5选项的RST包(带有SEQ号的附加约束以匹配下一个预期的限制)。服务器当然会拒绝数据包,但DPI会接受它并错误地终止连接,从而允许后续数据未经检查地通过。对于候选逃避包,我们通过生成各个字段的随机值来做相反的事情,并希望DPI会忽略它。请注意,由于服务器将接受规避数据包,因此大多数字段已经受到限制,因此我们没有太大的空间来选择不同字段的值。

B、 构造后续探测包
如第III-B节所述,在发送候选规避或插入数据包之后,我们可能仍然需要制作包含DPI所针对的坏关键字的附加后续数据包,以便推断DPI和服务器之间是否存在任何状态差异(否则没有可观察的反馈)。

要构造后续数据包,我们需要知道TCP连接的当前状态。如果当前的TCP状态不在已建立的状态,我们需要发送使其转换为已建立状态的数据包。如果当前的TCP状态已经是已建立的状态,那么我们可以直接发送具有正确序列和确认号的数据包。由于这个原因,我们在符号执行期间处理每个包之后记录当前的TCP状态。基于此,我们使用简化版的TCP状态机生成后续数据包,以便在需要时将连接从特定的TCP状态转换为已建立的状态。随后,我们发送一个带有敏感负载的数据包,并观察它是否触发DPI上的任何警报。

7 实施

我们的系统基于S2E 2.0[17],它使用KLEE作为其符号执行引擎。我们实现了SimTCP作为一组S2E插件,用2.5K的C++行编写,探测和外围脚本用Python的大约65K行编写。
A、 选择性混合执行

每当输入tcp_v4_rcv()时,我们就启动选择性混合执行,其中tcp头字段是符号化的。当当前程序计数器不在TCP作用域内时,即它离开TCP_v4_rcv函数,或它涉入某些其他领域(如netfilter),我们禁用分叉以使S2E以类似于具体执行的方式运行,只是它仍然保持和传播符号变量。这样,我们可以从符号执行切换到具体执行,然后再切换回符号执行。此外,我们修改KLEE,以防止在禁用分叉时向路径约束添加分支条件;因此,它不会过度约束符号变量。

S2E只检测基本块和指令,而不检测连接基本块的边缘。然而,在Linux TCP实现中,通常是边缘决定接受或拒绝的原因;例如,if和goto语句可以输入相同的基本块,但表示不同的原因(接受或拒绝)。因此,我们还检测边缘并实现一个事件。最后,我们通过限制相关边的执行次数来限制可以遍历的循环数和TCP选项的出现次数——我们在一个包中最多允许5个TCP选项,并且每个TCP选项只出现一次,除了NOP选项。我们不会遇到迭代次数是符号的任何其他循环。
B、 在线约束求解

我们使用最新的Z3[50]定理证明器作为我们的在线约束求解器来生成TCP头字段的具体值。如前所述,如果我们从服务器接收到SYN/ACK数据包,那么我们将其初始序列号添加到约束中,并再次咨询Z3,为以下探测数据包生成新的具体值。这是因为以下数据包将需要确认该号码。请注意,当我们多次咨询约束求解器(以生成后续数据包)时,我们需要传递为先前数据包生成的具体值,以保持一致性。例如,第一个包的有效载荷为4字节,第二个包的序列号需要前进4。

8 评估

9 讨论和限制

**路径爆炸。**在我们的评估中,我们表明只处理三个符号包就已经导致了路径爆炸——一小时内生成了数万条路径(处理三个包的结果)。这是因为可以有多个不同的路径到达相同的放置/接受点。这些路径中的每一个对应于一个唯一的包序列(由路径约束决定),这可能导致各种规避和插入策略。
为了解决路径爆炸问题,除了在TCP代码的范围内限制符号执行外,我们还根据自己的领域知识做出了一些修剪决策。我们将其总结在一个地方如下(在第四节和第五节中讨论的细节):1)限制TCP选项字段的出现,允许每个TCP选项只出现一次,因为冗余选项在触发任何新代码时都没有用处;而且我们在一个包中最多只允许5个TCP选项,由于大多数选项彼此独立,因此不太可能使用复杂的选项组合;2)到达丢弃点时终止执行路径,因为到达丢弃点的数据包不会导致任何状态更改;3)连接处于无法进一步传递数据的状态时终止执行路径,例如。,CLOSE_WAIT;4)仔细标记接受和丢弃点,我们的目标是覆盖所有接受和丢弃点,但不是所有执行路径,因此减少了搜索空间。
目前,我们以相同的概率从这些路径中随机抽取样本,并且不区分或区分它们的优先级。然而,更好的解决办法是了解这些路径之间的关系,避免访问不太可能产生任何成果的路径。一个例子是,对于到达同一接受点的不同路径,我们知道它们对应于服务器接受的数据包,但是我们希望它们被DPI忽略。在这种情况下,理论上我们应该选择更长的路径,因为它们会经过更多的角落案例(例如,更多的支票或不同的接受条件),DPI不太可能完美地处理它们。另一个例子是,在我们的评估中,我们发现有许多数据包序列共享两个接受数据包的相同前缀,而第二个数据包恰好是一个有效的规避数据包;这意味着,不管第三个数据包在序列中是什么,它总是会因为相同的原因成功地避开DPI(图4)。不幸的是,在离线路径探索阶段,我们无法判断第二个包是否是成功的规避包,也无法终止任何进一步的探索。我们计划将来使用我们从在线测试中获得的结果来删减离线分析。
**处理重叠数据作为规避策略。**我们的模型目前不能很好地处理重叠数据,并且不能像以前的工作那样生成所有的数据重叠策略。这是因为有必要对TCP实现如何逐出缓冲区中的数据进行建模。例如,在某些操作系统中,如果检测到数据重叠,则它们宁愿放弃旧副本并接受新副本。更一般地,我们需要建立一个数据包如何追溯性地改变前一个数据包的影响的模型,并且目前我们的模型假设每个数据包的影响是独立的,并且不能被撤销。我们计划通过扩展我们的模型作为未来的工作来处理这个角落的情况。
将SYMTCP扩展到其他TCP实现/DPIs/Network Protocols/Server端。尽管我们选择了一个特定版本的Linux内核来评估我们的系统,但是我们的系统不受任何特定版本的限制,并且可以很容易地应用到其他版本。最低要求是标记所有下降点,并可选地标记一些关键接受点(对接受路径进行分组),如第8节所示。由于TCP实现在整个内核版本中没有太大的变化,所以对于有经验的人来说,标记另一个版本应该需要较少的努力。在最新的Linux内核版本(v5.4.5)上做标记只用了不到一个小时。为了将我们的方法应用于另一个操作系统或TCP实现,这与当前的方法有很大的不同,我们可能需要根据覆盖率进行更多的路径修剪,即,如果符号执行无法覆盖所有所需的接受和丢弃点,则需要手动分析以提高覆盖率。将SYMTCP扩展到其他dpi很容易。通过符号执行的结果,我们可以立即使用生成的候选包探测新的DPI;但是,需要手动分析结果。原则上可以将SYMTCP扩展到另一个协议(我们相信插入和规避定义是通用的)。然而,由于特定于协议的调整,这可能会很棘手。例如,我们的修剪决策和抽象是特定于TCP的。此外,如果协议使用加密函数,则必须显式地处理它们,因为SMT解算器无法解决在加密函数中累积的复杂约束[5],[30]。此外,我们还需要标记下降和接受点。这些方面需要进一步研究。
在我们的演示中,我们使用SYMTCP帮助客户端避开DPI。我们的方法也可以应用到服务器端。在这种情况下,我们需要对客户端TCP实现建模,即在客户端的TCP实现上运行符号执行。例如,如果客户机使用的是Linux,那么进程应该类似于我们为服务器端TCP实现建模所做的操作。注意,由于客户端是TCP连接的发起方,我们需要考虑与发起方相对应的TCP状态,例如,探索与SYN_SENT状态相关的执行路径。
防御:流量标准化和每主机数据包重新组合。为了减轻DPI规避攻击,已经提出了一些解决方案来规范流量[23]、[18]、[47],其中数据包被主动操作,有时会注入额外的数据包以确认先前数据包的结果。这些标准化策略被认为可以防止许多规避策略。然而,它们是基于大量手工创建的规则(在[23]中有38条TCP规则,没有正式的保证)。我们相信我们的自动化系统事实上可以成为对抗这些防御的一个很好的测试。不幸的是,我们不知道任何实际的实现。在[39]中提出了另一种策略,作者认为DPI的行为应该针对它负责保护的每个主机(如intranet中的主机)进行调整。从理论上讲,这种策略是合理的,但在实践中,它的成本很高,因为DPI的行为需要针对不同的操作系统(甚至跨多个版本)进行定制。Snort最接近这一思路;不幸的是,它的Linux版本的TCP状态机显然易受攻击。此外,在某些情况下,例如国家一级的审查制度,根本不可能在因特网上建立大多数机器的每主机配置文件。

10 相关工作

**绕过深度包检查。**绕过深度数据包检查的一个主要研究方向是单向流量操作,即通过注入精心编制的网络数据包来使DPI系统从一个终端主机上解除同步。此攻击是实际的,因为它只需要部署在本地主机上,不需要远程主机的任何协作。其基本思想可以追溯到1998年Ptacek等人的一份报告中。。他们提出了对NIDS进行插入和规避攻击的思想,并列举了TCP和IP协议中的各种实现级差异。发现的策略是基于对过时的dpi和操作系统(FreeBSD 2.2)的分析,许多策略不再适用。Khattak等人和Wang等人。遵循同样的原则,对中国防火墙的规避行为进行了研究,并在实践中证明了其有效性。Li等人进行了一项全面的测量,利用类似的TCP和IP级别差异来规避各种各样的中间包,例如多个ISP的流量分类系统以及中国和伊朗的审查系统。以上研究都依赖于对操作系统中TCP实现的手工分析和DPIs的逆向工程。在这项工作中,我们建议朝着自动化DPI系统的规避测试迈出重要一步。博克等人的一项并行工作。[9] 通过改变现有的包跟踪,自动化审查规避策略发现。相比之下,我们提出了一种更具原则性的方法来寻找规避策略,方法是针对Linux上数据包处理逻辑中的角落情况,这些情况在DPI上可能会有不同的处理方式。
网络协议实现的符号执行。在过去的十年中,符号执行作为一种强大的形式化验证技术应运而生,并被广泛应用于网络协议和网络功能实现的分析和验证中。例如,在[14],[15]中,作者使用符号执行来提取TLS协议的基本组件(即X.509证书验证和PKCS#1签名验证)中的接受和拒绝路径,通过交叉验证不同的实现来发现语义错误。Kothari等人使用符号执行来查找协议操作攻击,其中恶意终端主机可以诱使远程对等端发送比它应该发送的攻击性更强的数据包。Song等人探索在符号执行中发送多个包的可能性,它们的目的是在从协议规范中提取基于规则的规范的情况下,发现低级和语义错误。
DPI模型推断。理想情况下,如果我们能够自动、完整地推断出DPI模型(即状态机),那么就更容易识别与最终主机状态机的差异。Argyros等人。提出了第一种在目标系统有足够的查询和观测的情况下学习符号有限自动机的算法。该算法被应用于正则表达式过滤器、TCP实现和Web应用防火墙(WAFs),以进行指纹识别和发现规避攻击。类似地,Moon等人通过离线生成查询和探测(尽管需要网络函数二进制文件的可用性),合成有状态网络函数(包括DPI中间盒的TCP状态机)的高保真符号模型。不幸的是,推断模型的完整性和准确性本质上依赖于查询。因此,我们选择将DPI视为一个完整的blackbox,而不尝试显式地学习其状态机。不过,在某种程度上,我们确实试图通过生成对它的适当查询(在Linux-TCP状态机的指导下)来“学习它的模型”。
基于语法的模糊和穷举测试。在描述其格式的语法指导下生成有意义的输入可能有助于模糊化。然而,模糊化倾向于生成过多的输入,在我们的情况下,在测试所有候选包时效率低下。此外,在实现级别定义语法或模型需要对TCP的所有微妙之处进行彻底的分析。因此,从规范中提取的模型不够详细,无法捕获协议的复杂性。相反,我们的工作可以看作是试图“提取”实现级模型。

11 总结

在本文中,我们探讨如何使用符号执行来引导在TCP层级产生插入和规避封包,以针对DPI中间盒进行自动化测试。我们根据这一思想,自始至终开发了一个系统,并用已知和新颖的策略对三种流行的DPI(Zeek(Bro)、Snort和GFW)进行了有效性验证。该系统可以很容易地扩展到其他DPI。我们相信,我们的工作是朝着自动测试DPI中间盒的一个重要步骤,在他们的鲁棒性,以防规避。

猜你喜欢

转载自blog.csdn.net/zheng_zmy/article/details/105444585