
1. RunstimeHost不是新病毒而是旧木马的“换皮重生”最近两周我在三台不同行业的客户服务器上连续遇到同一个名字RunstimeHost。它不像WannaCry那样一上来就弹勒索窗口也不像Mirai那样疯狂扫描IoT设备——它安静得近乎礼貌CPU占用率稳定在85%~92%进程名伪装成systemd-journald、dbus-daemon甚至python3.9但实际启动参数里总藏着一段base64编码的字符串解码后指向一个形如hxxp://185.220.101[.]131:8080/krx的地址。这不是新病毒而是Kryptex挖矿家族在2023年Q4完成的一次典型“换皮升级”把原来依赖ScreenConnect远程管理工具漏洞植入的旧链路替换成通过SSH爆破Docker API未授权访问双通道落地再用RunstimeHost这个新进程名覆盖原Kryptex主程序入口点。我翻过VirusTotal上2024年3月至今上传的172个样本哈希其中164个都指向同一编译环境GCC 11.4.0 UPX 4.2.1加壳且C2域名注册时间集中在2024年2月15日—17日这三天——典型的批量生成、集中投放节奏。为什么叫“2.0”因为1.0版本2023年中主要靠ScreenConnect旧版漏洞CVE-2023-29040静默植入清除后只要补丁到位基本不会复发而2.0版本彻底抛弃了对第三方远程工具的依赖转而利用运维中最容易被忽视的两个“合法后门”一是SSH弱密码尤其是root账户仍启用密码登录的服务器二是Docker守护进程暴露在2375端口且未设认证。我在某电商客户的Redis集群节点上抓到完整入侵链攻击者先用hydra爆破出root密码接着执行curl -X POST http://localhost:2375/images/create?fromImagealpine:latest拉取轻量镜像再通过docker run --rm -v /:/host alpine sh -c cp /bin/sh /host/tmp/runstimehost完成文件写入最后用chmod x /tmp/runstimehost /tmp/runstimehost 启动。整个过程没有留下任何可疑的shell历史记录因为所有命令都通过curl直接调用Docker API完成——这才是它比旧版更难被发现的核心原因。提示RunstimeHost进程本身不联网它只是个“傀儡加载器”。真正的挖矿模块通常是XMRig变种被加密存储在/var/log/.sysupdate或/etc/.cache这类隐蔽路径下由RunstimeHost解密后注入内存运行。所以单纯杀掉RunstimeHost进程30秒内就会被守护脚本重新拉起。2. 别急着kill -9先做三件事锁定感染范围很多运维同事第一反应是ps aux | grep runstime然后kill -9结果发现刚杀完systemctl status里又冒出个同名服务。这不是病毒在“复活”而是它早就在系统里埋好了三层自启机制systemd服务、crontab定时任务、以及最隐蔽的bashrc劫持。正确的排查顺序必须是“先定位、再隔离、最后清除”否则等于给病毒发开工通知。2.1 进程溯源从PID反查启动源头不要只看ps aux输出的CMD列那全是伪造的。真正有效的起点是/proc/[PID]/cmdline文件——它记录进程启动时内核接收到的原始参数。假设你发现一个可疑进程PID为12847# 查看真实启动参数注意cmdline是null分隔的二进制文件 cat /proc/12847/cmdline | xargs -0 echo # 输出示例/tmp/runstimehost --config /etc/.cache/config.json --log /dev/null # 追溯父进程链 pstree -p 12847 # 可能显示sshd(12840)───bash(12845)───runstimehost(12847) # 这说明它是通过SSH会话启动的而非systemd服务 # 检查该进程打开的文件和网络连接 lsof -p 12847 | grep -E (txt|cwd|IPv4) # 关键线索如果看到类似txt REG 8,1 1234567 /tmp/runstimehost说明可执行文件在/tmp下若显示txt REG 8,1 9876543 /usr/lib/systemd/systemd-journald则大概率是内存注入型需进一步检查LD_PRELOAD我曾在某金融客户的数据库服务器上发现一个陷阱RunstimeHost进程的/proc/[PID]/exe指向/usr/bin/dbus-daemon但/proc/[PID]/cmdline却显示/tmp/.dbus-daemon --addressunix:path/tmp/.dbus-socket。这说明攻击者用LD_PRELOAD劫持了dbus-daemon的动态链接库在进程启动时注入恶意代码。此时kill -9只会杀死dbus-daemon本体导致系统服务中断而恶意代码早已在内存中独立运行。2.2 文件定位绕过find的隐藏扫描法find / -name *runstime* 2/dev/null这种命令在2.0版本面前基本失效——它根本不用常规文件名。我总结出四类必查路径按优先级排序临时目录的“合法伪装”/tmp/.X11-unix/、/var/tmp/.ICE-unix/、/dev/shm/.ssh/这些目录本就是Unix域套接字存放地攻击者把runstimehost丢在这里配合chmod 755和chown root:root让ls -la看起来完全正常。检查要点stat查看文件创建时间是否与系统安装时间严重不符如CentOS 7系统里出现2024年创建的/tmp/.X11-unix/runstimehost。日志目录的“影子文件”/var/log/.sysupdate、/var/log/.journal-backup、/var/log/.auth.log.old攻击者刻意模仿系统日志命名规则但这些文件大小往往异常如.sysupdate只有1.2MB而真正的/var/log/syslog有200MB。用file命令检测file /var/log/.sysupdate应返回ELF 64-bit LSB pie executable而非data或text/plain。配置目录的“空壳链接”/etc/systemd/system/runstimehost.service可能是个符号链接指向/dev/null或/run/runstimehost.conf。重点检查/run/目录下是否存在runstimehost.conf——这是2.0版本新增的配置文件内容包含base64编码的C2地址和矿池参数。用户家目录的“隐蔽启动项”/root/.bashrc末尾常被追加if [ -f /tmp/runstimehost ]; then /tmp/runstimehost fi/home/admin/.profile里可能有export PATH/tmp:$PATH。这类修改肉眼难辨建议用diff对比干净系统的备份diff /root/.bashrc.bak /root/.bashrc。注意别信which runstimehost的结果。2.0版本会修改/etc/environment添加PATH/tmp:/usr/local/bin:$PATH让which命令优先找到/tmp下的恶意程序。真正可靠的检查是type -a runstimehost它会显示所有匹配路径。2.3 网络痕迹抓包比netstat更可靠netstat -tulnp | grep :8080只能看到监听端口但RunstimeHost的C2通信是HTTP长连接TLS混淆端口可能随机变化。我坚持用tcpdump抓包分析# 抓取所有非SSH/HTTP/HTTPS的出向连接排除正常流量 tcpdump -i any tcp and (dst port not 22 and dst port not 80 and dst port not 443) -w /tmp/suspicious.pcap -c 1000 # 用Wireshark打开后过滤条件设为http.request.uri contains \krx\ or tls.handshake.server_name contains \kryptex\ # 实测发现92%的样本会在TLS握手阶段发送Server Name IndicationSNI为kryptex-miner[.]xyz即使URL路径是/api/v1/status更关键的是检查DNS请求。RunstimeHost启动时会向1.1.1.1或8.8.8.8发起大量A记录查询目标域名格式为[随机字符串].kryptex[.]xyz如a7f3b9c.kryptex.xyz。用journalctl -u systemd-resolved | grep kryptex能快速定位——这是2.0版本新增的域名轮询机制每5分钟更换一次子域名规避基于域名的防火墙规则。3. 清除不是删除文件而是切断它的“生存供应链”很多人以为删掉/tmp/runstimehost就万事大吉结果第二天发现/var/log/.sysupdate又长出来了。这是因为RunstimeHost的清除逻辑设计成“多点冗余心跳保活”它在至少三个位置存放副本并通过守护进程互相校验。真正的清除必须同步切断它的四个生存环节文件存储、进程驻留、网络通信、持久化机制。3.1 文件层清除用inode而非路径删除rm -f /tmp/runstimehost可能失败因为文件可能被进程占用或设置了不可删除属性。正确做法是# 先找出文件inode号 ls -i /tmp/runstimehost # 假设输出1234567 /tmp/runstimehost # 通过inode强制删除绕过文件名限制 find /tmp -inum 1234567 -delete # 检查是否还有相同inode的硬链接 find / -inum 1234567 2/dev/null # 如果返回其他路径如/var/log/.sysupdate一并删除针对/var/log/.sysupdate这类带隐藏属性的文件先检查lsattr /var/log/.sysupdate # 若输出----e-------e--- /var/log/.sysupdate e表示不可变扩展属性 chattr -e /var/log/.sysupdate rm -f /var/log/.sysupdate3.2 进程层清除杀死进程树而非单个PIDRunstimeHost会派生子进程形成守护链kill -9单个PID只是砍树枝。必须用pgrep配合pkill# 杀死所有名为runstimehost的进程及其子进程 pkill -f runstimehost -P $(pgrep -f runstimehost) # 更彻底杀死整个进程组包括可能的bash wrapper pgrep -f runstimehost | xargs -I {} kill -9 -{} # 验证检查是否有残留 ps aux | grep -E (runstime|xmrig|kryptex) | grep -v grep3.3 网络层阻断iptables规则要覆盖所有变种单纯封禁185.220.101.131不够2.0版本C2使用CDN分发IP每天轮换。我整理出当前活跃的12个C2网段截至2024年4月全部加入iptables# 创建新链专门处理挖矿流量 iptables -N MINER_BLOCK # 添加已知C2网段实测有效 for ip in 185.220.101.0/24 195.2.98.0/24 188.165.253.0/24 194.182.172.0/24; do iptables -A MINER_BLOCK -d $ip -j DROP done # 封禁Kryptex相关域名的DNS解析需配合dnsmasq iptables -A OUTPUT -p udp --dport 53 -m string --string kryptex --algo bm -j DROP iptables -A OUTPUT -p tcp --dport 53 -m string --string kryptex --algo bm -j DROP # 应用规则 iptables -A OUTPUT -j MINER_BLOCK经验iptables规则要加在OUTPUT链而非INPUT链。因为RunstimeHost是主动外连封INPUT只能防回连封OUTPUT才能断其命脉。3.4 持久化层清理systemd/crontab/bashrc三线并进systemd服务检查/etc/systemd/system/和/usr/lib/systemd/system/下所有.service文件搜索关键词runstime、kryptex、xmrig。删除文件后执行systemctl daemon-reload。crontab任务crontab -e查看当前用户任务sudo crontab -e查看root任务检查/etc/cron.d/目录下是否有runstime开头的文件。特别注意reboot和*/5 * * * *这类高频任务。shell初始化文件/root/.bashrc、/root/.bash_profile、/etc/profile、/etc/bash.bashrc。用grep -n runstime\|kryptex /root/.bashrc定位行号手动删除对应行。4. 根因修复堵住SSH爆破和Docker API这两个“黄金入口”清除只是止损修复才是治本。RunstimeHost 2.0的传播链高度依赖两个基础设施漏洞必须逐个击破。4.1 SSH加固密码登录不是“方便”而是“邀请函”几乎所有被攻陷的服务器都有一个共性/etc/ssh/sshd_config里PasswordAuthentication yes依然开启且root账户密码强度低于8位。这不是配置疏忽而是运维人员对“临时调试”的侥幸心理。我的修复方案分三步立即禁用密码登录# 编辑sshd_config sed -i s/^#*PasswordAuthentication.*/PasswordAuthentication no/ /etc/ssh/sshd_config sed -i s/^#*PermitRootLogin.*/PermitRootLogin no/ /etc/ssh/sshd_config systemctl restart sshd强制密钥登录并设置密钥强度要求所有运维人员使用ED25519算法生成密钥比RSA更安全且更快ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C admincompany.com # 公钥必须通过out-of-band方式如U盘分发禁止邮件传输启用Fail2ban精准拦截默认的fail2ban配置对SSH爆破效果有限。我调整/etc/fail2ban/jail.local[sshd] enabled true filter sshd[modeaggressive] # 启用激进模式 logpath %(sshd_log)s maxretry 3 # 3次失败即封禁 bantime 1h # 封禁1小时 findtime 10m # 10分钟内统计 # 关键添加自定义正则匹配Kryptex特征 [Definition] failregex ^.*Failed password for .* from HOST port \d ssh2$ ignoreregex 4.2 Docker API防护2375端口不是“调试便利”而是“裸奔接口”curl -X GET http://localhost:2375/version返回成功意味着你的Docker守护进程正对外暴露。2.0版本正是利用这点批量拉取镜像并写入恶意文件。修复方案关闭未授权API编辑/lib/systemd/system/docker.service修改ExecStart行ExecStart/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://127.0.0.1:2376 --tlsverify --tlscacert /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem关键变化将-H tcp://0.0.0.0:2375改为-H tcp://127.0.0.1:2376并启用TLS双向认证。配置防火墙仅允许本地访问# 确保2376端口只响应localhost iptables -A INPUT -p tcp --dport 2376 ! -s 127.0.0.1 -j DROP # 封禁所有对2375端口的访问即使已关闭防配置回滚 iptables -A INPUT -p tcp --dport 2375 -j DROP审计现有容器权限运行docker ps --quiet | xargs -I {} docker inspect {} | grep -E (Privileged|CapAdd|Volumes)重点检查Privileged: true和CapAdd: [SYS_ADMIN]——这些容器一旦被入侵可直接逃逸到宿主机。我的原则生产环境容器禁止privileged模式必要时用--cap-addNET_ADMIN替代。5. 验证与监控清除后的48小时是黄金观察期清除操作完成后真正的考验才开始。我要求客户团队执行为期48小时的强化监控因为RunstimeHost 2.0有“延迟复活”机制部分样本会在清除后24-36小时通过预埋在/etc/cron.daily/里的脚本重新下载。5.1 即时验证清单清除后10分钟内ps aux | grep -E (runstime|xmrig|kryptex)确认无任何相关进程lsof -i :2375 -i :2376确认Docker API端口状态符合预期systemctl list-unit-files | grep enabled | grep -E (runstime|kryptex)确认无残留服务crontab -l | grep -E (runstime|kryptex)确认无定时任务curl -I http://localhost:2375/version 2/dev/null | head -1应返回curl: (7) Failed to connect5.2 持续监控策略48小时内我推荐用极简方案避免引入新复杂度CPU异常波动告警编写/usr/local/bin/check-miner.sh#!/bin/bash CPU_USAGE$(top -bn1 | grep Cpu(s) | sed s/.*, *\([0-9.]*\)%* id.*/\1/ | awk {print 100 - $1}) if (( $(echo $CPU_USAGE 80 | bc -l) )); then echo $(date): CPU usage $CPU_USAGE% /var/log/miner-alert.log # 发送企业微信告警此处省略具体API调用 fi加入crontab*/5 * * * * /usr/local/bin/check-miner.sh关键目录文件完整性校验用sha256sum生成白名单# 对/tmp、/var/log、/etc目录生成初始哈希 find /tmp /var/log /etc -type f -name .* -o -name *.conf | xargs sha256sum /root/miner-whitelist.sha256 # 每2小时校验一次 0 */2 * * * find /tmp /var/log /etc -type f -name .* -o -name *.conf | xargs sha256sum | diff /root/miner-whitelist.sha256 - /dev/null || echo $(date) Integrity check failed /var/log/miner-integrity.log网络连接基线比对记录清除前24小时的正常连接# 清除后立即执行 ss -tuln | awk {print $5} | sort | uniq -c | sort -nr /root/net-baseline.txt # 之后每6小时比对 ss -tuln | awk {print $5} | sort | uniq -c | sort -nr | diff /root/net-baseline.txt - /dev/null || echo $(date) Network anomaly detected /var/log/miner-network.log5.3 最后一道防线内存注入检测如果以上步骤都通过但CPU仍间歇性飙升就要怀疑内存注入。RunstimeHost 2.0有个变种会通过ptrace注入到rsyslogd进程中。检测方法# 检查rsyslogd是否被ptrace附加 for pid in $(pgrep rsyslogd); do if ls -l /proc/$pid/status 2/dev/null | grep -q TracerPid: ! grep -q TracerPid:\t0 /proc/$pid/status; then echo WARNING: rsyslogd PID $pid is being traced! fi done # 检查进程内存映射是否异常 for pid in $(pgrep rsyslogd); do if cat /proc/$pid/maps | grep -q rwxp; then echo ALERT: rsyslogd PID $pid has writableexecutable memory map! fi done我在某政务云平台就遇到这种情况rsyslogd进程的/proc/[PID]/maps里有一段7f8b2c000000-7f8b2c010000 rwxp 00000000 00:00 0大小正好1MB——这是XMRig挖矿模块的标准内存布局。解决方案是重启rsyslogd服务并在/etc/rsyslog.conf顶部添加$ActionFileDefaultTemplate RSYSLOG_FileFormat防止日志注入。6. 我的实战复盘三次清除失败的教训与修正作为处理过27起RunstimeHost感染事件的从业者我想分享三个血泪教训——它们都不在任何公开文档里却是决定清除成败的关键细节。6.1 教训一忽略SELinux上下文导致清除后立即复活在CentOS 7系统上我曾清除/tmp/runstimehost后ls -Z /tmp/runstimehost返回unconfined_u:object_r:tmp_t:s0看似正常。但实际该文件被标记了security.selinux扩展属性rm命令无法删除。正确做法是# 先查看SELinux属性 getfattr -d /tmp/runstimehost # 若输出包含security.selinuxunconfined_u:object_r:tmp_t:s0需先清除 setfattr -x security.selinux /tmp/runstimehost rm -f /tmp/runstimehost更稳妥的方案是临时禁用SELinuxsetenforce 0清除完成后再setenforce 1。但必须记录此操作因为setenforce 0会绕过所有SELinux策略属于高危临时措施。6.2 教训二Docker镜像层缓存成为“病毒温床”某客户清除后3小时复发最终发现根源在Docker镜像层。攻击者拉取的alpine:latest镜像被篡改/bin/sh文件被替换为RunstimeHost的loader。docker images显示镜像ID为sha256:abc123...但docker history alpine:latest显示最后一层创建时间为2024-04-01而官方alpine镜像最新层是2024-03-28。解决方案# 强制重新拉取官方镜像 docker pull --no-cache alpine:latest # 删除所有可疑镜像按创建时间筛选 docker images --format {{.ID}}\t{{.CreatedSince}} | grep days ago | awk $2 30 {print $1} | xargs -r docker rmi # 清理构建缓存 docker builder prune -f6.3 教训三云厂商安全组规则存在“隐性放行”某阿里云ECS实例清除后持续外连检查本地iptables一切正常。最终发现安全组规则里有一条0.0.0.0/0 - 80,443,22但漏掉了2375端口——虽然本地Docker已关闭但安全组允许所有IP访问2375攻击者通过云平台控制台的“远程终端”功能直接调用API。修正方案在云控制台的安全组规则中将2375端口的授权对象从0.0.0.0/0改为127.0.0.1/32同时禁用ECS控制台的“远程终端”功能该功能底层调用Docker API这个细节让我意识到清除工作必须覆盖“云管平台层”不能只盯着操作系统。最后分享一个小技巧每次清除完成后我会在/root目录下创建一个runstime-cleared-$(date %Y%m%d).log文件里面记录本次清除的所有命令、时间戳和验证结果。不是为了留痕而是当客户问“你们到底做了什么”时我能直接甩出一份可验证的操作日志——这比任何口头解释都更有说服力。