kernel security 1: introduction

2025-09-01

PS:kernel,我又回来啦

0. 写在前面

其实大三有一段时间是在学习kernel pwn的,后来很长一段时间不用又忘记了,如今重拾kernel,希望能更系统的学习。
大三的学习记录在https://wsxk.github.io/linux_kernel_basic_one/
事到如今,不过是再学一遍罢了~

1. 内核简介

1.1 什么是操作系统内核

操作系统(Operation System) 本质上也是一种软件,可以看作是普通应用程式与硬件之间的一层中间层,其主要作用便是调度系统资源、控制IO设备、操作网络与文件系统等,并为上层应用提供便捷、抽象的应用接口而 内核(kernel) 是操作系统最重要的一部分。

1.2 内核专享的外部资源

内核专享的外部资源(external resources),指只有内核才能访问的资源,即用户态无法直接访问其资源。
这里引出用户态和内核态的概念:用户态就是我们的进程执行的代码空间,内核态指内核执行的代码空间。
也就是说,用户态的程序想要访问一些外部资源,需要先陷进内核态,通常这个动作是通过系统调用完成(还有其他,接下来再举例)
一些常见的外部资源如下:

1. hlt指令:只在内核态才允许执行该指令
其作用是让 CPU 进入 halt(空闲/省电)状态,停止取指与执行,直到被“唤醒事件”打断。常用于内核的 idle 循环。(idle即空闲状态)

2. in 和 out指令:用于访问I/O端口空间的设备寄存器。其实就是和硬件外设交互,常用于老式设备
(现代的pcie将设备寄存器暴露为某个物理/虚拟地址范围,用普通 mov 读写(再配合内存屏障))


3. 一些特殊寄存器:
cr3:(control register 3) ,它是指向page table的指针,用于虚拟地址和物理地址之间的转换,通常用mov指令就可以修改值,但是必须要是内核态。
MSR_LSTAR (Model-Specific Register, Long Syscall Target Address Register): 它定义了syscall指令应该跳往哪个函数。 通常用wrmsr和rdmsr来读写这个寄存器

1.3 privilege level

CPU通过跟踪权限等级来控制资源的访问,权限等级的图如下:

Ring 3: Userspace, where we have been operating until now. Very restricted.
Ring 2: Generally unused.
Ring 1: Generally unused.
Ring 0: The Kernel. Unrestricted, supervisor mode.

可以看到虽然划分了4个层级,其实只有2个层级被使用到:ring3和ring0
然而随着VM(virtual machine)技术的兴起,虚拟机内核不能和主机os内核有一样的权限,在古早时期(21世纪初),虚拟机内核被放入ring 1中,这导致了严重的性能开销,运行在虚拟机中的进程,如果想要执行 主机os配合才能完成的操作时,需要从ring3->ring1->ring0,在ring1上模拟ring0操作(或者可以说ring1陷入ring0态)带来额外开销
为了减少开销,现代引入了ring-1级别(即hypervisor),它比ring0更高,因此当虚拟机os(guest os)产生了需要ring0的操作时,hypervisor会拦截到该操作,并把任务下发给在主机os,由其执行
看起来拦截并转给主机os执行想比模拟ring0,性能开销小得多。

1.4 不同类型的os模型

笼统的说,os的类型有三种:

monolithic kernel:宏内核,是一个单一的,统一的内核二进制文件,处理所以的操作系统层级的任务,驱动也会作为内核的一部分被加载到二进制文件中(这意味着驱动和内核都处于ring0,即 驱动即内核,驱动有问题=内核有问题)
- examples: Linux, FreeBSD

microkernel:微内核,理论上完美的内核(实际上要处理的消息太多了),由一个微小的"core"二进制文件提供进程间通信和与硬件基本交互,驱动程序是具有轻微特权的普通用户空间程序。
- examples: Minux, seL4

hybrid kernel:混合内核,微内核的特效与宏内核组件相结合
- examples: Windows (NT), MacOS

1.5 ring间的切换

通常情况下,ring间切换指的是 ring3<->ring0之间的状态转换。
x86/x86-64(以 Linux 为例),ring3<->ring0的切换的本质是CPU 触发特权门(IDT/GDT/MSR),把 CPL=3 切到 CPL=0,状态转换的方法如下所示:

methods Description
同步异常: 错误(faults)/陷阱(traps) faults & traps → kernel 错误和陷阱统称为异常
  os处理指令触发fault后并修复,会重新执行该指令,缺页异常(page-fault)就依靠这个原理
  os处理指令触发trap后并修复,不会重新执行该指令,常见的有int 3指令(以下3条均属于陷入(trap)的范畴)
  syscall/sysret(x86-64 主流;由 IA32_LSTAR/STAR/FM ASK MSR 配置入口)
  sysenter/sysexit(32 位“快速系统调用”)
  int 0x80(32 位旧式软中断;IDT 中该门 DPL=3 允许用户触发
异步中断 (hardware IRQ / IPI / NMI → kernel) 通常发生在外围设备,比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作

1.5.1 切换原理:以syscall为例

在系统启动时,处于ring0状态,内核会设置MSR_LSTAR指向syscall handler routine,即系统调用处理表
系统启动后,当用户态(ring3)想要和内核交互时,可以通过系统调用(syscall)进行:

1. Privilege level switches to Ring 0.
2. Control flow jumps to value of MSR_LSTAR.
3. Return address saved to rcx

详情可参考https://www.felixcloutier.com/x86/syscall
当内核处理完相应事务后,回退用户态时,通过sysret进行:

1. Privilege level switches to Ring 3.
2. Control flow jumps to rcx.

1.6 内核态和用户态空间的关联

用户态空间位于虚拟地址空间的 低地址处, 内核态空间位于虚拟地址空间的 高地址处。
诸如系统调用,并没有改变虚拟地址的映射,只不过内核态空间的访问需要ring0权限

2. kernel module

A kernel module is a library that loads into the kernel
kernel module通常是.ko结尾的文件(可以类比.so文件)

1. .ko也是elf文件
2. 这个文件会被加载到内核空间
3. 拥有和内核一样的执行权限

kernel module在日常生活中是很常见的,比如驱动(显卡驱动)、文件系统、网络功能都是通过kernel module部署进内核的。

2.1 kernel module交互方式

2.1.1 中断(这里虽然说是中断,本质上还是一个trap)

kernel module是能够注册interrupt handler(中断处理器)来捕获一些特殊的指令,比如int 42
当然,hook一些已有的指令也是可行的:

int3 (0xcc): normally causes a SIGTRAP, but can be hooked!
int1 (0xf1): normally used for hardware debugging, but can be hooked

甚至我们可以注册一个非法指令int 66,当运行到这个指令时,会跳转到我们的kernel module进行处理。

2.1.2 通过文件访问

kernel module可以通过在以下3种路径注册一个文件,这样用户态代码能够通过open()函数打开文件并于内核交互!

1. /dev: mostly traditional devices (i.e., /dev/dsp for audio)

2. /proc: started out in System V Unix as information about running processes. Linux expanded it into in a disastrous mess of kernel interfaces. The solution...

3. /sys: non-process information interface with the kernel.

如果kernel module编写了某些函数,那么用户态程序就可以像操作文件一样与kernel module交互:

// 1. 文件读写
//kernel module
static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset)
static ssize_t device_write(struct file *filp, const char *buf, size_t len, loff_t *off)
//userspace
fd = open("/dev/pwn-college", 0)
read(fd, buffer, 128);

// 2. ioctl
//kernel module
static long device_ioctl(struct file *filp, unsigned int ioctl_num, unsigned long ioctl_param)
//userspace
int fd = open("/dev/pwn-college", 0);
ioctl(fd, COMMAND_CODE, &custom_data_structure);

linux命令行上如何安装/查看/删除驱动:

安装: insmod mymodule.ko
通常会调用init_module系统调用

查看: lsmod

删除: rmmod mymodule
通常会调用delete_module系统调用

3. kernel 利用思路

总的来说,一般从3个方向考虑内核的利用:

1. From the network: remotely-trigged exploits (packets of death, etc). Rare!
几乎不存在

2. From userspace: vulnerabilities in syscall and ioctl handlers (i.e., launched from inside a sandbox!)
现在最多的手法

3.From devices: launch kernel exploits from attached devices such as USB hardware (https://www.pjrc.com/teensy/)
也比较少