手把手系列之——syzkaller原理及实现分析

以云看科技 2024-08-22 02:54:34

syzkaller是一个无监督的用覆盖率做引导的内核fuzz工具,由google安全人员研发并一直改进更新。自2015年推出至今google已经用它发现了4800多个内核漏洞,并且此工具完全开源,各个厂商及研究员也客制化此工具进行fuzz发现了大量问题,为linux内核主线及各个发行版的安全性带来了巨大提高,对于其他内核的测试也有拓展。本文以syzkaller平台在android平台进行fuzz为例介绍syzkaller的原理。

架构概述

先来看syzkaller官方对于原理解释的唯一一张图,这张图介绍了syzkaller的主体结构。

从这张图可以清晰的看出syzkaller的运行有两个环境,绿色的是host部分,是一台稳定的主机,用来管理和记录fuzz过程;黄色的是VM部分,一般是使用定制的内核作为fuzz的目标的测试环境。

Host运行的主要模块是syz-manager,功能是启动和调度VM,启动VM中的syz-fuzzer并监视其运行情况,持续记录语料库和崩溃信息,还有诸如复现崩溃并尝试寻找原因以及整理显示fuzz运行情况等辅助功能。

VM运行的主要模块是syz-fuzzer,也是整个fuzz过程的核心模块,功能是生成输入,接收覆盖率信息,变异模板,最小化fuzz进程等等,目标是生成最有效的测试输入从而最高效的发现问题。另外还有管理syz-executor执行模块,报告fuzz进度给syz-manager等辅助功能。

VM运行的另一个模块是syz-executor,这个模块其实是syz-fuzzer的子模块,主要功能是接收syz-fuzzer生成的测试输入,并转化为可执行文件运行,通过系统调用对kernel进行测试。

除了主要模块之外,syzkaller还有一些其他的辅助模块,比如官方最重要的云运行环境syzbot的部署相关模块等等这里就不涉及了。

原理概述

syzkaller有三个主要特点,让它成为了目前最流行的内核fuzz工具。分别是以系统调用为基础直观进行测试用例设计和变异,通过内核错误检查机制捕获fuzz发现的问题,通过内核覆盖率反馈通道引导fuzz进程。

1. 以系统调用为基础进行设计。

除了vdso的特殊设计,内核和外界的唯一接口为系统调用,所以syzkaller的测试输入就生成为一系列系统调用。为了让编写测试样例的模板更加直观接近系统调用,syzkaller设计了一种接近c语言并且突出系统调用和参数特征的模板语言。这种直观的方式同时方便了编写测试样例模板和分析发现的问题。在编写测试用例模板时可以自由控制系统调用每一个参数的类型和范围甚至前置条件,并且很好的体现在用例名称上,方便分析日志时发现是哪些操作导致了问题。

2. 使用内核错误检查机制捕获问题。

内核本身是一个比较封闭的系统,因此不太可能由fuzz工具自己探测到问题,不过内核自己可以在运行的同时检查自己是否有问题出现,使用的工具就是sanitizer。最通用的内核sanitizer目前是KASAN,直译为内核内存地址消毒剂。效果是内核在运行fuzz输入的系统调用的同时,不断细粒度检测每一块内存使用的情况,可以非常高效的发现内核内存破坏问题并进行直观报告。另外目前linux社区开发了多种sanitizer包括KCSAN,KMSAN等等,目的都是在运行中发现不同类型的问题,这些问题和报告都可以通过内核日志被syzkaller捕获到,从而反馈给研究人员,如果不使用这些机制,就只能等到内核panic才能知道出现了问题,发现问题的范围就会减小,同时分析难度大大增加。

3. 使用内核覆盖率反馈通道引导fuzz。

覆盖率标记了fuzz已经测试过的代码块和没有测试过的代码块,检测更多更深入的代码块有助于发现隐藏较深的问题,因此代码覆盖率是目前最流行的fuzz引导手段。和上一条一样,由于内核的封闭性,无法直接得知内核代码的执行情况。这就需要KCOV机制,即内核专门有一个文件节点可以读取到当前进程共触发到哪些内核代码块。通过收集覆盖率信息,syzkaller就可以找到更有效率的系统调用及参数组合,从而不断提高fuzz效率。

这里可以看到定制后的汇编代码,__sanitizer_cov_trace_pc就是覆盖率插桩,运行到这里就把这个地址记录下来,__asan_loadX就是检查不同长度的内存地址有没有越界,有的话会在日志打印出来,以下是反汇编的结果,可以看到在哪些操作之前会执行以上检查。

实现分析—初始化及syzkaller主循环流程

下面主要分析syzkaller的具体实现流程,先从syzkaller运行最开始的地方开始,也就是syz-manager的初始化流程。

1. 预处理阶段

syzkaller运行的所有参数写在配置文件里,因此预处理阶段主要工作是检查syzkaller编译是否正确,并加载配置文件,之后就进入Manager初始化函数RunManager()

2. VM创建阶段

这里解释一下这个VM其实是一个抽象的测试环境,为了能够测试不同类型的平台,google把这个VM设定为一个能执行一系列操作的抽象接口,每种不同的平台对于这些操作各有不同的实现。为了达到更高效率的fuzz,势必要使用多个VM,而管理这些VM的结构就是vmPool。vmPool实现了两个功能Count()计数有多少个VM,Create()创建VM和Close()关闭vmPool。而每一个独立的VM实现了以下接口:

l Copy() 复制文件到测试环境,

l Forward() 创建测试环境报告状态的端口映射,

l Run() 在测试环境运行指令,并返回内核命令行输出(内核日志),

l Info() 测试环境的额外信息获取(可选),

l diagnose() 分析运行时触发的一些问题(android目前不支持此功能),

l Close() 关闭测试环境连接,

l MonitorExecution() 监控测试环境反馈的信息,判断是否遇到了问题(各平台通用)。

以android平台的实现为例,VM创建的核心流程为加载config中配置的选项,寻找系统中的adb路径,使用adb寻找目标测试设备。

3. Manager创建阶段

这一步主要创建一个Reporter,在识别linux系统之后配置用户设置里ignores和interests的结果,设置解析漏洞源码的路径。之后初始化Manager实例。

manager初始化之后加载了数据库和种子文件,初始化prometheus监视器,用来记录syz_exec_total,syz_corpus_cover,syz_crash_total,之后创建http服务器,创建可访问目录,开始listen对用户提供结果报告入口,最后记录FuzzerBin,ExecprogBin,ExecutorBin,SSHKey,KernelObj,Image这些文件的最后修改时间,防止fuzz开始后这些依赖文件被修改。

4. RPC服务器创建阶段

RPC服务器和之前的向用户服务的服务器不同,是用来收集测试环境反馈的信息的服务器,之后每隔10秒收集一次mgr.stats被RPCServer修改的信息并输出。

5. 辅助功能启动阶段

辅助功能包括定期输出的corpus的Bench功能以及处理ctrl-C的控制fuzz退出的功能,有趣的是ctrl-C第一次被设计为在当前主循环结束后退出,第三次为强制杀死进程,而第二次其实只打印了一行log,更多的可能是安慰效果。

6. 启动vmLoop

VmLoop即为syzkaller主循环,在进入之前,manager将VM分组,一部分做FuzzingVM,一部分做ReproVM,也就是被捕获问题的可复现性和自动精简触发步骤。之后的主循环就是在分配Repro任务和分配Instance任务的状态中无限切换,并以分配Repro为优先,并在每次instance分配阶段结束后(不论是没有足够的VM继续分配用来fuzz或是已经处于可以Repro状态),都会把发现的崩溃列表加入待复现列表中,之后就进入具体的一个崩溃的复现流程runRepro。

总结

syzkaller作为目前内核fuzz最流行的工具,其创新性的设计思路和直观的扩展接口都非常值得学习。在实现层面,google的工程能力也非常优秀,本文章主要详细分析了初始化和fuzz主循环的架构。

来源-微信公众号:OPPO安珀实验室

出处:https://mp.weixin.qq.com/s/E5uiBKX_7I85W6Rc8rceiQ

0 阅读:0

以云看科技

简介:感谢大家的关注