基于ptrace与/proc/mem的Linux无文件进程注入:攻击实现与内存取证检测
字数 7365
更新时间 2026-04-16 12:28:41

Linux 无文件进程注入:攻击实现与内存取证检测教学文档

1. 引言

本文档旨在系统性地阐述基于 ptrace 系统调用和 /proc 伪文件系统的 Linux 无文件进程注入技术。该技术允许攻击者在不向磁盘写入任何文件的前提下,将恶意负载注入到正在运行的合法进程中,实现长期驻留。本文将深入剖析攻击的实现原理、具体步骤,并从防御者角度探讨相应的检测与取证方法。

2. 背景:Linux进程内存与调试机制

理解进程注入的前提是掌握Linux进程的内存模型和内核提供的进程间交互机制。

2.1 ELF进程的虚拟地址空间布局

一个典型的 x86_64 ELF 进程启动后,其虚拟地址空间(从高地址到低地址)包含以下关键区域:

  • .text 段 (r-x):存放机器指令,只读可执行。注入的代码(shellcode)不能直接写入此处,除非先通过 mprotect 修改权限。
  • .got / .got.plt 段 (rw-):全局偏移表,存放动态链接解析后的函数地址。这是GOT劫持攻击的核心目标。
  • [heap]:通过 brk/sbrk 扩展的堆区域,可读写但默认不可执行(受 NX 位保护)。
  • mmap 区域:动态库加载、匿名映射均在此区域。可通过远程调用 mmap 在此分配具有读、写、执行权限的内存。
  • [vdso]:内核映射到用户空间的虚拟动态共享对象,提供快速系统调用入口。

2.2 /proc 伪文件系统:进程的透视镜

Linux 的 /proc/[pid]/ 目录暴露了进程的运行时信息,对注入至关重要的两个文件是:

  1. /proc/[pid]/maps:以文本形式列出进程的所有虚拟内存映射区域,包括起止地址、权限标志、映射来源等。这是定位 GOT 表、代码段基址的核心信息源。
  2. /proc/[pid]/mem:将进程的整个虚拟地址空间以线性文件形式暴露。对该文件执行 lseek + read/write 操作等价于直接读写目标进程的内存。注意:从 Linux 内核 3.x 起,对该文件的写操作不再要求先执行 PTRACE_ATTACH

2.3 GOT/PLT 延迟绑定:劫持的窗口

动态链接的 ELF 文件通过 PLT 和 GOT 的配合实现库函数的运行时解析。首次调用库函数时,会触发动态链接器解析并填充 GOT 表项,后续调用则直接跳转。

  • GOT 表项位于可写内存段。攻击者若能定位某个函数(如 putsprintf)在 GOT 中的地址,并将其覆写为可控的 shellcode 地址,则目标进程下次调用该函数时就会跳转到攻击代码。这就是 GOT 劫持 的核心原理。
  • 防护:如果程序以 Full RELRO 编译,GOT 在启动后会被标记为只读,此时 GOT 劫持需要额外的 mprotect 调用来解除写保护。

3. 基于 ptrace 的经典进程注入

ptrace 是 Linux 调试的核心系统调用,也是传统的注入手段。

3.1 ptrace 系统调用族

请求类型 功能 注入中的用途
PTRACE_ATTACH 附加到目标进程,发送 SIGSTOP 获取控制权
PTRACE_PEEKTEXT 读取目标进程内存 备份原始指令
PTRACE_POKETEXT 写入目标进程内存 注入 shellcode
PTRACE_GETREGS 获取寄存器状态 保存原始上下文
PTRACE_SETREGS 设置寄存器状态 劫持 RIP 跳转
PTRACE_CONT 恢复进程执行 让注入代码运行
PTRACE_DETACH 断开附加关系 清理痕迹

基本流程ATTACH 目标进程 → 保存寄存器和指令 → 在 RIP 处写入 shellcode → 修改 RIP 指向 shellcodeCONT 恢复执行 → shellcode 运行后恢复原始状态 → DETACH

3.2 实验:ptrace 注入 shellcode

  1. 目标程序 (target.c):一个简单的循环程序,周期性地调用 puts()printf(),确保其 GOT 中有相关表项。
  2. 注入器 (injector.c)
    • 通过 ptrace 附加到目标进程。
    • 构造一段 shellcode,其功能是通过 write 系统调用向标准输出打印信息,并以 int3 指令结束触发 SIGTRAP 信号,通知注入器执行完毕。
    • 保存目标进程当前的指令和寄存器上下文。
    • shellcode 写入目标进程的 RIP 指向的地址。
    • 修改目标进程的 RIP 指向注入的 shellcode
    • 恢复目标进程执行,待其执行完 shellcode 并因 int3 暂停后,恢复原始指令和寄存器上下文,最后分离。

3.3 ptrace 注入的检测特征

  • TracerPid 字段:在 PTRACE_ATTACH 期间,目标进程的 /proc/[pid]/status 文件中的 TracerPid 字段会变为注入器的 PID。
  • SIGSTOP 信号PTRACE_ATTACH 会向目标进程发送 SIGSTOP 信号,这可能在审计日志中留下痕迹。
  • 这些特征使得纯 ptrace 注入容易被检测,从而催生了绕过 ptrace 的方案。

4. 基于 /proc/pid/mem 的无 ptrace 注入

此方法完全绕过 ptrace 系统调用,直接通过 /proc/[pid]/mem 文件读写目标进程内存。

4.1 原理与权限

  • 内核变化:自 Linux 内核 3.x 起,对 /proc/[pid]/mem 的写操作不再强制要求先执行 PTRACE_ATTACH
  • 权限模型:操作者必须与目标进程属于同一 UID,或拥有 CAP_SYS_PTRACE 能力。同时受 YAMA Linux 安全模块的 ptrace_scope 配置约束。

YAMA ptrace_scope 级别

含义 /proc/mem 的影响
0 经典模式 同 UID 即可读写,无额外限制
1 受限模式 只允许父进程或已声明的调试者访问
2 仅管理员 只有 CAP_SYS_PTRACE 能力才能访问
3 完全禁用 任何进程都不能通过此路径访问

4.2 实验:通过 /proc/mem 实现 GOT 劫持

攻击思路

  1. 解析 /proc/[pid]/maps,定位目标进程的 ELF 基址和各段位置。
  2. 通过 /proc/[pid]/mem 读取 ELF 头部,解析动态段找到 GOT 表。
  3. 定位目标函数(如 puts)在 GOT 中的表项地址。
  4. 在目标进程的某个可写段中(如数据段末尾)写入 shellcode
  5. 将 GOT 表中 puts 的表项值覆写为 shellcode 的地址。

关键步骤 (got_hijack.py)

  • 解析内存映射:读取并解析 /proc/[pid]/maps 文件。
  • 解析 ELF 与 GOT:读取 ELF 头、程序头表和动态段,定位 GOT 地址并解析其中的函数地址表项。
  • 定位目标函数:在解析出的 GOT 映射中查找 puts 函数的地址。
  • 写入 shellcode:在找到的可写段中分配空间,并通过 /proc/[pid]/mem 写入包含劫持消息和原始函数地址的 shellcode
  • 覆写 GOT:将 puts 在 GOT 中的表项值修改为 shellcode 的起始地址。

效果:此后,目标进程每次调用 puts(),都会先执行注入的 shellcode 打印劫持信息,然后再跳转回真正的 puts() 函数。整个过程没有 ptrace 调用,TracerPid 始终为 0。

局限性:此方法将 shellcode 写入了数据段(rw-),该段默认没有执行权限。在严格的 NX 环境下,需要先通过其他手段分配具有执行权限的内存。

5. 组合技:远程系统调用注入

结合前两种技术的优势,使用 ptrace 迫使目标进程执行系统调用以分配 RWX 内存,然后通过 /proc/mem 写入负载,最后恢复执行。

5.1 技术思路

  1. 远程 mmap:通过 ptrace 劫持目标进程的指令指针,设置寄存器并使其执行 mmap 系统调用,分配一块匿名、私有、具有读、写、执行权限的内存区域。
  2. 写入负载:通过 /proc/[pid]/mem 文件,将完整的 payload 写入上一步分配的 RWX 内存区域。
  3. 执行与恢复:再次通过 ptrace 修改 RIP 指向新的 payload 地址,执行完成后恢复原始上下文并分离。

5.2 实验:远程 mmap + payload 注入

流程 (remote_inject.c)

  • Phase 1: 附加:使用 ptrace 附加到目标进程。
  • Phase 2: 远程 mmap
    • 保存目标进程的原始寄存器状态。
    • 构造 mmap 系统调用所需的寄存器参数(rax=系统调用号,rdi=地址,rsi=长度,rdx=保护标志,r10=标志,r8=文件描述符,r9=偏移)。
    • 通过 PTRACE_SETREGS 设置寄存器,PTRACE_POKETEXT 在 RIP 处写入 syscall; int3 指令,然后 PTRACE_CONT 执行。
    • 目标进程执行 mmap 分配内存,触发 int3 暂停后,注入器读取返回值(RAX),即为分配的内存地址。
  • Phase 3: 通过 /proc/mem 写入:打开 /proc/[pid]/mem 文件,定位到远程 mmap 返回的地址,写入 payload
  • Phase 4: 执行 payload:修改目标进程的 RIP 指向 payload 地址,恢复执行。
  • Phase 5: 清理payload 执行完毕后,恢复原始寄存器状态并分离。

优势:获得了一块“合法”的 RWX 内存用于驻留 payload,且 ptrace 的附加时间窗口可以控制得很短。

5.3 技术难点

  • 信号处理:如果目标进程注册了 SIGTRAP 处理函数,int3 可能被其自身捕获。更健壮的做法是使用其他信号或单步执行。
  • 多线程竞态ptrace 以线程为单位。附加一个线程时,其他线程仍在运行,可能访问被修改的内存。安全做法是枚举并附加所有线程。
  • 栈对齐:x86_64 ABI 要求 call 指令执行时栈 16 字节对齐。如果 shellcode 调用库函数,需确保 RSP 对齐。
  • 强制访问控制:SELinux/AppArmor 可能拒绝带 PROT_EXEC 标志的 mmap 调用。

6. 检测与取证(蓝队视角)

6.1 基于 /proc 的实时检测

  • 匿名 RWX 内存:正常的应用程序极少拥有同时具备读、写、执行权限的匿名内存映射(JIT 编译器是少数例外)。扫描 /proc/*/maps 查找 rwxp 权限的匿名映射是高效指标。
  • TracerPid 字段:监控 /proc/*/status 中的 TracerPid 字段,非零值表示正在被 ptrace 追踪。

6.2 进程注入实时检测脚本

可以编写脚本周期性扫描所有进程的 /proc/[pid]/maps,检测匿名 RWX 区域和异常的 TracerPid。脚本核心包括:

  1. 遍历 /proc 下的所有进程目录。
  2. 解析每个进程的 maps 文件,查找 rwxp 权限且映射文件为空的条目。
  3. 读取每个进程的 status 文件,检查 TracerPid 字段。
  4. 输出可疑进程的 PID、命令行和具体发现。

6.3 使用 auditd 监控

配置 Linux 审计系统 (auditd) 规则,记录关键事件:

# 监控所有 ptrace 系统调用
-a always,exit -F arch=b64 -S ptrace -k process_injection
# 监控 process_vm_readv/writev 系统调用
-a always,exit -F arch=b64 -S process_vm_readv -S process_vm_writev -k process_injection
# 监控可疑的 mmap 调用(带 PROT_EXEC 标志)
-a always,exit -F arch=b64 -S mmap -F a2&0x4 -k exec_mmap

通过 ausearch 工具可以查询相关日志。

6.4 内存取证:LiME + Volatility3

对于事后分析,需获取物理内存转储进行分析。

  1. 获取内存:使用 LiME 工具加载内核模块转储物理内存。
  2. 使用 Volatility3 分析
    • linux.proc.Maps:列出进程内存映射,过滤 RWX 区域。
    • linux.elfs.Elfs:检查进程内存中 ELF 结构与磁盘文件是否一致,检测代码段被修改的情况。
    • linux.proc.Maps --dump:提取可疑内存区域进行离线反汇编分析。

关键发现模式

指标 正常情况 注入后的异常
匿名 RWX 段 极少出现(仅 JIT 引擎) 存在小型匿名 RWX 映射
.text 段哈希 与磁盘文件一致 被 POKETEXT 注入后不一致
GOT 表项 指向 libc 等合法库内 指向堆区或匿名映射区域
TracerPid 历史 始终为 0 审计日志中出现 ptrace 事件

6.5 YAMA LSM 加固

建议对生产服务器设置 ptrace_scope 为 1 或 2,以限制 ptrace/proc/mem 的访问。

echo "kernel.yama.ptrace_scope = 2" | sudo tee /etc/sysctl.d/99-ptrace.conf
sudo sysctl -p /etc/sysctl.d/99-ptrace.conf

7. 对抗与绕过思考

7.1 绕过 YAMA ptrace_scope

  • prctl(PR_SET_PTRACER):如果目标进程自身调用了 prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY),则任何进程都可以 ptrace 它。
  • 利用父子关系:如果攻击者能控制目标进程的祖先进程,可以从父进程发起 ptrace
  • CAP_SYS_PTRACE 能力:容器环境中,容器运行时可能赋予容器进程此能力。

7.2 process_vm_readv/writev:另一条路径

Linux 提供的 process_vm_readvprocess_vm_writev 系统调用允许跨进程内存读写,权限检查与 /proc/mem 类似,但走不同的内核路径,可能被部分监控方案遗漏。防御方应将其纳入监控规则。

7.3 eBPF 在检测中的潜力

eBPF 提供了更高效的内核态监控方案,可附加程序到关键跟踪点:

  • sys_enter_ptrace:捕获所有 ptrace 调用。
  • sys_enter_mmap:检测带 PROT_EXEC 标志的映射。
  • vfs_write kprobe:过滤对 /proc/*/mem 路径的写操作。
  • 利用 eBPF maps 实现跨事件关联分析。

7.4 攻击者的反取证手段

  • RWX→RX 降级payload 写入后,通过远程 mprotect 将权限从 RWX 改为 R-X,消除显眼的匿名 RWX 特征。
  • 复用已有 RX 段:不分配新内存,直接覆写目标进程已有代码段中不常执行的函数。
  • 时间窗口最小化:注入操作在毫秒级完成,降低被轮询检测捕获的概率。
  • 清理 payload:执行完成后通过远程 munmap 释放注入的内存,或用零覆写。

8. 总结:攻防矩阵

注入技术 核心原语 OPSEC 优势 检测手段 可能的绕过
ptrace POKETEXT ATTACHPOKETEXTSETREGSCONT 实现简单,兼容性好 TracerPid 监控、auditd ptrace 规则、SIGSTOP 信号 最小化 attach 时间窗口
/proc/mem GOT 劫持 /proc/pid/mem seek+write ptrace 调用,TracerPid 保持为 0 GOT 完整性校验、auditd /proc/mem 访问监控 使用 process_vm_writev 替代
远程 mmap + payload ptrace 远程 syscall + /proc/mem 写入 获得 RWX 内存,payload 持久驻留 匿名 RWX 区域扫描、eBPF tracepoint 监控 mprotect 降级 RWX→RX、munmap 清理

Linux 进程注入是内核机制“双刃剑”性质的典型案例。防御者应在理解攻击链路的基础上,在关键环节部署有针对性的检测点,如结合基于 /proc 的扫描、auditd 日志审计和 eBPF 实时监控,构建纵深防御体系。

免责声明:本文所有技术内容仅供安全研究和防御能力建设使用。未经授权对他人系统实施注入攻击属于违法行为。请在合法授权的测试环境中进行实验。

相似文章
相似文章
 全屏