未加星标

Syzkaller:Linux内核模糊测试工具分享

字体大小 | |
[系统(linux) 所属分类 系统(linux) | 发布者 店小二04 | 时间 2017 | 作者 红领巾 ] 0人收藏点击收藏

syzkaller是Google团队开发的一款针对linux内核进行模糊测试的开源工具,目前还在不断的维护之中。文章结尾有syzkaller的项目地址。

本文主要分为以下几个部分:

1.针对syzkaller的安装过程做一个解释说明;

2.简述syzkaller的工作过程;

3.部分源码的分析。

环境配置:

Ubuntu要求:64-bit Ubuntu14.04( 因作者未在32位Ubuntu上进行测试 );

1.Ubuntu上有svn(apt-get install subversion);

2.Ubuntu上有g++(apt-get install g++);

3.Ubuntu上有文本编辑器(如vim,apt-get install vim);

4.Ubuntu上有git(apt-get install git)。

若前趋条件全部具备,则可以开始搭建环境: 1.安装gcc

需要新版本的gcc eg. gcc-7.1.0而使用新版gcc中的coverage support

(安装GCC的先决条件,执行下列代码:

sudo apt-get install flex bison libc6-dev libc6-dev-i386 linux-libc-dev linux-libc-dev:i386 libgmp3-dev libmpfr-dev libmpc-dev

1.1 执行下列指令

(前趋条件2)($GCC为自定义目录(尽量为可写目录)):

svn checkout svn://gcc.gnu.org/svn/gcc/trunk $GCC cd $GCC svn ls -v ^/tags | grep gcc_7_1_0_release svn up -r 247494

解释:

1.svn checkout svn://gcc.gnu.org/svn/gcc/trunk $GCC

从站点拉取最新的gcc(笔者拉取下来的是gcc_8.0);

2.cd $GCC

3.svn ls -v ^/tags | grep gcc_7_1_0_release

4.svn up -r 247494

版本回退,将新版本(版本号具体是多少不记得了,第一步做完后能看到具体版本号)回退到247494。

1.2 修复GCC中的错误,错误在tree.h中:

diff --git a/gcc/tree.h b/gcc/tree.h index 3bca90a..fdaa7af 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -897,8 +897,8 @@ extern void omp_clause_range_check_failed (const_tree, const char *, int, /* If this is true, we should insert a __cilk_detach call just before this function call. */ #define EXPR_CILK_SPAWN(NODE) \ - (tree_check2 (NODE, __FILE__, __LINE__, __FUNCTION__, \ - CALL_EXPR, AGGR_INIT_EXPR)->base.u.bits.unsigned_flag) + (TREE_CHECK2 (NODE, CALL_EXPR, \ + AGGR_INIT_EXPR)->base.u.bits.unsigned_flag) /* In a RESULT_DECL, PARM_DECL and VAR_DECL, means that it is passed by invisible reference (and the TREE_TYPE is a pointer to the true

1.2.1 找到出错的文件:

cd $GCC/gcc

1.2.2 修改文件错误(我使用的是vim(前趋条件4))

sudo vim tree.h

1.2.3 找到第900行:

1.2.4 将:

(tree_check2 (NODE, __FILE__, __LINE__, __FUNCTION__, \ CALL_EXPR, AGGR_INIT_EXPR)->base.u.bits.unsigned_flag)

改为:

( TREE_CHECK2 (NODE, CALL_EXPR, \ AGGR_INIT_EXPR)->base.u.bits.unsigned_flag)

保存。

1.3 Build GCC(在$GCC下):

mkdir build mkdir install cd build/ ../configure --enable-languages=c,c++ --disable-bootstrap --enable-checking=no --with-gnu-as --with-gnu-ld --with-ld=/usr/bin/ld.bfd --disable-multilib --prefix=$GCC/install/ make -j64 make install

1.4 若安装成功,执行ls $GCC/install/bin/应该看到:

c++ gcc-ar gcov-tool x86_64-pc-linux-gnu-gcc-7.1.0 cpp gcc-nm x86_64-pc-linux-gnu-c++ x86_64-pc-linux-gnu-gcc-ar g++ gcc-ranlib x86_64-pc-linux-gnu-g++ x86_64-pc-linux-gnu-gcc-nm gcc gcov x86_64-pc-linux-gnu-gcc x86_64-pc-linux-gnu-gcc-ranlib 2 .安装待测内核Kernel:

2.1 从github上拉取待测内核的资源(前趋条件5) ($KERNEL为自定义路径)(速度很慢,建议直接将源码拷贝到目录下):

git clone https://github.com/torvalds/linux.git $KERNEL

2.2 生成内核的配置:

cd $KERNEL make defconfig make kvmconfig

2.3 更改内核配置文件.config

(我用的vim在配置文件最前面插入了下列四行(开启可调试/测试的配置)):

CONFIG_KCOV=y CONFIG_DEBUG_INFO=y CONFIG_KASAN=y CONFIG_KASAN_INLINE=y

2.4 重新生成配置

make oldconfig

2.5 用之前安装的GCC编译linux内核:

make CC='$GCC/install/bin/gcc' -j64

2.6 执行ls $KERNEL/vmlinux和ls $KERNEL/arch/x86/boot/bzImage,若能看到相应文件则说明安装成功。

3.安装Image(小型的Debian-wheezy的Linux镜像)

3.1 首先安装debootstrap:

sudo apt-get install debootstrap

3.2 用下列脚本安装Image:

#!/bin/bash # Copyright 2016 syzkaller project authors. All rights reserved. # Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. # create-image.sh creates a minimal Debian-wheezy Linux image suitable for syzkaller. set -eux # Create a minimal Debian-wheezy distributive as a directory. sudo rm -rf wheezy mkdir -p wheezy sudo debootstrap --include=openssh-server,curl,tar,gcc,libc6-dev,time,strace,sudo,less,psmisc wheezy wheezy # Set some defaults and enable promtless ssh to the machine for root. sudo sed -i '/^root/ { s/:x:/::/ }' wheezy/etc/passwd echo 'T0:23:respawn:/sbin/getty -L ttyS0 115200 vt100' | sudo tee -a wheezy/etc/inittab printf '\nauto eth0\niface eth0 inet dhcp\n' | sudo tee -a wheezy/etc/network/interfaces echo 'debugfs /sys/kernel/debug debugfs defaults 0 0' | sudo tee -a wheezy/etc/fstab echo "kernel.printk = 7 4 1 3" | sudo tee -a wheezy/etc/sysctl.conf echo 'debug.exception-trace = 0' | sudo tee -a wheezy/etc/sysctl.conf echo "net.core.bpf_jit_enable = 1" | sudo tee -a wheezy/etc/sysctl.conf echo "net.core.bpf_jit_harden = 2" | sudo tee -a wheezy/etc/sysctl.conf echo "net.ipv4.ping_group_range = 0 65535" | sudo tee -a wheezy/etc/sysctl.conf echo -en "127.0.0.1\tlocalhost\n" | sudo tee wheezy/etc/hosts echo "nameserver 8.8.8.8" | sudo tee -a wheezy/etc/resolve.conf echo "syzkaller" | sudo tee wheezy/etc/hostname sudo mkdir -p wheezy/root/.ssh/ rm -rf ssh mkdir -p ssh ssh-keygen -f ssh/id_rsa -t rsa -N '' cat ssh/id_rsa.pub | sudo tee wheezy/root/.ssh/authorized_keys # Build a disk image dd if=/dev/zero of=wheezy.img bs=1M seek=2047 count=1 sudo mkfs.ext4 -F wheezy.img sudo mkdir -p /mnt/wheezy sudo mount -o loop wheezy.img /mnt/wheezy sudo cp -a wheezy/. /mnt/wheezy/. sudo umount /mnt/wheezy

参考链接

3.3 若安装完成则能看到wheezy.img文件。

3.4 (还有一些对于运行syzkaller不必要但是很有用的工具:)

sudo chroot wheezy /bin/bash -c "apt-get update; apt-get install -y curl tar time strace gcc make sysbench git vim screen usbutils" sudo chroot wheezy /bin/bash -c "mkdir -p ~; cd ~/; wget https://github.com/kernelslacker/trinity/archive/v1.5.tar.gz -O trinity-1.5.tar.gz; tar -xf trinity-1.5.tar.gz" sudo chroot wheezy /bin/bash -c "cd ~/trinity-1.5 ; ./configure.sh ; make -j16 ; make install" cp -r $KERNEL wheezy/tmp/ sudo chroot wheezy /bin/bash -c "apt-get update; apt-get install -y flex bison python-dev libelf-dev libunwind7-dev libaudit-dev libslang2-dev libperl-dev binutils-dev liblzma-dev libnuma-dev" sudo chroot wheezy /bin/bash -c "cd /tmp/linux/tools/perf/; make" sudo chroot wheezy /bin/bash -c "cp /tmp/linux/tools/perf/perf /usr/bin/" rm -r wheezy/tmp/linux 4.安装QEMU:

4.1

sudo apt-get install kvm qemu-kvm

4.2 安装完后确保内核能够启动:

qemu-system-x86_64 \ -kernel $KERNEL/arch/x86/boot/bzImage \ -append "console=ttyS0 root=/dev/sda debug earlyprintk=serial slub_debug=QUZ"\ -hda $IMAGE/wheezy.img \ -net user,hostfwd=tcp::10021-:22 -net nic \ -enable-kvm \ -nographic \ -m 2G \ -smp 2 \ -pidfile vm.pid \ 2>&1 | tee vm.log

正常启动信息:

early console in setup code early console in extract_kernel input_data: 0x0000000005d9e276 input_len: 0x0000000001da5af3 output: 0x0000000001000000 output_len: 0x00000000058799f8 kernel_total_size: 0x0000000006b63000 Decompressing Linux... Parsing ELF... done. Booting the kernel. [ 0.000000] Linux version 4.12.0-rc3+ ... [ 0.000000] Command line: console=ttyS0 root=/dev/sda debug earlyprintk=serial ... [ ok ] Starting enhanced syslogd: rsyslogd. [ ok ] Starting periodic command scheduler: cron. [ ok ] Starting OpenBSD Secure Shell server: sshd.

4.3 After that you should be able to ssh to QEMU instance in another terminal:(这个不太理解在做什么)

ssh -i $IMAGE/ssh/id_rsa -p 10021 -o "StrictHostKeyChecking no" [email protected]

这里会要你输入[email protected]的身份验证信息,不知道哪有这个信息。但是可以以root用户执行上述命令,则不用身份验证。

4.4 结束QEMU进程:

kill $(cat vm.pid) 5.安装GO(前趋条件1)( 32-bit参考 ) wget https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz tar -xf go1.8.1.linux-amd64.tar.gz mv go goroot export GOROOT=`pwd`/goroot export PATH=$PATH:$GOROOT/bin mkdir gopath export GOPATH=`pwd`/gopath

一定要正确设置goroot和gopath变量。

6.安装syzkaller:

6.1 取得安装包:

go get -u -d github.com/google/syzkaller/... cd gopath/src/github.com/google/syzkaller/ mkdir workdir make

6.2 添加配置文件:

{ "http": "127.0.0.1:56741", "workdir": "/gopath/src/github.com/google/syzkaller/workdir", "vmlinux": "/linux/upstream/vmlinux", "image": "/image/wheezy.img", "sshkey": "/image/ssh/id_rsa", "syzkaller": "/gopath/src/github.com/google/syzkaller", "procs": 8, "type": "qemu", "vm": { "count": 4, "kernel": "/linux/arch/x86/boot/bzImage", "cpu": 2, "mem": 2048 } }

将配置中的相应字段改成自己主机上的路径。

配置字段含义

6.3 运行syzkaller manager: ./bin/syz-manager -config=my.cfg

执行./bin/syz-manager -config=my.cfg时,可能会出现:

failed to copy binary,可考虑使用sudo执行命令;

/sys/kernel/debug/kcov is missing. Enable CONFIG_KCOV and mount debugfs,则需要开启内核上的CONFIG_KCOV选项,也可以在配置文件中加入”cover”: false即不需要使用覆盖率的信息进行测试

该问题和Ubuntu内核版本无关,Ubuntu14和Ubuntu16上均出现此错误。

运行成功会看到以下界面:


Syzkaller:Linux内核模糊测试工具分享

至此,syzkaller的运行环境就全部搭建完成了。

那么接下来再简述一下syzkaller的工作原理:
Syzkaller:Linux内核模糊测试工具分享

整个syzkaller基于这样一个拓扑结构进行工作,从图上可以看到用户直接是通过syz-manager进行交互,而不直接与fuzzer接触,通过在syz-manager的配置文件中指定相应字段的值,该syz-manager基于这一些值进行与fuzzer的连接以及日志的文件输出等等。图中的VM在笔者这里是一个QEMU的实例(一般会启动4~8个这样的实例进行测试),真正执行测试的两大核心组件syz-fuzzer和syz-executor则在每个实例之中都存在。在syz-executor不断给被测kernel输入包含随机的syscall序列的程序的同时,sys-fuzzer也在不断接收内核的覆盖率信息以传给syz-executor提供更加有效的测试程序。

如果在这个过程中发生了crash,则该信息会在web端和workdir下显示相关的crash的log和report文件。

这是笔者运行出来的结果:


Syzkaller:Linux内核模糊测试工具分享

可以看到其中出现了四种类型的错误(其中三种是 典型错误 ),下面来分析一下错误产生的流程。

首先,整个程序的入口是manager.go之中的main函数:

func main() { flag.Parse() EnableLogCaching(1000, 1<<20) cfg, syscalls, err := mgrconfig.LoadFile(*flagConfig) if err != nil { Fatalf("%v", err) } initAllCover(cfg.Vmlinux) RunManager(cfg, syscalls) }

第一行是将各个选项转换为命令行参数;

第二行是设置web端的显示界面的最大显示行数为1000行;

第三行就开始读取配置文件中的相关选项中并得到相应的syscall了,整个过程如下:

LoadFile函数位于mgrconfig.go中: func LoadFile(filename string) (*Config, map[int]bool, error) { return load(nil, filename) }

Load函数如下:

func load(data []byte, filename string) (*Config, map[int]bool, error) { cfg := &Config{ Cover: true, Reproduce: true, Sandbox: "setuid", Rpc: "localhost:0", Procs: 1, } if data != nil { if err := config.LoadData(data, cfg); err != nil { return nil, nil, err } } else { if err := config.LoadFile(filename, cfg); err != nil { return nil, nil, err } } if !osutil.IsExist(filepath.Join(cfg.Syzkaller, "bin/syz-fuzzer")) { return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-fuzzer") } if !osutil.IsExist(filepath.Join(cfg.Syzkaller, "bin/syz-executor")) { return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-executor") } if !osutil.IsExist(filepath.Join(cfg.Syzkaller, "bin/syz-execprog")) { return nil, nil, fmt.Errorf("bad config syzkaller param: can't find bin/syz-execprog") } if cfg.Http == "" { return nil, nil, fmt.Errorf("config param http is empty") } if cfg.Workdir == "" { return nil, nil, fmt.Errorf("config param workdir is empty") } if cfg.Vmlinux == "" { return nil, nil, fmt.Errorf("config param vmlinux is empty") } if cfg.Type == "" { return nil, nil, fmt.Errorf("config param type is empty") } if cfg.Procs < 1 || cfg.Procs > 32 { return nil, nil, fmt.Errorf("bad config param procs: '%v', want [1, 32]", cfg.Procs) } switch cfg.Sandbox { case "none", "setuid", "namespace": default: return nil, nil, fmt.Errorf("config param sandbox must contain one of none/setuid/namespace") } cfg.Workdir = osutil.Abs(cfg.Workdir) cfg.Vmlinux = osutil.Abs(cfg.Vmlinux) cfg.Syzkaller = osutil.Abs(cfg.Syzkaller) if cfg.Kernel_Src == "" { cfg.Kernel_Src = filepath.Dir(cfg.Vmlinux) // assume in-tree build by default } syscalls, err := parseSyscalls(cfg) if err != nil { return nil, nil, err } if err := parseSuppressions(cfg); err != nil { return nil, nil, err } if cfg.Hub_Client != "" && (cfg.Name == "" || cfg.Hub_Addr == "" || cfg.Hub_Key == "") { return nil, nil, fmt.Errorf("hub_client is set, but name/hub_addr/hub_key is empty") } if cfg.Dashboard_Client != "" && (cfg.Name == "" || cfg.Dashboard_Addr == "" || cfg.Dashboard_Key == "") { return nil, nil, fmt.Errorf("dashboard_client is set, but name/dashboard_addr/dashboard_key is empty") } return cfg, syscalls, nil }

其中产生syscall的核心在parseSyscall函数中:

func parseSyscalls(cfg *Config) (map[int]bool, error) { match := func(call *sys.Call, str string) bool { if str == call.CallName || str == call.Name { return true } if len(str) > 1 && str[len(str)-1] == '*' && strings.HasPrefix(call.Name, str[:len(str)-1]) { return true } return false } syscalls := make(map[int]bool) if len(cfg.Enable_Syscalls) != 0 { for _, c := range cfg.Enable_Syscalls { n := 0 for _, call := range sys.Calls { if match(call, c) { syscalls[call.ID] = true n++ } } if n == 0 { return nil, fmt.Errorf("unknown enabled syscall: %v", c) } } } else { for _, call := range sys.Calls { syscalls[call.ID] = true } } for _, c := range cfg.Disable_Syscalls { n := 0 for _, call := range sys.Calls { if match(call, c) { delete(syscalls, call.ID) n++ } } if n == 0 { return nil, fmt.Errorf("unknown disabled syscall: %v", c) } } // mmap is used to allocate memory. syscalls[sys.CallMap["mmap"].ID] = true return syscalls, nil }

其中这个函数在判断配置文件之中是否有制定enable_syscalls或者disable_syscalls以确定需要进行测试的syscall集合。

若没有指定以上两个字段,则默认使用所有的syscall(sys.Calls),其中syscall相关信息存储在syscall.h中:

// AUTOGENERATED FILE #define __NR_syz_emit_ethernet 1000006 #define __NR_syz_extract_tcp_res 10

本文系统(linux)相关术语:linux系统 鸟哥的linux私房菜 linux命令大全 linux操作系统

主题: LinuxUbuntuLinux内核Debian开源FUTIAUUTSU
分页:12
转载请注明
本文标题:Syzkaller:Linux内核模糊测试工具分享
本站链接:http://www.codesec.net/view/561356.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 系统(linux) | 评论(0) | 阅读(49)