内核漏洞挖掘技术系列(4)——syzkaller(2)
字数 2409 2025-08-05 08:16:32

Syzkaller 内核漏洞挖掘技术深入解析

一、系统调用模板语法

Syzkaller 使用一种特定的系统调用模板语言来描述系统调用接口,基本语法结构如下:

syscallname "(" [arg ["," arg]*] ")"
arg = argname type
argname = identifier
type = typename [ "[" type-options "]" ]
typename = "const" | "intN" | "intptr" | "flags" | "array" | "ptr" | 
           "buffer" | "string" | "strconst" | "filename" | "len" | 
           "bytesize" | "bytesizeN" | "bitsize" | "vma" | "proc"
type-options = [type-opt ["," type-opt]*]

二、编译流程概述

系统调用模板的编译过程分为两个主要阶段:

  1. syz-extract:从模板中提取常量并生成 .const 文件
  2. syz-sysgen:将模板和常量编译为 Syzkaller 使用的代码

syz-sysgen 的四个关键步骤:

  1. assignSyscallNumbers:分配系统调用号,过滤不支持的调用
  2. patchConsts:将 AST 中的常量替换为实际值
  3. check:对 AST 进行语义检查
  4. genSyscalls:从 AST 生成 prog 对象

三、syz-extract 详细解析

1. 主要处理流程

  1. 调用 archFileList 获取系统调用模板文件和架构配置
  2. 调用对应 extractor 的 prepare 函数
  3. 分别对架构和文件调用 processArchprocessFile

2. 关键函数分析

processArch 函数:

  • 调用 ParseGlobParseparseTopRecoverparseTop
  • 解析模板文件为 AST
  • 调用 ExtractConstsCompile 提取常量标识符

Compile 函数:

  1. 调用 createCompiler 初始化内置别名和模板
  2. 调用 typecheck 进行多项检查:
    • checkDirectives
    • checkNames
    • checkFields
    • checkTypedefs
    • checkTypes
  3. 调用 extractConsts 提取常量信息

extractConsts 函数:

提取以下内容:

  • 常量 (consts)
  • 定义 (defines)
  • 包含头文件数组 (includeArray)
  • 包含目录数组 (incdirArray)

具体提取逻辑:

  1. 提取包含目录和头文件
  2. 将定义名称作为常量
  3. 为系统调用名添加前缀(Linux 是 __NR_
  4. 提取 call、struct、resource 等类型参数中的常量
  5. 提取 struct 类型 size 中的常量
  6. 提取 flag 和 resource 中的常量

processFile 函数:

  • 输入文件如 sys/linux/binder.txt
  • 输出文件如 sys/linux/binder_amd64.const
  • 调用 extractcompile 生成常量值

compile 函数:

  1. 使用 srcTemplate 模板将数据解析为源代码
  2. 编译为二进制文件
  3. 处理可能的错误(某些架构未定义的常量)

特殊处理:

  • i386/arm 架构上的 mmap 转换为 old_mmap,修复为 mmap2
  • 最终调用 SerializeConsts 序列化为 常量=值 格式写入文件

四、syz-sysgen 详细解析

1. 主要处理流程

  1. 创建 gen 文件夹存放生成的 go 文件
  2. .txt 文件提取 AST
  3. .const 文件提取常量
  4. 调用 Compile 函数(与 syz-extract 中的调用不同)

2. 关键函数分析

Compile 函数(完整流程):

  1. createCompiler:初始化编译器
  2. typecheck:类型检查
  3. assignSyscallNumbers:分配系统调用号
  4. patchConsts:替换常量实际值
  5. check:语义检查
  6. genSyscalls:生成 prog 对象

genSyscalls 函数:

  • 调用 genSyscall 生成单个系统调用
  • 按系统调用名排序
  • genSyscall 调用 genType 生成返回值,genFieldArray 生成参数
  • 调用 genResources 生成资源
  • 调用 genStructDescs 生成结构体描述

输出处理:

  1. generate 函数将结果输出到 io.Writer
  2. 写入 gen 文件夹下对应架构的 .go 文件
  3. 自动执行 init 函数中的 RegisterTarget
  4. writeExecutorSyscalls 写入配置:
    • executor/defs.h:配置信息
    • executor/syscalls.h:系统调用名和编号

五、关键实现细节

1. 模板解析过程

解析流程:ParseGlobParseparseTopRecoverparseTop

parseTop 函数根据标识符类型调用不同处理函数:

  • % 开头的指令
  • include 包含指令
  • define 定义指令
  • resource 资源定义
  • call 系统调用定义
  • struct 结构体定义
  • flags 标志定义

2. 常量提取策略

提取的常量类型包括:

  1. 系统调用编号(自动添加 __NR_ 前缀)
  2. 结构体大小和字段偏移
  3. 标志位值
  4. 资源定义值
  5. 类型定义中的常量

3. 架构适配处理

特殊架构处理:

  • 多架构编译时的常量过滤
  • i386/arm 的 mmap 特殊处理
  • 不同架构的系统调用号差异

六、总结与后续

本文详细分析了 Syzkaller 系统调用模板的编译过程,包括:

  1. 模板语法结构
  2. syz-extract 的常量提取机制
  3. syz-sysgen 的代码生成流程
  4. 关键函数实现细节

后续研究方向:

  1. Fuzz 过程分析
  2. Crash 复现机制
  3. Generate 和 Mutate 策略
  4. 执行监控和漏洞检测

通过理解这些底层机制,可以更有效地扩展 Syzkaller 的功能,提高内核漏洞挖掘的效率。

Syzkaller 内核漏洞挖掘技术深入解析 一、系统调用模板语法 Syzkaller 使用一种特定的系统调用模板语言来描述系统调用接口,基本语法结构如下: 二、编译流程概述 系统调用模板的编译过程分为两个主要阶段: syz-extract :从模板中提取常量并生成 .const 文件 syz-sysgen :将模板和常量编译为 Syzkaller 使用的代码 syz-sysgen 的四个关键步骤: assignSyscallNumbers :分配系统调用号,过滤不支持的调用 patchConsts :将 AST 中的常量替换为实际值 check :对 AST 进行语义检查 genSyscalls :从 AST 生成 prog 对象 三、syz-extract 详细解析 1. 主要处理流程 调用 archFileList 获取系统调用模板文件和架构配置 调用对应 extractor 的 prepare 函数 分别对架构和文件调用 processArch 和 processFile 2. 关键函数分析 processArch 函数: 调用 ParseGlob → Parse → parseTopRecover → parseTop 解析模板文件为 AST 调用 ExtractConsts → Compile 提取常量标识符 Compile 函数: 调用 createCompiler 初始化内置别名和模板 调用 typecheck 进行多项检查: checkDirectives checkNames checkFields checkTypedefs checkTypes 调用 extractConsts 提取常量信息 extractConsts 函数: 提取以下内容: 常量 (consts) 定义 (defines) 包含头文件数组 (includeArray) 包含目录数组 (incdirArray) 具体提取逻辑: 提取包含目录和头文件 将定义名称作为常量 为系统调用名添加前缀(Linux 是 __NR_ ) 提取 call、struct、resource 等类型参数中的常量 提取 struct 类型 size 中的常量 提取 flag 和 resource 中的常量 processFile 函数: 输入文件如 sys/linux/binder.txt 输出文件如 sys/linux/binder_amd64.const 调用 extract → compile 生成常量值 compile 函数: 使用 srcTemplate 模板将数据解析为源代码 编译为二进制文件 处理可能的错误(某些架构未定义的常量) 特殊处理: i386/arm 架构上的 mmap 转换为 old_mmap ,修复为 mmap2 最终调用 SerializeConsts 序列化为 常量=值 格式写入文件 四、syz-sysgen 详细解析 1. 主要处理流程 创建 gen 文件夹存放生成的 go 文件 从 .txt 文件提取 AST 从 .const 文件提取常量 调用 Compile 函数(与 syz-extract 中的调用不同) 2. 关键函数分析 Compile 函数(完整流程): createCompiler :初始化编译器 typecheck :类型检查 assignSyscallNumbers :分配系统调用号 patchConsts :替换常量实际值 check :语义检查 genSyscalls :生成 prog 对象 genSyscalls 函数: 调用 genSyscall 生成单个系统调用 按系统调用名排序 genSyscall 调用 genType 生成返回值, genFieldArray 生成参数 调用 genResources 生成资源 调用 genStructDescs 生成结构体描述 输出处理: generate 函数将结果输出到 io.Writer 写入 gen 文件夹下对应架构的 .go 文件 自动执行 init 函数中的 RegisterTarget writeExecutorSyscalls 写入配置: executor/defs.h :配置信息 executor/syscalls.h :系统调用名和编号 五、关键实现细节 1. 模板解析过程 解析流程: ParseGlob → Parse → parseTopRecover → parseTop parseTop 函数根据标识符类型调用不同处理函数: % 开头的指令 include 包含指令 define 定义指令 resource 资源定义 call 系统调用定义 struct 结构体定义 flags 标志定义 2. 常量提取策略 提取的常量类型包括: 系统调用编号(自动添加 __NR_ 前缀) 结构体大小和字段偏移 标志位值 资源定义值 类型定义中的常量 3. 架构适配处理 特殊架构处理: 多架构编译时的常量过滤 i386/arm 的 mmap 特殊处理 不同架构的系统调用号差异 六、总结与后续 本文详细分析了 Syzkaller 系统调用模板的编译过程,包括: 模板语法结构 syz-extract 的常量提取机制 syz-sysgen 的代码生成流程 关键函数实现细节 后续研究方向: Fuzz 过程分析 Crash 复现机制 Generate 和 Mutate 策略 执行监控和漏洞检测 通过理解这些底层机制,可以更有效地扩展 Syzkaller 的功能,提高内核漏洞挖掘的效率。