You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在内核模块中读取其他进程的堆内存?

内核态读取用户态进程内存的可行方法

以下是几种内核模块中常用的用户态进程内存读取方案,覆盖从高层封装到底层操作的不同场景:


1. 使用 copy_from_user() 基础接口(需配合地址空间切换)

这是内核访问用户态内存最基础的函数,但需要手动处理地址空间切换和内存区域合法性验证:

  • 核心逻辑:先获取目标进程的 task_struct 和 mm_struct,切换到用户态地址空间视角后,调用 copy_from_user() 完成数据读取。
  • 示例代码:
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/mm.h>

int read_user_memory(struct task_struct *target_task, unsigned long user_addr, void *kernel_buf, size_t len) {
    struct mm_struct *mm = get_task_mm(target_task);
    if (!mm) return -ESRCH;

    mmap_read_lock(mm);
    // 切换到用户态地址空间,让内核能识别用户态虚拟地址
    set_fs(USER_DS);
    // 先验证地址是否可读
    if (!access_ok(VERIFY_READ, (void __user *)user_addr, len)) {
        set_fs(KERNEL_DS);
        mmap_read_unlock(mm);
        mmput(mm);
        return -EFAULT;
    }
    // 执行读取,返回未成功读取的字节数
    long ret = copy_from_user(kernel_buf, (void __user *)user_addr, len);
    // 切回内核地址空间
    set_fs(KERNEL_DS);

    mmap_read_unlock(mm);
    mmput(mm);
    return ret == 0 ? 0 : -EFAULT;
}
  • 注意:必须锁定目标进程的内存描述符(mmap_read_lock),防止读取过程中进程内存区域被修改;get_task_mm 会增加 mm_struct 的引用计数,用完后需调用 mmput 释放。

2. 使用 access_process_vm() 高层封装接口

内核提供了封装好的 access_process_vm() 函数,已经内置了地址空间切换、权限验证和内存锁定逻辑,是更安全便捷的选择:

  • 核心逻辑:直接传入目标进程的 task_struct、用户态地址、内核缓冲区、读取长度和访问标志即可完成读取。
  • 示例代码:
#include <linux/mm.h>
#include <linux/sched.h>

ssize_t read_process_memory(struct task_struct *target_task, unsigned long user_addr, void *kernel_buf, size_t len) {
    // FOLL_FORCE 标志可强制读取部分特殊内存区域,根据需求调整
    ssize_t bytes_read = access_process_vm(target_task, user_addr, kernel_buf, len, FOLL_FORCE);
    return bytes_read >= 0 ? bytes_read : -EFAULT;
}
  • 注意:该函数会自动处理 mm_struct 的引用和锁定,无需手动操作;返回值为成功读取的字节数,失败返回负数错误码。

3. 遍历进程 VMA 结构(模拟 /proc/pid/maps 逻辑)

如果需要先获取进程的合法内存区域再读取,可以遍历目标进程的 vm_area_struct 链表(对应用户态 /proc/pid/maps 的内容),筛选出可读区域后进行读取:

  • 核心逻辑:遍历 mm->mmap 链表,检查每个 VMA 的权限标志(VM_READ),对合法区域执行内存读取。
  • 示例代码片段:
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/uaccess.h>

void scan_and_read_process_memory(struct task_struct *target_task) {
    struct mm_struct *mm = get_task_mm(target_task);
    if (!mm) return;

    mmap_read_lock(mm);
    struct vm_area_struct *vma;
    // 遍历所有内存区域
    for (vma = mm->mmap; vma; vma = vma->vm_next) {
        if (vma->vm_flags & VM_READ) {
            char buf[64];
            unsigned long read_addr = vma->vm_start;
            size_t read_len = min((size_t)(vma->vm_end - vma->vm_start), sizeof(buf));
            
            set_fs(USER_DS);
            copy_from_user(buf, (void __user *)read_addr, read_len);
            set_fs(KERNEL_DS);
            
            // 处理读取到的buf数据
        }
    }
    mmap_read_unlock(mm);
    mmput(mm);
}
  • 注意:遍历过程中必须持有 mmap_read_lock,避免进程内存区域被动态修改导致遍历出错。

4. 底层页表操作(特殊场景使用)

如果上述方法无法满足需求(比如读取未被映射的物理页),可以直接操作进程页表找到用户地址对应的物理页,再映射到内核空间访问:

  • 核心步骤:
    1. 获取目标进程的 pgd(页全局目录)
    2. 遍历页表(pgd -> pud -> pmd -> pte)找到用户地址对应的物理页框号(PFN)
    3. 使用 kmap() 将物理页映射到内核虚拟地址
    4. 直接访问内核地址读取数据,完成后调用 kunmap() 解除映射
  • 注意:该方法复杂度极高,需要处理页表转换异常、大页、页锁定等问题,仅推荐在特殊场景下使用;可以配合 get_user_pages() 函数辅助获取用户页的引用。

内容的提问来源于stack exchange,提问作者user24572871

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:05:29