参考:
使用Syzkaller&QEMU捕捉内核堆溢出Demo
内核漏洞挖掘技术系列(4)——syzkaller(1)
实验所需源码
本文主要参考第一篇文章,但是文章中很多步骤不全面,而且存在错误,故记录本实验过程。本实验前提是参照配置syzkaller&QEMU环境配置好syzkaller环境。
本文将演示使用Syzkaller联合QEMU触发一个内核溢出的bug。 包括三个步骤:
(1)在syzkaller中添加规则触发堆溢出
(2)编译一个带有堆溢出模块的kernel
(3)运行syzkaller&QEMU捕捉内核堆溢出
1.在syzkaller中添加规则
本例新增调用模板见proc_operation.txt,/syzkaller/sys/linux目录下的sys.txt中有通用的调用形式可以参考。
解释: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中看到结果。最后,重新编译生成带有相应规则的syzkaller二进制可执行文件。
调用规则:$号前的syscallname是系统调用名,$号后的type是指特定类型的系统调用。如上文的 open$proc 指的就是open这个类调用中proc这个具体的调用,这个名字是由规则编写者确定的,具体行为靠的是后面的参数去确定。 参数的格式如下: ArgumentName ArgumentType[Limit] ArgumentName是指参数名,ArgumentType指的是参数类型,例如上述例子有string、flags等类型。[ ]号中的内容就是具体的类型的值,不指定的时候由syzkaller自动生成,若要指定须在后文指定,以上文为例:
mode flags[proc_open_mode]
proc_open_mode = ...
因为我们给的例子是通过/proc/test这个内核接口的写操作来触发堆溢出,因此我们需要控制的参数是open函数中的file参数为“/proc/test”即可,其他操作参考sys.txt即可。
步骤:
(1)编译生成syz-extract
~/Desktop/ctf/kernel_fuzz/gopath/src/github.com/google/syzkaller$ make bin/syz-extract
(2)编译生成syz-sysgen
~/Desktop/ctf/kernel_fuzz/gopath/src/github.com/google/syzkaller$ make bin/syz-sysgen
(3)用syz-extract生成.const文件(本例proc_operation.txt有错误,read和write不需要返回值)可选择只生成你新增的.txt
~/Desktop/ctf/kernel_fuzz/gopath/src/github.com/google/syzkaller$ bin/syz-extract -os linux -sourcedir "/home/john/Desktop/ctf/kernel_
fuzz/linux/linux" -arch amd64 sys/linux/proc_operation.txt
同目录下生成了proc_operation_amd64.const。
(4)然后运行syz-sysgen
~/Desktop/ctf/kernel_fuzz/gopath/src/github.com/google/syzkaller$ bin/syz-sysgen
(5)重编译syzkaller
进入syzkaller源码目录,运行:
$ make clean
$ make all
(6)拷贝编译好的syz-fuzzer到目标系统:
scp -P 10021 -i ./stretch.id_rsa -r ../gopath/bin [email protected]:/root/bin
2.编译带有堆溢出的内核模块
漏洞:代码见test.c,主要是proc_write()函数有堆溢出。
编译:参考https://www.cnblogs.com/zhangjy6/p/5462644.html
$ make
$ scp -P 10021 -i ./stretch.id_rsa -r ./test/test.ko [email protected]:/root/bin
目标机器上$ sudo insmod test.ko
问题:无法insmod
我本机linux-headers版本是4.4.0-103-generic,目标内核版本是5.2.0-rc1+(查看版本命令$ uname -r),编译出来的driver在目标机上无法ismod,需安装linux-headers-5.2.0-rc1+。
解决:安装linux-headers-5.2.0-rc1+方法,先在网页上找到对应版本的headers。
$ wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.2-rc1/linux-headers-5.2.0-050200rc1_5.2.0-050200rc1.201905191930_all.deb
$ wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.2-rc1/linux-headers-5.2.0-050200rc1-generic_5.2.0-050200rc1.201905191930_amd64.deb
$ sudo dpkg -i *.deb
修改KDIR =/usr/src/linux-headers-5.2.0-050200rc1-generic
结果:还是不匹配,很无奈,要么重新编译内核吧,我选择linux-4.4.0.184版本,大不了把本机内核也更新为这个版本。(参考https://www.itsmearunchandel.co.in/linux/ubuntu/upgrade-kernel-version-in-ubuntu.html; 内核源码https://kernel.ubuntu.com/~kernel-ppa/mainline/v4.4.184/)
问题:应该更新了也还是不行,linux-headers-5.2.0-050200rc1-generic跟linux-headers-5.2.0-rc1+还是不匹配。
解决:将驱动模块编译进内核。所以要么把模块编译到目标内核,要么魔改许多module里的数据结构进行错版本加载。
(参考https://blog.csdn.net/qq_33487044/article/details/81949703和https://kouriba.iteye.com/blog/1632767)
make menuconfig->Enable loadable module support->Forced module loading(同时关闭Module versioning support,只开启前3个)。
修改drvier目录下的MAKEFILE文件,在最后一行添加: obj-y += testmod/
然后在testmode目录下,添加一个MAKEFILE 文件,文件的内容为:obj-m := hello.o
步骤如下:
(1)test.c拷贝到/linux/drivers/char/目录下
(2)/linux/drivers/char/Kconfig下添加
menu "Character devices" #已有
config TEST_MODULE
tristate "Heap Overflow Test" #在menuconfig中显示的名字
default y #默认选项
help
This file is to test a buffer overflow.
endmenu #已有
(3)/linux/drivers/char/Makefile下添加
obj-$(CONFIG_TEST_MODULE) += test.o
(4)如果/linux/drivers/char/是新目录,需修改/linux/drivers/Kconfig(加上source "drivers/char/Kconfig");修改/linux/drivers/Makefile(加上obj-$(CONFIG_TEST_MODULE) += char/)。
(5)配置文件选择该模块$ make menuconfig -> Device Drivers -> Character devices -> Heap Overflow Test (*表示直接编如内核,M表示模块形式,)
(6)运行内核,查看模块是否加载
$ls /proc/test 或 $dmesg|grep test
3.运行syzkaller捕捉溢出
(1)修改my.cfg(只允许某些调用,速度更快):
"enable_syscalls": [
"open$proc",
"read$proc",
"write$proc",
"close$proc"
],
(2)运行虚机测试:
$ bin/syz-manager -config ./my.cfg -v 10
用浏览器打开“127.0.0.1:56741”,运行一段时间后出现以下日志:
```
PROC_DEV:into open!
==================================================================
BUG: KASAN: slab-out-of-bounds in copy_from_user arch/x86/include/asm/uaccess.h:698 [inline] at addr ffff88003c1f5e20
BUG: KASAN: slab-out-of-bounds in proc_write+0x64/0x90 drivers/mod_test/test.c:45 at addr ffff88003c1f5e20
Write of size 4096 by task syz-executor0/2569
CPU: 0 PID: 2569 Comm: syz-executor0 Not tainted 4.11.0-rc8+ #23
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014
Call Trace:
__dump_stack lib/dump_stack.c:16 [inline]
dump_stack+0x95/0xe8 lib/dump_stack.c:52
kasan_object_err+0x1c/0x70 mm/kasan/report.c:164
print_address_description mm/kasan/report.c:202 [inline]
kasan_report_error mm/kasan/report.c:291 [inline]
kasan_report+0x252/0x510 mm/kasan/report.c:347
check_memory_region_inline mm/kasan/kasan.c:326 [inline]
check_memory_region+0x13c/0x1a0 mm/kasan/kasan.c:333
kasan_check_write+0x14/0x20 mm/kasan/kasan.c:344
copy_from_user arch/x86/include/asm/uaccess.h:698 [inline]
proc_write+0x64/0x90 drivers/mod_test/test.c:45
proc_reg_write+0xf6/0x180 fs/proc/inode.c:230
__vfs_write+0x10b/0x560 fs/read_write.c:508
vfs_write+0x187/0x520 fs/read_write.c:558
SYSC_write fs/read_write.c:605 [inline]
SyS_write+0xd4/0x1a0 fs/read_write.c:597
entry_SYSCALL_64_fastpath+0x18/0xad
RIP: 0033:0x450a09
RSP: 002b:00007ff6efd15b68 EFLAGS: 00000216 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00000000006f8000 RCX: 0000000000450a09
RDX: 0000000000000090 RSI: 0000000020d09000 RDI: 0000000000000005
RBP: 0000000000000046 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000216 R12: 0000000000000000
R13: 00007ffc210fd8ff R14: 00007ff6efd16700 R15: 0000000000000000
Object at ffff88003c1f5e20, in cache kmalloc-512 size: 512
...
Dumping ftrace buffer:
(ftrace buffer empty)
Kernel Offset: disabled
```
这就是内核被crash时打印出来的调用信息,我们可以看到溢出发生在proc_write+0x64/0x90 drivers/mod_test/test.c:45处,也就是我们添加进去的带有堆溢出的模块。说明我们用Syzkaller添加规则捕捉到了堆溢出的代码。
结果我啥都没跑出来。
4.漏洞复现
workdir/crashes目录下包含crash之前执行的程序。/tools目录下几个工具值得关注: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