syzkaller基本使用
196082 慢慢好起来

前言

这篇文章在前面已经是埋了许多坑了,靠后的几篇文章都提到了我想学syzkaller,但是总有事耽搁以至于拖到了现在。本来没有打算将基本的使用方法写成文章的,但是就是前面的坑过多,如果不写出来的话就会觉得心里膈应。这一篇不会涉及到syzkaller的实现原理,只能默默祈祷不会太水。

一样的,这里也就不多提如何编译了,网上的资料很多。

syzkaller使用

工作原理

经常看我文章的朋友或许看出来了,我不是很愿意将图片上到博客,主要原因还是我没有使用图床所以尽量少的上传图片,所以我一般放到博客的图片都是较为有用的图片。

这里简单提一下上图:

首先syz-manager作为的是syzkallmer的控制中枢,其会启动多个vm实例 ( 图中一个黄色卡片代表一个实例 ) 并进行监视,同时通过RPC启动syz-fuzzer

syz-fuzzer负责引导整个fuzz的过程。第一步,生成input。第二步,启动syz-executor进程进行fuzz。第三步,从被fuzz的内核的/sys/kernel/debug/kcov获得覆盖 ( coverage ) 相关信息。最后,通过RPC将新的覆盖送回syz-manager

syz-executor负责执行单个输入。

配置文件

在正式使用前我们需要为其额外编写配置文件

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": "/media/psf/pwn/fuzz01",
"kernel_obj": "/home/parallels/linux-5.11",
"image": "/media/psf/pwn/fuzz01/bullseye.img",
"sshkey": "/media/psf/pwn/fuzz01/bullseye.id_rsa",
"syzkaller": "/home/parallels/fuzz/gopath/syzkaller",
"procs": 8,
"type": "qemu",
"vm": {
"count": 4,
"kernel": "/media/psf/pwn/fuzz01/bzImage",
"cpu": 2,
"mem": 2048
}
}

${WORKDIR}是需要替换为所需的工作目录,之后生成的crash文件将会位于其中。${LINUX}为Linux源码目录。${IMAGE}为方才制作的系统镜像与密钥文件目录。${GOPATH}替换为安装Syzkaller所使用的GOPATH。

启动syzkaller

启动就很简单了,直接输入

1
./bin/syz-manager -config=config.json # config.json为前面提到的配置文件

启动成功后通过访问localhost:56741即可获取到syzkaller的状态

syzlang编写指南

如果只是上述流程中那样一直挂着可以出洞的话,大公司的服务器可比我这电脑好得不知道哪去了,所以我们需要人工配置系统调用模板,以有针对性的进行漏洞挖掘。

syzkaller 使用它自己的声明式语言来描述系统调用模板,在安装目录下的 docs/syscall_descriptions.mddocs/syscall_descriptions_syntax.md 中有着相关的说明。

我们需要使用 syzlang 来编写特定的系统调用描述文件(也叫规则文件),syzkaller 会根据我们的描述文件有针对性地进行 fuzz。

syzlang语法

1
2
3
4
5
6
7
8
syscallname "(" [arg ["," arg]*] ")" [type] ["(" attribute* ")"]
arg = argname type
argname = identifier
type = typename [ "[" type-options "]" ]
typename = "const" | "intN" | "intptr" | "flags" | "array" | "ptr" |
"string" | "strconst" | "filename" | "glob" | "len" |
"bytesize" | "bytesizeN" | "bitsize" | "vma" | "proc"
type-options = [type-opt ["," type-opt]]

上述即是syzlang的语法结构,这简单介绍一下上面符号的含义。

"" 表示这个符号内的内容应按照其原样进行匹配

| 表示的含义大差不差,意味取左右两边皆可

= 表示左边的表达式应为右边的形式

[] 表示取其内部的一个值

* 表示和正则一样,即为0个或多个

所以其写法为,syscallname + 多个arg组成。arg由标识符identifier与操作类型type构成。type由操作类型名typename以及对应类型的类型选择type-options组成,最后根据typename的不同,type-options跟一个或两个type-opt

根据上述规则可以很轻松的理解Google官方提供的一个模板

1
2
3
4
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_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

这里注释的写法就和python一样

1
# aaaa

文件包含的写法基本就和C一样

1
include<linux/fs.h>

参数

前面中提到了参数的形式

1
arg = argname + type

其有一个参数名加一个操作类型构成。下面根据例子详细讲一下

1
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)

上述是内核中read系统调用的声明,当我们使用 libc 的 wrapper 进行 read 系统调用时,形式如下:

1
2
3
4
unsigned int 	my_file_fd = open("/dev/test", O_RDONLY);
char my_buf[114514];
size_t my_count = 114514;
read(my_file_fd, my_buf, my_count);

在上述例子中fd、buf、count即为argnamemy_file_fd、my_buf、my_count即为type

那么在syzlang编写系统调用时就应该写为fd my_file_fd。( 这里假设my_file_fd已定义为resources )

类型

前面提到了arg是由argname type构成,也提到了type是由typename type-options构成。

1
type = typename [ "[" type-options "]" ]

首先这里详细提一下类型名,即该 type 的类型,例如 C 当中的int、char、void 等等。

常规的类型名包括:( 直接搬!)

  • opt:这是一个可选参数(例如 mmap 的 fd)

其余 type-options 是基于特定 type 的,如下:

  • const:整型常数
    • 类型选项:
      • 值(value):例如 0
      • 基础类型(underlying type):intNintptr 之一
  • intNintptr:一个有着特殊含义的整型,下文会进行详细说明
    • 类型选项:
      • 可选范围区间:例如 "1:100" 表示取值值的区间为 [1, 100]
      • 可选参数
  • flags:值的集合
    • 类型选项:
      • 对 flags 描述的引用
      • 基础整型类型:例如 int32
  • array:一个可变长/固定长度的数组
    • 类型选项:
      • 元素的 type
      • 可选长度区间:例如固定长度 "5" 或者长度范围 "5:10"(包括边界)
  • ptrptr64:指向一个对象的指针
    • 类型选项:
      • 方向:inoutinout
      • 对象的 type
    • 无论对象指针大小如何,ptr64 永远为 8 字节
  • string:一块有着 0 终止符的内存缓冲区
    • 类型选项:
      • 常量字符串/对字符串的引用
        • 前者:例如 "foo"作为常规字符串进行解析,或者deadbeef作为4个 16 进制字节进行解析
        • 后者:若是特殊类型 filename 则会生成文件名
  • stringnoz:一块没有 0 终止符的内存缓冲区
    • 类型选项:(同 string)
  • glob:匹配目标文件的 glob(?)模式
  • fmt:一个表示一个整数的字符串
    • 类型选项:
      • 格式与值:前者可取值为 dechexoct;后者可以是一个 resource、int、flags、const 或 proc
    • 最终的结果通常是固定尺寸的
  • len:另一个 字段 的长度(对于 array 而言为元素的数量)
    • 类型选项:
      • 对象的 argname
  • bytesize:与 len 类似,不过单位是字节
    • 类型选项:
      • 对象的 argname
  • bitsize:与 len 类型,不过单位是比特位
    • 类型选项:
      • 对象的 argname
  • offsetof:一个 字段 在其 parent struct 中的偏移
    • 类型选项:
      • 字段
  • vmavma64:指向一组页的指针(用作 mmap/munmap/mremap/madvise 的输入)
    • 类型选项:
      • (可选)页的数量或页的范围:前者例如 vma[7],后者例如 vma[2-4]
    • vma64 的长度恒为 8 字节
  • proc:单个进程的整型(详见下面的描述)
    • 类型选项:
      • 值的区间的起始
      • 每个进程的值的数量
      • 基础类型
  • text:特定 type 的机器码
    • 类型选项:
      • 代码类型:x86_real, x86_16, x86_32, x86_64, arm64
  • void:type with static size 0
    • 通常在模板以及可变长(varlen)联合体中使用,不能用作系统调用的参数

结构体/联合体/指针 中使用时,flags/len/flags 的构成中尾部还可以跟着 type type-options

接着提一下类型选项

1
type-options = [type-opt ["," type-opt]]

形式如上,从一开始的语法规则来看type-options对于type即为可选项,也可以同时拥有多个type-options,同样根据前面的语法规则可以看出来要使用type-options是应如下例一样

1
flags flags[open_flags]

上面这个例子的解析,我们这个参数名为flags参数,输入的类型为flags,其类型选项为对一个flags描述open_flags的应用,即为取open_flags中的值。

1
open_flags = 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

其中的open_flags定义的内容如上,这些值可以通过include语句从内核源码中被包含进来。

系统调用

在前面提到了系统调用的模板如下

1
syscallname "(" [arg ["," arg]*] ")" [type] ["(" attribute* ")"]

其中除了attribute都已经做过一定解释了,这里在对其做分解分析。

1
2
3
4
open(file ptr[in, filename], flags flags[open_flags], mode flags[open_mode])

open_flags = 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
open_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

这里对open系统调用,存在以下三个输入:

  • file 参数:一个指针类型,其 type-opetions 的第一个为 in,意为由该指针指向特定对象,第二个为 filename,为特殊的 string 对象,对于 filename,syzlang 会进行文件生成,将文件名作为输入
  • flags 参数:一个 flags类型,其 type-optionsopen_flags ,意为从我们定义的 flags——open_flags 中取值
  • mode 参数:一个flags类型,其 type-optionsopen_mode ,意为从我们定义的 flags——open_mode 中取值

一般来说,系统调用都会存在返回值,在syzlang中可以忽略掉返回值也可以选择接收,如果选择接收则应形如上式在系统调用后面加上一个type,例如open系统调用会返回一个文件描述符,若是我们像将其返回的的文件描述符存到一个变量中如test_fd,我们应当写成如下形式:

1
2
3
4
open(file ptr[in, filename], flags flags[open_flags], mode flags[open_mode]) test_fd

open_flags = 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
open_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

既然文件描述符中已经存在我们的变量,那么在后续也是可以继续使用的,如下:

1
close(fd test_fd)

随后就是前面提到的,在type后还有一个可选参数attribute,其有以下可选值:

  • disabled:该系统调用将不用于 fuzzing;这个属性通常用于临时禁用某些系统调用,或者禁用特定的参数组合
  • timeout[N]:系统调用在默认值以外的额外的超时时间,单位为毫秒(ms)
  • prog_timeoout[N]:若一个程序包含了该系统调用,则该属性为整个程序的执行的超时时间,若存在多个定义了该属性的系统调用则取最大值
  • ignore_return:在回退反馈中忽视这个系统调用的返回值;用于不返回固定的错误码(例如 -EFAULT)而是返回其他值的系统调用
  • break_returns:忽略回退反馈中程序中所有后续系统调用的返回值

接着就是系统调用的变种(variants)可以在系统调用名后面使用 $ 符号进行额外的指定

1
2
socket$inet_tcp(domain const[AF_INET], type const[SOCK_STREAM], proto const[0]) sock_tcp
socket$inet_udp(domain const[AF_INET], type const[SOCK_DGRAM], proto const[0]) sock_udp

例如socket系统调用可以用于创建很多类型的socket,上述定义了两种不同的变体。而变种的作用主要是区分syscall,类似于别名的效果。

整型

整型也是一种 type,其可选项为 int8int16int32int64,表示相应大小的整型,intptr 用以表示一个指针大小的整型,对应 C 语言中的 long,通过添加 be 后缀表示这个整型存储为大端序。

可以用 int32[0:100] 或 int32[0:4096,512] 的格式为一个 512 对齐的 int 指定一个整数的值范围。

1
read$eventfd(fd fd_event, val ptr[out, int64], len len[val])

结构体

syzlang中同样可以存在结构体,联合体这一说,既然前面已经提到了这么多,这里就放一个它的一个语法结构

1
2
3
structname "{" "\n"
(fieldname type ("(" fieldattribute* ")")? "\n")+
"}" ("[" attribute* "]")?

可以看出来结构其实和c的很类似,定义一个structname,然后内部成员包含了fieldname / type以及后面可以添加(fieldattribute),这里的属性与前面的是有一定差距的,这里的属性只有方向in / out / inout

最后可以看到在结构体结尾可以被[attribute]来添加属性,这里存在以下属性:

  • packed:该结构体不同字段之间没有 padding(例如 C 中有一个结构体 struct T{int a; char b;};,char 为 1 字节,int 为 4 字节,那么该结构体便会对 4 字节对齐,在其两个字段之间就会有 3 字节的 padding)
  • align[N]:指定该结构体对 N 字节对齐,padding 的内容并未指定(通常为0)
  • size[N]:结构体被填充到指定的大小 N
1
2
3
4
5
test_struct {
field0 const[1, int32] (in)
field1 int32 (inout)
field2 fd (out)
} [packed]

与结构体类似,联合体的语法结构如下:

1
2
3
unionname "[" "\n"
(fieldname type ("(" fieldattribute* ")")? "\n")+
"]" ("[" attribute* "]")?

与前面的区别主要是最后的[attribute]中的可选性的区别,这里主要有:

  • varlen:联合体的大小可变(为指定的字段的长度),若未指定则该联合体大小为其最大字段的大小(类型 C 语言)
  • size[N]:该联合体被填充到指定的大小 N

资源

资源的定义是作为一个系统调用的输出作为另一个系统调用输入的值。在前面的时候其实已经是提到过了,使用open系统调用打开的文件描述符再交由close系统调用进行关闭,如果要实现这样的效果则需要声明一个资源。资源的形式如下:

1
"resource" identifier "[" underlying_type "]" [ ":" const ("," const)* ]

其中的identifier即位其标识也就是名字,后面的underlying_type 可以是 int8, int16, int32, int64, intptr 或者是另一个资源。常量集合可以作为可选参数,表示该资源的特殊值(比如说 0xdeadbeef),特殊值偶尔被用作资源的值,若未指定特殊值,则会使用特殊值 0

1
2
3
4
5
6
7
resource fd[int32]: 0xffffffffffffffff, AT_FDCWD, 1000000
resource sock[fd]
resource sock_unix[sock]

socket(...) sock
accept(fd sock, ...) sock
listen(fd sock, backlog int32)

资源不一定是系统调用的返回,他可以像其他任何数据一样被使用。

1
2
3
4
5
6
7
8
9
resource my_resource[int32]

request_producer(..., arg ptr[out, my_resource])
request_consumer(..., arg ptr[inout, test_struct])

test_struct {
...
attr my_resource
}

对于更为复杂的生产者/消费者场景,字段属性也可以被利用,例如:

1
2
3
4
5
6
7
8
9
10
resource my_resource_1[int32]
resource my_resource_2[int32]

request_produce1_consume2(..., arg ptr[inout, test_struct])

test_struct {
...
field0 my_resource_1 (out)
field1 my_resource_2 (in)
}

类型别名

这个的形式很类似于C语言中的typedef

1
type identifier underlying_type
1
2
3
4
5
6
7
8
9
10
11
type bool8		int8[0:1]
type bool16 int16[0:1]
type bool32 int32[0:1]
type bool64 int64[0:1]
type boolptr intptr[0:1]

type fileoff[BASE] BASE

type filename string[filename]

type buffer[DIR] ptr[DIR, array[int8]]

在布尔中的取值返回就是0和1所以可以使用intN[0:1]来达到效果,不过在后面每次使用会使的易读性大打折扣,所以可以定义为bool。

类型模板

其形式如下:

1
2
3
4
type optional[T] [
val T
void void
] [varlen]

其在的简单的用法为

1
2
3
4
5
6
7
8
9
type buffer[DIR] ptr[DIR, array[int8]]
type fileoff[BASE] BASE
type nlattr[TYPE, PAYLOAD] {
nla_len len[parent, int16]
nla_type const[TYPE, int16]
payload PAYLOAD
} [align_4]

syscall(a buffer[in], b fileoff[int64], c ptr[in, nlattr[FOO, int32]])

长度

你可以使用关键字 lenbytesizebitsize 来指定结构体当中特定字段的长度,若是 len 的参数为一个指针,则其取值为指针所指对象的大小,若要表示一个 N 字节的字中字段的长度,则应当使用 bytesizeN,其中 N 的取值可以为 1、2、4、8。

1
2
3
4
5
6
write(fd fd, buf ptr[in, array[int8]], count len[buf])

sock_fprog {
len len[filter, int16]
filter ptr[in, array[sock_filter]]
}

在上面的write系统调用中,第三个参数的类型为len[buf],这里的含义表示buf的长度。在 sock_fprog 这个结构体当中,我们给其字段 len 设置的值为其 filter 字段的长度,类型为 int 16。

1
2
3
4
5
6
7
8
9
s1 {
f0 len[s2] # length of s2
}

s2 {
f0 s1
f1 array[int32]
f2 len[parent, int32]
}

若要表示父类的长度,可以使用 len[parent, intN],若要在结构体互相嵌入时表示更顶层的父类的长度,可以指定特定父类的类型名称。

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
s1 {
a ptr[in, s2]
b ptr[in, s3]
c array[int8]
}

s2 {
d array[int8]
}

s3 {
# This refers to the array c in the parent s1.
e len[s1:c, int32]
# This refers to the array d in the sibling s2.
f len[s1:a:d, int32]
# This refers to the array k in the child s4.
g len[i:j, int32]
# This refers to syscall argument l.
h len[syscall:l, int32]
i ptr[in, s4]
}

s4 {
j array[int8]
}

foo(k ptr[in, s1], l ptr[in, array[int8]])

这里可以看出len也适用于更为复杂的路径寻址。

进程

proc 类型用于表示每个进程的数值。这样做的目的是为每个执行者提供一个单独的数值范围,这样他们就不会相互干扰。

最简单的例子是一个端口号。proc [20000, 4, int16be] 类型意味着我们要从 20000 开始生成一个 int16be 整数,并为每个进程分配 4 个值。因此,执行者编号 n 将得到 [20000 + n * 4, 20000 + (n + 1) * 4] 范围内的值。

整型常量

整型常量可以指定为十进制、0x 开头的十六进制、用单引号 ' 包裹的字符,或者从内核头文件中提取出来的由 define 定义的常量(比如说 O_RDONLY)。

1
2
3
4
5
6
foo(a const[10], b const[-10])
foo(a const[0xabcd])
foo(a int8['a':'z'])
foo(a const[PATH_MAX])
foo(a ptr[in, array[int8, MY_PATH_MAX]])
define MY_PATH_MAX PATH_MAX + 2

其他

描述文件还包括用以进行内核头文件包含的 include 指令,用以包含内核头文件目录的 incdir 指令,以及用以设置常量的 define 指令。

syzkaller executor 还定义了一些伪系统调用,我们可以在描述文件中使用这些伪系统调用。这些伪系统调用被扩展为 C 代码,可以执行用户自定义的一些操作。

尝试捕捉简单溢出洞

syz-extract

第一步是从内核源文件中提取符号常量的值:syz-extract 会根据 syzlang 文件从内核源文件中提取出使用的对应的宏、系统调用号等的值,生成 .const 文件

syz-sysgen

第二步便是将描述翻译成 Golang 代码:syz-sysgen 通过 syzlang 文件与 .const 文件进行语法分析与语义分析,生成抽象语法树,最终生成供 syzkaller 使用的 golang 代码,分为如下四个步骤:

  • assignSyscallNumbers:分配系统调用号,检测不支持的系统调用并丢弃
  • patchConsts:将 AST 中的常量替换为对应的值
  • check:进行语义分析
  • genSyscalls:从 AST 生成 prog 对象

实战流程

首先编写一个具有漏洞的驱动

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
104
105
106
107
108
109
110
111
112
113
114
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>

#define DEVICE_NAME "intel_rapl_msrdv"
#define CLASS_NAME "intel_rapl_msrmd"
#define DEVICE_PATH "/dev/intel_rapl_msrdv"

static int major_num;
static struct class *module_class = NULL;
static struct device *module_device = NULL;
static struct file *__file = NULL;
struct inode *__inode = NULL;

static int __init test_init(void);
static void __exit test_exit(void);

static int test_open(struct inode *, struct file *);
static ssize_t test_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t test_write(struct file *, const char __user *, size_t, loff_t *);
static int test_release(struct inode *, struct file *);
static long test_ioctl(struct file *, unsigned int, unsigned long);

static struct file_operations test_op =
{
.owner = THIS_MODULE,
.unlocked_ioctl = test_ioctl,
.open = test_open,
.read = test_read,
.write = test_write,
.release = test_release,
};

static int test_open(struct inode *__inode, struct file *__file)
{
return 0;
}

static ssize_t test_read(struct file *__file, char __user *user_buf, size_t size, loff_t *__loff)
{
return 0;
}

static ssize_t test_write(struct file *__file, const char __user *user_buf, size_t size, loff_t *__loff)
{
char *param;

param = kmalloc(512, GFP_KERNEL);
copy_from_user(param, user_buf, 4096);

return size;
}

static int test_release(struct inode *__inode, struct file *__file)
{
return 0;
}

static long test_ioctl(struct file *__file, unsigned int cmd, unsigned long param)
{
return 0;
}

static int __init test_init(void)
{
major_num = register_chrdev(0, DEVICE_NAME, &test_op);
if (major_num < 0)
return major_num;

module_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(module_class))
{
unregister_chrdev(major_num, DEVICE_NAME);
return PTR_ERR(module_class);
}

module_device = device_create(module_class, NULL, MKDEV(major_num, 0), NULL, DEVICE_NAME);
if (IS_ERR(module_device))
{
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
return PTR_ERR(module_device);
}

__file = filp_open(DEVICE_PATH, O_RDONLY, 0);
if (IS_ERR(__file))
{
device_destroy(module_class, MKDEV(major_num, 0));
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
return PTR_ERR(__file);
}
__inode = file_inode(__file);
__inode->i_mode |= 0666;
filp_close(__file, NULL);

return 0;
}

static void __exit test_exit(void)
{
device_destroy(module_class, MKDEV(major_num, 0));
class_destroy(module_class);
unregister_chrdev(major_num, DEVICE_NAME);
}

module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("196082");
MODULE_INFO(intree, "Y");

可以看到这里存在明显的堆溢出。

1
2
3
4
5
6
7
8
obj-m += vuln_device.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := ~/linux-5.11
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

通过上述的Makefile编译

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

resource fd_111[fd]

open$test(file ptr[in, string["/dev/intel_rapl_msrdv"]], flags flags[vuln_open_flags], mode flags[vuln_open_mode]) fd_111
read$test(fd fd_111, buf buffer[out], count len[buf])
write$test(fd fd_111, buf buffer[in], count len[buf])


vuln_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
vuln_open_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

编写syzlang随后放入到sys/linux/目录下。

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

编译 syz-extract 和 syz-sysgen

1
./bin/syz-extract -os linux -sourcedir "~/linux-5.11" -arch amd64 vuln_test.txt

通过syz_extract生成对应的const文件,随后重新运行

1
2
3
./bin/syz-sysgen
make generate
make

随后配置config文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"target": "linux/amd64",
"http": "127.0.0.1:56741",
"rpc": "127.0.0.1:0",
"sshkey": "/media/psf/pwn/fuzz01/bullseye.id_rsa",
"workdir": "/media/psf/pwn/fuzz01",
"kernel_obj": "/home/parallels/linux-5.11",
"syzkaller": "/home/parallels/fuzz/gopath/syzkaller",
"sandbox": "setuid",
"type": "isolated",
"enable_syscalls": [
"open$test",
"read$test",
"write$test"
],
"vm": {
"targets": [
"127.0.0.1:10021"
],
"pstore": false,
"target_dir": "/home/fuzzdir",
"target_reboot": true
}
}

这里写法与上面略有不同,不过看一下是挺好理解的。其中的enable_syscalls主要是限制只允许调用什么系统调用。

1
sudo ./bin/syz-manager -config=/media/psf/pwn/fuzz01/config.json

最后通过上述命令启动fuzz。

Descriptions Count Last Time Report
KASAN: slab-out-of-bounds Write in test_write 1 2023/11/10 16:31 reproducing

当跑出一个crash会出现如上表格,其一个含义可以看到是在test_write中触发了一个slab-out-of-bounds,也就是越界。然后最后的report显示的状态是正在尝试重现这个crash运气不佳的是这个并不能重现,如果能够重现则会显示其对应的结果,如果一个品相好的漏洞其report会显示has C repo表示有该crash的C语言代码。

1
2
r2 = open$test(&(0x7f0000000380), 0x10000, 0x2)
write$test(r2, &(0x7f00000003c0)="da0f433ee1bffca3085037fc1dcce3ed4bce4ba262cfd2069703973bbd42cb061bc099e240f5a5645d2d34a9cb270151cd6edcdd7e26dbb7ff57eae5352b1920eb5e2354178f009c8c3b86de024a06cd9c24386cf0db616ae8ef502687bc61db26bb28ff8f81500321e73feb9a639fbdf287fa98c6a0e6de0510c675e920cf4e5dc6e2cb9fe4e4be66ddb29bf45283b0f9359a0028e4ffc65c54ec966e16113a6b49e3ba85f9f29aa297e978a5944426f644de7a37cfd242563e97b2ec55f3633fe9569b776c3a299ec199153afd8187b798f515ae9fdf30ebda4d9bfad062514718bef675a6528e2879aeb56ad583077ffe63f4a0e994ef211deb84f15379f6b1fa2067cf9e4d4bd7bfc5ed7ec8133ae06be457e498087f306d661684531a7f353dfeb8982d89a1a6a3554b5bbe27379c20c9bae3a5672ea0176721d8e38fbb656064062ff431efac310c5a3dc36d754b2407a82d63e152ecefa945219d0149fd17ae8c37b563ae571108ea67e7642da91f629d797380411d153b68e2ce1050f75c8be7ee1f4c626127223ee46d61986db8685710a79b8cf636570c5a522a2d83971fb5fa457326030a2899e3acfb54a4764cb8e46892c49e4ca23b633384886260f23e6ca06522928c82364950d8e24feab8c02b2442a1a102eb4374c721b6c59f66ad306ce03215d5f4db91151b0690669a09b5b59f249b64172a0009be02dd91a8c8733f9b9a64c72b3342c1c7e770e52fe6c31fff0d856ac0de9344555449e2c817f27363ef1694d10c2dd92b15f0698b003f586444a1b529c2ec2d338b64ba2bfa2ecd285b4631f8df46969a646a291c7e967c323589e49e5e01b29547848f62e9672a07e9a223ebc612058a4d1849ba105b09bbfbb2ee4dc62d32ee80ed4302a2921ed7de6f51bf068fe420debed43a0a0ecad74bba4b0d576afe8e4df6900d7fe7104427c87c1559576020c751bc040f905caf5c155331af5645872210cc76dc3a356bb3abdd373c9e02f1770f5df9b9de527e6a3359016c56199e35a832b658a410e2a97f549edf96f8781aeb5fa08e922f333d86026c62f1a24f865074202002a85042bb85ddbcbf4430b446bb0cdf984682f1d8fc7a1889352407d1c781a653fe489ece1e7ffbdc15cb0cec3ff776b7d1a34fc9642fcbdc8f2a732dae5f00a692e78dbd4f6a94986f35b38ac8aecbe3c2bc4a7932b5e933c08c4c5f8b90eb1ce3c80af44fd11d2715ec76b369e6468170a93757083a701a2d84c08a1602533ee4ab629f0839a984546a8aa1b7fee547fa4d78f6d67397ea1e9f853e22da69fe40ba837cc6a64a1ad873d971fe383ae2c862301672c236f758aa1ede00483cc4f40b066e1e3e9997ba723141726af050ba7789c9e7451a0baf52e9d49b44e8d92ab5df925598e6d9b8b10bee1089c6ff3f9dd9dfd660f832c025fcb20a159bd305563647ae9c4f0fedf7f3fd9d2d0589956cee8f5ec040f043cd931539fa7880fd69147e5a32c468611774f18987adca95a0eaa0b962f4c2fdcb91ac52ec28e39704b924bd3fc0e1f796bbd9c8b815b646b682f1d4796b9e567112453291220a7f5d2012a565f7abfba7fd318e72530b1cf04d74b42e64627c6936587c81d9bae45fc0cc2815674946427529151641aba2284c17c8ca26d7b8e0cf45ccb12db3705693a9eeb5b92d84830aae5aa170e6317544681b24a8fcb01ff769d3e89f65e9b205540239d43b81a6c2679810a3467ff0bef5bce56bcb853b60744315fc0f2899e6db88edd5ff2c8d11bd0ea553b3b1766f2ba0ac02e46805a89cfb8f71d743666556f530b759528877b8b71aa6f882616c8a795ef295961007c07479673af54818ccc79b5e43e2bbdcf00ff3d9cbfe679c6e7fc688dd0797c0a7a7e22cbdad3f1f3e3e99f8ed3b17b7f04de17e5817d48abb90203163317ee630fae9bbbe06887208ce9138b0ce0bb8ce511229698c71351c8cbdef2dcf0279c4b334c55254babf6bf4b00a7a2e89557550f338ab5e332824fbf4f1f774b970248916bdd4922fe69c825d62950c6b6a98f49795ef6fbacfc65fd8e879bad0e2963f46be54ae3c699339384cbde926d360501a6a0f0f21090d8f735301c04230d969ec4e88f1e18fc62e656ac03610dc1772295df75ca08fbd4e1fd7f39eb2cb349878a4936d35f335d1eac7da574276a6d43672393a747dd7182c27100370667700f3de9b0389cf8ac812f5ad07c7517a2a64ca22b17f120011e2a70d95cb8f7835f7cc0481defaf20665a0418e1385d0306ee26c5a861ef69e37ccb91237c9c106e9b483ae4e1c3957e02f1f5b247c835dff73502bf518d1c80e827c7d6221f1c8ca9226d2a1ef904f5025504544f4024c60ff5bf23f5b79cee9720745bf36a86179d7d0af1758e663bcd6e98d2f9339dc9ecc038a1521bb16edfbb9071fb09c2917a2144c90cd752b5683e321b70271089252da45fbb01ec19e9bc9261f7f50e129d44f6ecfed3222f7bc9943356b46f0aa97f1b03ef95890543c22a00655a8c99fdccd4ef04a7d72084d5c77a96025d5095ed608b19d0f5a7ae778fa4eab4a8f739b5ceb3fc97f11ec67a9b6c6d7ff88a4395e8bc27e8b7034de7be8683c958f2773f2bb985a9bede7e1d39aa7899e1e88aa7d7af00a9a9f084a094636af8160a14626d2c0dc142656911e418aef80dcace137e41ad996984975c62f63e5f0fdc53a699944c2e06d275264375a5330e4a295918e6f0f6e57d82e2de705eb9ce5791eac9a117d9912c6d6ae1ffdb08389dfd1eea539d2e9069520076873608d22975dd38bfd1df04c4f9dcac4bfed02af53802f8582ac59321a8453e69a7cb5a5cee4a6940466a5cecb19c9527c94d0441ad4328297f3e92714ee3153b459f45cad7e2a2197da7bb5f3ea498e0b9e49405e8f4c881eb6a9555b82072d5a3fa06b935d0c127244d747a637d09fceaffc64f983aa7df54d59f265ed28ebe79f41ebd72c2a401b88adf0daf4158bc47faebfec1fcf934b55a9bac5da808566db2216c21c17b26e25dc8566e3a14bbddd83b8d2db809a5098f5404e67958fa801f073ba48e5c0b4a5a1a98df77d8d81dd1054c7e8ece8ac44d4c21435e830c7d28927b1a61670886116d4f819bd406dd61c0bc7e57933b9071cd1de7fe9e5e8bfb66c3e637f3dc27329c936b47c2ce5f7bd8d96e889c9a4cfe6ffcfcc1b2de5b1b6cc417952d07413fbaa586bc997db9dc916a529d6e6ef7d358f41d35f888825f6491bea834adb7d57a6fdfe7b61e6ed369329b009404352f168b1adf1683fa8c860e272dcdc5593e100d83fa4bf2fcb4832bd6da307c3910675e36f5df01e1e90253948f17a6b286f50afff315e562d8feb83c8dd68397319022dfd951be06d494e9bba6dc0ddc8c8b20821522ae68bb02b749925c94f7d80566cdfdef0f6076cf7542ec7c064d0a9e1a7f71d5b67a88a94ad19d4a73e3f16d68709e0e5ca166d37c8baa384434732bba3e7d08fba66f012d22e21077a23001f1f0160f4efee36be51edb83910cd2e7362fdc173d5699b634e75489a16580c733ffce3df11d5f482c7ff82cd8c843c0d513404758016955862d308c1e0e36ad3d98652cf837782423451c4e8638990fb65eff25c380caed61a0a8fbd1cc484ecc70467efc17310cef1fd3c075d339f40067b71b15541571be112f85f89afc6fc842b263e3ce57116176402a76e695fd28eacc2da85ee62aafa6bcb896d554f843ee152e6b84b8a3c6aaeea7443b5290cec768ba0ba2aada7dee7438b76861d2434104fac6a86f7448981ab970d27c5268d3d7eac9ff26b657affacbd0dbdc30ecfd0e305fba89461c1d85aef2feb30f0cb9c94a46578d55957984deed9ec02f796246c366adc7cb32eeae18f2d56a86702c04c1dd172a70899d28808d2050d7cdaf78c6975c5dd56ab3435fc40b8019a6bbd8964bc212abb6ab3e82e12604650433964cb48093e46fe79439022f6b862ed96c6bfa59f834a28ae857df0805b6e7b4ed5db1bee79ed3999485131468ebaa043304ce67f01a6b67ac7e0d744326e0c21a857620b0e999534e36c72855a1f674ec301dc06fc1640ee4af225a3f8a1a6ed344bff7b377e957eb6a606c4fb4d82ba4238d3eedeb4ec74ee2dc7341edab9dedfcb49828b901abac4991f5886f663d8d07e77b2b8b4b12c896f28149ca896eb2d9dcb2f18a5ad6c7918e57d0e730d012f39328a94172a9f41946348ed5eed60b87512aec77114c4582ab7a29f0a19e84e52c11b07f14d53b3310cadac1546e0b042243e6afb376192ecd87be7b574f852f411ff5a8a6f7fdb743b568e89a34ba3547b2959f3760e382b75441bea3713c81e31391b10751f80bdb42216d7644d8ab5aedb5ed437e0e7954a1ef4bd6c3cc2aa45b652c86281345eaa3dbaceb684224222db02c9a638475364135284a21097286ef864706c9df5c350dc5933a5ba1d46795f49316705d7ae7cb59f5e91994a6c57ae525189243cd36b8ab1c351eaaa04ace3bccfb1287c4c60667524562b8f350da09e6ca212194d49e6550b2289170c64e8c9d019b4d4f7d888e62b5458c5b28b2eda131079f5e66d35014d5460e7e785bcc7ffc6141384498c55dad57901729fc056a5595850ceb1d5c6d31a6894b8d8b85f9173f9ebc158d2d43952c48d4f3590a4e4fae41ea0aa9c8ab78557fd4e9ca5611b62ca6f0b5f6c46592537f008cd5083b08bc3fd1218773cb10fa44933fbaeed4afa57afae135c8a1898b06a5c2855e3f8a6bfe8ba9a589bceb5f9a5376f220cfdfc977e7416abd7258b40e9df634d966d7d8e665ab3ab6495bd07dcdf32ac3f62eaaf2e18ba093f46c96b7ac2e6fca971dfec82c72fd7241aac6774655ad19b8bebd1766e1afa44510682369083ae89734a7809539193d48146fe83f2401b3a33a0857f8cd2a305bc59e58bf41360fe0abe63a6f99bd12a8d1988a3c52dc3ac7091c71ffbab698284079799bbd66370b473b542dab968670fb8b4444af52b5070601df1702738cb29aceae70c6b916d11bdfc6814d5f57a6b257f62587f4e35525074fc031db80f98b9b59130d831bd8bbaa45ed2544dcb34badeac02b794c19875e3edb4b4b78156995df60b4f03a03a9eae88bc5bc071d0f948f38f9a92511b5939acc135820d10c708edb737c9ad7c7dd80cb9cba5347a712e946409bd400c7bb5badc9ecf969ca886260e184ebcb5826b6de23bd0e0a3935321299d45d0e151c782cde6239490566f11b91bac99fd0d202f20abd4c8fad629c4596b857730ac92cc6e67e3177692dd27f1eaa48ca6d19649d0692bd6ab77772c04535c91660f961222ae3a2fbb2663f4fb737f153595ce9a8b865c2dd36eb19b459e933322ddef02a2412e8dbd3b48e951eb9497f3837a6a3b567c43edb3c4f74bf5356187c591f4419dbc21cec3eb31ed865a6146cb455b6a381f0663f8a09c16eda030839fce28a85faa27a0adbb504cc20e3a1af0c6b29f8a7a8b9cbf2e16cfc4ae9413338a6fe08e30f9c495869828203e774cff77da575ec0399368c5568a5d98e51047ece46b2612fe5ab2613761b58d6dcbd079c7d253db81a540f2b921bd0599b70c3119803fc5e97954feaf4a7f813d77a03a811d2582f14989fca781dc28aa96958b9546cc83fa543bc00928a602f3f4be13ec7f1acb78d57e6ebd599e2a077656ffaf04d9a971a017f2391301fdcd7053925d41f9d355dc229ac94fcc0f0b01070b3cb485cbae3356a5421ff428067e9be643933615b0d3bcbe3a2a6d25", 0x1000)

上面是截取的log文件,其中会记录系统调用,调用的参数等一系列信息。

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
==================================================================
BUG: KASAN: slab-out-of-bounds in instrument_copy_from_user include/linux/instrumented.h:135 [inline]
BUG: KASAN: slab-out-of-bounds in _copy_from_user+0x66/0xd0 lib/usercopy.c:15
Write of size 4096 at addr ffff88810686f800 by task syz-executor/3452
CPU: 0 PID: 3452 Comm: syz-executor Not tainted 5.11.0 #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
Call Trace:
__dump_stack lib/dump_stack.c:79 [inline]
dump_stack+0x9c/0xcf lib/dump_stack.c:120
print_address_description.constprop.0+0x1a/0x140 mm/kasan/report.c:230
__kasan_report mm/kasan/report.c:396 [inline]
kasan_report.cold+0x7f/0x10e mm/kasan/report.c:413
check_memory_region_inline mm/kasan/generic.c:179 [inline]
check_memory_region+0x17c/0x1e0 mm/kasan/generic.c:185
instrument_copy_from_user include/linux/instrumented.h:135 [inline]
_copy_from_user+0x66/0xd0 lib/usercopy.c:15
test_write+0x4f/0x70 [vuln_device]
vfs_write+0x1bf/0x760 fs/read_write.c:603
ksys_write+0x100/0x210 fs/read_write.c:658
do_syscall_64+0x33/0x40 arch/x86/entry/common.c:46
entry_SYSCALL_64_after_hwframe+0x44/0xa9
RIP: 0033:0x7f1a044ec96d
Code: c3 e8 17 32 00 00 0f 1f 80 00 00 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f1a0325bbf8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007f1a04628f80 RCX: 00007f1a044ec96d
RDX: 000000000000003f RSI: 0000000020001580 RDI: 0000000000000004
RBP: 00007f1a0454a4af R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fff371812bf R14: 00007fff37181460 R15: 00007f1a0325bd80
Allocated by task 3452:
kasan_save_stack+0x1b/0x40 mm/kasan/common.c:38
kasan_set_track mm/kasan/common.c:46 [inline]
set_alloc_info mm/kasan/common.c:401 [inline]
____kasan_kmalloc.constprop.0+0x84/0xa0 mm/kasan/common.c:429
test_write+0x3f/0x70 [vuln_device]
vfs_write+0x1bf/0x760 fs/read_write.c:603
ksys_write+0x100/0x210 fs/read_write.c:658
do_syscall_64+0x33/0x40 arch/x86/entry/common.c:46
entry_SYSCALL_64_after_hwframe+0x44/0xa9
The buggy address belongs to the object at ffff88810686f800
The buggy address is located 0 bytes inside of
The buggy address belongs to the page:
page:00000000a4947c90 refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10686e
head:00000000a4947c90 order:1 compound_mapcount:0
flags: 0x200000000010200(slab|head)
raw: 0200000000010200 ffffea0004335180 0000000300000003 ffff888100041280
raw: 0000000000000000 0000000080080008 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88810686f900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ffff88810686f980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffff88810686fa00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
^
ffff88810686fa80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff88810686fb00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
==================================================================

这里就是report给出的是kernel相关的信息,比如函数调用栈,以及寄存器信息等。

至于这里为什么无法重现出我觉得一大原因就是我没对这个驱动载入设置自启动,导致一次kernel panic之后没有重新加载驱动(下班了不搞了!)。其实有kernel pwn基础的朋友读懂前面的log和kernel panic的信息都会特别轻松的,这里不详细说了!

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量