Janus工具介绍
记录时间:2019.12.6。这一次偷懒就直接对着上课写的PPT讲了,仅供参考。
github:
https://github.com/sslab-gatech/janus
https://taesoo.kim/pubs/2019/xu:janus-slides.pdf
- 1.硬编码:文件路径来生成随机的系统调用,即类似于path=”/bin/sh”的类型,将数据直接嵌入到程序或其他可执行对象的源代码。
- 问题:文件操作是一种上下文相关的工作负载(workload),映像与执行的文件操作之间存在依赖关系。而这种硬编码不能生成有意义的文件操作序列,并且覆盖文件系统的深层代码路径。
- 2.随着文件系统与操作系统的复杂性不断增加,fuzz困难度上升的情况下,再现已发现的bug依赖于VM,QEMU或Linux用户模式(UML)会产生巨大的时间开销。
- 常见的fuzzer测试依赖于最小完整性检查的回归测试。这是一种修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误的方法。而重用实例则虽然能减少重新加载的开销,但会进一步导致操作系统状态不稳定,最终导致不稳定的执行与不可恢复的bug。同时由于前面积累了许多的变化,也难以确定是哪一个调用真正地造成了崩溃。除却时间,空间开销也是很重要的一点。由于文件系统的庞大性,大量涉及IO操作,会导致传统的变异效率低下。
Janus采用了新的方法。对于变异的对象,我们不是单纯修改非零块,而是从映像内提取真正有用的元数据(metadata)进行更改,以进行加速。但是这种元数据块的任意修改可能会造成不可恢复的错误,所以我们对于其受校验和保护的位置进行保护;对于硬编码的文件路径,我们使用了一种状态推测,即根据现在运行的文件操作来推测其路径的变化情况,从而达到维持依赖的效果,并将其与当前的测试结果作为反馈来生成新的系统调用,并指导下一次的变异方向。对于变异的种子(seeds),也根据我们的抽象模型分为了两个方面,分别为 磁盘映像(image)与挂载在磁盘上的文件操作序列(file operation) 作为文件系统的一种简单描述。接下来从几个组件来介绍Janus运行的基本框架与流程。
第一部分是语料库(corpus)组成。对于种子中的映像部分,我们将其映射到内存上构建的一个持久缓冲区(persistent memory buffer),使用映像解析器(image
parse)解析其映像类型并定位其元元数据块的位置。映射的原因在于减少修改映像时从磁盘到内存的反复IO造成的开销。接着记录我们每一块的数据偏移量与大小,对于受检验和保护的位置进行了额外的保护,以保证其安全性。接下来对种子中的文件信息(file_obj)进行获取,给定一个初始的状态(statue),并且记录其上的文件程序(program)。根据程序与状态生成对应的唯一的系统调用。最后打包元数据,程序,状态作为我们语料库内的一个初始实例(test case)。
第一个维度是我们的映像,我们采用一个映像变异器(image mutator)对打包好的元数据块blob进行变异。为了产生更多类型的变异,我们采用了8种变异策略对随机偏移量(random_offset)操作:按位(bit)翻转(filp),设置(set)特定的字节(byte),双字(word),四字(dword),增加(inc)随机字节,双字,四字,设置随机字节。产生的特殊值(-1,0,等)在评估中可以产生更多不正确处理的文件系统。
第二个维度是文件操作。每一个文件操作被集中到了一个系统调用(syscall)上,每个系统调用被解释为系统调用号(nr),参数(arguments),返回索引(ret_index),我们的程序便是这些系统调用的一个有序列表,作用为修改突变后的映像与维护一个变量库(variable bank)。此外,他还记录了一个活动文件描述符列表(active file descriptors), 它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。(已被打开,但尚未被该程序关闭),参数与返回值为常量/变量的索引。Janus常用的系统调用对应了状态变化,如open()引入了新文件描述符,mkdir()与link(),symlink()建立了新文件或目录,rmdir()与ulink()删除文件或目录,rename()更新文件路径,setxattr()与removexattr()更新文件的扩展信息。用例程序不变时,改变系统调用参数或者添加新的参数值,就可以生成新的映像状态。同时只在完成程序执行后维护推测的映像状态,避免了对现有参数进行任何可能导致映像状态变化的更改,因为这样的突变可能会使发生变化的调用之后的系统调用无效。
我们常使用系统调用变异(Syscall mutate)与系统调用生成(Syscall generate)两种方法。对于变异,我们随机选择一个系统调用,使用一个新值列表代替旧值列表;对于生成,我们附加一个新的系统调用,随机生成平凡参数(trivial arguments)并存储在变量库内作为候选值,且该值与推测的运行状态无关。对于整型,生成随机数;对于指针型,至指向用户数据或是内核输出数据。前者为随机数数组,后者为固定数组。
关于顺序与轮数,由于提取的元数据表示映像的初始状态,当映像经过多次系统调用后,元数据对文件操作执行的影响逐渐减小。因此,JANUS总是首先尝试修改元数据。引入新的文件操作可以成倍地增加程序的变异空间,也可以抹去映像过去操作的变化。因此,JANUS更喜欢修改现有的系统调用,而不是生成新的系统调用。
最后一个组件为基于LKL的执行器(LKL-based executor)。LKL指linux内核库,是一种向用户空间程序公开内核接口的库操作系统。我们派生的新实例将经过LKL系统调用挂载一个修改后的映像,调用系统调用fuzzer生成的文件操作,进而生成一个工作负载去对内存上的持久的映像缓冲区进行写时复制(COW)。写时复制可以保证在映像缓冲区被生成的工作负载操作时,除了发生变异的块之外,映像缓冲区中的其他部分不会发生变化。对于可能产生的内存错误,使用**内核地址杀毒器(KASAN)**进行检测,分配影子内存(shadow memeory)来记录原始内存的每个字节是否可以被安全访问。它是基本输入输出操作系统(BIOS)程序在随机访问存储器(RAM)中的一个备份,能被更快的访问。LKL启动时便构建了从LKL内存空间到影子内存的映射。最后得到的内容,根据其崩溃情况记录结果路径到语料库内或是崩溃结果报告内。
实现了AFL风格的路径覆盖,图片分析一致。本工具对于硬件要求较高,笔者与组员是使用了云端的实例才成功完成了最初步的复现,也算是初步尝试了fuzzing。在Janus的基础上,这个github作者还有一个改进版Hydra,相较于janus多了词法语法分析的内容,有时间将会另外补充。
来源:CSDN
作者:eunsummeo
链接:https://blog.csdn.net/qq_40398985/article/details/103588434