syzkaller fuzz 工具的使用方法及实践实例

Syzkaller 是google安全研究人员开发并维护的内核fuzz工具(2015年在github开源),由go语言编写,含少部分c/c++代码。支持多个操作系统内核,如linux、windows、darwin、openbsd等等,其对linux的支持最为全面。

本篇文章将搭建syzkaller环境的过程记录下来,供之后参考。

更新了一个视频:内核fuzz工具Syzkaller使用方法介绍

x86-64 linux虚拟机

fuzz对象是linux kernel,架构是x86-64,使用qemu模拟运行。

编译syzkaller

以syzkaller github上最新版为准,项目一直在更新,今天(2022年2月)编译的时候,跟我上次(2020年12月)编译有挺多地方都不一样了。

官方指导文档

  1. 下载go语言编译器

    1
    2
    3
    4
    5
    wget https://dl.google.com/go/go1.17.6.linux-amd64.tar.gz
    tar -xf go1.17.6.linux-amd64.tar.gz
    # 后面这两条命令建议设置到~/.bashrc文件中,通过source ~/.bashrc命令更新配置
    export GOROOT=`pwd`/goroot
    export PATH=$GOROOT/bin:$PATH
  2. 下载并编译syzkaller

    1
    2
    3
    4
    5
    git clone https://github.com/google/syzkaller
    cd syzkaller
    make
    # 如果fuzz目标是arm 64位,则需指定交叉编译器,如下
    # make CC=aarch64-linux-gnu-g++ TARGETARCH=arm64

    编译过程中遇到一个问题,是因为虚拟机内存给少了,给到12G内存后不再报错。

编译完成后,在syzkaller目录下会出现一个bin目录,里面就是我们后面需要用到的二进制。如下是我编译的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~/syzkaller$ tree ./bin
./bin
├── linux_amd64
│   ├── syz-execprog
│   ├── syz-executor
│   ├── syz-fuzzer
│   └── syz-stress
├── syz-db
├── syz-manager
├── syz-mutate
├── syz-prog2c
├── syz-repro
├── syz-runtest
├── syz-sysgen
└── syz-upgrade

编译linux内核

  1. 以linux 5.14内核为例,先将源码下载到本地

    1
    git clone --branch v5.14 git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
  2. 生成默认配置文件

    1
    2
    3
    cd linux
    make defconfig
    make kvm_guest.config
  3. 更改.config文件

    为了fuzz效率,以下内核配置必须打开。更多可选配置见Linux kernel configs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # Coverage collection.
    CONFIG_KCOV=y # must

    # Debug info for symbolization.
    CONFIG_DEBUG_INFO=y

    # Memory bug detector
    CONFIG_KASAN=y
    CONFIG_KASAN_INLINE=y

    # Code coverage works better when KASLR Is disabled
    # CONFIG_RANDOMIZE_BASE is not set

    # 可选
    CONFIG_KCOV_INSTRUMENT_ALL=y
    CONFIG_KCOV_ENABLE_COMPARISONS=y
    CONFIG_DEBUG_FS=y

    CONFIG_DEBUG_KMEMLEAK=y
  4. 重新生成配置文件并编译

    1
    2
    make olddefconfig
    make -j4
  5. 漫长的等待后,就能在目录下看到vmlinux (kernel binary) 和 bzImage (packed kernel image)

    1
    2
    3
    4
    ~$ ls linux/vmlinux
    linux/vmlinux
    ~$ ls linux/arch/x86/boot/bzImage
    linux/arch/x86/boot/bzImage

配置qemu虚拟机

  1. 安装qemu

使用sudo apt install qemu或下载最新qemu源码编译。前者的版本较低,有些特性不支持。建议使用后者。

  1. 生成image

使用debootstrap构建linux启动镜像:

1
2
3
4
5
6
7
sudo apt-get install debootstrap
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

# 可以使用./create-image.sh -h查看更多帮助选项

完成之后目录内容如下:

1
2
3
4
5
6
7
8
9
~/s_image$ ll
total 502072
drwxr-xr-x 3 bling bling 4096 1月 9 09:03 ./
drwxr-xr-x 25 bling bling 4096 1月 9 00:45 ../
drwxr-xr-x 21 root root 4096 1月 9 02:25 chroot/
-rwxr-xr-x 1 bling bling 6360 1月 9 00:13 create-image.sh*
-rw------- 1 bling bling 1675 1月 9 09:03 stretch.id_rsa
-rw-r--r-- 1 bling bling 398 1月 9 09:03 stretch.id_rsa.pub
-rw-r--r-- 1 bling bling 2147483648 1月 9 09:04 stretch.img
  1. 启动虚拟机

启动虚拟机试试

1
qemu-system-x86_64 -m 2G -smp 2 -kernel /home/bling/linux/arch/x86/boot/bzImage -append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0" -drive file=/home/bling/qemu-img/stretch.img,format=raw -net user,hostfwd=tcp:127.0.0.1:10021-:22 -net nic,model=e1000 -enable-kvm -nographic -pidfile vm.pid 2>&1 | tee vm.log

对以上各参数我的理解如下:

1
2
3
4
5
6
7
8
9
10
-kernel xxx/bzImage:用bzImage作为内核镜像,qemu的这个功能用来测试不同内核非常方便。
-append cmdline:将cmdline作为内核的命令行参数
-hda xxx/xxx.img:指定xxx.img作为硬盘镜像
-net user,hostfwd=tcp::10021-:22 -net nic:客户机与宿主机之间通过指定的端口进行通讯
-enable-kvm:开启kvm虚拟化
-nographic:非图形界面启动
-m 2G:分配2G内存给虚拟系统
-smp 2:指定虚拟机由2个CPU
-pidfile xxx.pid:将qemu进程pid储存在xxx.pid这个文件中
2>&1 | tee vm.log:将执行过程中的输出同时定向到标准输出和vm.log文件中

参考了两篇文章:(1)hostfwd的问题 (2)make 2>&1 | tee log.txt 命令解析

qemu启动起来之后,运行ssh测试一下是否连通,便于后期syzkaller运行出错时定位问题。

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

曾经有一次连接时在这里出了问题,宿主机上ssh无法连接到虚拟机。原因如下:

由于上一步中create-image.sh中如下eth0跟实际qemu虚拟机中运行的网卡名称不一样,导致网卡没有分配IP地址。最后解决方法如下:qemu启动虚拟机,root用户身份登录后,设置网卡IP地址。

参考文章:(1)网卡没分配IP地址的解决方法(2)rsa公私钥知识点(3)一个自己生成公私钥配置的方法

1
2
# 关闭qemu虚拟机
kill $(cat vm.pid)

启动syzkaller - qemu

为了使syzkaller运行起来,在syzkaller目录下,新建一个workdir目录,并新建一个config文件用于配置运行所需参数(命名为xxx.cfg)

1
2
mkdir workdir
./bin/syz-manager -config=abcd.cfg

cfg文件的格式如下,根据实际情况各参数可做更改:

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": "/home/bling/gopath/src/github.com/google/syzkaller/workdir",
"kernel_obj": "/home/bling/linux",
"image": "/home/bling/s_image/stretch.img",
"sshkey": "/home/bling/s_image/stretch.id_rsa",
"syzkaller": "/home/bling/gopath/src/github.com/google/syzkaller/",
"procs": 8,
"type": "qemu",
"vm": {
"count": 4,
"kernel": "/home/bling/linux/arch/x86/boot/bzImage",
"cpu": 2,
"mem": 2048
}
}

执行成功后,如下图所示:

启动syzkaller - isolated

对于两台通过网络连接的独立机器,使用syzkaller进行fuzz时,需要设置ssh连接并更改config配置文件。

ps. 由于没有两台物理机,这里复用上一步的qemu,不同点在于将虚拟机和物理机看作两台独立运行的机器

可参考如下步骤,先配置好待测试设备,然后再运行syzkaller。

配置好待测试设备

在使用syzkaller进行fuzz之前,需要确保目标设备上运行着上一步编译的内核,并且设置好了ssh连接。

可参考官方文档:setup_linux-host_isolated

提炼一下,需保证如下几点:

  1. 待测试设备上运行着ssh服务

  2. 打开ssh的AllowTcpForwarding选项

    1
    2
    ~# grep Forwarding /etc/ssh/sshd_config
    AllowTcpForwarding yes
  3. 配置ssh无密码连接

    1
    2
    3
    # 在本地生成公私钥对文件
    ssh-keygen -t rsa
    # 然后,将公钥文件拷贝到目标测试机器的/root/.ssh/目录下,并重命名为authorized_keys

    运行syzkaller

  4. 在本地和目标设备上各创建一个工作目录

    1
    2
    3
    4
    5
    6
    # 本地:
    cd syzkaller
    mkdir workdir
    # 远端:
    cd home
    mkdir fuzzdir
  5. 在syzkaller目录下新建一个fuzz.cfg文件

    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",
    "rpc": "127.0.0.1:0",
    "sshkey" : "//home/bling/qemu-img/stretch.id_rsa",
    "workdir": "/home/bling/syzkaller/workdir",
    "kernel_obj": "/home/bling/linux",
    "syzkaller": "/home/bling/syzkaller",
    "sandbox": "setuid",
    "type": "isolated",
    "vm": {
    "targets" : [ "127.0.0.1:10021" ],
    "pstore": false,
    "target_dir" : "/home/fuzzdir",
    "target_reboot" : false
    }
    }
    • target指定待测试设备的操作系统内核及cpu架构
    • vm.targets指定待fuzz设备的ip地址及ssh端口(默认是22)
    • enbale_syscalls:测试特定的几个系统调用
    • disable_syscalls:不调用某几个系统调用

    对各个字段的解释,贴两个官方文档:

    config文件中各字段的解释

    isolated情况下vm字段中参数的解释

    ps. “kernel_obj”删除不指定也是可以的,区别在于无法在web面板中查看代码覆盖率情况

  6. 启动syzkaller

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

提高fuzz效率

syzkaller无法根据源码自动分析出有哪些设备节点,也无法获得系统调用及参数信息。如果不针对内核版本进一步定制,那么fuzz效率将非常低。

定制syscall description

syscall descriptions官方说明

syzkaller自己定义了一套描述系统调用模版的声明式语言(syzlang),我称之为描述文件/声明文件。

为了提高fuzz效率,我们必须为目标系统量身定制这种声明文件。通常一个设备节点对应一个声明文件。

所谓的声明文件就是一个txt,根据syzkaller定义的语法,在这个txt文档中描述设备节点的接口信息以及参数格式。

用syzlang描述系统调用

定制(即对新的内核接口,增加系统调用描述文件)是一个比较繁琐的过程,官方给了如下文档用作参考:

举一个例子,从系统调用open()函数开始,它的函数定义如下:

1
int open(const char *pathname, int flags, mode_t mode);
  • 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
  • mode:S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

用syzlang来描述它:

1
2
3
4
resource fd_test[fd]
open(file ptr[in, filename], flags flags[open_flags], mode flags[open_mode]) fd_test
# 或
# open(file ptr[in, string["/dev/xxx"]], flags flags[open_flags], mode flags[open_mode]) fd_test
  • file ptr[in, filename]:open()函数的第一个参数,命名为file,是一个输入指针类型,指针指向一个文件名字符串

  • flags flags[open_flags]:flags表明该参数是open_flags数组中的任意一个值

    • open_flags = O_WRONLY, O_RDWR, O_APPEND, ...
  • mode flags[open_mode]:flags表明该参数是open_mode数组中的任意一个值

    • open_mode = S_IRUSR, S_IWUSR, S_IXUSR, ...
  • fd_test:open()函数的返回值将给fd_test,这是一个文件描述符

fd_test将被用于其他系统调用,如read、write、ioctl

1
2
read(fd_1 fd_test, buf buffer[out], count len[buf])
write(fd_1 fd_test, buf buffer[in], count len[buf])
  • buffer是任意大小的数组空间,填满了int8类型的值。可以是输入参数也可以作为输出参数。
  • len[buf]得到buf的长度

ioctl通常承载更多更复杂的功能,syzkaller针对ioctl提供了一种通用的描述方法:

1
ioctl(fd_1 fd_test, cmd intptr, arg buffer[in])

上面这种方式并不准确,为了更精确地触发ioclt中各个case分支,通常需要做额外的适配工作:

1
2
3
ioctl$DRM_IOCTL_VERSION(fd fd_dri, cmd const[DRM_IOCTL_VERSION], arg ptr[in, drm_version])
ioctl$VIDIOC_QUERYCAP(fd fd_video, cmd const[VIDIOC_QUERYCAP], arg ptr[out, v4l2_capability])
...
  • DRM_IOCTL_VERSIONVIDIOC_QUERYCAP是ioctl中的某两个case分支

  • drm_versionv4l2_capability分别是以上两个分支其arg指针指向的结构体(同样需要用syzlang描述)

可以对比如下参考文件,学习基本使用方法:

将描述信息编译进syzkaller

那么,整个定制过程分为4步:

  1. 根据目标内核模块的信息,撰写符合syzlang语法的txt声明文件
  2. syz-extract根据txt及linux源码,提取符号常量的值,生成中间文件***.const文件
  3. syz-sysgen根据const文件生成syzkaller执行时使用的go文件
  4. 重新编译syzkaller

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

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

它俩的关系是这样的:

1
2
3
4
5
6
+-------+            +---------+           +------+
|xxx.txt+----------->|xxx.const+---------->|xxx.go|
+---+---+ +---------+ +------+
| syz-extract syz-sysgen ^
| |
+-----------------------------------------+

我们针对某个驱动接口写出xxx.txt,然后使用syz-extract利用txt和源码生成const文档,最后执行syz-sysgen时syzkaller会根据txt和const生成一个go文件。可在sys/linux/gen/amd64.go和executor/syscalls.h中看到结果。

一次定制示例(isolated)

  • 编写一个有漏洞的驱动接口,并将其编译进内核(或者使用打ko的方式)。
  • 编写驱动接口对应的txt文件,将其放入syzkaller/sys/linux目录下,生成go文件并重新编译syzkaller。
  • 运行syzkaller,改config文件指定fuzz接口提高速率,最后分析crash。

编译进内核

方法1:将内核模块编译进内核

一个有漏洞的内核模块

  1. kernel_src/drivers/char目录下,新建一个testxy.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;
    }
  2. 打开char/目录下的Kconfig文件,添加:

    1
    2
    3
    4
    5
    config TESTXY_MODULE
    tristate "heap overflow test"
    default y
    help
    This file is to test a buffer overflow
  3. 打开char/目录下的Makefile文件,添加:

    1
    obj-$(CONFIG_TESTXY_MODULE) += testxy.o

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

  4. make menuconfig时可以在Device Drivers -> Heap Overflow Test (*表示直接编入内核,M表示模块形式) 处看到刚刚添加的测试模块。

    1
    2
    3
    make clean
    make menuconfig
    make -j8
  5. 用新的内核启动虚拟机,查看模块是否加载成功

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

    定制txt系统调用描述文件

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

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

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

    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
  7. 使用syz-extract生成const文件。指定txt文件名,可单独生成该文件对应的const文件。

    1
    bin/syz-extract -os linux -arch amd64 -sourcedir "/home/bling/linux" proc_testxy.txt
  8. 运行syz-sysgen

    1
    bin/syz-extract
  9. 重新编译syzkaller

    1
    2
    make generate
    make

    验证能否成功触发crash

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"target": "linux/amd64",
"http": "127.0.0.1:56741",
"rpc": "127.0.0.1:0",
"sshkey" : "//home/bling/qemu-img/stretch.id_rsa",
"workdir": "/home/bling/syzkaller/workdir",
"kernel_obj": "/home/bling/linux",
"syzkaller": "/home/bling/syzkaller",
"sandbox": "setuid",
"type": "isolated",
"enable_syscalls":[
"open$testxy",
"read$testxy",
"write$testxy",
"close$testxy"
],
"vm": {
"targets" : [ "127.0.0.1:10021" ],
"pstore": false,
"target_dir" : "/home/fuzzdir",
"target_reboot" : false
}
}

启动syzkaller进行测试:

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

触发到漏洞分支!(图片跟笔记稍微有点区别,图片是两年前的,笔记是22年更新的)

打ko

方法2:insmod方式植入内核模块

源码及makefile文件:test.cMakefile。编译完成后,insmod进待fuzz的linux系统中。

通过如下5个步骤完成定制:

  1. 在sys/linux/目录下,新建针对目标内核模块的txt声明文件(本例中将其命名为proc_test.txt

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

    resource fd_111[fd]

    open$aaa(file ptr[in, string["/proc/newtest"]], flags flags[proc_open_flags], mode flags[proc_open_mode]) fd_111
    read$aaa(fd_a fd_111, buf buffer[out], count len[buf])
    write$aaa(fd_a fd_111, buf buffer[in], count len[buf])

    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

  2. 执行syz-extract

    1
    bin/syz-extract -os linux -arch amd64 -sourcedir "/home/bling/linux" proc_test.txt

    该步骤将在sys/linux/目录下产生一个名为proc_test.txt.const的文件

  3. 执行syz-sysgen

    1
    bin/syz-sysgen

    该步骤将更新syzkaller/sys/linux/gen/amd64.go,自动添加上新定义的系统调用,如下片段:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {NR:2,Name:"open$aaa",CallName:"open",Args:[]Field{
    {Name:"file",Type:Ref(9200)},
    {Name:"flags",Type:Ref(5481)},
    {Name:"mode",Type:Ref(6042)},
    },Ret:Ref(11058)},

    {Name:"read$aaa",CallName:"read",Args:[]Field{
    {Name:"fd",Type:Ref(11058)},
    {Name:"buf",Type:Ref(10466)},
    {Name:"count",Type:Ref(6978)},
    }},

    {NR:1,Name:"write$aaa",CallName:"write",Args:[]Field{
    {Name:"fd",Type:Ref(11058)},
    {Name:"buf",Type:Ref(8697)},
    {Name:"count",Type:Ref(6978)},
    }},
  4. 重新编译syzkaller

    1
    2
    make generate
    make
  5. 指定syscall,重新运行syzkaller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    {
    "target": "linux/amd64",
    "http": "127.0.0.1:56741",
    "rpc": "127.0.0.1:0",
    "sshkey" : "//home/bling/qemu-img/stretch.id_rsa",
    "workdir": "/home/bling/syzkaller/workdir",
    "kernel_obj": "/home/bling/linux",
    "syzkaller": "/home/bling/syzkaller",
    "sandbox": "setuid",
    "type": "isolated",
    "enable_syscalls": ["open$aaa", "read$aaa", "write$aaa"],
    "vm": {
    "targets" : [ "127.0.0.1:10021" ],
    "pstore": false,
    "target_dir" : "/home/fuzzdir",
    "target_reboot" : true
    }
    }

    执行./bin/syz-manager -config=abcd.cfg,开始fuzz。

附录

1 安装GCC

gcc下载地址:gcc下载

宿主机自带的gcc版本过低的话,需要在原本的基础上新装一个高版本的gcc。这里我选择源码安装,并且将它安装到一个单独目录,这样今后想卸载的话,直接删除该目录即可。

1
2
3
4
5
6
7
8
9
10
11
12
gcc7.4.0依赖的gmp,mpfr和mpc版本如下:
gmp='gmp-6.1.0.tar.bz2'
mpfr='mpfr-3.1.4.tar.bz2'
mpc='mpc-1.0.3.tar.gz'

安装过程参考链接:
https://blog.csdn.net/lwbeyond/article/details/77718040(主要参考该文档)
https://blog.csdn.net/xs1102/article/details/89175293
https://blog.csdn.net/davidhopper/article/details/79681695

安装gmp到configure步骤时,出现“no usable m4”错误:
https://blog.csdn.net/wangqing_12345/article/details/52484723

2 txt文件语法

以ioctl为例,用户态调用规则:

1
2
3
4
5
6
int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
/*
inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。
cmd 由用户空间直接不经修改的传递给驱动程序
arg 可选。
*/

定制过程:

1、 分析目标驱动的ioctl函数实现。 2、 找到每个cmd对应的代码块,分析arg的解析过程(结构体)。 3、 把所有这类结构体,按照 syzkaller 的规则写成类似go语言的结构体定义。结构体中如果包含了其他结构体,也都要写上 。 4、 每个cmd码都写成一个ioctl系统调用,以上面的结构体为参数。如:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct ion_client *client = filp->private_data;
struct ion_device *dev = client->dev;
struct ion_handle *cleanup_handle = NULL;
int ret = 0;
unsigned int dir;

union {
struct ion_fd_data fd;
struct ion_allocation_data allocation;
struct ion_handle_data handle;
struct ion_custom_data custom;
} data;

dir = ion_ioctl_dir(cmd);

if (_IOC_SIZE(cmd) > sizeof(data))
return -EINVAL;

if (dir & _IOC_WRITE)
if (copy_from_user(&data, (void __user *)arg, _IOC_SIZE(cmd)))
return -EFAULT;

switch (cmd) {
case ION_IOC_ALLOC:
{
struct ion_handle *handle;

handle = ion_alloc(client, data.allocation.len,
data.allocation.align,
data.allocation.heap_id_mask,
data.allocation.flags);
if (IS_ERR(handle))
return PTR_ERR(handle);

data.allocation.handle = handle->id;

cleanup_handle = handle;
break;
}
case ION_IOC_FREE:
{
struct ion_handle *handle;

handle = ion_handle_get_by_id(client, data.handle.handle);
if (IS_ERR(handle))
return PTR_ERR(handle);
ion_free(client, handle);
ion_handle_put(handle);
break;
}
case ION_IOC_SHARE:
case ION_IOC_MAP:
{
struct ion_handle *handle;

handle = ion_handle_get_by_id(client, data.handle.handle);
if (IS_ERR(handle))
return PTR_ERR(handle);
data.fd.fd = ion_share_dma_buf_fd(client, handle);
ion_handle_put(handle);
if (data.fd.fd < 0)
ret = data.fd.fd;
break;
}
case ION_IOC_IMPORT:
{
struct ion_handle *handle;

handle = ion_import_dma_buf(client, data.fd.fd);
if (IS_ERR(handle))
ret = PTR_ERR(handle);
else
data.handle.handle = handle->id;
break;
}
case ION_IOC_SYNC:
{
ret = ion_sync_for_device(client, data.fd.fd);
break;
}
case ION_IOC_CUSTOM:
{
if (!dev->custom_ioctl)
return -ENOTTY;
ret = dev->custom_ioctl(client, data.custom.cmd,
data.custom.arg);
break;
}
default:
return -ENOTTY;
}

if (dir & _IOC_READ) {
if (copy_to_user((void __user *)arg, &data, _IOC_SIZE(cmd))) {
if (cleanup_handle)
ion_free(client, cleanup_handle);
return -EFAULT;
}
}
return ret;
}

对应定制的txt:

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
include <asm/ioctl.h>
include <linux/fcntl.h>
include <../drivers/staging/android/uapi/ion.h>

resource fd_ion[fd]
resource fd_ion_generic[fd]

resource ion_handle[int32]

openat$ion(fd const[AT_FDCWD], file ptr[in, string["/dev/ion"]], flags flags[open_flags], mode const[0]) fd_ion
ioctl$ION_IOC_ALLOC(fd fd_ion, cmd const[ION_IOC_ALLOC], arg ptr[inout, ion_allocation_data])
ioctl$ION_IOC_FREE(fd fd_ion, cmd const[ION_IOC_FREE], arg ptr[in, ion_handle_data])
ioctl$ION_IOC_MAP(fd fd_ion, cmd const[ION_IOC_MAP], arg ptr[inout, ion_fd_data])
ioctl$ION_IOC_SHARE(fd fd_ion, cmd const[ION_IOC_SHARE], arg ptr[inout, ion_fd_data])
ioctl$ION_IOC_IMPORT(fd fd_ion, cmd const[ION_IOC_IMPORT], arg ptr[inout, ion_fd_data])
ioctl$ION_IOC_SYNC(fd fd_ion, cmd const[ION_IOC_SYNC], arg ptr[inout, ion_fd_data])
ioctl$ION_IOC_CUSTOM(fd fd_ion, cmd const[ION_IOC_CUSTOM], arg ptr[inout, ion_custom_data])

ion_allocation_data {
len intptr
align intptr
heapid int32
flags int32
handle ion_handle
}

ion_handle_data {
handle ion_handle
}

ion_fd_data {
handle ion_handle
fd fd_ion_generic
}

ion_custom_data {
cmd int32
arg intptr
}

参考文章推荐

内核漏洞挖掘技术系列(4)——syzkaller(1)

【漏洞挖掘】使用Syzkaller&QEMU捕捉内核堆溢出Demo

Syzkaller Crash Demo

从0到1开始使用syzkaller进行Linux内核漏洞挖掘

Using syzkaller, part 1: Fuzzing the Linux kernel

Using syzkaller, part 2: Detecting programming bugs in the Linux kernel

Linux Kernel Pwn III:使用 syzkaller 进行漏洞挖掘

Highlighting syzkaller descriptions syntax with Rouge