在AFL和AFLnet的代码库之上实现,另一个针对网络服务器的灰盒模糊测试。
它通过拍摄长期内存区域的快照,并通过应用模糊哈希算法(Locality-Sensitive Hashing)将内存内容映射到唯一的状态标识符来推断目标服务器的当前协议状态。
—>相比于AFLnet:不依赖状态信息。
思路
在目标二进制文件中注入代码
—> 跟踪内存分配和网络 I/O 操作
—> 每次请求-回复交换时,拍摄长期内存区域的快照
—> 应用模糊哈希(Locality-Sensitive Hashing,LSH)将内存中的每个状态映射到一个唯一的协议状态标识符
设计
1 伪代码
客户端和服务器建立一个会话,该会话由一系列请求消息及其相应的回复消息组成
伪代码: # 跟踪long-lived data,丢弃short-lived data。 long-lived data ← allocate() while iterate indefinitely do short-lived data ← allocate() request ← receive() reply ← process(request, long-lived data, short-lived data) send(reply) deallocate(short-lived data) end while deallocate(long-lived data) # 当模糊测试成功达到新的协议状态时,新状态会产生长期数据结构的新内容 # 每次请求-回复交换结束时对此类数据进行快照 # 使用这个快照作为当前协议状态的代理,通过模糊散列为每个唯一的内存状态分配一个唯一的状态标识符
2 扩展补充
不仅获得有关代码覆盖率(例如,执行了哪些语句和分支)的反馈,还获得有关执行期间达到的协议状态的反馈。
3 概览
编译目标程序的源代码(源代码的可用性)
—> 通过启动二进制文件来运行目标服务器
—> StateAFL(客户端) ,fuzz input(a sequence of request messages): 对于序列中的每个请求消息,模糊器通过 TCP/IP 发送它,等待回复消息,然后移动到下一个请求消息
—> 反馈由一系列状态组成,每个状态对应一个请求/回复迭代
4 具体 实现
对堆和栈内存区域进行初始化,以确保它们未使用的部分仍然具有固定且可预测的值。
4.1 收集协议状态反馈
探针:一系列调用外部函数的调用指令。【以便在服务器执行检测的兴趣点时执行操作】
编译时检测将探针编织到目标服务器的代码中【链接到 StateAFL 提供的库】;探针被插入分配和释放内存以及在网络上发送和接收数据的代码的特定点。将有关进程的运行时信息传递给外部函数(例如,内存区域的地址和大小)。
4.1.1 探针功能
(i) on_allocate:跟踪所有数据结构,分配了堆或堆栈内存区域时调用(malloc)。获得输入内存区域的地址和大小。
(ii) on_free:更新由 on_allocate 跟踪的数据结构的状态,堆或堆栈内存区域已被释放调用(free)。
(iii) on_send 和 on_receive:服务器向客户端传输数据或从客户端接收数据(例如,在套接字上写入或读取)时调用。
(iiii) on_process_start:网络服务器启动时执行。初始化内部数据结构(例如 alloc_records_map 和 alloc_dumps_queue)、内部状态机和共享内存区域以与模糊器通信。
(iiiii) on_process_end:网络服务器终止时执行。分析网络服务器在其执行期间分配的数据结构,识别哪些数据是长期存在的,并计算协议状态的序列,以与模糊器共享。
4.1.2 数据结构
alloc_record:存储在映射中,使用内存区域的地址作为键 (i) 分配内存区域的迭代次数 (ii) 分配内存区域的迭代次数被释放(由 on_free 填充) (iii) 内存区域的地址 (iv) 内存区域的大小 alloc_dump: (i) 拍摄快照的迭代次数 (ii) 对内存区域的 alloc_records_map 的引用
网络服务器最初在地址 addr0 分配一个长期存在的数据结构addr_record0
—> 网络服务器迭代三个请求/回复交换,分配了 3 个短暂的内存区域(分别为 addr_record1、addr_record2 和 addr_record3)。分别在发送回复后释放它。
4.1.3 dump_current_state
当前迭代终止时,StateAFL 检查 alloc_record ,状态机移动到下一次迭代。此时on_send 函数调用 dump_current_state(遍历 alloc_records_map 中所有当前分配的堆和堆栈区域,获取每个内存区域内容的快照,将它们保存到 alloc_dump)
就算释放内存区域,它的alloc_record结构仍然被alloc_dump结构保存和引用。所有 alloc_dump 结构都排入 alloc_dumps_queue。
dump_current_state 转储长期数据结构 (alloc_dump0) 的当前内容,以及第一个短期数据结构 (alloc_dump1) 的内容;在第二次和第三次迭代结束时,dump_current_state 再次转储长期数据结构(alloc_dump2 和 alloc_dump4)的当前内容;
4.2 执行后分析
on_process_end 调用 save_state_seq ,通过查找生命周期跨越所有迭代的数据来识别长期数据的转储。
迭代 alloc_dumps_queue
—> 针对同一协议迭代计算所有转储的并集的哈希函数
—> 采用哈希值将内存内容映射到唯一的状态标识符
—> 一次用一个转储更新哈希值来逐步计算哈希值
—> 当算法找到新迭代的转储(d.iter_no_dumped > prev_iter_no)时,前一次迭代的状态标识符被最终确定并推送到序列,并为下一次迭代重复分析,直到分析完所有转储
—> 生成一系列状态,网络服务器进行的每次迭代都有一个状态(state_id)
—> 返回给 StateAFL
—> 覆盖新状态时,状态机将通过添加新状态和序列中先前状态的新转换来更新。
fuzzs 是先前已执行该状态的变异输入的数量;
paths是之前选中状态时,代码或状态覆盖率增加的次数;
selected 是该状态之前被选中的次数
该表跟踪覆盖每个状态且“有趣”的输入,即增加的代码或状态覆盖率。
—> 最后,模糊器识别输入中达到选定状态的消息,并将同一输入中的后续消息作为变异目标。
创新?
- 基于编译时检测和模糊散列技术,自动从进程内存中推断协议状态
- 无需定制消息模板
- 将 StateAFL 集成到网络服务器的公共基准测试中,使用脚本来自动执行可重复的实验,可在 https://github.com/profuzzbench/profuzzbench 获得
请问图片怎么看
已经更新😊