这是内核漏洞挖掘技术系列的第五篇。
第一篇:内核漏洞挖掘技术系列(1)——trinity
第二篇:内核漏洞挖掘技术系列(2)——bochspwn
第三篇:内核漏洞挖掘技术系列(3)——bochspwn-reloaded(1)
第四篇:内核漏洞挖掘技术系列(3)——bochspwn-reloaded(2)

前言

syzkaller(https://github.com/google/syzkaller)是google的安全研究人员开发并维护的内核fuzz工具。它主要是用go写的,也有少部分C代码,支持akaros/fuchsia/linux/android/freebsd/netbsd/openbsd/windows等系统,发现的漏洞多达上千。不过它支持最全面的还是linux系统,对其它系统的支持都不同程度差一点,也不支持Darwin/XNU。有研究人员做过移植syzkaller fuzz windows WSL[2]和Darwin/XNU[1][5]的尝试,也都取得了较好的成果。可以说,syzkaller是当今宇宙最强大的内核fuzz工具了。我们将从整体架构开始,介绍syzkaller一些实现的细节。目前在github上syzkaller除了master分支之外还有一个long-line分支和usb-fuzzer分支,usb-fuzzer分支是xairy用来挖掘USB驱动漏洞的一个分支,在OffensiveCon2019上讲过[7]。

本文分析的syzkaller版本是开始计划写作这个系列文章时从github下载的当时最新的版本,当读者读到这篇文章时代码肯定已经有了变化,所以文章仅供参考,若有变动之处还需自行分析。

整体架构

下面是官方原理介绍中给的一张图。

syz-manager通过ssh调用syz-fuzzer,syz-fuzzer和syz-manager之间通过RPC进行通信。syz-fuzzer将输入传给syz-executor,从kernel中读取代码覆盖率信息。syz-executor执行syscall系统调用。我们再看一下代码的整体目录。

  • Godeps目录:go的依赖包管理。
  • dashboard目录:主要与syzbot有关,syzbot会自动fuzz linux内核主线分支并向内核邮件列表报告发现的错误。我们可以在https://syzkaller.appspot.com上看到相关的情况。

  • docs目录:相关文档。

  • executor目录:文章最开始已介绍syz-executor。
  • pkg目录:配置文件。该目录下的结构如下图。

    • ast目录:解析并格式化sys文件。
    • bisect目录:通过二分查找,编译代码测试确定引入含有漏洞代码的commit和引入修复的commit。
    • build目录:包含用于构建内核的辅助函数。
    • compiler目录:从文本描述生成系统调用,类型和资源的sys描述。
    • config目录:加载配置文件。
    • cover目录:提供处理代码覆盖信息的类型。
    • csource目录:根据syzkaller程序生成等价的c程序。
    • db目录:存储syz-manager和syz-hub中的语料库。
    • email目录:解析处理邮件相关功能。
    • gce目录:对Google Compute Engine(GCE) API的包装。
    • gcs目录:对Google Compute Storage(GCS) API的包装。
    • hash目录:提供一些hash函数。
    • host目录:检测host是否支持一些特性和特定的系统调用。
    • html目录:提供一些web端显示fuzz结果的html页面用的辅助函数。
    • ifuzz目录:生成和变异x86机器码。
    • instance目录:提供用于测试补丁,镜像和二分查找的临时实例的辅助函数。
    • ipc目录:进程间通信。
    • kd目录:windows KD调试相关。
    • log目录:日志功能。
    • mgrconfig目录:管理解析配置文件。
    • osutil目录:os和文件操作工具。
    • report目录:处理内核输出和检测/提取crash信息并符号化等。
    • repro目录:对crash进行复现并进行相关的处理。
    • rpctype目录:包含通过系统各部分之间的net/rpc连接传递的消息类型。
    • runtest目录:syzkaller程序端到端测试的驱动程序。
    • serializer目录:序列化处理。
    • signal目录:提供用于处理反馈信号的类型。
    • symbolizer目录:处理符号相关信息。
    • vcs目录:处理各种库的辅助函数。
  • prog目录:目标系统相关信息以及需要执行的系统调用。

  • sys目录:系统调用描述。该目录下的结构如下图。

    syzkaller使用它自己的声明式语言来描述系统调用模板,docs目录下的syscall_descriptions.md中可以找到相关的说明。这些系统调用模板被翻译成syzkaller使用的代码需要经过两个步骤。第一步是使用syz-extract从linux源代码中提取符号常量的值,结果被存储在.const文件中,例如/sys/linux/tty.txt被转换为sys/linux/tty_amd64.const。第二步是根据系统调用模板和第一步中生成的const文件使用syz-sysgen生成syzkaller用的go代码。可以在/sys/linux/gen/amd64.go和/executor/syscalls.h中看到结果。如果需要对新的系统调用进行测试,需要将新的系统调用的描述添加到适当的文件中:各种sys/linux/.txt文件保存特定内核子系统的系统调用,例如bpf或socket。/sys/linux/sys.txt包含更多常规系统调用的描述。也可以为一个全新的子系统添加sys/linux/.txt文件。docs目录下的syscall_descriptions_syntax.md中可以找到语法描述,Project Zero也有相关博客[8]。回想一下我们第一篇文章介绍的trinity,也是需要自己编写系统调用模板。但是syzkaller的模板比trinity强大多了,系统调用和参数都可以自己定义。在sys/*.txt文件中描述了上千个系统调用,但是linux内核一直在持续的更新,每个新版本的发布系统调用和数据结构都在发生变化,需要人工更新系统调用模板,这个过程目前还没有实现自动化[9]。下面给大家简单介绍一下人工更新系统调用模板的步骤。
    当我们下载linux内核源码之后,比较一下最新的版本和上一个版本uapi/*.h文件发生了哪些变化。

    git diff -U0 v4.20 v4.19 include/uapi/*.h | grep "+++"


    Linus在linux 3.7版本中接受了David Howell的用来解决递归引用的补丁。递归引用这个问题通常发生在inline函数中:比如头文件A中的inline函数需要头文件中B的struct,但是同时B中也有一个inline函数需要A中的一个struct。所以David Howell把include和arch/xxxxxx/include目录中的内核头文件中有关用户空间API的内容分割出来,放到新的uapi/子目录中相应的地方。除了解决递归引用的问题之外,这样做简化了仅供内核使用的头文件的大小,并且使得跟踪内核向用户空间呈现的API的更改变得更容易。对C库维护者、测试项目(如LTP)、文档项目(如man-pages)还有我们利用syzkaller做内核fuzz的人来说有很大的帮助。
    在我们正式开始尝试之前,大家还可以看看[3]上的一个例子,找找感觉。这篇文章中编写了一个含有漏洞的内核模块加载到内核中,然后在cfg文件的enable_syscalls中指定了相应的系统调用,运行syzkaller之后能够看到相关的crash信息。下面假设我们想对bpf模块做fuzz。根据linux\include\uapi\linux\bpf.h中的改动修改bpf.txt。由于syzkaller默认make时没有编译syz-extract和syz-sysgen,首先编译出这两个文件。

    用syz-extract生成.const文件。

    因为这里我编译的linux内核是amd64的,所以只生成了bpf_amd64.const。对于其它的架构只需要修改__NR_bpf的系统调用号即可。然后运行syz-sysgen。

    自己编译内核分析过CVE-2017-16995的同学应该还记得编译内核时需要开启CONFIG_BPF_SYSCALL相关选项。在cfg文件中指定enable_syscalls为bpf之后我们就可以运行syz-manager做fuzz了。

  • sys-ci目录:持续运行syzkaller的系统。

  • sys-fuzzer目录:文章最开始已介绍syz-fuzzer。
  • sys-hub目录:将多个syz-manager连接在一起并允许它们交换程序。
  • sys-manager目录:文章最开始已介绍syz-manager。
  • tools目录:一些工具。该目录下的结构如下图。


这些工具大都是对pkg目录下代码的一些封装,check_links.py用来检查文档中的链接是否正确。还有几个create*.sh是用来生成镜像的,demo_setup.sh包含设置syzkaller+qemu环境所有的操作。对于我们来说最重要的几个工具是syz-execprog、syz-repro和syz-prog2c。为syzkaller发现的bug生成能够复现的程序的过程是自动化的,但是如果不能自动化生成这个程序则需要一些工具手动复现。workdir/crashes目录下包含crash之前执行的程序。如果在config文件中设置的procs大于1那么是并行执行的,在这种情况下引发crash的程序可能在更前面的地方。syz-execprog和syz-prog2c可以帮助我们找到引发crash的程序。syz-execprog以各种模式执行单个或一组程序,首先在循环中运行log中所有的程序来确认确实它们之一引发了crash。

./syz-execprog -executor=./syz-executor -repeat=0 -procs=16 -cover=0 crash-log

然后尝试识别是哪个程序导致的crash。

./syz-execprog -executor=./syz-executor -repeat=0 -procs=16 -cover=0 single-program

syz-execprog在本地执行程序,所以需要将syz-execprog和syz-executor复制到带有测试内核的VM中并在那里运行它。一旦确认了引发崩溃的单个程序,尝试通过注释掉单个系统调用并删除无关的数据来缩小范围。还可以尝试将所有mmap调用合并为一个。给syz-execprog加上-threaded=0 -collide=0标志确认这个程序仍然能够导致crash。
如果不能复现的话,尝试把每个系统调用移到单独的线程中[4]。然后通过syz-prog2c得到C程序,C程序应该也可以导致crash。这个过程在某种程度上也可以通过syz-repro自动化,需要提供config文件和crash报告。

./syz-repro -config my.cfg crash-qemu-1-1455745459265726910
  • vendor目录:依赖包。
  • vm目录:提供VM接口。该目录下的结构如下图。


我们可以看到该目录下提供了qemu/kvm/vmm等多种虚拟化方案,还包括google自家的轻量沙箱gvisor和云服务,甚至可以使用Odroid C2和远程的物理机做fuzz。vm.go把它们封装成统一的接口方便调用。这里再提一个在编译比较老的内核时启用KASAN之后经常会遇到的一个错误,可以参考[6]patch一下代码。

总结

这篇文章我们简单聊了聊syzkaller的整体架构和如何人工更新系统调用模板,在接下来的文章中会逐步介绍syzkaller一些实现的细节。

参考资料

1.PanicXNU 3.0
2.WSL Reloaded
3.Syzkaller crash DEMO
4.use-after-free in ip6_setup_cork
5.Drill the Apple Core:Up & Down
6.Kernel panic when kasan is applied
7.Coverage-Guided USB Fuzzing with Syzkaller
8.Exploiting the Linux kernel via packet sockets
9.sys/linux: automatic syscall interface extraction

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖