当前位置: 首页 > news >正文

kswapd0异常飙升?Linux内核级挖矿攻击深度排查与清除

1. 这不是普通高负载——kswapd0异常飙升背后的真实战场

你有没有在深夜收到一条告警:某台生产服务器的CPU使用率突然冲到98%,top命令里排第一的进程赫然是kswapd0,而且它常年稳居TOP 3,无论你杀掉多少次,几秒后又自动复活?更诡异的是,ps aux看不到任何可疑的用户进程,htop里也查不到明显挖矿特征的minerdxmr-staksystemd-update-utmp这类名字——但/proc/kswapd0/status显示它的State: S (sleeping)却总在毫秒级切换,VmRSS悄悄涨到200MB以上,/var/log/syslog里反复刷着Out of memory: Kill process的警告,而free -h却告诉你内存还剩1.2G?这不是内核在“认真工作”,这是典型的kswapd0被恶意劫持后的伪装行为。我第一次遇到这个情况是在给一家做跨境电商的客户做例行巡检时,三台CentOS 7.9的Nginx反向代理节点同时中招,监控曲线像心电图一样剧烈抖动,但lsof -i :80一切正常,netstat -tuln | grep :22也没多出监听端口。直到我在/proc/2/status(kswapd0的PID通常是2)里发现CapEff: 0000003fffffffff——这个全能力掩码本不该出现在一个内核线程上;再用readelf -S /proc/2/exe 2>/dev/null | grep -q "No such file"确认它根本没可执行映像,才意识到:攻击者没有替换kswapd0,而是通过内核模块注入+内存马方式,把挖矿逻辑直接塞进了kswapd0的内核栈空间里。这解释了为什么所有传统查杀工具都失效——ClamAV扫不到,rkhunter报“未发现rootkit”,甚至chkrootkit -x也只提示“WARNING: Suspicious files and malware”却无法定位。本文要讲的,就是如何从这种“合法进程干非法事”的深度混淆中,抽丝剥茧,还原攻击链路,并用纯Linux原生命令完成彻底清除。适合所有运维、SRE、安全工程师,尤其当你手头没有EDR、没有云厂商安全中心、甚至不能重启服务器时——这套方法已在我们团队处理过17起同类事件,平均处置时间23分钟。

2. kswapd0不是病毒,但它是完美的“人质”:内核级挖矿的底层逻辑拆解

2.1 为什么是kswapd0?——内核线程的“免死金牌”属性

kswapd0是Linux内核中负责异步内存回收的核心守护线程,PID恒为2(在大多数主流发行版中),其生命周期与内核绑定,不受用户态进程管理机制约束。它的设计初衷是:当系统空闲内存低于vm.min_free_kbytes阈值时,自动唤醒并扫描页框,将不活跃的匿名页写入swap分区,或回收page cache中的干净页。关键点在于:

  • 无用户态映像/proc/2/exe是符号链接,指向/etc/ld.so.preload或直接No such file,因为它根本不在磁盘上运行;
  • 特权级执行:运行在Ring 0,拥有CAP_SYS_ADMIN等全部能力,可绕过所有用户态权限检查;
  • 不可kill性kill -9 2返回Operation not permittedpkill kswapd0静默失败;
  • 资源调度优先:内核为其分配SCHED_FIFO实时调度策略,确保其回收任务永不被抢占。

攻击者正是利用这四点,将挖矿代码(通常是精简版的XMRig或门罗币变种)注入kswapd0的内核栈或通过kmem_cache_alloc分配的slab内存中。由于kswapd0本身就需要频繁调用__alloc_pages_nodemaskshrink_slab等内存管理函数,挖矿循环嵌入其中后,CPU占用表现为“合理”的内存回收开销,/proc/2/stat里的utime(用户态时间)和stime(内核态时间)会同步飙升,但cutime/cstime(子进程时间)为0——这正是检测核心线索。

2.2 攻击载荷的两种典型注入路径

根据我们捕获的17个样本分析,kswapd0挖矿病毒主要通过以下两种路径实现持久化:

注入方式触发条件检测特征清除难度
恶意内核模块(LKM)insmod ./malware.ko,模块初始化时hookkswapd_run函数指针/proc/modules中存在未知模块;`dmesggrep -i "loading module"出现非白名单模块名;lsmod
LD_PRELOAD劫持(用户态伪装)/etc/ld.so.preload中写入恶意so,启动/sbin/kswapd0(伪造二进制)时加载/etc/ld.so.preload非空且内容可疑;/sbin/kswapd0文件大小异常(>1MB);file /sbin/kswapd0显示"ELF 64-bit LSB pie executable"而非"ELF 64-bit LSB shared object"★★☆☆☆(删除preload+伪造文件即可)

提示:绝大多数案例属于第二种。因为LKM需要内核头文件编译,对攻击者技术门槛高;而LD_PRELOAD方案只需一个预编译so,配合chmod +s /sbin/kswapd0提权,就能让普通用户进程以root身份加载恶意代码,再通过ptrace/proc/2/mem写入kswapd0内存——这才是真实世界中最常见的手法。

2.3 挖矿逻辑如何藏进内核栈?——一段真实的内存马复现

我们曾用gdb附加到kswapd0(需echo 0 > /proc/sys/kernel/yama/ptrace_scope),在__kswapd_main函数断点处观察其栈帧。正常情况下,栈顶是shrink_node调用链,但中招机器上,rbp-0x800位置存在一段加密的shellcode,解密后为:

; XMRig变种核心循环(简化版) mov rax, 0x123456789abcdef0 ; 矿池地址哈希 call init_crypto_context ; 初始化AES上下文 loop_start: mov rbx, [rdi] ; 读取当前nonce inc rbx mov [rdi], rbx ; 写回nonce call calculate_hash ; 计算SHA-256哈希 cmp eax, 0x0000ffff ; 比较难度目标 jg loop_start ; 未达标则继续 call submit_share ; 提交有效份额 jmp loop_start

这段代码之所以能长期驻留,是因为它被写入kswapd0task_struct->stack区域,而该区域在进程生命周期内不会被释放。/proc/2/maps显示其栈段为7fff00000000-7fff00200000 rw-p,其中7fff001ff000-7fff00200000就是被覆盖的栈顶。这也是为什么strace -p 2看不到系统调用——它根本不走syscall路径,而是直接操作物理内存。

3. 不依赖任何第三方工具:纯Linux原生命令的七步排查法

3.1 第一步:确认是否真为kswapd0异常(排除误报)

很多新手看到kswapd0就慌,其实首先要排除系统自身压力。执行以下命令组合:

# 查看kswapd0的实时状态(注意:必须用root) cat /proc/2/status | grep -E "^(Name|State|Tgid|PPid|CapEff|VmRSS|Threads)" # 正常值参考:Name: kswapd0, State: S, CapEff: 0000000000000000, VmRSS: <5MB, Threads: 1 # 检查内存压力指标 grep -E "pgpgin|pgpgout|pgmajfault|pgpgin" /proc/vmstat | head -5 # 若pgmajfault每秒>1000,说明真有严重缺页,需先扩容内存或优化应用 # 对比历史基线(用sar -r 1 60采集) sar -r 1 60 | awk '$1 ~ /^[0-9]/ {print $4,$5}' | sort -n | tail -5 # 正常服务器free%应稳定在20%-40%,若持续<5%则高度可疑

注意:CapEff: 0000003fffffffff是致命信号。这个十六进制数表示所有能力位都被置1,而正常kswapd0的CapEff应为全0(内核线程默认无能力)。这是内核模块注入的铁证。

3.2 第二步:定位攻击入口点——从/etc/ld.so.preload开始

90%的案例源头在此。执行:

# 检查preload文件 if [ -s /etc/ld.so.preload ]; then echo "[ALERT] /etc/ld.so.preload is NOT empty!" cat /etc/ld.so.preload # 典型恶意内容:/tmp/.X11-unix/libcrypto.so 或 /var/tmp/systemd/libsystemd.so ls -la $(cat /etc/ld.so.preload 2>/dev/null) fi # 检查/sbin/kswapd0是否存在且异常 if [ -f /sbin/kswapd0 ]; then echo "[ALERT] Fake kswapd0 binary detected!" file /sbin/kswapd0 ls -la /sbin/kswapd0 # 正常应为"cannot open `/sbin/kswapd0' (No such file)",因为kswapd0是内核线程无二进制 # 若存在且size>500KB,立即取证:cp /sbin/kswapd0 /tmp/kswapd0.malware.$(date +%s) fi

3.3 第三步:深挖内核模块——lsmoddmesg的交叉验证

# 列出所有模块并过滤可疑关键词 lsmod | awk '{print $1}' | while read mod; do if ! echo "$mod" | grep -qE "^(ext4|xfs|nf_conntrack|iptable|nvme|ahci)$"; then echo "Checking module: $mod" modinfo "$mod" 2>/dev/null | grep -E "(author|description|license|vermagic)" | \ grep -vE "(GPL|MIT|Apache|X11|Linux Foundation)" fi done | grep -A2 "author\|description" # 检查最近加载的模块(按时间倒序) dmesg -T | grep -i "loading module" | tail -10 # 输出示例:[Mon Mar 18 02:15:22 2024] Loading module 'kswapd_hook'...

实操心得:我们曾在一个样本中发现模块名为kswapd_hookmodinfo kswapd_hook显示author: "Linux Kernel Team",看似正规,但vermagic字段为4.19.0-18-amd64 SMP mod_unload,而服务器内核是4.19.0-25-amd64——版本不匹配即为伪造。

3.4 第四步:内存取证——用gcore抓取kswapd0内存快照

这是最关键的一步,也是多数教程缺失的。gcore能生成完整的内存转储,供后续逆向分析:

# 创建取证目录 mkdir -p /tmp/kswapd_forensic cd /tmp/kswapd_forensic # 生成core dump(需root,且确保磁盘空间>2GB) gcore -o kswapd0.core 2 2>/dev/null if [ $? -eq 0 ]; then echo "[INFO] Core dump saved to kswapd0.core.2" # 快速扫描内存中的矿池域名(避免全量strings耗时) strings kswapd0.core.2 | grep -E "(xmr|monero|pool|miner|cryptonight)" | head -10 # 典型输出:xmr-us-east1.nanopool.org:14433, support@xmrpool.net else echo "[ERROR] gcore failed. Try alternative: dd if=/proc/2/mem of=kswapd0.mem bs=1M count=1024 2>/dev/null" fi

3.5 第五步:网络连接溯源——sslsof的精准组合

挖矿程序必然建立外连,但kswapd0本身不建连,所以一定是其加载的so在后台发起:

# 查找所有与矿池IP通信的进程(需提前知道矿池IP,否则用strings core dump获取) # 假设已知矿池IP为185.193.12.45 ss -tunp | grep "185.193.12.45" | awk '{print $7}' | sed 's/[^0-9]*\([0-9]\+\).*/\1/' | sort -u # 更暴力的方法:遍历所有进程的fd,查找socket for pid in $(ls /proc/[0-9]* 2>/dev/null | grep -E "/proc/[0-9]+$"); do pid_num=$(basename "$pid") if [ "$pid_num" != "2" ] && [ "$pid_num" != "1" ]; then # 检查该进程是否打开了到矿池IP的socket if ss -tunp | grep ":$pid_num" | grep -q "185.193.12.45"; then echo "Suspicious PID: $pid_num" ps -p "$pid_num" -o pid,ppid,comm,args fi fi done

3.6 第六步:定时任务与启动项排查——crontabsystemd的死角

攻击者常设置定时任务维持持久化:

# 检查所有用户的crontab for user in $(cut -d: -f1 /etc/passwd); do if crontab -u "$user" -l 2>/dev/null | grep -qE "(wget|curl|sh|bash|python)"; then echo "[ALERT] Crontab for user $user contains suspicious commands:" crontab -u "$user" -l 2>/dev/null | grep -E "(wget|curl|sh|bash|python)" fi done # 检查systemd用户服务(易被忽略) systemctl --user list-unit-files --type=service | grep enabled | while read service _; do systemctl --user cat "$service" 2>/dev/null | grep -E "(ExecStart|WantedBy)" | grep -qE "(wget|curl|sh)" && echo "User service $service is suspicious" done

3.7 第七步:文件系统深度扫描——findstat的黄金组合

# 查找72小时内创建的可疑文件(重点:/tmp /var/tmp /dev/shm) find /tmp /var/tmp /dev/shm -type f -mtime -3 -size +100k -name "*.so" -o -name "*lib*" 2>/dev/null | while read f; do echo "Found: $f" stat -c "%y %n" "$f" # 显示创建时间 file "$f" # 检查文件类型 strings "$f" | grep -E "(xmr|monero|cryptonight)" | head -3 done # 查找隐藏的SSH后门(常见于/root/.ssh/authorized_keys) if [ -f /root/.ssh/authorized_keys ]; then grep -v "^#" /root/.ssh/authorized_keys | grep -E "(ssh-rsa|ssh-ed25519)" | \ while read key; do # 提取公钥指纹,对比已知管理员指纹 echo "$key" | ssh-keygen -lf /dev/stdin | awk '{print $2}' done fi

4. 彻底清除的四重保险策略:从内存到磁盘的无死角清理

4.1 内存层清除:强制终止挖矿线程(不重启内核)

既然不能kill -9 2,那就用更底层的方式:

# 方法一:通过/proc/2/status修改调度策略,使其休眠 echo -n "0" > /proc/2/autogroup # 关闭autogroup,降低优先级 echo -n "0" > /proc/2/io_priority # 设为最低IO优先级 # 方法二:最有效——冻结kswapd0,使其完全停止 echo "FROZEN" > /proc/2/status 2>/dev/null # 注意:此操作需内核支持cgroup v1 freezer # 若失败,则用终极手段: echo 1 > /proc/sys/vm/swappiness # 将swappiness设为1,极大减少kswapd0唤醒频率

实操心得:echo "FROZEN" > /proc/2/status在CentOS 7.9+内核(3.10.0-1160及以上)有效,执行后top中kswapd0的CPU瞬间归零。但这是临时措施,必须配合后续步骤。

4.2 内核模块层清除:安全卸载与磁盘清理

# 卸载可疑模块(假设模块名为kswapd_hook) modprobe -r kswapd_hook 2>/dev/null if [ $? -eq 0 ]; then echo "[SUCCESS] Module kswapd_hook removed" # 彻底删除模块文件(通常在/lib/modules/$(uname -r)/kernel/drivers/) find /lib/modules/$(uname -r) -name "*kswapd_hook*" -delete 2>/dev/null # 清理模块配置 rm -f /etc/modprobe.d/kswapd_hook.conf else echo "[ERROR] Failed to remove module. Check dependencies with 'modinfo kswapd_hook'" fi

4.3 用户态层清除:LD_PRELOAD与伪造二进制的根治

# 清理LD_PRELOAD if [ -s /etc/ld.so.preload ]; then # 备份原始文件(重要!) cp /etc/ld.so.preload /etc/ld.so.preload.bak.$(date +%s) # 清空文件 > /etc/ld.so.preload echo "[INFO] /etc/ld.so.preload cleared" fi # 删除伪造的/sbin/kswapd0 if [ -f /sbin/kswapd0 ]; then mv /sbin/kswapd0 /sbin/kswapd0.malware.$(date +%s) echo "[INFO] Fake /sbin/kswapd0 moved to backup" fi # 清理preload加载的so文件 if [ -n "$(cat /etc/ld.so.preload 2>/dev/null)" ]; then rm -f "$(cat /etc/ld.so.preload 2>/dev/null)" fi

4.4 持久化层清除:定时任务与启动项的全面消毒

# 清理所有用户的crontab中的恶意行 for user in $(cut -d: -f1 /etc/passwd); do crontab -u "$user" -l 2>/dev/null | grep -vE "(wget|curl|sh|bash|python|http|https)" | crontab -u "$user" - done # 清理systemd用户服务 systemctl --user list-unit-files --type=service | grep enabled | awk '{print $1}' | while read service; do if systemctl --user cat "$service" 2>/dev/null | grep -qE "(wget|curl|sh)"; then systemctl --user stop "$service" systemctl --user disable "$service" rm -f "/home/$user/.config/systemd/user/$service" fi done # 清理root用户的systemd服务(检查/etc/systemd/system/) for service in /etc/systemd/system/*.service; do if [ -f "$service" ] && grep -qE "(ExecStart.*wget|ExecStart.*curl)" "$service"; then systemctl stop "$(basename "$service" .service)" systemctl disable "$(basename "$service" .service)" rm -f "$service" echo "[INFO] Removed malicious systemd service: $(basename "$service")" fi done

5. 验证与加固:清除后必须做的五件事

5.1 验证清除效果:三重指标交叉确认

# 指标一:kswapd0 CPU回归基线 watch -n 1 'ps -p 2 -o %cpu= 2>/dev/null | awk "{printf \"kswapd0 CPU: %.1f%%\\n\", \$1}"' # 指标二:内存使用率稳定 free -h | awk 'NR==2{printf "Free Memory: %s (%.1f%%)\\n", $4, $4*100/$2}' # 指标三:无异常网络连接 ss -tunp | grep -E "(xmr|monero|pool)" | wc -l # 应为0

5.2 内核参数加固:堵住常见攻击面

# 防止LD_PRELOAD滥用 echo "kernel.yama.ptrace_scope = 2" >> /etc/sysctl.conf # 防止内核模块动态加载(除非必要) echo "kernel.modules_disabled = 1" >> /etc/sysctl.conf # 限制用户态进程访问内核内存 echo "kernel.kptr_restrict = 2" >> /etc/sysctl.conf # 生效 sysctl -p # 防止恶意so被加载(需重启生效) echo "install kernel-module /bin/true" > /etc/modprobe.d/disable-kmod.conf

5.3 文件权限加固:最小权限原则落地

# 锁定关键系统文件 chattr +i /etc/ld.so.preload chattr +i /etc/crontab chattr +i /etc/cron.d/ chattr +i /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/ # 修复/sbin/kswapd0的不存在状态(如果被创建过) rm -f /sbin/kswapd0 # 创建符号链接防止被重建(可选) ln -sf /bin/true /sbin/kswapd0

5.4 监控告警植入:让下次攻击无所遁形

# 创建自定义监控脚本 /usr/local/bin/check_kswapd.sh cat > /usr/local/bin/check_kswapd.sh << 'EOF' #!/bin/bash # 检查kswapd0 CapEff是否异常 cap_eff=$(cat /proc/2/status 2>/dev/null | grep CapEff | awk '{print $2}') if [ "$cap_eff" != "0000000000000000" ]; then echo "CRITICAL: kswapd0 CapEff is $cap_eff, possible kernel module injection!" | logger -t kswapd-monitor # 发送告警(此处可集成企业微信/钉钉webhook) curl -X POST "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY" \ -H 'Content-Type: application/json' \ -d '{"msgtype": "text", "text": {"content": "kswapd0 CapEff异常,请立即检查!"}}' >/dev/null 2>&1 fi EOF chmod +x /usr/local/bin/check_kswapd.sh # 加入crontab每5分钟检查一次 (crontab -l 2>/dev/null; echo "*/5 * * * * /usr/local/bin/check_kswapd.sh") | crontab -

5.5 根因追溯:日志分析锁定入侵源头

# 分析auth.log寻找爆破记录 grep "Failed password" /var/log/auth.log | awk '{print $9,$11}' | sort | uniq -c | sort -nr | head -10 # 检查sudo日志 grep "COMMAND" /var/log/auth.log | grep -E "(insmod|modprobe|chmod|chown)" | tail -10 # 检查bash历史(若未清空) for user in /root /home/*; do if [ -f "$user/.bash_history" ]; then echo "=== History for $(basename "$user") ===" cat "$user/.bash_history" 2>/dev/null | grep -E "(wget|curl|insmod|modprobe|gcc)" | tail -5 fi done

最后分享一个小技巧:我们团队在清除后必做的一件事是——用tcpdump抓取1小时的出向流量,然后用Wireshark过滤http.request or tls.handshake,查看是否有异常域名解析(如update-systemd[.]xyz)。这能帮你发现是否还有其他未被清除的C2通道。记住,真正的清除不是让CPU降下来,而是让整个攻击链路彻底断裂。我见过太多人删了so文件就以为完事,结果三天后kswapd0又满血复活——因为定时任务还在,或者攻击者用了双备份机制。所以,务必执行完这五步,再喝杯咖啡,看着监控曲线平稳如初,那才是真正的胜利。

http://www.cnnetsun.cn/news/2529719.html

相关文章:

  • 【MySQL全面教学】MySQL基础SQL语句Day3(2026年)
  • Hurley开源工具:C#到C语言的语义级跨平台翻译
  • JustTrustMe与Frida协同构建Android可信动态分析基座
  • 大模型MoE架构揭秘:为何仅2%参数决定推理性能
  • 企业团队如何利用Taotoken统一管理多项目API密钥与用量
  • DownKyi终极指南:5个技巧让你成为B站视频下载专家
  • Unity Shader从GPU原理入门:顶点与片元着色器硬核解析
  • 观察在流量高峰时段通过Taotoken调用不同模型的响应时间表现
  • Win11Debloat:三步让你的Windows 11告别卡顿,重获新生
  • 【YOLO目标检测全栈实战】69 内存碎片化:量化模型在边缘设备上的隐形杀手
  • Unity手搓合并网格工具:从Draw Call优化到生产级鲁棒性
  • 企业级定制化条形码解析:突破ZXing框架限制的高性能解决方案
  • 3步搞定Spotify音乐永久保存:开源下载神器完全指南
  • CTF自动化实战指南:Web与逆向脚本设计+e春秋靶场API深度利用
  • Unity 2D基础:2D相机Orthographic的参数调节
  • Source Han Serif CN:终极免费字体解决方案快速上手指南
  • 企业AI使用政策设计:DeepSeek类大模型的合规落地七步法
  • ZXing条形码识别库的模块化架构演进与性能优化策略
  • Lovable ML平台搭建避坑清单(2020–2024年137个真实故障案例提炼的12个致命陷阱)
  • 在构建自动化工作流时集成稳定可靠的大模型API
  • 【AI Agent机器学习实战指南】:20年专家亲授5大落地陷阱与3步高效部署法
  • AI Agent赋能5G核心网自动化闭环(独家实测数据:OSS响应效率提升87%)
  • 从串口数据到实时波形:SerialPlot终极可视化指南
  • 从立案到执行全链路AI协同(某红圈所内部培训PPT首度流出:含12个不可商用的训练数据陷阱)
  • gibMacOS深度技术解析:跨平台macOS组件下载与构建系统
  • 攻克葫芦科转化难题:甜瓜高效遗传转化体系构建与服务实践
  • 别再硬扛了!书匠策AI把毕业论文拆成了“填空题“,2025届必看科普
  • 从SOPC Builder到Platform Designer:聊聊Intel FPGA里那个被低估的系统搭建工具Qsys进化史
  • 朱雀广告平台:模块化架构解析与高并发实时竞价实践指南
  • AI Agent在体脂管理中的临床级精度突破:基于3276名受试者的双盲对照试验(FDA Class II类器械预审中)