跳转至

挖矿木马反复拉起

01 前置理论与核心机制

1. 为什么常规的 kill -9 和 rm -f 会失效?

  • 表象特征:线上服务器 CPU 使用率异常飙升,定位到恶意进程后,执行 kill -9 强制终止,进程会立即更换新的 PID 重新启动;直接执行 rm -f 删除磁盘上的可执行文件,数秒后文件又会自动重新生成。同时排查定时任务(crontab)与系统服务(systemd),均未发现异常配置。
  • 失效原因:常规单点清理失效,是因为恶意程序在底层利用了 Linux 的 fork 进程派生机制与 /proc 虚拟文件系统映射机制,实现了进程互锁守护与文件自恢复。

2. 进程自愈的两项底层机制

  • 双进程互相守护 (Fork & Monitor):程序启动后,会调用 fork() 派生出父子两个进程(监控进程与工作进程)。两个进程互相监听对方的存活状态(通过 waitpid 阻塞等待或轮询检查 getppid)。一旦单独终止其中一个进程,另一个存活进程检测到对端退出,会在毫秒级时间内重新 fork 拉起新进程。除非在同一时间片内将两者同时终止,否则无法彻底清理。
  • 内存镜像文件恢复 (/proc/self/exe):在 Linux 中,只要进程仍在内存中运行,即便删除了磁盘上的物理文件,该程序对应的 Inode 与数据块依然驻留在内核内存中。存活的木马进程会定时通过 access() 检测磁盘路径是否存在,一旦发现原文件被删除,便会直接读取 /proc/self/exe(指向当前运行程序内存镜像的符号链接),将内存中的二进制数据流重新写回磁盘,实现文件自恢复。

02 故障排查与清理闭环 SOP

面对具备互相守护机制的恶意进程,不能直接单点执行 kill -9,需利用系统信号先“冻结”其 CPU 执行流,按以下步骤彻底清理:

步骤一:定位异常的守护进程组

首先找出高 CPU 占用的异常进程及其关联的父子进程。若恶意程序伪装了进程名,需结合 ps -ef 或 pstree -p 梳理父子进程关系(PPID):

# 查找恶意进程,重点观察同名或存在父子关联的成对进程 PID
ps -ef | grep malware
  • 观测点:记录这对互相守护的进程 PID(例如:2139 和 2140)。

步骤二:利用 SIGSTOP 信号冻结进程执行流 (核心步骤)

切勿直接执行 kill -9。优先向这两个进程同时发送 SIGSTOP 暂停信号:

# 发送 STOP 信号,将父子进程同时挂起冻结
kill -STOP 2139 2140
  • 原理解析:进程一旦收到 SIGSTOP 信号,会被操作系统内核强制切换为暂停状态(T 状态),剥夺 CPU 调度时间片。此时两个进程既无法执行 fork() 派生新进程,也无法发起文件 I/O 读取 /proc 恢复磁盘文件,互相守护的监控循环被彻底中断。

步骤三:强制终止进程与清理磁盘文件

确认所有关联进程均处于挂起冻结状态后,统一发送 SIGKILL 信号将其彻底终止,并删除磁盘上的可执行文件:

# 1. 对已被冻结的所有关联 PID 发送 SIGKILL 强制终止
kill -9 2139 2140

# 2. 移除磁盘上的恶意程序源文件
rm -f /tmp/malware

步骤四:最终状态核验

复查系统进程与文件状态,确认恶意程序已被彻底清除:

ps -ef | grep malware
ls -l /tmp/malware

  • 验证点:确认进程未再重新拉起,磁盘文件未再生成,系统 CPU 使用率回落至正常水位。

03 真实生产故障现象与源码级解析

机制 1:双进程互相守护逻辑还原

恶意程序通常使用循环结合 fork() 与 waitpid() 监测对端进程状态:

    pid_t pid
     pid

        pid
       pid

             pid_t current_ppid

             current_ppid  monitor_pid  current_ppid

机制 2:基于 /proc 的文件自恢复逻辑还原

即使磁盘上的 /tmp/malware 被删除,只要进程存活,其自检线程可通过以下代码从内存映射中还原文件:

 exec_path F_OK

    stdifstream  stdiosbinary
    stdofstream exec_path stdiosbinary
     src  dst
        dst  src

04 生产排障速查表(守护型木马清理)

操作方式执行命令动作定性与原理实际效果误区:单点终止kill -9 <PID>仅终止单个工作或监控进程触发对端进程存活检测,立即派生出新 PID 进程误区:直接删文件rm -f /tmp/malware仅解除磁盘目录项硬链接触发进程文件自检,通过 /proc/self/exe 立即还原文件正解:批量冻结kill -STOP <PID1> <PID2>剥夺 CPU 时间片,挂起全部关联进程进程暂停执行,中断进程派生与文件 I/O 自愈能力正解:批量终止kill -9 <PID1> <PID2>对已处于挂起状态的进程执行销毁关联进程组被同时终止,无法触发守护逻辑正解:清理源文件rm -f /tmp/malware删除磁盘上的物理可执行文件因内存中已无存活进程支撑,文件不再复现

05 面试应答参考话术

面试官问:“线上发现一个挖矿进程占用了很高的 CPU,但是用 kill -9 杀掉后立刻又启动了新的 PID,把磁盘文件删掉也会自动恢复,定时任务里也查不到异常。遇到这种情况怎么排查处理?”

可以这样回答:遇到这种无法直接杀掉且没有定时任务介入的木马,通常是因为它在代码层面利用了 Linux 的两个底层特性: 第一是 双进程互相守护。程序启动后会 fork 出父子进程互相监听存活状态,如果只单独 kill 其中一个,另一个存活进程检测到对端退出,会立即在几毫秒内重新 fork 出一个新进程。 第二是 利用 /proc 内存映射恢复文件。在 Linux 中只要进程还在运行,即便删掉了磁盘上的文件,程序本体依然驻留在内存中。存活进程一旦检测到磁盘文件被删,就会直接读取 /proc/self/exe 这个内存镜像,把二进制数据重新写回磁盘。解决这种问题的核心思路不是直接“杀”,而是先“冻结”。具体分为三步:第一步:定位关联进程组。

通过 ps -ef 或 pstree -p 梳理进程树,把这一对或多对互相守护的恶意进程 PID 全部找出来。第二步:发送 SIGSTOP 信号批量冻结。

执行 kill -STOP <PID1> <PID2>,向这些进程发送挂起信号。进程一旦被 SIGSTOP 冻结,就会失去 CPU 调度执行权,既无法执行 fork 去拉起新进程,也无法执行文件 I/O 去读取 /proc 恢复文件。第三步:彻底终止与清理文件。

在关联进程全部处于挂起暂停状态后,再统一执行 kill -9 <PID1> <PID2> 将它们彻底终止,最后执行 rm -f 删除磁盘上的可执行文件。完成应急清理后,由于该服务器曾被植入恶意程序,说明存在安全漏洞且权限可能已泄露,后续需进一步排查入侵入口(如弱口令、未授权访问或 Web 漏洞),并在修复漏洞后安排重装系统。

06 面试高频追问与应答预案

追问 1:如果恶意程序启动后将自身进程名修改为 [kworker/u4:1] 伪装成系统内核线程,如何快速甄别?

  • 回答要点:
  • 核查父进程 ID (PPID):Linux 系统中真实的内核态线程(如 kworker、ksoftirqd 等),全部是由 2 号进程(kthreadd) 派生并管理的,其 PPID 必然为 2(且真实的内核线程在 /proc/<PID>/exe 下指向空,不对应任何磁盘物理文件)。
  • 甄别方法:如果发现一个名为 [kworker] 的进程占用了极高的 CPU,通过 ps -ef 查看其 PPID 不是 2(例如是 1 或其他普通进程号),或者执行 ls -l /proc//exe 能够看到具体的磁盘文件路径(哪怕带有 deleted 标记),即可 100% 确认为伪装成内核线程的用户态木马。

追问 2:为什么不能直接通过修改 /proc/<PID>/exe 的权限(chmod 000)来阻止木马恢复磁盘文件?

  • 回答要点:
  • /proc 是 Linux 内核在内存中动态维护的虚拟文件系统(procfs),并非真实的磁盘存储。/proc/<PID>/exe 本质上是一个特殊的内核态符号链接,系统底层不允许对其直接执行 chmod 修改权限。
  • 即便尝试修改,只要恶意进程拥有对应用户的运行权限且持有自身内存空间的读取权,唯有通过进程信号(如 SIGSTOP)剥夺其 CPU 调度权,才能从根本上阻断其执行 read() 与 write() 系统调用。

07 守护型木马冻结与清理脚本(kill_ghost_malware.sh)

该脚本用于自动检索匹配指定关键字的异常父子进程组,支持一键发送 SIGSTOP 信号将关联进程全部冻结挂起,并在确认后统一销毁。

#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: kill_ghost_malware.sh
# 功能描述: 自动扫描关联的异常进程组,通过 SIGSTOP 信号批量冻结并清理 (需人工确认)
# 使用方式: 在宿主机以 root 权限执行: ./kill_ghost_malware.sh
# ==============================================================================

set -o pipefail

TARGET_NAME=$1

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'

if [ -z "$TARGET_NAME" ]; then
    echo -e "${RED}用法: $0 ${PLAIN}"
    exit 1
fi

echo -e "${BLUE}================================================================${PLAIN}"
echo -e "${BLUE}           守护型恶意进程组 (Fork & Monitor) 冻结清理工具       ${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"

# 1. 提取所有关联的 PID (排除当前脚本自身与 grep 进程)
echo -e "\n${YELLOW}[1/3] 正在扫描与 '${TARGET_NAME}' 关联的进程列表...${PLAIN}"
# 修复了这里的管道符乱码
PIDS=$(ps -ef | grep "$TARGET_NAME" | grep -v "grep" | grep -v "$0" | awk '{print $2}')

if [ -z "$PIDS" ]; then
    echo -e "  -> ${GREEN}[OK] 未检测到包含关键字 '${TARGET_NAME}' 的运行中进程。${PLAIN}"
    exit 0
fi

# 格式化输出进程树与对应的物理文件路径
echo -e "  -> ${RED}[ALERT] 检测到以下关联进程:${PLAIN}"
for pid in $PIDS; do
    if [ -d "/proc/$pid" ]; then
        PPID_VAL=$(awk '/^PPid:/ {print $2}' "/proc/$pid/status" 2>/dev/null)
        CMD_LINE=$(tr '\0' ' ' /dev/null)
        EXE_PATH=$(readlink "/proc/$pid/exe" 2>/dev/null)
        # 修复了这里的输出字符串管道符乱码
        echo -e "     PID: ${RED}${pid}${PLAIN} | PPID: ${YELLOW}${PPID_VAL}${PLAIN} | 命令行: ${CMD_LINE}"
        echo -e "     -> 映射物理路径: ${YELLOW}${EXE_PATH}${PLAIN}"
    fi
done

# 2. 冻结确认阶段
echo -ne "\n  -> ${YELLOW}是否立即发送 SIGSTOP 信号批量冻结上述进程?(阻断自愈逻辑) [y/N]: ${PLAIN}"
read -r CONFIRM

if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
    echo -e "  -> ${YELLOW}[INFO] 已取消操作。${PLAIN}"
    exit 0
fi

# 3. 执行 SIGSTOP 批量冻结
echo -e "\n${YELLOW}[2/3] 正在执行批量挂起冻结 (kill -STOP)...${PLAIN}"
# 将多行 PID 转换为单行参数一次性传入 kill,减少时间差
PID_LIST=$(echo "$PIDS" | tr '\n' ' ')
kill -STOP $PID_LIST 2>/dev/null

echo -e "  -> 已发送 SIGSTOP 信号至 PID 列表: ${PID_LIST}"
echo -e "  -> ${GREEN}[SUCCESS] 关联进程组已全部进入暂停状态 (T),丧失 CPU 调度与文件恢复能力。${PLAIN}"

# 4. 执行最终终止清理
echo -ne "\n  -> ${YELLOW}进程已安全冻结。是否立即执行 kill -9 强制终止这些进程?[y/N]: ${PLAIN}"
read -r CONFIRM_KILL

if [[ "$CONFIRM_KILL" =~ ^[Yy]$ ]]; then
    echo -e "\n${YELLOW}[3/3] 正在执行批量强制终止 (kill -9)...${PLAIN}"
    kill -9 $PID_LIST 2>/dev/null
    echo -e "  -> 已强制终止 PID 列表: ${PID_LIST}"
    echo -e "  -> ${GREEN}[SUCCESS] 恶意进程组已彻底清理。请立即根据上方打印的“映射物理路径”执行 rm -f 删除磁盘源文件。${PLAIN}"
else
    echo -e "  -> ${YELLOW}[INFO] 未执行终止操作。目标进程当前仍保持挂起 (STOP) 状态,方便进行内存取证。${PLAIN}"
fi

echo -e "\n${BLUE}================================================================${PLAIN}"