CPU飙升遇挖矿病毒?从伪装systemd脚本到持久化清理全复盘

发布时间:2026/9/15 12:25:55
CPU飙升遇挖矿病毒?从伪装systemd脚本到持久化清理全复盘 那天下午监控弹了一条告警生产环境的CPU使用率直接飙到接近100%load average从平时的1以下一路冲到20多。第一反应是业务出问题了结果登录服务器一看top输出里根本没见到业务进程的影子反而是几个命令名极其可疑的进程占着几百的CPU。再往下查发现一个伪装成systemd服务的脚本文件.systemd-service.sh正躺在系统目录里那一刻就明白了——服务器被植入挖矿病毒了。 这篇就完整记录我从发现告警、锁定进程、拆解病毒逻辑、清除持久化到最后溯源修复的整个过程。标题里的.systemd-service.sh不是某个特定样本的名字而是一类挖矿木马非常典型的伪装手法把自己包装成看起来像是系统自带服务的脚本混在systemd相关的目录和文件里试图蒙混过关。这套排查思路对建站运维、云服务器管理、自建机房的新人都适用看完之后至少能知道遇到CPU跑满时该从哪里下手哪些文件要重点检查以及怎么防止清理完又被攻击者打回来。 ## 1. 攻击者的全部伪装目录从入口到落地 ### 1.1 一套典型的挖矿病毒攻击链路 先说清楚这类挖矿病毒是怎么进到服务器里的不然后面排查会一头雾水。就我遇到的情况以及同时期群里同行反馈的案例来看常见入口有这么几类Redis类中间件未授权访问、SSH弱口令爆破、数据库弱口令、Web中间件漏洞比如框架RCE、Docker API未加访问控制。攻击者拿到执行权限后第一件事不是立刻挖矿而是先“安家”——下载挖矿程序、投放持久化脚本、设置定时任务、清掉日志一套动作通常在几分钟内完成。 这里有个关键点成熟的挖矿木马都自带“初始化脚本”和“挖矿主程序”两段。初始化脚本负责下载主程序、做各种持久化配置挖矿主程序则真正去连接矿池、消耗CPU跑哈希。本文主角.systemd-service.sh就属于前者我甚至见过它在脚本头部的注释里直接写着THIS SCRIPT MANAGES MINER PROCESS连掩饰都懒得做。这个脚本一旦在启动列表里被拉起来它会去检查挖矿主进程是否存活不存活就从远程地址拉新文件或者直接执行内嵌的挖矿命令。 ### 1.2 .systemd-service.sh的伪装手法借systemd之名 恶意进程之所以叫.systemd-service.sh意图很明显让管理员在ps或者ls的时候误以为它是系统服务的一部分。它通常被放在几个特定位置我见过且比较好分辨的有 - /usr/lib/systemd/systemd-service.sh - /usr/lib/systemd/system/systemd-service.sh - /etc/systemd/system/systemd-service.sh - /root/.systemd-service.sh带点号隐藏文件 仔细看路径它专门挑/usr/lib/systemd/这种正常人不会天天翻的目录落脚文件名也取得非常具有误导性。如果不是CPU告警在先我大概率不会专门去/usr/lib/systemd/翻文件。脚本本身是Shell脚本用file命令一看就知道 bash file /usr/lib/systemd/systemd-service.sh # 输出结果类似POSIX shell script executable (binary data)这里有个值得注意的细节脚本被执行后它会设法把自己的命令行参数改得很像系统进程有些变种还会在/proc/self/cmdline里写入systemd-service之类的字符串。也就是说你用ps aux去看进程名可能看到的是一个叫[systemd]的进程或者一个占着300% CPU的kswapd0。这就是为什么光靠ps命令查不够必须配合/proc文件系统来核实进程真实身份后面会详细讲。1.3 病毒落地后的文件分布脚本、二进制、日志和锁文件完整的落地文件群不止.systemd-service.sh一个。我在清理这台服务器时把完整文件清单整理如下这也算是这类木马的标准物料包文件/目录用途典型路径初始化Shell脚本拉取挖矿程序、写定时任务、自守护/usr/lib/systemd/systemd-service.sh挖矿主程序连接矿池计算CPU消耗大户/tmp/.Xauthority、/var/tmp/...、/usr/bin/...守护脚本定时检查主程序是否存活死了就拉起/etc/cron.hourly/...、/usr/lib/...update日志/输出重定向挖矿程序运行输出用于隐藏自身/dev/null或nohup.out锁文件保证同一时间只有一个挖矿实例/tmp/.systemd-service.lock、/var/run/.lock看这个清单你会发现它做事的逻辑其实挺朴素的脚本负责“活着”主程序负责“干活”。最常见的组合就是Shell脚本把挖矿程序的输出转向/dev/null再用nohup挂到后台这样就算你SSH断开了挖矿进程也不会被杀掉。锁文件则是为了防止多个定时任务同时触发导致挖矿进程被重复拉起、互相打架。理解了这套逻辑后续清理时就会知道光杀进程不删脚本、不清理定时任务等于白干——几分钟后定时任务又会把一切拉起来。2. 三分钟锁定元凶CPU异常与可疑进程定位2.1 第一条线索top输出里的异常CPU占用登录服务器后我最先执行的是top这是排查CPU问题的第一步。当时界面输出很有代表性一个进程的CPU占用在300%以上另一个命令名很普通的进程也占着200%多而且它们排在最顶部业务进程压根看不到影子。除了CPUtop右上角的load average三个值都在20以上说明过去1、5、15分钟系统整体始终处于严重过载状态。这里顺便给新手补一个判断方法load average要看它和CPU核数的关系。如果是8核CPUload长期超过8通常建议警戒线是核数×0.7也就是5.6左右就说明有任务在排队等着CPU处理系统确实被什么东西打满了。我当时这台机器是8核load已经到了20多意味着大约有12个以上的任务在排队情况相当严重。建议执行top后按P让进程按CPU占用排序或者top -c直接显示完整命令行不要只看被截断的进程名。很多挖矿木马会把进程名伪装成系统核心进程只看前几个字母根本分辨不出来只有完整命令行才能暴露真实行为。我当时就是通过top -c看到某个进程的命令行实际上是从/usr/lib/systemd/systemd-service.sh拉起的才立刻意识到这是个伪装。2.2 深挖进程身份/proc文件系统才是照妖镜进程名可以被伪造/proc目录下的信息不容易伪造。这是排查过程中最反直觉也最关键的一步不要相信ps命令显示的名字直接进/proc/pid目录看证据。我列几个排查命令# 查看进程的实际启动命令/proc/pid/cmdline 里是用 \0 分隔的完整参数 tr \0 /proc/1234/cmdline # 查看进程的符号链接指向哪个可执行文件如果指向的文件已被删除会显示 (deleted) ls -l /proc/1234/exe # 查看进程工作目录 ls -l /proc/1234/cwd # 查看进程打开的文件列表被占用的病毒文件会在这暴露 ls -l /proc/1234/fd | head -50这里有一个很妙的排查点/proc/pid/exe。如果病毒把自己的可执行文件删了但进程还在运行exe链接会显示(deleted)这本身就是强信号。我那次排查就发现挖矿主程序的exe指向一个/tmp/下PATH已删除的文件立刻就能确定它有鬼。另一个重要信息是/proc/pid/status里的Name字段虽然进程名可以伪装但PPid父进程PID做不了假——当看到挖矿进程的父进程是1systemd或者某个cron进程说明它是被服务或定时任务拉起来的这对后续查找持久化入口很有帮助。2.3 网络出方向连接矿池流量特征CPU占用高只是为了定位“谁在挖”真正坐实“挖矿”行为的还要看网络连接。挖矿程序必须连接矿池地址才能工作所以检查服务器出方向的网络连接几乎等于直接看它的“犯罪记录”。命令如下ss -anpt | grep ESTAB当时输出里有一个TCP连接非常扎眼本地端口是随机高位端口远端地址是一个境外IP端口是4444。矿池通信的端口多为3333、4444、5555、14444、45560这些非常规端口或443、80这类伪装端口。这里我说明一下我并没有直接去连那个IP去验证业务上完全没有这样的连接需求所以基本可以断定就是挖矿通信。如果ss命令没看到明显的连接也别急着排除有些挖矿程序会通过DNS查询矿池域名来解析不建立持久连接。这时候可以临时抓包看DNS请求tcpdump -i eth0 -n port 53 -c 100看到频繁查询一个和挖矿有关的陌生域名也能作为参考证据。我处理过另一台机器进程本身还没建立TCP连接但DNS日志里全是某个矿池域的解析记录一查一个准。3. 抽丝剥茧从.systemd-service.sh到持久化机制3.1 脚本内容拆解排查不能停留在“知道它是个病毒”这一步必须把脚本逻辑看懂才能确保清理干净。把.systemd-service.sh下载到本地或用cat直接看内容建议不要直接在原服务器上编辑先cp一份做备份再分析。这类脚本核心逻辑通常包括几段#!/bin/sh # 这一段负责启动挖矿主程序并保活 if ! pgrep -f xmrig|kworkerds|sysupdate; then nohup /tmp/.x/xx -o stratumtcp://xxx.pool.xxx:4444 -u 钱包地址 /dev/null 21 fi # 这一段负责写入 crontab 实现持久化 (crontab -l; echo */5 * * * * /usr/lib/systemd/systemd-service.sh) | crontab - # 这一段负责将自身复制到系统目录并设置定时任务 cp /root/.systemd-service.sh /usr/lib/systemd/systemd-service.sh echo */10 * * * * root /usr/lib/systemd/systemd-service.sh /etc/cron.d/systemd-service这是我整理的常见结构不同变种细节不一样但骨架基本都是这三个功能拉起挖矿进程、给自己加定时任务、把自己复制到更隐蔽的路径。读脚本时重点留意三件事它调用的挖矿程序路径在哪即真正的CPU消耗者是谁。它写了哪些定时任务文件、哪些systemd服务文件。它有没有从远程地址通过wget或curl下载文件下载到哪个路径。3.2 systemd服务伪装服务文件与启动链更高级的变种会直接创建一个systemd服务单元文件让病毒实现开机自启。位置通常在/etc/systemd/system/下文件名可能叫systemd-service.service或multi-user.target.wants/链接。我当时检查到的一个疑似服务文件是这样的cat /etc/systemd/system/systemd-service.service配置内容大致是[Unit] DescriptionSystem Service [Service] Typeforking ExecStart/usr/lib/systemd/systemd-service.sh Restartalways RestartSec60 [Install] WantedBymulti-user.target看到ExecStart指向那个脚本又配了Restartalways这就意味着即使你手动杀掉挖矿进程systemd会在最多60秒后把它重新拉起来。这解释了为什么有人杀完进程没多久CPU又飚上去不是杀不干净而是启动服务一直在等你杀完再拉起。遇到这种情况不能只kill挖矿进程就算完。正确操作是先systemctl disable掉恶意服务再删除服务文件最后systemctl daemon-reload刷新服务状态。否则即使你把服务文件删了运行中的服务单元还会驻留在systemd内存里照样可以重启拉起。我还习惯在daemon-reload后再systemctl list-units | grep一下确认残留服务单元已经被彻底移除。3.3 crontab等多重持久化手段挖矿木马的持久化最喜欢用crontab因为最简单、最稳定。我检查了下面几个路径都有异常/var/spool/cron/root针对root用户的定时任务内容就是定期执行那个脚本。/etc/cron.d/系统级定时任务目录里面被塞了一个systemd-service文件。/etc/cron.hourly/按小时执行的目录也被放了一个同名脚本。/etc/crontab主配置文件尾部可能被追加了定时任务。清理crontab时不要只执行一遍crontab -r就完事。crontab -r只清当前用户的其他目录完全不受影响。我建议逐个检查# 查看root用户的定时任务 crontab -l -u root # 查看各定时任务目录 ls -la /etc/cron.d/ ls -la /etc/cron.hourly/ ls -la /etc/cron.daily/ cat /etc/crontab逐行看有没有不该出现的路径或脚本有就删除对应行或文件。这里有一个我踩过的坑/var/spool/cron/root里的任务可能伪装成一个看起来正常的注释比如# systemd service check但下一行就跟着恶意脚本路径。所以别只看注释要看实际执行了什么东西。4. 清理与止血删除病毒文件并恢复CPU负载4.1 清理前取证与隔离正式动手清理前先做取证。很多人一上来就kill加rm结果事后想复盘发现进程信息、文件内容、网络连接全没了连攻击者怎么进来的都看不出。我的做法是先把现场信息完整保存下来# 保存当前进程列表、网络连接、登录记录 ps aux /tmp/cpu_check/ps_before.log ss -anpt /tmp/cpu_check/net_before.log last -20 /tmp/cpu_check/last_before.log history /tmp/cpu_check/history_before.log # 把可疑脚本和挖矿程序打包备份不要直接跑只做归档 cp /usr/lib/systemd/systemd-service.sh /tmp/cpu_check/ cp /tmp/.x/xx /tmp/cpu_check/ 2/dev/null || true这一步能防两个问题一是有时候清理不彻底需要二次分析备份还在二是可以向同事或安全机构反馈样本方便别人做威胁分析。我处理过的另一台服务器就是靠这个备份的脚本定位到了它下载矿程序的外网地址进而确认了攻击者的C2基础设施。取证时还有个小细节注意保持SSH会话不要断尽量用tmux或screen开一个新会话或者先把输出重定向到文件再分析。万一清理过程中网络被攻击者断开你至少还有一份本地日志。4.2 按顺序清理恶意进程清理顺序很重要我之前写过一条经验先杀子进程再杀父进程最后删文件。如果反过来先删文件进程还在内存里运行它会自己再把文件写回来或者报错反而多出很多干扰信息。具体操作建议用kill -9把挖矿主进程、守护脚本进程、脚本父进程逐个杀掉# 根据进程号结束这里只是示例PID kill -9 3245 kill -9 3246 # 结束后再用 pgrep 确认没有残留 pgrep -af systemd-service|xmrig|kworkerds注意pgrep -af的输出要仔细看-a表示显示完整命令行能区分出哪些是系统真正的kworker哪些是伪装进程。我这里补充一句系统自带的kworker是内核进程不会占几百CPU更不会出现在/tmp目录下。如果看到一个叫kworkerds多了一个s的进程或者占用超高CPU的kworker那很可能就是木马。对照/proc/pid/exe和cmdline就能判断。4.3 清理定时任务与systemd服务进程杀干净后立刻转到持久化清理操作链如下# 1. 禁用并删除恶意 systemd 服务 systemctl stop systemd-service.service 2/dev/null systemctl disable systemd-service.service 2/dev/null rm -f /etc/systemd/system/systemd-service.service rm -f /etc/systemd/system/multi-user.target.wants/systemd-service.service systemctl daemon-reload # 2. 清理 crontab crontab -u root -r rm -f /etc/cron.d/systemd-service rm -f /etc/cron.hourly/systemd-service # 同时手动打开 /etc/crontab 检查把异常行注释或删除 # 3. 删除病毒脚本与挖矿主程序 rm -f /usr/lib/systemd/systemd-service.sh rm -f /root/.systemd-service.sh rm -rf /tmp/.x/ # 示例路径按实际排查结果为准 rm -f /tmp/.systemd-service.lock清完这些后再跑一次ss -anpt确认没有矿池连接并查看负载uptime top -bn1 | head -20正常情况下CPU占用会迅速回落load average虽然不会立刻降到很低它是一段时间的滑动平均但趋势应该是向下的。我那次操作完10分钟内CPU占用就从400%降到接近0%说明问题确实出在这套病毒上。4.4 验证CPU恢复正常清理完成后最怕一件事看似清干净了实际残留了某个隐藏定时任务。所以验证要持续一段时间。我建议清理完立刻记录一次top和uptime。30分钟、1小时后再各检查一次确认CPU没有再次飙升。期间留意有没有新增的未知连接、未知进程。如果CPU再次升高别慌那说明还有没发现的持久化手段。常见残留位置包括/etc/ld.so.preload通过预加载劫持命令、authorized_keys后门、/root/.bashrc里的自动执行脚本。这些我放到下一节一起讲。另外/etc/ld.so.preload这个位置特别隐蔽它可以让系统里所有命令都被劫持ps、top、ls都可能显示假象如果遇到“杀完又复活、查不到真进程”的顽固情况一定要检查它。5. 溯源入侵路径与修复漏洞5.1 从日志与指纹回溯清完病毒不等于结束真正的战场在溯源——搞清楚攻击者是从哪个口子进来的。如果这个口子不堵上清理得再干净对方随时能再用同样的方法打进来。根据我的经验优先检查以下日志# 1. SSH认证日志 grep Accepted /var/log/secure | tail -100 grep Failed password /var/log/secure | tail -100 # 2. crontab修改时间 ls -la /var/spool/cron/ /etc/cron.d/ # 3. 可疑用户与登录记录 cat /etc/passwd | grep -E /bin/bash|/bin/sh last -20 # 4. 系统命令是否被替换stat检查时间和大小 stat /bin/ps /bin/ls /usr/bin/top印象比较深的一次排查中我在/var/log/secure里看到某个云服务器默认的root密码登录记录来源IP每天都在尝试某天突然成功了一次时间点刚好和病毒文件创建时间对得上。这就基本能锁定弱口令爆破进来的。另一次是在/etc/passwd里发现多了个叫sysadmin的用户还带着/bin/bash明显是留的后门账号。每次接手这类机器/etc/passwd必须过一遍。5.2 常见的五个突破口结合大量同类案例挖矿病毒能进来突破口不外乎这几个。我整理了个表方便对照自查入口漏洞特征自查方法SSH爆破root弱口令、密码长期不换查看secure日志登录来源检查密码复杂度Redis未授权访问6379端口暴露在公网无密码或弱密码redis-cli -h IP info看是否能直接连上Docker API未防护2375端口暴露公网无TLS扫描对外开放端口看是否有2375Web中间件RCE框架版本漏洞、反序列化、命令执行查看中间件日志、访问记录数据库弱口令MySQL/PostgreSQL密码简单检查数据库登录日志更换强密码我这次这台机器事后排查是SSH弱口令爆破进来的。Secure日志里那个IP连续爆破了好几天最后某次尝试成功登录之后立刻从它的服务器上拉了恶意脚本执行整个入侵到挖矿落地大概也就几分钟。这个场景在真实环境里非常普遍尤其是没有配密钥登录、密码还设置的比较简单的机器基本等于把大门敞开。5.3 修复与加固的第一步找到入口后立刻修复。我当时做了这几件事修改服务器SSH密码为高强度随机密码大小写字母数字特殊字符25位以上。编辑/etc/ssh/sshd_config关闭密码登录只保留密钥认证。检查/root/.ssh/authorized_keys文件删掉来路不明的公钥。防火墙层面限制SSH端口来源IP只允许办公网段连接。删除异常系统用户清理/etc/passwd中不认识的bash用户。这过程中有一个特别容易被忽略的点攻击者可能在.ssh/authorized_keys里植入自己的公钥之后就算密码改了他照样能用密钥免密登录。所以改密码和检查公钥必须同时做。另外很多挖矿木马会把对称密钥写到/etc/ld.so.preload里实施重定向导致你执行ps看到的是过滤后的假结果——检查这个文件如果内容不为空且不是自己配置的库立刻清理并重装相关核心命令。6. 生产服务器怎么避免再次中招6.1 第一步关掉能关的入口前面说过绝大多数挖矿入侵都建立在“暴露面过大”这个前提上。服务器上只应该开放业务必需的端口其它一律不用。这里说的是最小化原则的具体落地关闭root远程登录改用普通用户加sudo。推荐SSH密钥登录禁用密码登录这条能做到95%的爆破攻击就被挡在门外了。Redis、MySQL、MongoDB这类服务绑定内网或localhost不暴露公网。Docker API使用TLS加密或者干脆不要对外提供无认证的API。云平台安全组里配置白名单只放行自己的办公IP访问运维端口。我见过太多人把6379、3306、27017等端口直接暴露在公网上然后中招后来问为什么。其实这些问题在安全组阶段就能杜绝。每开放一个端口都想一下真的需要让所有人访问吗不是的话就锁起来。6.2 网络层限制出方向访问控制很多人只做入方向防火墙完全忽略出方向这是挖矿病毒能长驻的原因之一。挖矿程序需要连接矿池才能运作如果能从网络层断开到矿池地址的连接即使服务器被种了木马它也干不了活。云服务器可以在安全组出方向做白名单只允许访问常用更新源、业务API等必要的公网地址其它境外IP和非常规端口一律拒绝。如果你用的是传统防火墙可以使用firewalld或iptables限制出方向策略。注意这个操作需要提前规划好业务使用的出方向地址不然容易误伤正常服务。我当时修复后也给这台机器加了安全组出方向白名单同时在服务器上配了防火墙规则禁止访问常见矿池端口。虽然无法穷举所有矿池但大多数挖矿木马默认用的端口就那几十个能拦住很大一部分。6.3 建立基础的防御基线避免再次中招不能只依赖事后清理得有一套日常基线配置。我给自己的服务器定了这么几条供大家参考SSH配置PermitRootLogin no、PasswordAuthentication no、MaxAuthTries 3。超时断开空闲连接ClientAliveInterval 300、ClientAliveCountMax 2。安装fail2ban对多次认证失败的IP自动封禁几分钟。系统账号最小化删掉不必要的用户sudoers里只加可信用户。敏感服务Redis、Docker等启动配置里设置认证、端口绑定。使用集中配置管理工具如Ansible统一维护配置避免人工改漏。这些不是新东西但每一条都对应某个真实攻击链路的环节。fail2ban对付爆破尤其有效自动封禁机制能在攻击者尝试到成功之前就把他拒之门外。6.4 日常监控与巡检脚本最后分享一个我自己在用的巡检思路不需要装重型的监控系统一个简单的Shell脚本加crontab就能覆盖大部分挖矿特征。脚本逻辑就是检查CPU占用TOP进程、检查新增用户、检查计划任务、检查陌生端口和连接#!/bin/bash # /root/scripts/security_check.sh LOG/var/log/security_check.log DATE$(date %Y-%m-%d %H:%M:%S) echo [$DATE] 安全巡检开始 $LOG # 1. 占用CPU最高的3个进程 ps aux --sort-%cpu | head -4 $LOG # 2. 系统用户中可登录用户是否正常 awk -F: $7 ~ /bash|sh/ {print $1} /etc/passwd $LOG # 3. 查看定时任务目录是否有新文件 ls -la /etc/cron.d/ /etc/cron.hourly/ $LOG # 4. 对外TCP连接关注未知IP ss -anpt | grep ESTAB $LOG echo ----- $LOG配合crontab每10分钟执行一次*/10 * * * * /bin/bash /root/scripts/security_check.sh平时不用天天盯但中了招之后这个日志就是宝贵的回溯依据。尤其是CPU TOP进程那段能在病毒进程还在运行的第一时间留下记录排查效率提升非常明显。经历过这台服务器被植入挖矿病毒之后我最大的一个改变是看任何CPU问题都不会轻易信ps显示出来的进程名一定会去/proc下核实一遍任何清理动作都会先备份再动手清理完后反复检查定时任务和系统服务。挖矿病毒不算多高级的攻击但它对业务可用性的破坏非常直接CPU被打满、账单飙升、服务不可用每一条都够让人头疼。希望这次的逐层分析和实操记录能帮你下次遇到类似问题时少走几步冤枉路。