6.3 KiB
6.3 KiB
Linux 平台分支:VMA / mmap-mprotect 追踪 / process_vm_readv
观测对象是进程的 VMA(virtual memory area)——Windows 的 VAD 在这里叫 VMA,但事件与读取路径完全不同。
一、VMA 枚举与信号
- 主线:
/proc/<pid>/maps(或smaps,含更细的统计)→ 筛可执行 VMA → backing-file/provenance 判断 → 线程 PC/栈归属 → dump maps每行字段:address perms offset dev inode pathnameperms四字符含义:r读 /w写 /x执行 /s共享 /p私有(COW)pathname为空 = 匿名映射;无文件映射另显示为[heap]、[stack]、[vdso]、[anon:<名字>]、[anon_shmem:<名字>]
- 高效查询:Linux 6.11 起提供
PROCMAP_QUERYioctl(struct procmap_query,定义在linux/fs.h),可按条件过滤单个 VMA,适合程序化扫描——比解析文本更稳(内核文档:Documentation/filesystems/proc.rst) - 高价值信号:
- 匿名
r-x/rwxVMA(无文件背书却可执行) /memfd:<名字> (deleted)或 "deleted" 的文件背书可执行映射(载体已从文件系统消失)- 正常 ELF 映射的内容与磁盘上的 ELF 不一致(代码被改写/替换,对应 Windows 的 module stomping)
- heap/匿名 VMA 事后变为可执行(W→X 转换)
- 线程 PC/RIP 落进这些区域;栈上的返回地址指向这些区域
- 匿名
- |注意:"匿名可执行"是 signal,不是 verdict——JIT(JVM / V8 / Mono)、Wine/Proton、QEMU TCG 都产生这类 VMA;要看 provenance 与执行史(见本节末)
二、动态侧(trigger):不要只 hook libc
- 只 hook
libc的mmap()/mprotect()会被直系统调用绕过 - 更底层的事件源:
- syscall tracepoint / kprobe(
bpftrace、perf)——直接看mmap/mprotect/memfd_create等系统调用 - BPF LSM——把 BPF 程序挂到 LSM hook 上;内核 BPF LSM 文档(
Documentation/bpf/bpf_lsm.rst,新版为prog_lsm.rst)正是拿file_mprotect作示例:int file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot);,示例程序SEC("lsm/file_mprotect")在命中时返回-EPERM(即可审计、也可拦截) - 可挂 hook 的清单见内核
include/linux/lsm_hooks.h(新版在security/security.c);挂载用BPF_RAW_TRACEPOINT_OPEN或 libbpfbpf_program__attach_lsm;需特权(文档提到CAP_MAC_ADMIN与CAP_SYS_ADMIN) - 采集侧只需要 executable protection transition 这类事件作 trigger,落点即 dump 时机(见 re-ebpf 的 eBPF 侧细节)
- syscall tracepoint / kprobe(
三、执行上下文归属
- 线程:枚举
/proc/<pid>/task/;读stat/status得到状态与位置线索;对可疑线程做栈回溯(gdb/eu-stack)取返回地址 - 判据:线程 PC 或栈上返回地址落在匿名/已删除背书的可执行 VMA 内
四、Dump
process_vm_readv():跨进程地址空间读取,直接按 [基址, 长度] 取区域;权限受 ptrace access check 约束(含 Yamaptrace_scope,见 re-memdump 坑)- 其他路径:
ptrace(PTRACE_ATTACH+PTRACE_PEEKDATA/process_vm_readv)、gcore(整进程 core,注意它是单进程镜像不是整机镜像)、/proc/<pid>/mem(同样受 ptrace 权限约束) - Linux 的可执行格式是 ELF:擦头/无头的载荷同样存在——保存整区后按内容分类(完整 ELF / 手工映射 ELF / 裸 shellcode / JIT code / 解密 blob)
- 内存 vs 磁盘 diff:
/proc/<pid>/maps给出 backing 路径的,取磁盘副本与内存内容逐页/摘要比对(发现代码被替换);配[[re-variant]]思路做差异定位
五、误报与白名单
- 白名单/降权:JVM、V8/Node、Mono/.NET、Wine/Proton、QEMU TCG、解释器生成的 trampoline、安全软件自身的探针
- 评分与 Windows 分支同构:匿名可执行 + 线程 PC/栈命中 + 近期 W→X + 内容熵/ELF 特征 + 无已知运行时来源
工具与验证
- 基础:
cat /proc/<pid>/maps(验证能看到 address/perms/pathname 三段);smaps看Rss/Private_Dirty等 - 事件:
bpftrace -l 'tracepoint:syscalls:*mprotect*'应与目标内核实际提供的 tracepoint 对得上;BPF LSM 需内核CONFIG_BPF_LSM与 BTF(验证ls /sys/kernel/btf/vmlinux) - 采集:
python3 -c "import ctypes; ..."或直接用 gdbdump memory验证process_vm_readv路径可达
该平台的坑
- 只 hook libc 就以为覆盖:直系统调用绕过——用 tracepoint/kprobe 或 BPF LSM
- 把 VMA 当 VAD 直接翻译:Linux 没有"模块链与 VAD 交叉"那套(模块 = 文件背书映射),替代物是 maps 的 pathname/inode 与磁盘文件比对
process_vm_readv报 EPERM:先看 Yama ptrace_scope 与目标进程关系(re-memdump 坑),不要误判为载荷消失- 把匿名可执行当恶意:JIT/Wine/TCG 大量产生——先基线、再评分
六、内核态载荷(LKM / rootkit)的采集
用户态扫描看不到的内核态载荷,采集逻辑与用户态同构但要换观测面(分析方法见 re-kernel 的 Linux 分支):
- cross-view 是前提:
lsmod//proc/modules干净不构成结论(rootkit 可把模块从链表摘掉,模块仍在运行)。交叉比对:- 模块视图:
/proc/modules、/sys/module/<name>/ - 符号视图:
/proc/kallsyms(受kptr_restrict影响) - 节地址视图:
/sys/module/<name>/sections/ - 内核可执行内存区间(离线镜像侧见 re-mem-forensics;在线侧需要内核态手段,属授权场景)
- 视图互相矛盾时,优先信任离线内存取证,而不是被分析系统自己给出的输出
- 模块视图:
- inline text patch 的发现:内核 text 与磁盘
vmlinux/ 模块副本逐段比对(函数头被改写但地址不变——与用户态的 inline hook 同构) - hook 落点不止 syscall table:
file_operations、seq_operations、proc handlers、网络协议回调、ftrace、kprobes 都被用过——"syscall table 干净"不是结论 - 采集时机:内核态载荷同样有"用后清除"(模块自卸、hook 复原)——命中即 dump
- 授权边界:内核态采集通常需要加载自己的模块 / 用 kprobe 一类机制,只在自有或已授权设备上做,并记录停止条件