syzkaller 黑盒测试1:环境搭建
近期需要使用syzkaller对某Linux发行版系统内核进行测试,但是未提供内核源码,只能在黑盒条件下测试。这是笔者第一次接触syzkaller,对测试流程不太熟悉。另外,网上很少有syzkaller黑盒测试的内容,所以在此记录以下。可以预见,黑盒测试之后还会有很多难点,如适应性函数评估、崩溃信息搜集等,后续内容或许在后期能够记录。
ps: 中间因为一些事情拖更了,可能有部分细节遗忘,但主要问题应该都有描述。有疑问的读者朋友可以评论区讨论。
环境配置
环境准备
syzkaller 在安装前,需要一些依赖环境(包、go环境等)。国内的go环境需要换源,否则可能连接不稳定,而且速度较慢。
如果准备在docker环境中运行,可以直接使用官方提供的镜像,只需要另外安装go环境即可。
syzkaller install
环境配置好后,安装过程比较简单:
git clone https://github.com/google/syzkaller
cd syzkaller
make
编译后的文件在syzkaller/bin
中,可以将其添加到环境变量,方便以后直接使用。
虚拟机配置
-
准备好待测试的虚拟机镜像
-
使用
qemu-img
为虚拟机建立一个虚拟磁盘qemu-img create -f qcow2 disk_name size qemu-img create -f qcow2 ubuntu.qcow2 20G # example
-
启动虚拟机,安装镜像
安装脚本示例:(部分现代系统需要图形化安装界面才能启动,可以使用VNC远程连接)
#! zsh qemu-system-x86_64 \ -smp 6,cores=3,threads=2 \ -enable-kvm \ -m 4G \ -drive file=test.qcow2,format=qcow2,if=virtio \ -cdrom /data/downloads/test.iso \ -machine usb=on \ -device usb-tablet \ -vnc :0 \ -cpu host \ -daemonize
-
为虚拟机配置SSH端口映射
-netdev user,id=net0,restrict=on,hostfwd=tcp:127.0.0.1:10022-:22
-
虚拟机配置SSH相关信息(注册公钥等)注意:一定要注册一个公钥,并验证登录,syzkaller使用公钥连接SSH
syzkaller 试启动
在选定的测试目录下,创建my.cfg
syzkaller 配置文件
{
"target": "linux/amd64", # 目标架构
"http": "127.0.0.1:8080", # syzkaller 监控终端的IP和端口号
"workdir": "/data/exp/workdir", # 工作目录,用于存放corpus, crash等
# "kernel_obj": "/root/kernel_test/linux", # kernel object文件,黑盒测试没有
"image": "/data/exp/test.qcow2", # 虚拟机镜像文件路径
"sshkey": "/root/.ssh/id_rsa", # 注册的SSH key对应私钥
"syzkaller": "/root/syzkaller", # syzkaller home
"cover": false, # 不使用coverage导向,因为黑盒环境镜像编译时默认不适用kcov
"procs": 8, # 每个虚拟机中并行的进程数
"type": "qemu", # qemu mode
# "enable_syscalls": [ # 限制syscall范围,后面测试会用到
# "open$proc",
# "read$proc",
# "write$proc",
# "close$proc"
# ],
"vm": { # vm配置
"count": 4, # 虚拟机总数,一般4台,根据服务器资源配置
"image_device": "drive format=qcow2,if=virtio,file=", # 镜像文件附加参数,可以允许现代接口,如virtio等,这些参数会被syzkaller内部整合
"cpu": 2, # 虚拟机CPU数
"mem": 2048 # 内存大小(MB)
}
}
- 有关参数配置的详细内容,参见源码config
使用以下命令启动:
syz-manager -config=my.cfg
若启动失败,可使用-debug
选项导出启动虚拟机所用的参数,在单独环境中测试排查。
若启动成功,进入运行状态,也可从web端监控syzkaller状态
若成功启动,可以退出。注意:目前syzkaller无法捕获任何warning或panic,原因在后面会说到。
崩溃测试
syzkaller的原理是监听dmesg
内核消息,通过正则表达式分析dmesg
中新出现的内核信息,可以捕获warning, panic, KASAN等消息
本节,我们将手动注入一个内核驱动漏洞,验证syzkaller能否捕获这个漏洞。
驱动编写
编写一个字符设备驱动,使用WARNING
触发内核警告,以模拟漏洞。
#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 "panic_test"
#define DEBUG_FLAG "PROC_TEST_DEV: "
MODULE_AUTHOR("Zheng");
MODULE_LICENSE("Dual BSD/GPL");
int test_open (struct inode *, struct file *);
ssize_t test_read (struct file *, char __user *, size_t, loff_t *);
ssize_t test_write (struct file *, const char __user *, size_t, loff_t *);
static struct file_operations my_drv = {
.open = test_open,
.read = test_read,
.write = test_write,
};
int test_open (struct inode * test_inode, struct file * test_file) {
// Do nothing
return 0;
}
ssize_t test_read (struct file *test_file, char __user *buf, size_t size, loff_t *offset)
{
// panic
WARN(1, DEBUG_FLAG"device read invoked, panic\n");
return (ssize_t)size;
}
ssize_t test_write (struct file *test_file, const char __user *buf, size_t size, loff_t *offset)
{
// panic
WARN(1, DEBUG_FLAG"device write invoked, panic\n");
return (ssize_t)size;
}
int __init test_init(void)
{
struct proc_dir_entry *test_entry;
const struct file_operations *my_fops = &my_drv;
test_entry = proc_create(MY_DEV_NAME, S_IRUGO|S_IWUGO, NULL, my_fops);
if (!test_entry)
printk(DEBUG_FLAG "proc init failed\n");
printk(DEBUG_FLAG "panic test drive is up");
return 0;
}
module_init(test_init);
- 该驱动程序将在
/proc
内创建一个名为panic_test
的文件,对该文件进行读写操作,都会触发系统Warning,可以用echo
命令+重定向手动触发测试 - 以上程序基于kernel v4开发,ubuntu20.04及以后的内核相关接口有变化,具体驱动参考panic_proc
驱动编译、加载
使用Makefile编译
ifneq ($(KERNELRELEASE),)
obj-m := panic_test.o
else
KERN_DIR ?= /home/zheng/Temp/osFuzz/4.19.90-24.4.v2101.ky10.x86_64
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *.o.d *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order *.mod Module.symvers
关于将驱动加载进内核的操作,请参见我的另一篇博客Linux modules-load 启动时加载驱动模块
驱动测试
在一个终端中使用dmesg -w
命令监视内核消息,再另一个终端中执行:
echo "1" > /proc/panic_test
不出意外的话,可以看到dmesg监控终端捕获到了Warning,并显示了back trace(图中是Panic,大体类似)
syzkaller测试
增加规则
在syzkaller/sys/linux
下增加一条panic_test.txt
接口配置,
include <linux/fs.h>
open$proc(file ptr[in, string["/proc/panic_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
重新编译
首先使用syz-extract
工具将txt配置转换为.const
文件,该工具需要提前编译
cd syzkaller
make bin/syz-extract
syz-extract -os linux -sourcedir "header_dir" -arch amd64 panic_test.txt
接下来,将文件转为go
文件,供syzkaller直接使用,并重新编译syzkaller
syz-sysgen
make TARGETOS=linux TARGETARCH=amd64
运行syzkaller
将配置文件中enable_syscalls
的注释删除,限定只测试这些syscall,加快实验效率
如果正常工作,应当马上发现crash,并开始复现过程
但是,由于qemu mode的一些机制,syzkaller捕获不到dmesg消息
修改 syzkaller
经过分析,qemu mode的syzkaller需要内核源码文件手动编译,使用配套的create-image.sh
创建文件系统,并在其中进行测试,在启动时增加参数earlyprintk
,将dmesg
信息打印到serial中,syzkaller直接读取,获得信息。
但是,黑盒测试中,我们无法做到(因为没有kernel object文件),所以需要使用SSH监听dmesg
,这与isolated mode相同
在qemu.go
文件中的Run
函数开头,增加以下内容(部分是原有代码):
func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (
<-chan []byte, <-chan error, error) {
// Listening on remote dmesg (for blackbox fuzzing only)
argsDmesg := append(vmimpl.SSHArgs(inst.debug, inst.sshkey, inst.port), inst.sshuser+"@localhost")
dmesg, err := vmimpl.OpenRemoteConsole("ssh", argsDmesg...)
if err != nil {
return nil, nil, err
}
rpipe, wpipe, err := osutil.LongPipe()
if err != nil {
dmesg.Close()
return nil, nil, err
}
inst.merger.Add("ssh", rpipe)
inst.merger.Add("dmesg", dmesg)
...
}
以上代码将SSH连接监控dmesg
也作为消息源加入管道,使syzkaller能够捕获消息
修改完后,编译测试,能够捕获到测试驱动的warning信息。
问题和解决方案
RPC connection failed
After SSH connection is established,fail to create an RPC client, conncetion refused
Edit target SSH security policy, /etc/ssh/sshd_config
AllowTcpForwarding yes
then restart SSH Service
aller能够捕获消息**
修改完后,编译测试,能够捕获到测试驱动的warning信息。
问题和解决方案
RPC connection failed
After SSH connection is established,fail to create an RPC client, conncetion refused
Edit target SSH security policy, /etc/ssh/sshd_config
AllowTcpForwarding yes
then restart SSH Service