[译]如何在任意进程中修改内存保护属性
字数 1899 2025-08-18 11:35:38

在任意进程中修改内存保护属性的技术研究

引言

在现代操作系统中,每个进程都有自己的虚拟地址空间,由内存页组成,每个页都有保护标志(读取、写入和执行)。在Linux系统中,修改内存保护属性的标准方法是使用mprotectpkey_mprotect系统调用,但这些调用只能作用于当前进程。本文将详细介绍三种在任意进程中修改内存保护属性的方法。

方法一:代码注入

原理概述

通过ptrace机制让目标进程从其自身上下文中调用mprotect

实现步骤

  1. 附加到目标进程

    • 使用ptrace附加到目标进程
    • 如果进程有多个线程,需要停止所有其他线程
  2. 寻找可执行内存区域

    • 检查/proc/PID/maps找到可执行内存区域
    • 在该区域写入系统调用操作码0f 05
  3. 设置寄存器

    • rax设置为mprotect的系统调用号(10)
    • 参数分别存储在:
      • rdi: 起始地址
      • rsi: 长度
      • rdx: 所需的保护标志
    • rip设置为步骤2中使用的地址
  4. 恢复进程

    • 系统调用返回后恢复进程
    • 恢复被覆盖的内存和寄存器
    • 从进程分离并恢复正常执行

局限性

  • 无法绕过seccomp保护机制
  • 如果进程启用了seccomp并限制了mprotect调用,内核将终止进程

方法二:模拟内核模块中的mprotect

原理概述

在内核模式下实现mprotect功能,绕过用户模式的限制。

实现步骤

  1. 获取任务结构

    pid_struct = find_get_pid(params.pid);
    task = get_pid_task(pid_struct, PIDTYPE_PID);
    mm = get_task_mm(task);
    
  2. 查找内存区域

    • 使用find_vma函数通过地址搜索内存映射
    • 操作vm_area_struct结构:
      • vm_flags: 体系结构无关的保护标志
      • vm_page_prot: 体系结构相关的保护标志
  3. 修改保护属性

    • 更改vm_flags为所需保护标志
    • 调用vma_set_page_prot_func更新vm_page_prot
    • 调用change_protection_func更新页表中的保护位

问题与局限性

  1. 仅实现了mprotect的基本功能,缺少完整实现
  2. 使用了未导出的内核函数(vma_set_page_prot_funcchange_protection_func)
  3. 依赖内核内部结构,容易随内核版本变化而失效

方法三:使用目标进程的内存映射

原理概述

在内核线程中使用目标进程的内存上下文执行mprotect

实现步骤

  1. 获取内存映射对象

    • 与方法二相同,获取目标进程的mm_struct
  2. 创建工作队列

    • 使用内核的工作队列接口创建内核线程
    • 在自定义工作例程中:
      use_mm(suprotect_work->mm);
      suprotect_work->ret_value = do_mprotect_pkey(
          suprotect_work->start,
          suprotect_work->len,
          suprotect_work->prot,
          -1);
      unuse_mm(suprotect_work->mm);
      
  3. 调度工作

    • 当收到修改请求时,调度工作队列执行

优点与局限性

优点

  • 不直接操作内部结构,更稳定
  • 在内核线程上下文中执行,绕过用户模式限制

局限性

  • 仍然需要使用kallsyms获取未导出的do_mprotect_pkey函数
  • 需要内核线程支持

技术要点总结

  1. 内存保护基础

    • 内存页保护标志:读(R)、写(W)、执行(X)
    • x64架构下无法创建只写内存页(总是可读)
  2. Linux与Windows对比

    • Windows:VirtualProtectEx可直接操作其他进程
    • Linux:mprotect只能操作当前进程
  3. 关键技术

    • ptrace机制:观察和控制其他进程执行
    • seccomp限制:阻止特定系统调用
    • 内核任务结构:task_structmm_struct
    • 内存区域管理:vm_area_struct结构
  4. 内核模块开发要点

    • 使用find_get_pidget_task_mm获取进程信息
    • 工作队列接口创建内核线程
    • use_mm/unuse_mm切换内存上下文

最佳实践建议

  1. 方法选择

    • seccomp限制:优先使用方法一(代码注入)
    • seccomp限制:使用方法三(内存映射)
    • 方法二(模拟内核模块)仅适用于学习研究
  2. 稳定性考虑

    • 避免直接依赖未导出内核函数
    • 考虑内核版本兼容性
  3. 安全考虑

    • 需要root权限
    • 操作其他进程内存需谨慎

扩展思考

  1. 性能影响

    • 方法一涉及进程暂停/恢复,对性能影响较大
    • 方法三使用工作队列,性能较好
  2. 未来发展

    • 内核API变化可能影响方法二和三
    • seccomp机制可能进一步加强
  3. 替代方案

    • 考虑使用LD_PRELOAD注入代码
    • 研究process_vm_writev等系统调用

通过这三种方法的比较研究,我们深入理解了Linux内存管理和进程间操作的核心机制,为系统级开发和调试提供了重要技术参考。

在任意进程中修改内存保护属性的技术研究 引言 在现代操作系统中,每个进程都有自己的虚拟地址空间,由内存页组成,每个页都有保护标志(读取、写入和执行)。在Linux系统中,修改内存保护属性的标准方法是使用 mprotect 或 pkey_mprotect 系统调用,但这些调用只能作用于当前进程。本文将详细介绍三种在任意进程中修改内存保护属性的方法。 方法一:代码注入 原理概述 通过 ptrace 机制让目标进程从其自身上下文中调用 mprotect 。 实现步骤 附加到目标进程 : 使用 ptrace 附加到目标进程 如果进程有多个线程,需要停止所有其他线程 寻找可执行内存区域 : 检查 /proc/PID/maps 找到可执行内存区域 在该区域写入系统调用操作码 0f 05 设置寄存器 : rax 设置为 mprotect 的系统调用号(10) 参数分别存储在: rdi : 起始地址 rsi : 长度 rdx : 所需的保护标志 rip 设置为步骤2中使用的地址 恢复进程 : 系统调用返回后恢复进程 恢复被覆盖的内存和寄存器 从进程分离并恢复正常执行 局限性 无法绕过 seccomp 保护机制 如果进程启用了 seccomp 并限制了 mprotect 调用,内核将终止进程 方法二:模拟内核模块中的mprotect 原理概述 在内核模式下实现 mprotect 功能,绕过用户模式的限制。 实现步骤 获取任务结构 : 查找内存区域 : 使用 find_vma 函数通过地址搜索内存映射 操作 vm_area_struct 结构: vm_flags : 体系结构无关的保护标志 vm_page_prot : 体系结构相关的保护标志 修改保护属性 : 更改 vm_flags 为所需保护标志 调用 vma_set_page_prot_func 更新 vm_page_prot 调用 change_protection_func 更新页表中的保护位 问题与局限性 仅实现了 mprotect 的基本功能,缺少完整实现 使用了未导出的内核函数( vma_set_page_prot_func 和 change_protection_func ) 依赖内核内部结构,容易随内核版本变化而失效 方法三:使用目标进程的内存映射 原理概述 在内核线程中使用目标进程的内存上下文执行 mprotect 。 实现步骤 获取内存映射对象 : 与方法二相同,获取目标进程的 mm_struct 创建工作队列 : 使用内核的工作队列接口创建内核线程 在自定义工作例程中: 调度工作 : 当收到修改请求时,调度工作队列执行 优点与局限性 优点 : 不直接操作内部结构,更稳定 在内核线程上下文中执行,绕过用户模式限制 局限性 : 仍然需要使用 kallsyms 获取未导出的 do_mprotect_pkey 函数 需要内核线程支持 技术要点总结 内存保护基础 : 内存页保护标志:读(R)、写(W)、执行(X) x64架构下无法创建只写内存页(总是可读) Linux与Windows对比 : Windows: VirtualProtectEx 可直接操作其他进程 Linux: mprotect 只能操作当前进程 关键技术 : ptrace 机制:观察和控制其他进程执行 seccomp 限制:阻止特定系统调用 内核任务结构: task_struct 和 mm_struct 内存区域管理: vm_area_struct 结构 内核模块开发要点 : 使用 find_get_pid 和 get_task_mm 获取进程信息 工作队列接口创建内核线程 use_mm / unuse_mm 切换内存上下文 最佳实践建议 方法选择 : 无 seccomp 限制:优先使用方法一(代码注入) 有 seccomp 限制:使用方法三(内存映射) 方法二(模拟内核模块)仅适用于学习研究 稳定性考虑 : 避免直接依赖未导出内核函数 考虑内核版本兼容性 安全考虑 : 需要root权限 操作其他进程内存需谨慎 扩展思考 性能影响 : 方法一涉及进程暂停/恢复,对性能影响较大 方法三使用工作队列,性能较好 未来发展 : 内核API变化可能影响方法二和三 seccomp 机制可能进一步加强 替代方案 : 考虑使用 LD_PRELOAD 注入代码 研究 process_vm_writev 等系统调用 通过这三种方法的比较研究,我们深入理解了Linux内存管理和进程间操作的核心机制,为系统级开发和调试提供了重要技术参考。