长亭百川云 - 文章详情

技术进展 | HNPFuzzer:基于共享内存的高速网络协议模糊测试框架

FuzzWiki

106

2024-07-13

基本信息

**原文名称:**A Framework of High-speed Network 

Protocol Fuzzing based on Shared Memory

**原文作者:**Junsong Fu;Shuai Xiong;Na Wang;

Ruiping Ren;Ang Zhou等

**原文链接:**https://ieeexplore.ieee.org/document/

10262045

**发表期刊:**IEEE Transactions on Dependable and 

Secure Computing,2023

**开源代码:**https://github.com/veltavid/HNPFuzzer-ProFuzzBench?tab=readme-ov-file

一、概述

在论文中,作者提供了对网络协议模糊测试迭代中时间消耗分布的详细分析,并发现协议模糊测试的效率可以在多个方面得到极大提升。受到这些有趣发现的启发,作者设计了一个名为HNPFuzzer的高速网络协议模糊测试框架。

首先,客户端控制器(CUC)和服务端测试(SUT)之间通过套接字接口传输消息非常耗时,这是降低网络协议模糊测试效率的最大因素。在HNPFuzzer中,CUC和SUT不通过套接字接口通信,而是基于共享内存传输消息。为实现这一目标,作者分析了客户端/服务器架构,收集了与套接字接口相关的库函数,尤其是像write()或read()这样可以写入或读取文件描述符的函数,因为套接字是一种文件描述符。然后,作者通过LD_PRELOAD机制对这些相关函数进行挂钩,并设计了一种基于共享内存的连接控制器来控制客户端和服务器之间的数据传输。

其次,协议模糊测试中的同步过程引起的时间延迟不可忽视,这与无状态软件模糊测试完全不同。如果没有精心设计的同步器,系统容易陷入异步状态。因此,作者基于共享内存在HNPFuzzer中精心设计了一个同步器。

第三,现有模糊测试工具中的fork服务实例在模糊测试迭代后被直接杀死,重新启动实例浪费时间。如果可以模糊测试不止一次实例,那么模糊测试过程可以更有效率。在HNPFuzzer中,作者设计了一个监控实例环境的机制,并在迭代后检查其状态。如果实例没有被污染,执行另一个模糊测试迭代。这样,模糊测试的效率得到了进一步提高。

作者分析了他们的方案与现有的不同方向上的网络协议模糊测试方案的关系。一个有趣的观察是,不同方向上的上述方法并不相互竞争,反而可以共同合作,形成一个更好的网络协议模糊测试工具。因此,论文的方案也可以被视为一个框架。

二、HNPFuzzer的动机

论文首先描述了网络协议模糊测试迭代的工作流程,包含至少一个服务器和一组客户端,它们通常通过网络连接。为了便利,模糊测试过程通常将服务器程序和客户端程序放置在同一台本地机器上。论文中假定客户端(CUC)完全由模糊测试工具控制,可以向服务端测试(SUT)发送任何消息。模糊测试迭代涉及四条链:**CUC的消息链、来自SUT的响应链、SUT的函数链和状态链。**在初始化阶段,CUC构建了本次测试迭代的消息测试序列,核心消息用于引导SUT到达测试者关注的目标状态,并向SUT发送此模糊测试迭代的有效载荷。在SUT端,现有的模糊测试器通常需要在新的测试迭代开始时重启服务实例。

论文接着分析了一个模糊测试迭代中时间消耗分布,如图1,通过对AFLNet进行一系列修改并重复运行不同服务以获取不同行为的时间消耗比例。从数据可以看出,基于套接字的进程间通信在大部分服务中占据绝大部分时间,说明套接字接口引起的延迟是网络协议模糊测试低效率的一个主要原因。此外,同步问题在网络协议模糊测试中不容忽视,例如客户端的poll()调用会引入显著的时间延迟。论文还讨论了服务端初始化的时间成本问题。

通过分析模糊测试迭代的过程及其时间消耗构成,论文得出结论认为可以通过以下几个方面显著缩短测试迭代的时间:(1)通过基于共享内存模拟网络函数缩短CUC和SUT之间消息传输的时间消耗;(2)基于共享内存重新设计同步机制,减少同步的时间成本;(3)在一些情况下,可以在一个服务器实例中发送多个测试用例,而不是只有一个,从而减少服务器初始化的时间成本。因此,HNPFuzzer引入了持久模式。HNPFuzzer通过挂钩实现,并且对CUC和SUT完全透明,这意味着CUC和SUT认为它们确实是通过套接字接口而不是共享内存进行通信。虽然基于快照的网络协议模糊测试方案的目标也是缩短测试迭代的时间消耗,但它们的主要思想与HNPFuzzer完全不同,因此不与HNPFuzzer竞争,反而可以与HNPFuzzer合作提高网络协议模糊测试的效率

图1 网络协议模糊测试迭代中时间消耗分布

三、HNPFuzzer的整体架构

HNPFuzzer的整体架构主要包括初始化阶段和模糊测试循环阶段两个部分,如图2。在初始化阶段,为了提高模糊测试的效率,引入了AFL首创的持久模式,这种模式允许多次对目标软件进行测试而无需新启动进程。为确保在主事件循环的入口处程序状态不变,HNPFuzzer需要检查长期存活的数据是否被修改以及是否存在因内存泄露导致的影响。为此,通过扩展afl-clang-fast工具并基于LLVM框架,对目标应用程序进行了定制化的插桩,具体包括对可能修改长期存活内存区域的存储指令(如图3)和对动态内存管理库函数(如malloc()、free())的调用指令进行跟踪。

在模糊测试循环阶段,HNPFuzzer首先对测试目标协议进行分析,实现特定于协议的消息解析器,并构建测试用例。每一轮测试中,通过connect()函数建立客户端与服务器之间的连接。在此阶段,连接控制器负责客户端与服务器之间的通信,但HNPFuzzer并不通过套接字接口传输消息,而是将特定的套接字I/O操作转化为共享内存读写操作,以此提高模糊测试的效率。此外,通过共享内存基同步器确保消息在适当的时间被传输,以缩短消息延迟时间。持久模式允许HNPFuzzer重复利用同一服务实例进行多轮测试,进一步提高了测试效率。 

图2 HNPFuzzer的整体架构

 图3 搜索长期存活数据相关存储指令的算法

**、连接控制器**

连接控制器是系统中最关键的组件,它负责实现持久模式并支持客户端与服务器之间通过共享内存交换消息。整个连接流程分为连接创建、消息交换和连接终止三个阶段。连接控制器在连接创建和终止阶段实施收集信息来检测长期存活数据的修改,以及追踪堆内存分配以保护持久模式服务器不受内存泄漏的影响。连接终止时,连接控制器将检查进程是否违反了两个规则来决定是否终止服务器进程。

在消息交换阶段,连接控制器通过LD_PRELOAD挂钩实现替换原有套接字接口,通过捕获特定库函数来介入客户端和服务器间的消息传输。连接创建时,连接控制器通过分析客户端/服务器模型识别用于与客户端通讯的套接字,并使用引用计数和fd_table数组跟踪由于fork(), dup()或dup2()引起的文件描述符的复制。

当CUC和SUT建立连接后,它们开始传输测试用例和响应消息。在数据传输过程中使用了两个固定大小的缓冲区,因为SUT可能需要同时接收和发送消息。但由于共享内存区域无法扩展,消息大于缓冲区必须拆分成多个部分进行传输。传输响应消息时,作者引入了一个额外的bufc缓冲区,SUT在调用send()时,先将消息写入bufc,而不是直接放入共享内存缓冲区。只有当SUT等待来自CUC的新消息或关闭连接套接字时,才将bufc中的消息发送给CUC。

关于终止连接,内存监视器首先确定SUT是否正处于主事件循环中,然后跟踪在主事件循环期间分配的内存区域,并保留其开始地址在称为θ的块集中。如果这些内存区域中的任何一个被释放,内存监视器将从θ中移除相应的地址。对于长期内存,当出现将某些长期内存区域写入存储指令时,内存监视器将变量G设为true,表示全局状态可能已改变。

一旦连接关闭,连接控制器将验证是否违反了两条规则,即块集θ应为空且变量G不应被修改。如果θ为空且G为false(全局状态保持不变),连接控制器将引发SIGSTOP以通知fork服务器连接终止,否则直接退出。

在模糊器创建新连接之前,它将与fork服务器交互,需要做出决定,即是让停止的进程恢复(启用持久模式)还是fork一个新进程。

五、共享内存同步器

共享内存同步器是连接控制器的核心组成部分,用于在模糊测试过程中同步不同组件的状态。为实现原子操作,作者引入名为会话状态(session_state)的变量以及两个函数:shm_wait()和shm_notify()。

shm_wait()的作用是阻塞线程直到特定会话状态被触发,而shm_notify()则用于设置会话状态以恢复被阻塞的线程。在HNPFuzzer中,作者选择“忙等待”机制来实现这两个原语。

会话状态也存在于共享内存中。会话状态可以是“C”或“S”,“C”状态代表客户端在发送或接收消息,而当服务器即将发送或接收消息时应该阻塞;同理,“S”状态则代表服务器在发送或接收消息,客户端在即将发送或接收消息时应阻塞,如图4。

因此,任何一方完成当前任务或者在对方完成下一任务之前无法继续运行时,都应该使用shm_notify()改变会话状态,以通知对方执行下一个任务,这实质上是状态转换。通过共享内存中变量的修改而不是盲目等待来实现双方的同步。

a) S → S: 当客户端受到shm_wait()的阻塞时,会出现此状态转换。

b) C → C: 当服务器受到shm_wait()的阻塞时,会出现此状态转换。

c) S → C: 这种转换在以下五种情况下会发生:

    i.服务器完成了消息的发送;

    ii.消息长度超过了共享内存缓冲区,此时服务器通知客户端发送剩余部分;

    iii.服务器开始接收消息,但发现共享内存缓冲区为空;

    iv.客户端接收到SIGALRM信号;

    v.服务器退出或停止。

d) C → S: 这种转换在以下四种情况下会发生:

    i.客户端完成了消息发送;

    ii.消息长度超过了共享内存缓冲区,此时客户端通知服务器发送剩余部分;

    iii.每个会话开始时;

    iv.客户端即将结束会话。例如,客户端完成了消息读取并且没有更多消息发送。

 

图4 会话状态转换

六、HNPFuzzer的性能评估

(1)模糊测试吞吐量

实验中,作者使用消息处理速度这一比执行速度更细粒度的度量来表示模糊器的吞吐量。

与AFLNET相比,HNPFuzzer将吞吐量提高了最高44259.38%,平均而言吞吐量提高了3966.09%。

在Proftpd和Dcmtk上,HNPFuzzer的吞吐量并没有得到很大提升。为了分析Proftpd的可能原因,作者在HNPFuzzer中运行Proftpd 10000次,并记录各种操作的时间消耗。作者发现,在Proftpd上HNPFuzzer的IPC时间消耗仅占总时间的11.76%,76.01%的时间用于等待fork-server的状态码。因此作者推断,**根本原因是Proftpd服务器后续操作中的延迟,这在其他模糊器中通过杀死服务器进程来避免。**此外,HNPFuzzer中的插桩也降低了服务程序的执行速度。通过消融实验,作者还得到了Dcmtk的原因,这与持久模式有关。

图5 ProFuzzBench上各种模糊器的模糊测试吞吐量

(2)消融实验

实验可以分为四组,即仅持久模式、仅同步机制、共享内存IPC(与同步器合作)和全部机制。

值得注意的是,如果没有同步机制,共享内存IPC无法工作。否则,一个消息可能会在接收之前被覆盖,状态机也无法正确构建。

考虑到基于UDP的网络协议没有连接概念,HNPFuzzer中的控制器无法检测到这些协议的终止。因此,在模糊测试这些协议时,将禁用HNPFuzzer的持久模式。

在Proftpd、Pureftpd以及Live555中,初始化时间消耗所占比例最小,持久模式没有提高它们的性能,但由于自身开销而产生减速影响。

Dcmtk是一个异常服务,它虽然有繁琐的初始化,但被持久模式减速了。作者分别禁用了内存监控和持久模式规则验证来运行Dcmtk,发现Dcmtk在这两种情况下都达到了更高的吞吐量,这意味着1)插桩给Dcmtk带来了巨大的开销;2)持久模式要求的两个规则经常无法满足,服务器实际上无法持久运行。因此,可以推断当由插桩内存监控引起的延迟超过持久模式可以节省的时间时,应该停用持久模式

HNPFuzzer中的同步器可以通过减少poll()中手动配置的时间延迟来减少时间浪费。第二列中的实验结果表明,同步器也获得了更高的平均吞吐量(+127.48%)。然而,某些仅使用同步器的服务却性能下降,这个问题有两个可能的原因。一方面,手动配置的时间本身不会引入太多时间浪费,同步器没有节省时间而是由于多余的通知机制增加了开销。另一方面,如果不启用基于共享内存的IPC,同步器本身无法检测到一些会话状态转换事件,这引入了额外的开销。

例如,要求服务器检查是否有任何即将到来的消息。如果启用了基于共享内存的IPC,可以通过检查共享内存缓冲区内容长度来轻松实现,而在同步器是唯一生效的组件时,必须使用poll()来检测此事件。

基于共享内存的IPC是作者系统中最强大的组件,它带来了平均加速3846.57%。这种改进是通过与同步器合作实现的,没有同步器,请求和响应之间的对应关系会变得无序,导致状态机进一步失去同步。因此,当同步器被停用时,无法衡量共享内存IPC的效果

共享内存IPC能加速所有的服务。然而,尽管Forked-daapd获得了更高的吞吐量(+47.7%),但这比仅使用同步器时低(+220.24%),这暗示了共享内存IPC带来的潜在不利影响。这些负面表现的原因可以归因于与共享内存IPC一起引入的锁和信号掩码所产生的开销。它们都被用来保证共享内存读写操作的原子性。

图6 ProFuzzBench上HNPFuzzer的消融实验结果

(3)代码边覆盖情况

实验表明,在大多数网络服务中,HNPFuzzer表现优于其他模糊器。此外,HNPFuzzer的曲线更平滑,这表明HNPFuzzer发现的大量代码边较少依赖于某些随机生成的测试用例。

然而,类似于吞吐量,HNPFuzzer在Dcmtk上的表现也不如AFLNET。考虑到HNPFuzzer没有提高Dcmtk的模糊测试吞吐量,这是合理的。在这种情况下,HNPFuzzer中的复杂机制可能对模糊测试效率有不良影响。

图7 ProFuzzBench上各种模糊器的代码边覆盖情况

图8 各种模糊器在24小时内发现的代码边数量

(4)漏洞发现

实验结果展示了各种模糊器在24小时内触发的去重crashes数量。可以观察到HNPFuzzer对于大多数协议触发了最多的崩溃。此外,作者发现HNPFuzzer不仅可以在Dnsmasq和Tinydtls中触发更多崩溃,而且还具有在其他模糊器测试下从未崩溃的Forked-daapd中触发崩溃的能力。总的来说,在漏洞发现方面,HNPFuzzer是这四种协议模糊器中表现最好的。

作者观察到从接收SIGTERM到服务器终止的巨大延迟应该是其他模糊器在Forked-daapd上效率低下的原因。因此,HNPFuzzer避免发送SIGTERM的持久模式应该得到发现这个漏洞的功劳。

图9 各种模糊器触发的去重crashes数量

(5)与其他类似原理网络协议模糊器的比较

SnapFuzz旨在消除同步中自定义延迟的需要,并用UNIX域套接字替代互联网套接字,这与作者基于共享内存的同步器和IPC有类似的理念。

图10 模糊测试吞吐量(exec/s)相对于AFLNET的改进率

参考文献

[1] M. Boehme, C. Cadar, and A. ROYCHOUDHURY, "Fuzzing: Challenges and reflections," IEEE Software, vol. 38, no. 3, pp. 79–86, 2021.

[2] V. J. Man`es, H. Han, C. Han, S. K. Cha, M. Egele, E. J. Schwartz, and M. Woo, "The art, science, and engineering of fuzzing: A survey," IEEE Transactions on Software Engineering, vol. 47, no. 11, pp. 2312–2331, 2021.

[3] H. Huang, P. Yao, R. Wu, Q. Shi, and C. Zhang, "Pangolin: Incremental hybrid fuzzing with polyhedral path abstraction,"2020 IEEE Symposium on Security and Privacy (SP), pp. 1613–1627, 2020.

[4] J. Jung, S. Tong, H. Hu, J. Lim, Y. Jin, and T. Kim, "Winnie : Fuzzing windows applications with harness synthesis and fast cloning," inNetwork and Distributed System Security Symposium, 2021.

[5] S. Schumilo, C. Aschermann, A. Abbasi, S. W  ̈orner, and T. Holz, "Nyx: Greybox hypervisor fuzzing using fast snapshots and affine types," in USENIX Security Symposium, 2021, pp. 2597–2614.

[6] Z. Zhang, W. You, G. Tao, Y. Aafer, X. Liu, and X. Zhang, "Stochfuzz: Sound and cost-effective fuzzing of stripped binaries by incremental and stochastic rewriting," 2021 IEEE Symposium on Security and Privacy (SP), pp. 659–676, 2021.

[7] M. Zalewski. American Fuzzy Lop. Accessed: Feb. 2023. [Online]. Available: https://lcamtuf.coredump.cx/afl/.

—END—

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2