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]/ 目录暴露了进程的运行时信息,对注入至关重要的两个文件是:
/proc/[pid]/maps:以文本形式列出进程的所有虚拟内存映射区域,包括起止地址、权限标志、映射来源等。这是定位 GOT 表、代码段基址的核心信息源。/proc/[pid]/mem:将进程的整个虚拟地址空间以线性文件形式暴露。对该文件执行lseek+read/write操作等价于直接读写目标进程的内存。注意:从 Linux 内核 3.x 起,对该文件的写操作不再要求先执行PTRACE_ATTACH。
2.3 GOT/PLT 延迟绑定:劫持的窗口
动态链接的 ELF 文件通过 PLT 和 GOT 的配合实现库函数的运行时解析。首次调用库函数时,会触发动态链接器解析并填充 GOT 表项,后续调用则直接跳转。
- GOT 表项位于可写内存段。攻击者若能定位某个函数(如
puts、printf)在 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 指向 shellcode → CONT 恢复执行 → shellcode 运行后恢复原始状态 → DETACH。
3.2 实验:ptrace 注入 shellcode
- 目标程序 (
target.c):一个简单的循环程序,周期性地调用puts()和printf(),确保其 GOT 中有相关表项。 - 注入器 (
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 劫持
攻击思路:
- 解析
/proc/[pid]/maps,定位目标进程的 ELF 基址和各段位置。 - 通过
/proc/[pid]/mem读取 ELF 头部,解析动态段找到 GOT 表。 - 定位目标函数(如
puts)在 GOT 中的表项地址。 - 在目标进程的某个可写段中(如数据段末尾)写入
shellcode。 - 将 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 技术思路
- 远程
mmap:通过ptrace劫持目标进程的指令指针,设置寄存器并使其执行mmap系统调用,分配一块匿名、私有、具有读、写、执行权限的内存区域。 - 写入负载:通过
/proc/[pid]/mem文件,将完整的payload写入上一步分配的 RWX 内存区域。 - 执行与恢复:再次通过
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。脚本核心包括:
- 遍历
/proc下的所有进程目录。 - 解析每个进程的
maps文件,查找rwxp权限且映射文件为空的条目。 - 读取每个进程的
status文件,检查TracerPid字段。 - 输出可疑进程的 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
对于事后分析,需获取物理内存转储进行分析。
- 获取内存:使用 LiME 工具加载内核模块转储物理内存。
- 使用 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_readv 和 process_vm_writev 系统调用允许跨进程内存读写,权限检查与 /proc/mem 类似,但走不同的内核路径,可能被部分监控方案遗漏。防御方应将其纳入监控规则。
7.3 eBPF 在检测中的潜力
eBPF 提供了更高效的内核态监控方案,可附加程序到关键跟踪点:
sys_enter_ptrace:捕获所有ptrace调用。sys_enter_mmap:检测带PROT_EXEC标志的映射。vfs_writekprobe:过滤对/proc/*/mem路径的写操作。- 利用 eBPF maps 实现跨事件关联分析。
7.4 攻击者的反取证手段
- RWX→RX 降级:
payload写入后,通过远程mprotect将权限从RWX改为R-X,消除显眼的匿名 RWX 特征。 - 复用已有 RX 段:不分配新内存,直接覆写目标进程已有代码段中不常执行的函数。
- 时间窗口最小化:注入操作在毫秒级完成,降低被轮询检测捕获的概率。
- 清理
payload:执行完成后通过远程munmap释放注入的内存,或用零覆写。
8. 总结:攻防矩阵
| 注入技术 | 核心原语 | OPSEC 优势 | 检测手段 | 可能的绕过 |
|---|---|---|---|---|
ptrace POKETEXT |
ATTACH → POKETEXT → SETREGS → CONT |
实现简单,兼容性好 | 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 实时监控,构建纵深防御体系。
免责声明:本文所有技术内容仅供安全研究和防御能力建设使用。未经授权对他人系统实施注入攻击属于违法行为。请在合法授权的测试环境中进行实验。