- 吞吐量低
+ AFL
- 可以通过用户模式QEMU支持纯二进制模糊测试
- 由于缺乏特殊的硬件支持,用户模式QEMU无法成功模拟大多数物联网程序。
+ 总结
- 现有的物联网固件模糊测试工具不能提供令人满意的代码覆盖率。(前面一直提吞吐量,最后总结的时候提代码覆盖率???)
- 最先进的模糊器(如,AFL)无法轻松应用于物联网程序模糊测试。
动机
- 我们决定基于仿真来构建模糊器
- 由于大多数IOT固件只有二进制程序,因此最好是基于仿真进行插桩。
- 仿真比直接在IOT设备上fuzz吞吐量更高。
- 导致全系统仿真吞吐量低的原因
- 内存地址转换
- 在系统模式仿真中,QEMU使用软件MMU为每次内存访问执行地址转换。
- 动态代码翻译
- 在系统模式仿真中,块链接限制于同一物理页面的基本块,那意味着翻译器的调用频率高于用户模式仿真。
- 系统调用仿真
- 在系统模式仿真中,系统调用交给仿真的操作系统和硬件,速度较慢。并不是所有的系统调用都需要仿真的操作系统和硬件来支持。
- 内存地址转换
增强进程仿真
概述
问题陈述
- 增强进程仿真的目标是在满足以下要求的情况下,在用户模式仿真中正确执行物联网固件的程序:
- 固件可以在系统仿真器(如系统模式QEMU)中被正确仿真。
- 固件运行POSIX兼容的操作系统。
- 通过增强进程仿真,实现以下设计目标:
- 透明度(解决挑战一:兼容性)在增强进程仿真中运行的用户级程序应该表现地像在系统模式仿真中运行一样。
- 高效率(解决挑战二:性能)理想情况下,它应该近似与纯用户模式仿真的性能。
解决方案概述
- 固件在系统模式仿真器中启动,用户级程序(包括要模糊测试的程序)在仿真器中正确启动。
- 当要模糊测试的程序达到预定点(如主函数入口点),进程执行将迁移到用户模式仿真,以获得较高执行速度。只有在极少情况下,才会将执行迁移回系统模式执行,以确保执行的正确性。
- 为最小化迁移成本,两种仿真模式通过RAM文件实现内存共享。系统模式仿真将RAM文件视为物理内存,而用户模式仿真通过虚拟地址访问共享内存。
- 当在用户模式仿真中没有建立页面映射时,需要将进程执行迁移到系统模式仿真以建立该映射。
- 通过正确的内存映射,进程应该能在用户模式仿真中正确执行,直到到达系统调用(直接在主机OS执行系统调用通常不起作用,因为主机OS 与IOT OS不同,底层硬件层也不同),借助RAM文件将进程执行迁移到系统模式仿真以处理此系统调用,当系统调用返回时,将进程执行迁移回用户模式仿真。
内存映射
引导
- 在系统模式仿真中启动IOT固件,并进一步启动指定的物联网固件程序,通过DECAF提供的虚拟机自检VMI监控指定的物联网程序的执行,并在执行到达预定分叉点时得到通知,遍历指定进程的页表,收集虚拟到物理的页映射信息,并将其发送到用户模式仿真端,然后,对于虚拟地址到物理地址的每次映射,用户模式仿真端通过调用mmap来建立内存映射:
mmap(va,4096,prot,MAP_FILE,ram_fd,pa)
- 本质上讲,我们将RAM文件的一页以物理地址作为偏移量映射到指定的虚拟地址。
- 从这一点开始,系统模式仿真中的执行暂停,CPU状态被发送到用户模式仿真,并在那里恢复执行。
- 理解:这里是指在程序刚启动的时候如何将系统模式仿真和用户模式仿真进行内存共享------就是先在系统模式仿真运行,然后将页表发送给用户模式仿真,用户模式仿真构建虚拟内存与物理内存的映射,实现两个模式下内存映射的统一,即初始化(也就是引导)
页面错误处理
- 在用户模式仿真的进程执行过程中,如果访问的内存地址已经映射到此虚拟地址空间,则执行应该会成功进行。否则,主机CPU将引发页面故障,将进程迁移到系统模式仿真以建立映射。
- 在用户模式仿真中为页面错误注册了信号处理程序,主机OS将页面错误事件传递给用户模拟仿真,在接收到该信号时,用户模拟仿真记录故障指令处的CPU状态,暂停执行,将CPU状态传递给系统模式仿真端,期望在系统模式仿真中处理页面错误,并为故障虚拟地址建立新的映射。
- 在系统模式仿真接收到CPU状态并恢复执行时,由于页面不存在,仿真处理器将引发页面错误,物联网固件OS中的页面故障处理程序将对此页面故障作出响应,并尝试建议映射。
- 一个关键问题是确定页面映射何时建立以及错误何时发生
- 映射何时建立
- 如果当前执行在指定的进程内,则意味着执行已经从内核返回到用户空间,可以在TLB中找到新建立的映射,将其与CPU状态一块发送给用户模式仿真,它通过调用mmap创建这个新的映射并恢复执行。
- 错误何时发生
- 如果由于某种原因,发生了错误,进程被中止,可以依赖虚拟机自检VMI来获得通知,然后双方的整个执行都会终止。
- 映射何时建立
- 理解:物联网固件程序会一直在用户模式仿真下运行,当发生页面错误时,将错误抛给系统模式仿真,由他解决问题建立新映射,映射建立后切换到用户模式仿真。
预加载页面映射
- 现代操作系统以惰性方式加载内存页。尽管当一个新进程启动时,所有代码页都被分配到其地址空间中,但直到第一次内存访问导致页面错误,才真正建立从每个虚拟页面到其物理页面的映射。
- 对于每个模糊迭代,子进程都会从父进程中重复分叉,因此总是有一系列由未映射的代码页引起的页面错误,开销巨大拖慢模糊速度。
- 在物理内存中预加载给定进程的代码页,并执行两种模式之间的映射,可以避免每次模糊迭代时重复加载代码页,从而加快模糊吞吐量。
- 在引导过程中,在系统模式仿真中模拟对每个程序代码页的访问,以迫使操作系统将每个页面映射到进程的地址空间,从而减少由预加载页面引起的页面错误数目。
系统调用重定向
- 如果系统调用引起的异常没有得到正确处理,物联网程序的用户模式仿真可能会失败。
- 为了确保执行的正确性,我们必须将系统调用从用户模式仿真重定向到系统模式仿真。
- 与<页面错误处理>处理方法一样。
- 优化与文件系统相关的系统调用
- 作者发现许多系统调用都与文件系统有关。
- 物联网固件程序要么试图访问固件中已经存在的文件或目录,要么是新创建的临时使用的文件或目录。
- 优化方案:将固件镜像中的文件系统挂载在主机OS上,这样当处于用户模式仿真的物联网固件程序需要访问文件系统时无需切换的系统模式仿真,可以直接在用户模式仿真中通过主机操作系统访问文件系统。
Firm-AFL的设计与实现
AFL的工作流程
- AFL是一种覆盖引导的灰盒模糊测试器。维护一个种子队列,存储所有种子,包含用户选择的初始种子和从现有种子变异出来的可以导致程序到达新的代码覆盖范围的种子。
- 模糊测试的主要代码程序是
afl-fuzz
,它从种子队列中挑选一个种子,执行随机变异,生成一个输入,把输入喂给目标程序。(目标程序一般是二进制可执行文件) - 为了从目标程序执行中收集代码覆盖信息,AFL使用用户模式QEMU启动程序,并对目标程序的分支转换进行插桩,这样
代码覆盖信息就被编码并存储在位图里
。 - 由于模糊测试过程中,需要重复执行目标程序,AFL利用fork server机制来加快这一过程。
- 每有一个输入,就调用ececve()函数来进行程序的链接,库函数的初始化等操作,大大地降低了fuzzing的效率。
- AFL通过在目标程序中插入fork server的逻辑代码来保证在fuzzing的时候只进行一次程序的链接,库函数的初始化等操作,而通过fork()函数的copy-on-write机制,大大提高了fuzzing的效率。
- 通过在二进制程序中插入fork server代码,该fork server会在main函数之前执行,它会暂停,等待AFL fuzzing端的输入,当AFL fuzzing端”发号施令”给fork server之后,fork server此时就通过fork()函数来生成子进程,子进程继续main函数的逻辑(执行模糊测试),由于fork server已经将各种资源都加载好,所以每次子进程只需要执行main函数的代码即可。
- AFL与fork server是父子关系,fork server与要fuzzing的目标程序是父子关系。AFL通过管道与fork server进行通信,fork server通过waitpid()函数等待要fuzzing的目标程序子进程完成,得到其退出时的状态,通过管道传给AFL进程。
带有增强进程仿真的AFL
Bootstrapping
- 要模糊测试IoT固件镜像中的程序,需要启动镜像并在系统启动后启动固件程序。
这是在fork-server里的系统模式仿真中完成的。
- 利用Firmadyne来正确仿真固件镜像。
- 利用VMI,可以捕捉目标程序启动或终止的精确时刻以及目标程序的执行何时达到预先确定的fork点。
Forking
- 区别于AFL默认的fork点为主函数入口点。我们对寻找IoT程序中通过网络接口触发的漏洞感兴趣,所以关注与网络相关的系统调用。因此,这些系统调用中任何一个的第一次调用都将成为fork点。
- 区别于AFL标准工作流中简单利用fork系统调用创建子进程,执行fuzz任务。我们不仅需要为用户模式仿真fork一个子进程,还需要为系统模式仿真"fork"一个虚拟机实例,因为两种模式需要同步。
- fork一个虚拟机实例代价太昂贵了。
- 可以在fork点创建虚拟机的快照snapshot。当一次模糊测试执行完成,可以恢复快照。
- 系统模式仿真QEMU提供了
save_snapshot
函数可以将所有CPU寄存器和内存空间保存到特定文件中。然而文件写入和读取仍然非常缓慢。- 基于copy-on-write原则实现了轻量级快照机制。
- 原理是不全部记录初始的内存状态,只记录那些在程序运行时会被修改的页的初始状态。当新一轮的fuzz要开始时,将保存的页的初始状态写回。(即只记录会变的,不变的不去动)
- 具体来说,首先将映射到系统模式仿真QEMU的RAM文件标记为只读,然后内存写入将导致故障,复制该页并标记为可写。因此,我们记录了在一次模糊测试执行期间修改过的所有内存页,当恢复快照时,只需要将这些记录的页面写回。
Feeding input
- 输入是我们提供的。对于从网络接口接收输入的IoT程序,可以直接在用户模式仿真中检测与网络相关的系统调用,直接喂给输入,不用重定向这些系统调用到系统模式仿真。
Collecting coverage information
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0
(img-v73VUCZS-1725715502527)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0