Syzkaller实验报告(Crash Demo)

实验配置

测试工具:Syzkaller

测试对象:linux6.7.0-rc4,编译进一个有堆溢出漏洞的驱动程序。

测试环境:

虚拟机:Kali 6.5.0

gcc 7.4.0

g++ 7.4.0

(注:gcc版本不要太高,当我使用gcc11的时候,编译时直接就检测出了堆溢出的问题。)

测试过程:

参考Syzkaller Crash Demo,复现了这个Demo 。

Debian-GNU-Linux-Profiles/docs/harbian_qa/fuzz_testing/syzkaller_crash_demo.md at master · hardenedlinux/Debian-GNU-Linux-Profiles (github.com)

实验环境搭建过程在我的另一篇博客中:Set up Syzkaller (记录安装过程) | Xjelly‘s Blog

一、关键步骤:

1.添加导致堆溢出的规则到syzkaller并重新构建

1.Syzkaller语法

syzkaller自己定义了一套描述系统调用模版的声明式语言(syzlang),称之为描述文件/声明文件。为了提高fuzz效率,我们必须为目标系统量身定制这种声明文件。通常一个设备节点对应一个声明文件。所谓的声明文件就是一个txt,根据syzkaller定义的语法,在这个txt文档中描述设备节点的接口信息以及参数格式。

The grammar of *.txt

1
2
3
4
open$proc(file ptr[in, string["/proc/test"]], flags flags[proc_open_flags], mode flags[proc_open_mode]) fd
...
proc_open_flags = O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, FASYNC, O_CLOEXEC, O_CREAT, O_DIRECT, O_DIRECTORY, O_EXCL, O_LARGEFILE, O_NOATIME, O_NOCTTY, O_NOFOLLOW, O_NONBLOCK, O_PATH, O_SYNC, O_TRUNC, \__O_TMPFILE
proc_open_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

系统调用的声明由系统调用名称、参数和返回值组成,系统调用名称的格式如下所示:

1
SyscallName$Type

前面的"Syscallname"是系统调用的名称,由内核提供的接口,前面的"Syscallname"是系统调用的名称,由内核提供的接口,后面的"Type"是系统调用的具体类型。在我的例子中:

1
open$proc

表示具有限定类型"proc"的系统调用"open()",名称由编写者确定,限制由后续参数确定,参数的格式如下:

1
ArgumentName ArgumentType[Limit]

ArgumentName是参数的名称,ArgumentType是参数的类型。在我的例子中,有几种参数类型,比如string、flags等。"[Limit]"将限制参数的值,如果没有指定,syzkaller将生成一个随机值。

1
2
mode flags[proc_open_mode]
proc_open_mode = ...

在我们的例子中,参数"mode"的类型是"flags",它将从“proc_open_mode = …”中选择一些值。
声明的最后是返回值。在我的例子中,"fd"是文件描述符。
一些常见的系统调用声明写在源代码树$(SYZKALLER_SOURCE)/sys/sys.txt中。

  • More infomation about programmer can be found on this

在我的例子中,堆溢出可以通过写入/proc/test来触发。因此,我们应该将"open"的参数"file"限制为"/proc/test",其他可以参考sys.txt文件。

2.添加堆溢出规则

syzkaller源码中,找到sys/linux/目录,新建一个文件,命名为proc_operation.txt,内容如下:

1
2
3
4
5
6
7
8
9
include <linux/fs.h>

open$proc(file ptr[in, string["/proc/test"]], flags flags[proc_open_flags], mode flags[proc_open_mode]) fd
read$proc(fd fd, buf buffer[out], count len[buf])
write$proc(fd fd, buf buffer[in], count len[buf])
close$proc(fd fd)

proc_open_flags = O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, FASYNC, O_CLOEXEC, O_CREAT, O_DIRECT, O_DIRECTORY, O_EXCL, O_LARGEFILE, O_NOATIME, O_NOCTTY, O_NOFOLLOW, O_NONBLOCK, O_PATH, O_SYNC, O_TRUNC, __O_TMPFILE
proc_open_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

3.在syzkaller目录下编译 syz-extract 和 syz-sysgen

如果syzkaller/bin目录下,没有syz-extract和syz-sysgen这两个文件的话,需要执行如下命令编译:

1
2
make bin/syz-extract
make bin/syz-sysgen

接下来我们使用 syz-extract 生成 .const 文件:指定txt文件名,可单独生成该文件对应的const文件。(注意自行更改源码的路径。)

1
bin/syz-extract -os linux -sourcedir "root/source/linux" -arch amd64 proc_operation.txt

运行syz-sysgen

1
bin/syz-sysgen

重新编译syzkaller

1
2
make clean
make all

修改配置文件:

启动syzkaller的配置文件如下。为了更快看到crash结果,增加了“enable_syscalls”项,只允许某些系统调用,能更快地触发漏洞。

增加:

1
2
3
4
5
6
"enable_syscalls": [
"open$proc",
"read$proc",
"write$proc",
"close$proc"
],

完整的配置文件如下:(注意自行修改文件路径。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"target": "linux/amd64",
"http": "127.0.0.1:56741",
"workdir": "/root/syzkaller/workdir",
"kernel_obj": "/root/linux",
"image": "/root/linux/IMAGE/bullseye.img",
"sshkey": "/root/linux/IMAGE/bullseye.id_rsa",
"syzkaller": "/root/syzkaller",
"procs": 8,
"type": "qemu",
"enable_syscalls": [
"open$proc",
"read$proc",
"write$proc",
"close$proc"
],
"vm": {
"count": 4,
"kernel": "/root/linux/arch/x86/boot/bzImage",
"cpu": 2,
"mem": 2048
}

}

2.将堆溢出代码编译到内核

使用demo中的test.c

kernel_src/drivers/char目录下,新建一个test.c。这是一个有漏洞的内核模块,漏洞代码片段如下:

1
2
3
4
5
6
7
static ssize_t proc_write (struct file *proc_file, const char __user *proc_user, size_t n, loff_t *loff)
{
char *c = kmalloc(512, GFP_KERNEL);
copy_from_user(c, proc_user, 4096);
printk(":into write!\n");
return 0;
}

打开char/目录下的Kconfig文件,添加:

1
2
3
4
5
config PROC_OP
bool "/proc/test virtual device support"
default y
help
This is a Syzkaller test case device driver.

image-20231212150009164

打开char/目录下的Makefile文件,添加:

1
obj-$(CONFIG_PROC_OP) += test.o

image-20231212150113541

若/linux/drivers/char/是新目录,还需修改/linux/drivers/Kconfig(加上source “drivers/char/Kconfig”);修改/linux/drivers/Makefile(加上obj-$(CONFIG_PROC_OP) += char/)。

回到linux目录,修改.config,增加:CONFIG_PROC_OP=y

image-20231212150435433

重新编译内核:

1
2
make clean
make -j8

用新的内核启动虚拟机,查看模块是否加载成功

1
2
3
4
# 查看模块对应设备节点是否存在
ls /proc/test1
# 查看模块加载时的log信息
dmesg | grep "proc init"

image-20231212103819275

3.运行syzkaller来寻找该漏洞

1
./bin/syz-manager -config=my.cfg

二、测试情况

1.整体情况

image-20231212135834139

image-20231212135853700

image-20231212135923710

2.漏洞种类及其数量

Bad rss-rounter state:7

NULL pointer:80

generaral protection fault:18

waring:若干

3.漏洞种类及其成因

Bad rss-router state

属于网络漏洞的一种。它是由于操作系统或网络设备中的错误实现导致的。

RSS(Receive Side Scaling)是一种网络流量分发技术,可以将传入的网络流量分散到多个CPU核心来进行处理,提高网络性能。而Bad rss-router state漏洞是指在实现RSS的过程中,出现了错误的路由状态。这可能会导致网络流量被错误地分发到不正确的CPU核心,造成网络性能下降、数据包丢失或网络拥塞等问题。

这种漏洞的成因可能包括操作系统或网络设备中的软件错误、缓冲区溢出、内存错误或配置错误等。在本次测试中,极有可能是因为堆溢出而引发的。

NULL pointer deference

内核代码中出现了对空指针的解引用操作,导致系统无法正确处理而引发的错误。

在计算机编程中,空指针是指没有指向有效内存地址的指针。当内核代码中的某个地方尝试解引用空指针时,就会触发"unable to handle kernel NULL pointer dereference"错误。

general protection fault

一般保护错误时,表示内核尝试访问未分配给它的内存区域,或者访问了无效的内存地址。

以上三种错误都很有可能是堆溢出引发的。

4.关于reproducing

这次实验由于时间原因,没有分析漏洞复现。如果以后有机会就补上吧。

三、实验中的难点

1.配置时踩到的坑

踩坑详情可见我的博客:Set up Syzkaller (记录安装过程) | Xjelly‘s Blog

1.编译内核时缺程序:bc (Basic Calculate)

解决办法:安装bc。

2.创建镜像时wget时拒绝连接

解决办法:尝试修改各种网络设置,无效。由于文件只有一个且不是很大,于是手动复制。

3.运行create-image.sh时无法下载所需的包

解决办法:更换为国内镜像源并更新。

4.启动QEMU时在虚拟机中嵌套使用虚拟机

解决办法:在虚拟机设置中开启虚拟化,并在主机中关闭Hyper-v。Hyper-v和虚拟化不能同时使用。

5.启动QEMU时报错:Failed to mount /sys/kernel/config

解决办法:重新编译内核。有可能是配置的时候什么地方手误写错了。

6.测试syzkaller时无法启动网络接口
Failed to start Raise network interfaces

解决办法:修改网卡设置。修改/etc/network/interfaces中的eth0改为enp0s4。因为syzkalelr的启动命令和QEMU不一样,所以在测试QEMU时没有发现这个问题。

7.虚拟机磁盘空间不足

解决办法:扩容。

2.测试时遇到的困难

1.将带有漏洞的驱动程序编译入内核时报错:

从编译结果的末尾查找出错信息,一无所获,原来是编译的报错在编译信息的中间,在十几分钟的编译过程中被我忽视了。

image-20231211231603079

查找了各个Makefile,却一无所获:

image-20231211224217172

image-20231211224243458

image-20231211231542949

真正的报错:

image-20231212004409263image-20231212004506417

第一个报错是关于test.c中的函数proc_create的参数类型不匹配的报错。

在linux6.7.0版本下:proc_create的最后一个参数要求是 const struct proc_ops * 而不是旧的 const struct file_operations *。

所以修改了demo test.c中 结构体a的数据类型:

image-20231212144243463

第二个报错是关于堆溢出的报错,因为检测到了用户复制的数据会超出缓存区大小。

这个就有点尴尬,因为我的本意就是要编写一个堆溢出漏洞作为测试demo。

首先我怀疑了内核版本的问题,于是我切换到linux5.14,与参考文章[2]一样的测试版本,结果发现居然连这个版本的UAF漏洞都检查出来了。于是我才意识到应该是编译器的问题。

image-20231212013934764

在上述的试验中,我使用的是gcc 11和g++ 11,高版本的编译器可能对程序的安全性做了更多的检查。

所以我换用了低版本的编译器。

1
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 --slave /usr/bin/g++ g++ /usr/bin/g++-7

image-20231212015919301

这下终于不再报错了:

image-20231212022506820

终于成功地编译了内核。

image-20231212100925194

四、参考文献

【1】Debian-GNU-Linux-Profiles/docs/harbian_qa/fuzz_testing/syzkaller_crash_demo.md at master · hardenedlinux/Debian-GNU-Linux-Profiles (github.com)

【2】syzkaller fuzz 工具的使用方法及实践实例 | blingbling’s blog (blingblingxuanxuan.github.io)

【3】[原创]从0到1开始使用syzkaller进行Linux内核漏洞挖掘-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com

【4】[Syzkaller安装 Fuzz Qemu amd64 Kernel | BruceFan’s Blog (pwn4.fun)](http://pwn4.fun/2019/05/31/Syzkaller安装 Fuzz Qemu amd64 Kernel/)

【5】Using syzkaller, part 2: Detecting programming bugs in the Linux kernel (collabora.com)

【6】零基础syzkaller挖掘Linux内核漏洞_debug earlyprintk=serial slub_debug=quz-CSDN博客

五、其他

把syzkaller开着跑了一下午,看看结果~

image-20231212185720865

image-20231212185741272

image-20231212185810128

image-20231212185829559

image-20231212185845239