/proc 虚拟文件系统取证

Linux 内核把自己的运行时状态挂在 /proc 上。那不是磁盘上的目录——里面绝大多数「文件」是内核在你读取的瞬间现生成的。

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:procfs、运行时快照、在线取证、进程命令行、环境变量、文件描述符、/proc/net/tcp、十六进制解码 难度:高级 前置知识:Linux 进程模型、socket 状态机、TCP/IP 协议基础

一、概述

Linux 内核把自己的运行时状态挂在 /proc 上。那不是磁盘上的目录——里面绝大多数「文件」是内核在你读取的瞬间现生成的。

这个性质决定了整件事的全部前提:

/proc 里的内容只能在线获取。断电后的磁盘镜像里,/proc 是一个空目录。

跟 Windows 取证里「内存态工件丢失」算是同一类问题。但 Linux 有个明显优势:不做内存镜像,只要在断电前用只读命令把关键项导出成文件,就能拿到大量带时间戳的运行时证据。

而这批证据在镜像里完全无法重建。

所以 /proc 的取证价值几乎全押在一件事上:现场有没有做在线采集,采得全不全。这个判断得在现场做完,事后补不回来。

这一层的位置很特殊:它不属于「拿到镜像之后」的任何环节,它属于「拿到镜像之前」。

上游是现场处置决策。是否断电、是否先做内存采集、是否在断电前导出 /proc,这三个决定必须在现场几秒内做完,依据见远程勘验与在线取证。

它自己要在采集前确认容器环境。在容器内 /proc 是宿主机视角还是容器视角,取决于挂载方式与 cgroup 配置。判错会把宿主机的进程误认成容器的。

下游支撑归属分析。/proc/net/tcp 里的连接要反查进程才能定性,「哪个进程走的」是流量分析的必答项;环境变量里的凭据进入后续分析,但只做离线分析与最小化提取,不做在线使用。

它与内存取证是替代关系而非并列:来不及做完整内存镜像时,逐项导出 /proc 是成本低得多的折中方案。

能提供四类其它地方拿不到的证据:

证据类型 典型位置 为什么磁盘上找不到
进程完整命令行 /proc/<pid>/cmdline 进程退出即消失;bash_history 只记交互命令,不记脚本参数
进程环境变量 /proc/<pid>/environ 常含数据库口令、API token、云凭据;不落盘、无法 grep 到
被删除但仍打开的文件 /proc/<pid>/fd/ 文件已从目录树删除,但 inode 仍被进程持有——经典反取证场景
真实网络连接 /proc/net/tcp、/proc/<pid>/net/tcp 内存态;日志里可能完全没有

具体到每个子项的判读点:

子项 内容 关键判读点
/proc/<pid>/cmdline NUL 分隔的参数序列 可执行文件路径与参数都能取到,与历史文件互补
/proc/<pid>/environ NUL 分隔的环境变量 敏感度高,摘录必须脱敏
/proc/<pid>/fd/ 打开的文件描述符软链 指向 (deleted) 的条目即被删除但仍被持有的文件
/proc/<pid>/maps、status 内存映射与状态 指向的匿名可执行区是进程注入的线索
/proc/net/tcp、tcp6 连接与监听状态 地址与端口是十六进制小端编码,直接读会得到反的结论
/proc/<pid>/net/tcp 命名空间视图 与 /proc/net/tcp 内容可能不同,容器内差异尤其明显
/proc/<pid>/cgroup 容器归属线索 判定进程属于哪个容器的基础
危险项 /proc/kcore 等 /proc/kcore 相当于内核内存视图,绝不能在原机上读取

三个判定要先立起来:十六进制编码的解码方向、命名空间的隔离效果,以及删除状态下文件仍被持有的含义。最后一类是最有价值、也最容易被忽略的。

答得出来的是:案发当时系统上有哪些进程在跑、每个进程带什么参数、谁持有哪些已删除的文件、当时有哪些网络连接及其归属进程,以及某些文件在被删除后是否仍被某个进程占用。

答不出来的,以及几个必须先认清的边界:

离线镜像里的一切。 镜像中的 /proc 是空目录。这不是「没有进程」,而是「这个证据形态不适用于离线分析」——报告里写错这句话,会让一次本可在线完成的调查变成不可能。

历史。 /proc 是当前快照,不记录任何过去。它能回答「现在有什么」,不能回答「一小时前有什么」。

已退出进程的任何信息。 进程一退出,/proc/<pid> 立即消失,没有历史记录可查。

网络连接的目的地内容。 它给出的是连接与进程归属,「传输了什么」需要网络侧材料,见网络流量与 PCAP 取证。

容器内的完整视图。 若 /proc 未按容器隔离,你看到的可能混入了宿主机进程,边界判定不清时不能直接下结论。

敏感凭据的后续使用。 environ 里的 token 与口令只做离线记录与最小化摘录,任何在线使用都不在取证范围内。

要读懂这些,得先有 Linux 进程模型——进程是运行态实体,/proc 是它的文件系统投影而非存储;了解文件描述符与 inode 的引用关系,否则没法解释「文件已删除但 fd 仍指向它」;掌握 socket 状态机与 TCP/IP 基础,否则十六进制解码出来的地址端口方向会搞反;熟悉十六进制与小端序的换算,这是 3.4 解码环节的前提;了解容器与 cgroup 的基本概念,能识别 /proc/<pid>/cgroup 的归属信息。

还有一条纪律:所有操作不得改变系统状态,涉及写盘或触发内核动作的项必须在采集前排除。

二、核心原理

2.1 为什么 /proc 离线读不到

/proc 是一个由内核导出的虚拟文件系统(procfs)。目录项和文件内容都是读取时按需生成的:

读 /proc/1234/cmdline  →  内核从当前 task_struct 生成字节流返回
进程退出              →  /proc/1234/ 整个目录立即消失

由此可得:

场景 /proc 是否有内容
机器运行中 有(实时快照)
断电后做 dd 镜像 空目录(proc 只是镜像里的一个空壳目录)
机器运行中做在线/内存镜像 仍需单独采集——dd 磁盘镜像不包含 /proc 内容
采集 DigiForensics-mem.raw 后关机 /proc 内容只在内存镜像里,需从内存中恢复(难);最好是在关机前先导出

给现场处置的建议:如果委托方可能需要进程/连接证据,在断电前用 3.1 的只读命令把 /proc 关键项导出到独立分区或外接介质。

这个动作成本极低,几秒就完事,但决定了这类证据是否永久可得。一旦断电,这批证据就没了。

2.2 /proc/<pid>/ 关键子项

路径 内容 格式要点 取证价值
/proc/<pid>/cmdline 完整命令行 ★ 参数之间用 NUL(\0)分隔,不是空格 ★★★★★ 进程实际执行了什么;能证明脚本的真实参数
/proc/<pid>/environ 环境变量 NUL 分隔的 KEY=VALUE ★★★★★ 常含口令/token/云凭据
/proc/<pid>/cwd 当前工作目录 软链接(readlink 取目标) ★★★★ 进程在哪个目录操作文件
/proc/<pid>/exe 可执行文件本体 软链接;已被删除时目标带 (deleted) ★★★★★** (deleted) 是"文件已删但仍在运行"的铁证**
/proc/<pid>/root 进程的根目录 软链接;容器场景有用 ★★★ chroot/容器判断
/proc/<pid>/fd/ 打开的文件描述符 目录,每项是软链到实际文件 ★★★★★ 能看到已删除但仍打开的文件
/proc/<pid>/maps 内存映射布局 文本,含每段权限和路径 ★★★★ 定位注入的匿名可执行段、无文件映射的可疑内存
/proc/<pid>/status 进程状态 文本键值对 ★★★ Name/Pid/PPid/Uid/Gid/Threads/State
/proc/<pid>/stat 进程统计 单行文本,字段多且是第 4 个字段是命令名(含括号) ★★★ 取启动时间的换算基础
/proc/<pid>/statm / smaps 内存用量/细节 文本 ★★ smaps 可看是否有 RWX 匿名段
/proc/<pid>/mountinfo 该进程看到的挂载 文本 ★★★ 容器场景判断挂载来源
/proc/<pid>/cgroup cgroup 归属 文本 ★★★★ 容器/系统d 服务归属判定
/proc/<pid>/loginuid 触发登录审计的 uid 文本 ★★★ 登录归属(需 pam_loginuid 启用)

⚠️ cmdline 与 environ 用 NUL 分隔,不是空格也不是换行。 直接 cat 会看到"连成一片"或终端不换行。必须用 tr '\0' ' ' 或 xargs -0 转换。 这是最常见的 /proc 读取错误之一。

⚠️ /proc/<pid>/exe 显示 (deleted) 的含义:文件的目录项已被删除(unlink),但 inode 仍被该进程持有映射——所以程序还在运行,但磁盘上已经没有这个文件了。这正是"删除日志文件后继续写入日志"的实现基础,也是证明"文件曾存在过"的最强证据。

2.3 /proc/net/tcp 的结构与十六进制编码

/proc/net/tcp(tcp6/udp/udp6 同理)是当前网络连接与套接字的快照,第一行是表头:

  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
   0: 0100007F:1F90 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 12345 ...
   1: 0100007F:1F90 0201A8C0:C350 01 00000000:00000000 00:00000000 00000000     0        0 54321 ...

前 4 列是要解析的重点:sl(序号)、local_address(本地地址)、rem_address(对端地址)、st(状态)。

编码规则是本篇的核心知识点,一句话:地址部分是 32 位(IPv4)或 128 位(IPv6)的十六进制,但按 4 字节一组、每组内部字节序反转(小端)存放。

拿 0100007F(IPv4 的 127.0.0.1)走一遍:

十六进制字符串:  01 00 00 7F
每 2 位倒序(字节交换):  7F 00 00 01
按点分十进制读:  127.0.0.1

解码步骤因此是:把 8 位十六进制看作一个小端 32 位整数,再用网络字节序解释。等价于"每 2 位倒序一次"。

端口部分是普通十六进制,没有字节交换:1F90 = 0x1F90 = 8080(十进制)。

本篇要求的核心验证:0100007F:1F90 → 地址 0100007F 倒序为 7F000001 → 127.0.0.1;端口 1F90 → 8080。结果 = 127.0.0.1:8080 ✓

IPv6 的规则:地址是 128 位 = 4 个 32 位字,每个 32 位字各自做一次小端字节交换,再按网络序拼成 16 字节。

/proc/net/tcp6 中的值 解码结果
00000000000000000000000001000000 ::1
000080FE0000000000000001000000 fe80::1
B80D012000000000000000001000000 2001:db8::1

IPv4 与 IPv6 的区分看地址字段的十六进制长度:8 个字符 = IPv4(/proc/net/tcp),32 个字符 = IPv6(/proc/net/tcp6)。两者不会出现在同一个文件里。

状态字段 st 是一字节十六进制(常用):

st 状态 取证含义
0A LISTEN 监听中的端口;非预期端口的 LISTEN = 值得深挖
01 ESTABLISHED 活跃连接;指向外部 IP 的 ESTABLISHED = 外传/入侵通道
06 TIME_WAIT 连接刚关闭(攻击者断开后常见)
08 CLOSE_WAIT 对端已关闭、本端未关闭(进程未正确收尾,程序异常或恶意常留)

2.4 /proc 下的网络与其它全局文件

路径 内容
/proc/net/tcp、tcp6、udp、udp6 连接与套接字快照(本网络命名空间)
/proc/net/arp ARP 缓存(近期通信过的同网段 IP,局域网取证有用)
/proc/net/route 路由表(可判断网关/内网网段)
/proc/net/dev 各网卡流量统计(收发字节数,粗略判断外传量)
/proc/net/unix Unix domain socket
/proc/net/xt_qtaguid/stats 按 UID 统计流量(部分内核/发行版才有)
/proc/modules 已加载内核模块(与第 04 篇的模块排查联动)
/proc/mounts、/proc/mountinfo 挂载表(判断 tmpfs、bind mount、容器挂载)
/proc/uptime 系统运行时长(秒) ★
/proc/stat 系统启动以来的 CPU 时间累计

/proc/uptime 是"这台机器上次什么时候启动的"的快速答案:uptime 秒数 + 当前时间 = 启动时刻。

但断电机器的 /proc 是空的,所以这个方法只在线可用。离线的等价做法是从日志/journal 的首次启动记录推断。

2.5 /proc/kcore 与危险项

/proc/kcore 是内核内存的转储接口,大小约等于物理内存(甚至更大)。它绝不是普通文件。

绝对不要 cat、cp、dd,也不要挂载后去浏览它。在多数系统上尝试读取会导致系统立即挂起或崩溃(它可能触发巨大的不可恢复读),某些配置下还可能造成数据泄露。

它属于内存取证的范畴——需要时用专门的内存采集工具(在断电前从另一个接口获取),不是本篇的操作对象。

同理需要谨慎的还有:/proc/self/mem(需权限,且可致崩溃)、/proc/kallsyms(需 root,部分系统已限制)。

2.6 容器环境下的 /proc 差异

在容器里,/proc 的"视角"是容器的,不是宿主机的。 这是取证时必须先判定的前提。

差异点 宿主视角 容器内视角
/proc/<pid> 所有进程 只能看到容器内 PID 命名空间里的进程(容器 PID 1 = 应用自身)
/proc/net/tcp 宿主全部连接(默认 net namespace) 仅该网络命名空间内的连接(--network=host 时才共享宿主)
/proc/mounts 宿主完整挂载 容器内的挂载(含 overlay)
/proc/<pid>/root / 容器自己的根文件系统 ★
/proc/1/environ systemd 的环境 容器的启动命令与环境(容器取证的第一入口)

容器取证的关键入口(若 /proc 是在容器内采集的):

容器内的 PID 1 命令行 = 容器启动了什么

tr '\0' ' ' < /proc/1/cmdline; echo tr '\0' '
' < /proc/1/environ | grep -iE 'PASSWORD|SECRET|TOKEN|KEY|DATABASE|DSN'

容器的 cgroup 归属(回到宿主可见的容器 ID)

cat /proc/1/cgroup

容器看到的挂载(含 overlay 层)

cat /proc/1/mounts | grep -E 'overlay|/etc/hosts|/etc/resolv.conf'

判定方法很简单:若 /proc/1/cmdline 显示的不是 systemd/init 而是应用名(nginx、python、java 等),说明这份 /proc 是在容器命名空间内采集的。

此时"看不到某进程"是命名空间隔离的结果,不代表进程不存在。这一点必须在报告中说明,否则会把"命名空间隔离"误判为"无此进程"。


三、操作步骤

3.1 第 1 步:不破坏系统的现场采集(在断电/关机之前执行)

目标:把 /proc 里的关键项导出为普通文件。 全程只读,不 kill 任何进程。

#!/bin/bash

在线 /proc 采集脚本(只读;输出到 /evidence/proc-snapshot)

OUT=/evidence/proc-snapshot mkdir -p "$OUT"

1) 全局快照

uptime > "$OUT/uptime.txt" date -u +'%Y-%m-%dT%H:%M:%SZ' > "$OUT/collected-at-utc.txt" cp /proc/net/tcp "$OUT/net-tcp.txt" 2>/dev/null cp /proc/net/tcp6 "$OUT/net-tcp6.txt" 2>/dev/null cp /proc/net/arp "$OUT/net-arp.txt" 2>/dev/null cp /proc/net/route "$OUT/net-route.txt" 2>/dev/null cp /proc/modules "$OUT/modules.txt" 2>/dev/null cp /proc/mounts "$OUT/mounts.txt" 2>/dev/null

2) 逐进程采集关键项(NUL 分隔必须转换!)

for p in /proc/[0-9]*; do pid=$(basename "$p"); d="$OUT/pid-$pid"; mkdir -p "$d" tr '\0' ' ' < "$p/cmdline" > "$d/cmdline.txt" 2>/dev/null # ★ NUL→空格 tr '\0' '
' < "$p/environ" > "$d/environ.txt" 2>/dev/null # ★ NUL→换行 readlink "$p/exe" > "$d/exe.txt" 2>/dev/null readlink "$p/cwd" > "$d/cwd.txt" 2>/dev/null cp "$p/status" "$d/status.txt" 2>/dev/null cp "$p/maps" "$d/maps.txt" 2>/dev/null cp "$p/cgroup" "$d/cgroup.txt" 2>/dev/null ls -l "$p/fd" > "$d/fd-list.txt" 2>/dev/null # 含 "(deleted)" done

3) 全盘建立 fd → 目标 索引(已删除文件一眼可见)

grep -l 'deleted' "$OUT"/pid-/fd-list.txt 2>/dev/null grep -h 'deleted' "$OUT"/pid-/fd-list.txt 2>/dev/null

只读原则:/proc/<pid>/environ 和 /proc/<pid>/fd/ 通常需要 root(environ 受 PTRACE_MODE_READ 限制),用 sudo 读取是必要的,但仍属只读。

绝不要 kill、kill -9、写 /proc/.../mem,也不要尝试 echo > /proc/.../...。

3.2 第 2 步:进程清单与高价值筛选

进程清单(命令行 + exe + 用户)

cd /evidence/proc-snapshot for d in pid-*; do pid=${d#pid-} printf '%-8s %-10s %s
' "$pid" "$(cat $d/exe.txt 2>/dev/null | xargs basename 2>/dev/null)"
"$(cat $d/cmdline.txt 2>/dev/null | cut -c1-90)" done | sort -n

★ 筛出 exe 指向已删除文件的进程("删了文件还在跑")

grep -l 'deleted' pid-*/exe.txt 2>