syzkaller 环境搭建

一、简介

Syzkaller 是一个无监督的覆盖引导的内核 fuzzer,是目前类似里程碑一样的 fuzz 。

项目地址 - syzkaller

本人对 syzkaller 的工作机制比较感兴趣,同时课题组也会经常用到 syzkaller,因此了解 syzkaller 是一个必不可少的过程。

在研究内核fuzz的工作机制之前,我们需要先学会如何搭建它的环境。

二、环境搭建

  • syzkaller 使用 Go 语言编写,因此需要获取 go 语言的 tool chain。不过我倒没怎么在这上面踩过坑,直接

    1
    2
    sudo apt-get install golang # 此时的 golang 是 1.15 版
    make all

    就完成了所有 syzkaller 程序的编译。

  • qemu 安装不多说

    1
    sudo apt install qemu-system-x86
  • 接下来是获取内核源码,要拉n久:

    1
    git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux.git

    本人使用的 git commit id: a90af8f15bdc9449ee2d24e1d73fa3f7e8633f81

    参考一下 syzkaller 官方文档,先 make defconfig 生成默认的内核编译配置,之后手动在 .config 文件中添加以下选项:

    添加这些选项的目的是为了更好的被测试。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Coverage collection.
    CONFIG_KCOV=y

    # Debug info for symbolization.
    CONFIG_DEBUG_INFO=y

    # Memory bug detector
    CONFIG_KASAN=y
    CONFIG_KASAN_INLINE=y

    # Required for Debian Stretch
    CONFIG_CONFIGFS_FS=y
    CONFIG_SECURITYFS=y

    之后重新 make olddefconfig 更新编译参数,并执行以下命令以编译完整内核(包括驱动):

    1
    make -j `nproc`
  • 配置Imgage 镜像

    首先安装 debootstrap,它是 linux 下用来构建一套基本根文件系统的工具。

    1
    sudo apt-get install debootstrap

    之后在 linux 项目目录下键入以下命令,以创建 Debian Stretch Linux image

    1
    2
    3
    4
    5
    mkdir image
    cd image/
    wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
    chmod +x create-image.sh
    ./create-image.sh

    创建好后,同级目录下会多出几个文件:

    image-20211121114611675

  • 上述操作全部完成后,执行以下命令来尝试启动

    注意自行替换 kernel 和drive file 的路径。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    qemu-system-x86_64 \
    -m 2G \
    -smp 2 \
    -kernel /usr/class/linux/arch/x86/boot/bzImage \
    -append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0" \
    -drive file=/usr/class/linux/image/stretch.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

    如果成功的话,就会出现 syzkaller login。用户名键入root,无需输入密码,即可进入终端:

    image-20211121115312828

    之后我们还可以再另外一个终端里键入以下命令以进入 ssh

    之所以还要测试 ssh 能否成功工作,是因为 syzkaller 会用到 ssh。

    1
    ssh -i $IMAGE/stretch.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost

    image-20211121115439899

    确认无误后,直接执行 poweroff 关闭 kernel。

  • 尝试执行 syzkaller-manager

    先新建一个my.cfg,这个文件将是 syzkaller 的配置文件,内容如下:

    务必自行替换里面的各种路径。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "target": "linux/amd64",
    "http": "127.0.0.1:56741",
    "workdir": "/usr/class/syzkaller/workdir",
    "kernel_obj": "/usr/class/linux",
    "image": "/usr/class/linux/image/stretch.img",
    "sshkey": "/usr/class/linux/image/stretch.id_rsa",
    "syzkaller": "/usr/class/syzkaller",
    "procs": 8,
    "type": "qemu",
    "vm": {
    "count": 4,
    "kernel": "/usr/class/linux/arch/x86/boot/bzImage",
    "cpu": 2,
    "mem": 2048
    }
    }

    之后开跑看看:

    1
    2
    3
    cd syzkaller
    mkdir workdir
    ./bin/syz-manager -config=my.cfg

    貌似跑的相当顺利:

    image-20211121120608962

    syzkaller Web端 也工作正常:

    image-20211121120652065

    很好,环境这边没咋踩坑(笑)

三、crash 测试

俗话说,是骡子是马拉出来溜溜,我们将一个带有漏洞的驱动编入 kernel,之后尝试让 syzkaller fuzz出来。

这里主要参考链接 github 中的 syzkaller crash demo 演示。

1. 将漏洞驱动编译进 kernel

这里,我们使用的是其他人已经准备好的漏洞驱动 test.c

简单讲讲这个漏洞驱动:

  • 初始加载驱动时,会在 /proc 文件夹下创建文件 proc。而针对于该 proc 的读写操作,内核实际会调用 proc_* 系列函数来进行处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #define MY_DEV_NAME "test"
    static struct file_operations a = {
    .open = proc_open,
    .read = proc_read,
    .write = proc_write,
    };


    static int __init mod_init(void)
    {
    struct proc_dir_entry *test_entry;
    const struct file_operations *proc_fops = &a;
    printk(DEBUG_FLAG":proc init start!\n");

    // 创建 proc
    test_entry = proc_create(MY_DEV_NAME, S_IRUGO|S_IWUGO, NULL, proc_fops);
    if(!test_entry)
    printk(DEBUG_FLAG":there is somethings wrong!\n");

    printk(DEBUG_FLAG":proc init over!\n");
    return 0;
    }
  • 驱动中的漏洞代码部分如下所示,可以看到当我们针对 proc 文件进行写入操作时,会造成内核堆溢出:

    1
    2
    3
    4
    5
    6
    7
    8
    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);
    // 溢出,原先只有 512 字节,但是复制了 4096
    copy_from_user(c, proc_user, 4096);
    printk(DEBUG_FLAG":into write!\n");
    return 0;
    }

尝试将其加载进内核。

1
2
3
4
5
6
# 下载源代码至 linux/drivers/char/test.c
proxychains wget -v https://github.com/hardenedlinux/Debian-GNU-Linux-Profiles/raw/master/docs/harbian_qa/fuzz_testing/test.c -O linux/drivers/char/test.c
# 修改 Makefile
echo "obj-y += test.o" >> linux/drivers/char/Makefile
# 尝试编译
make

报错,提示:

image-20211121142921325

这里主要有两个地方出问题:

  • 原先代码中使用的 struct file_operations 需要替换成 struct proc_ops

  • linux kernel 编译时会进行静态编译检测,这类通过静态推断便能得出的溢出问题将不予编译:

    1
    2
    char *c = kmalloc(512, GFP_KERNEL);
    copy_from_user(c, proc_user, 4096);

    绕过静态检测也很简单,引入一个可变量即可。

因此我简单修改了一下这个驱动文件,修改后如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#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 struct proc_ops a = {
.proc_open = proc_open,
.proc_read = proc_read,
.proc_write = proc_write,
};


static int __init mod_init(void)
{
struct proc_dir_entry *test_entry;
const struct proc_ops *proc_fops = &a;
printk(DEBUG_FLAG":proc init start!\n");

test_entry = proc_create(MY_DEV_NAME, S_IRUGO|S_IWUGO, NULL, proc_fops);
if(!test_entry)
printk(DEBUG_FLAG":there is somethings wrong!\n");

printk(DEBUG_FLAG":proc init over!\n");
return 0;
}

static ssize_t proc_read (struct file *proc_file, char __user *proc_user, size_t n, loff_t *loff)
{
printk(DEBUG_FLAG":finish copy_from_use,the string of newbuf is");

return 0;
}

static ssize_t proc_write (struct file *proc_file, const char __user *proc_user, size_t n, loff_t *loff)
{
char *c = kmalloc(n + 512, GFP_KERNEL);

size_t size = copy_from_user(c, proc_user, n + 4096);
printk(DEBUG_FLAG":into write %ld!\n", size);
return 0;
}

int proc_open (struct inode *proc_inode, struct file *proc_file)
{
printk(DEBUG_FLAG":into open!\n");
return 0;
}

module_init(mod_init);

然后make一下,之后就可以在 /proc/test 下找到目标 proc:

1
2
root@syzkaller:~# ls -al /proc/test 
-rw-rw-rw-. 1 root root 0 Nov 21 06:43 /proc/test

很好,漏洞驱动已经成功加载进 kernel 中,接下来直接 poweroff 掉,开始配置 syzkaller。

2. 配置 syzkaller 规则

syzkaller/sys/linux/ 创建一个对应于这个漏洞驱动的处理规则 test.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

在 syzkaller 项目根目录下执行以下命令以创建对应的 .const 文件

1
bin/syz-extract -os linux -sourcedir "/usr/class/linux" -arch amd64 test.txt

执行以下命令重新构建 syzkaller

1
2
bin/syz-sysgen
make all

此时 syzkaller 构建完成。

可能你会有些疑问,为什么要配置这些规则,这些规则的配置基于什么,它的语法结构是什么样的。

以及,为什么要创建 .const 文件,创建后为什么要再跑一遍 syz-sysgen。

这些问题正是我写下这行文字时所产生的疑问。

不过暂时不急,因为后面都会慢慢讲到。

3. syzkaller 开跑

在最后真正的执行前,我们需要修改一下 syzkaller 的运行配置。修改 my.cfg ,添加上以下内容:

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

之后执行 syzkaller 开跑:

1
bin/syz-manager -config my.cfg -vv 10

过一小段时间(很快)后便可以得到:

image-20211122092540576

此时 syzkaller 正在复现 crash,crash样例不太直观:

image-20211122093042147

不过问题不大,再等亿段时间,我们便可以在网页端查看到其效果 可能是我点背,等了半个小时没等到 reprocude 结束(复现要花n久的时间…):

image-20211122095516144

试用了一段时间,不得不说一下,当 syzkaller 检测到 crash 时,貌似它会停下所有 VM 跑来复现这个 crash。这样的话个人感觉可能会带来一点性能开销?(不是很懂)

四、参考链接

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2022 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~