[译]如何在任意进程中修改内存保护属性
字数 1899 2025-08-18 11:35:38
在任意进程中修改内存保护属性的技术研究
引言
在现代操作系统中,每个进程都有自己的虚拟地址空间,由内存页组成,每个页都有保护标志(读取、写入和执行)。在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功能,绕过用户模式的限制。
实现步骤
-
获取任务结构:
pid_struct = find_get_pid(params.pid); task = get_pid_task(pid_struct, PIDTYPE_PID); mm = get_task_mm(task); -
查找内存区域:
- 使用
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
- 与方法二相同,获取目标进程的
-
创建工作队列:
- 使用内核的工作队列接口创建内核线程
- 在自定义工作例程中:
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);
-
调度工作:
- 当收到修改请求时,调度工作队列执行
优点与局限性
优点:
- 不直接操作内部结构,更稳定
- 在内核线程上下文中执行,绕过用户模式限制
局限性:
- 仍然需要使用
kallsyms获取未导出的do_mprotect_pkey函数 - 需要内核线程支持
技术要点总结
-
内存保护基础:
- 内存页保护标志:读(R)、写(W)、执行(X)
- x64架构下无法创建只写内存页(总是可读)
-
Linux与Windows对比:
- Windows:
VirtualProtectEx可直接操作其他进程 - Linux:
mprotect只能操作当前进程
- Windows:
-
关键技术:
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内存管理和进程间操作的核心机制,为系统级开发和调试提供了重要技术参考。