Syzkaller内核模糊测试技术详解与实战
一、简介
Syzkaller是Google安全研究人员开发并维护的开源内核Fuzz工具,主要由dvyukov维护。该工具使用Go语言编写,包含少量C代码,具有部署快速、使用简便的特点,支持多种操作系统(Linux、Android、Windows、openbsd、darwin等),其中对Linux系统的支持最为全面。
内核通过系统调用进行交互,因此Syzkaller的Fuzzing通过不断构造系统调用序列来驱动内核执行。这些系统调用组合会触发内核中的各种逻辑路径,进而暴露潜在的安全漏洞。为了生成更高效的测试用例,Syzkaller通过内核插桩机制获取覆盖率反馈来引导Fuzzing过程。
二、基本架构
2.1 组件构成
Syzkaller主要分为两个核心组件:
syz-manager(主控模块):
- 运行在宿主机上,每个虚拟机对应一个syz-manager启动的管理进程
- 内置fuzzing核心逻辑(包括系统调用生成、变异、语料更新等)
- 收集并记录覆盖率和崩溃信息
- 提供Web UI展示统计信息
syz-executor(执行模块):
- 运行于每个虚拟机内部
- 接收syz-manager下发的系统调用程序(syscall sequence)
- 启动临时子进程执行单一测试输入(一系列系统调用)
- 收集KCOV覆盖率信息,反馈执行结果
- 报告崩溃、挂起或异常状态
- 以C++静态编译方式实现,进程间通过共享内存通信,保证简洁高效
2.2 工作流程
- syz-manager生成系统调用序列,封装成二进制文件
- 通过SSH将测试程序传输到目标虚拟机
- syz-executor接收程序,执行系统调用组合以触发内核不同代码路径
- 利用内核的KCOV插桩收集覆盖率信息,作为反馈引导Fuzz
- syz-manager监控虚拟机运行状态,遇内核panic自动重启虚拟机(借助虚拟机快照和watchdog机制)
- 崩溃和覆盖率数据被收集并持久化,便于后续分析和复现
三、环境配置
3.1 硬件要求
CPU虚拟化支持:
egrep -wo 'vmx|svm' /proc/cpuinfo | sort | uniq
- 输出
vmx表示Intel CPU支持虚拟化 - 输出
svm表示AMD CPU支持虚拟化 - 无输出需在BIOS中开启虚拟化功能(Intel VT-x或AMD-V)
内核KVM支持:
sudo modprobe kvm
sudo modprobe kvm_intel # Intel CPU
# 或
sudo modprobe kvm_amd # AMD CPU
3.2 编译Syzkaller
安装Go编译器:
# 下载Go 1.23.7
wget https://go.dev/dl/go1.23.7.linux-amd64.tar.gz
# 解压到/usr/local
sudo tar -C /usr/local -xzf go1.23.7.linux-amd64.tar.gz
# 设置环境变量
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$GOROOT/bin:$GOPATH/bin:$PATH
sudo apt install gcc
# 配置Go国内代理(可选)
export GOPROXY=https://goproxy.cn,direct
安装依赖:
sudo apt update
sudo apt install build-essential clang llvm libncurses5-dev libssl-dev libelf-dev bc
sudo apt install make flex bison libncurses-dev libelf-dev libssl-dev
编译Syzkaller:
git clone https://github.com/google/syzkaller
cd syzkaller
make all
# 交叉编译(如arm架构)
make CC=aarch64-linux-gnu-g++ TARGETARCH=arm64
编译完成后,执行文件位于syzkaller/bin/目录。
3.3 编译Linux内核
下载并配置内核(以6.12.38为例):
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.38.tar.xz
tar -xvf linux-6.12.38.tar.xz && cd linux-6.12.38
make defconfig
make kvm_guest.config
启用必要配置选项:
scripts/config --enable CONFIG_KCOV # 启用KCOV
scripts/config --enable CONFIG_KASAN # 启用KASAN
scripts/config --enable CONFIG_KASAN_INLINE # 允许内联检测代码
scripts/config --enable CONFIG_DEBUG_INFO
scripts/config --disable CONFIG_DEBUG_INFO_NONE
scripts/config --enable CONFIG_DEBUG_INFO_DWARF4 # 开启内核调试信息生成
# 网络相关配置
scripts/config --enable CONFIG_NET_SCHED
scripts/config --enable CONFIG_NET_CLS_ACT
scripts/config --enable CONFIG_NET_SCH_FQ
scripts/config --enable CONFIG_NET_SCH_NETEM
scripts/config --enable CONFIG_NET_SCH_INGRESS
scripts/config --enable CONFIG_NET_SCH_HTB
scripts/config --enable CONFIG_NET_SCH_PRIO
scripts/config --enable CONFIG_CONFIGFS_FS # 启用configfs文件系统支持
scripts/config --enable CONFIG_SECURITYFS # 启用securityfs文件系统
scripts/config --enable CONFIG_KCOV_JIT
scripts/config --enable CONFIG_DEBUG_KERNEL
scripts/config --enable CONFIG_LOCKDEP
重新生成配置并编译:
make olddefconfig
make -j$(nproc)
编译后生成的关键文件:
bzImage:经过压缩的可启动内核镜像vmlinux:带有调试符号的未压缩内核镜像
3.4 Qemu配置
安装Qemu:
sudo apt install qemu-system-x86
创建虚拟机镜像:
sudo apt install debootstrap
cd /linux
# 下载syzkaller的create-image.sh脚本
wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
chmod +x create-image.sh
# 运行创建镜像脚本(Linux项目不能存在于挂载目录上)
./create-image.sh
脚本运行后生成的文件:
bullseye.img:虚拟机硬盘镜像文件bullseye.id_rsa:SSH私钥bullseye.id_rsa.pub:SSH公钥
启动虚拟机测试:
qemu-system-x86_64 \
-m 2G \
-smp 2 \
-kernel /linux/arch/x86/boot/bzImage \ # 指定bzImage文件
-append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0" \
-drive file=/linux/bullseye.img,format=raw \ # 指定文件系统
-net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
-net nic,model=e1000 \
-enable-kvm \
-nographic \
-pidfile vm.pid \
2>&1 | tee vm.log
测试SSH连接:
ssh -i /linux/bullseye.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost
四、基本用法
4.1 配置文件
创建Syzkaller配置文件test.cfg:
{
"target": "linux/amd64", // 目标平台架构
"http": "127.0.0.1:56741", // WebUI接口
"workdir": "/linux/workdir/", // 工作目录
"kernel_obj": "/linux/", // 内核路径
"image": "/linux/bullseye.img", // QEMU虚拟机镜像
"sshkey": "/linux/bullseye.id_rsa", // 连接虚拟机用的私钥
"syzkaller": "/syzkaller/", // syzkaller源码路径
"procs": 8, // 每个VM中并发fuzz实例数量
"type": "qemu", // 虚拟机类型
"vm": {
"count": 4, // 启动的VM数量
"kernel": "/linux/arch/x86/boot/bzImage", // bzImage路径
"cmdline": "net.ifnames=0", // 内核参数(简化网络设备名)
"cpu": 2, // 每个VM的CPU数量
"mem": 2048 // 每个VM的内存(单位:MB)
}
}
4.2 启动运行
基本启动:
syz-manager -config test.cfg
调试模式:
syz-manager -config test.cfg --debug
启动后可通过http://localhost:56741访问WebUI界面。
4.3 KCOV覆盖率分析
Syzkaller使用KCOV接口收集代码覆盖信息,可通过以下地址查看覆盖率:
http://127.0.0.1:$(PORT)/cover
覆盖率颜色标注说明:
- 已覆盖:黑色
- 部分覆盖:橙色
- 函数未执行:暗红色
- 未覆盖:红色
- 未插桩:灰色
生成覆盖率报告:
# 构建syz-cover工具
make cover
# 获取原始覆盖率数据
wget http://localhost:<syz-manager端口>/rawcover
# 生成覆盖率报告
bin/syz-cover --config <syzkaller配置文件路径> rawcover
# 导出函数覆盖率CSV文件
bin/syz-cover --config <syzkaller配置文件路径> --csv <输出文件名> rawcover
# 导出源码行覆盖率JSON文件
bin/syz-cover --config <syzkaller配置文件路径> --json <输出文件名> rawcover
五、Syzlang系统调用描述语言
Syzkaller定义了一套声明式语言syzlang,用于描述系统调用的接口和参数格式。
5.1 基本结构
syzlang文件包含以下元素:
| 元素 | 示例 | 说明 |
|------|------|------|
| include | include <linux/fs.h> | 引用头文件(仅标注) |
| syscall | open(file filename, flags flags[open_flags], mode flags[open_mode]) fd | 定义系统调用 |
| flags | open_flags = O_RDONLY, O_RDWR, O_CREAT | 枚举值集合 |
| bitmask | prot_flags = PROT_READ, PROT_WRITE | 按位或组合 |
| struct | sock_fprog { len len[filter, int16]; filter ptr[in, array[sock_filter]] } | 定义结构体 |
| union | union u [ type1, type2 ] | 定义联合体 |
| 别名 | type buffer ptr[in, array[int8]] | 类型模板/别名 |
| 注释 | # comment | 单行注释 |
示例:
open(file filename, flags flags[open_flags], mode flags[open_mode]) fd
read(fd fd, buf buffer[out], count len[buf])
close(fd fd)
open_flags = O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, O_CREAT
open_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP
5.2 系统调用描述格式
基本格式:
syscallname(argname1 type1, argname2 type2, ...) return_type
常用类型:
intN/intptr:普通整数类型(带有可选范围、对齐)flags[flagname]:枚举或位标志ptr[in, type]:指针(方向可选in/out/inout)array[type, size]:数组(长度可选固定/范围)string[filename]:字符串(如生成文件路径)len[fieldname]/bytesize[fieldname]:指定字段长度
类型模板与别名:
type buffer[DIR] ptr[DIR, array[int8]]
type optional[T] [
val T
void void
] [varlen]
5.3 调用属性
可选关键字修饰系统调用行为:
no_generate:不自动生成调用(用于seed触发)no_minimize:崩溃最小化时跳过该调用ignore_return:不使用返回值作为反馈路径timeout[N],prog_timeout[N]:增加fuzz超时时间disabled:禁用该系统调用(调试时有用)
5.4 类型系统
基本类型:
int8, int16, int32, int64
const[123, int32] # 常量
bool8, bool32 # 布尔类型
指针类型:
ptr[in, struct_name] # 输入指针
ptr[out, buffer] # 输出指针
数组:
array[int32, 4] # 固定长度数组
array[int8, VAR] # 动态数组
联合体(Union):
union my_union [
type1
type2
type3
]
结构体(Struct):
my_struct {
field1 int32
field2 ptr[in, buffer]
...
}
5.5 常量和标志
常量定义:
define SOME_CONST 0x1000
标志定义:
flags open_flags = O_RDONLY, O_WRONLY, O_CREAT, O_APPEND
位掩码:
bitmask prot_flags = PROT_READ, PROT_WRITE, PROT_EXEC
5.6 资源类型
用于管理系统资源(如文件描述符、socket等):
resource fd[int32]: 0xffffffffffffffff, AT_FDCWD
open(...) fd
read(fd fd, ...)
close(fd fd)
5.7 高级特性
对齐约束:
my_struct {
field1 int32
field2 ptr[in, buffer]
} [align[8]]
修饰符:
[packed]:紧凑布局,取消默认padding[varlen]:用于描述变长结构体或联合体的尾部
5.8 编写技巧
- 命名规范:遵循内核已有命名,不要创造新名字
- 资源顺序:in/out/inout决定调用顺序
- 特殊值:不要随意添加-1、INT_MAX等未声明值
- 标志使用:flag类型可用于枚举、位标志、混合模式
- 声明顺序:不要求先声明后使用,推荐按重要性排序
5.9 使用流程
- 调研接口,了解相关系统调用
- 编写描述文件并放入
syzkaller/sys/linux目录 - 使用syz-extract和syz-gen生成Go描述代码
六、Headerparser工具
Headerparser是自动化生成syzlang描述的辅助工具,可减轻手动编写工作量。
环境配置:
pip3 install pycparser
基本用法:
python3 headerparser.py --filenames=./test_headers/th_b.h
输出结果示例:
B {
B1 len|fileoff|flags|intN #(unsigned long)
B2 len|fileoff|flags|intN #(unsigned long)
}
struct_containing_union {
something len|fileoff|flags|int32 #(int)
a_union.a_char ptr[in|out, string]|ptr[in, filename] #(char*)
a_union.B_ptr ptr|buffer|array #(struct B*)
}
可直接复制Structure Metadata部分内容到syzkaller设备描述文件中。
七、实战案例
7.1 漏洞驱动示例
驱动代码(存在堆溢出漏洞):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define MY_DEV_NAME "test"
#define DEBUG_FLAG "PROC_DEV"
static ssize_t proc_read(struct file *proc_file, char __user *proc_user, size_t n, loff_t *loff);
static ssize_t proc_write(struct file *proc_file, const char __user *proc_user, size_t n, loff_t *loff);
static int proc_open(struct inode *proc_inode, struct file *proc_file);
static const struct proc_ops proc_fops = {
.proc_open = proc_open,
.proc_read = proc_read,
.proc_write = proc_write,
};
static int __init mod_init(void) {
struct proc_dir_entry *test_entry;
printk(DEBUG_FLAG ": proc init start!\n");
test_entry = proc_create(MY_DEV_NAME, 0666, NULL, &proc_fops);
if (!test_entry)
printk(DEBUG_FLAG ": failed to create /proc entry!\n");
else
printk(DEBUG_FLAG ": proc init over!\n");
return 0;
}
在proc_write函数中,kmalloc分配了n+512字节缓冲区,但copy_from_user复制了n+4096字节,导致堆溢出。
7.2 编译进内核
# 将漏洞驱动文件复制到内核目录
mv test.c linux/drivers/char/
# 修改Makefile
echo "obj-y += test.o" >> linux/drivers/char/Makefile
# 编译内核
make -j$(nproc)
7.3 编写Syzlang描述
创建syzkaller/sys/linux/vuln.txt:
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
生成常量文件:
bin/syz-extract -os linux -sourcedir /root/linux-6.12.38 -arch amd64 vuln.txt
重新构建Syzkaller:
# 将syscall描述文件转换为go代码结构
bin/syz-sysgen
# 重建syzkaller
make all
7.4 配置和运行Fuzzing
修改test.cfg,添加系统调用限制:
{
// ... 其他配置不变
"enable_syscalls": [
"open$proc",
"read$proc",
"write$proc",
"close$proc"
]
}
开始Fuzzing:
syz-manager -config test.cfg -vv 10
7.5 崩溃复现工具
syz-crush(重复执行崩溃程序):
# 构建工具
make crush
# 运行崩溃复现
mkdir workdir
bin/syz-crush -config ./test.cfg ./crashes/736f88e1c13700f40338df50cd9a6364a46963cf/repro.cprog
# 可选参数
bin/syz-crush -config ./test.cfg -debug -infinite=true -strace ./crashes/.../repro.cprog
syz-repro(生成可复现的测试用例):
bin/syz-repro -config=test.cfg ./crashes/736f88e1c13700f40338df50cd9a6364a46963cf/repro.prog
# 生成C语言复现程序
bin/syz-repro -config=test.cfg -crepro=repro.c ./crashes/.../repro.prog
八、崩溃分析
8.1 崩溃报告结构
Syzkaller为每种唯一的内核崩溃保存专属目录:
syzkaller_workdir/crashes/
├── 6e512290efa36515a7a27e53623304d20d1c3e/
│ ├── description # 崩溃简短描述
│ ├── log0 # 原始内核日志及测试输入(第一次崩溃)
│ ├── report0 # 后处理符号化报告(第一次崩溃)
│ ├── log1 # 原始日志(重复崩溃)
│ ├── report1 # 后处理报告(重复崩溃)
│ ├── report.prog # 复现crash的程序
│ └── report.cprog # repro.prog的C可执行复现程序
8.2 特殊崩溃类型
| 崩溃类型 | 说明 |
|---------|------|
| no output from test machine | 测试机无任何输出,通常指内核死锁、严重挂起或崩溃 |
| lost connection to test machine | SSH连接中断,可能内核panic或网络故障导致 |
| test machine is not executing programs | 测试机看似在线,但长时间未执行测试程序,可能挂起或陷入死循环 |
九、总结
Syzkaller不仅是一个内核模糊测试工具,更是一种系统化、闭环化的内核安全研究框架。它通过精心设计的syzlang描述系统调用接口,结合覆盖率驱动(KCOV)和内存检测(KASAN/KCOV/UBSAN)形成反馈闭环,使模糊测试从随机尝试升级为"智能探索"。
关键优势:
- 自动化程度高:从测试用例生成到崩溃复现全流程自动化
- 反馈驱动:基于覆盖率指导测试方向,提高效率
- 系统化设计:完整的工具链和生态系统支持
- 多平台支持:支持多种操作系统和架构
这种方法论强调自动化、反馈驱动和系统化设计,让Fuzzing不再是盲目的尝试,而是有策略、有方向的漏洞探索,对内核安全研究具有深远的启发意义。