☰
NAS定向攻击样本分析:内核级后门从还原到清除
2026/10/17 13:00:57 网站建设 项目流程

最近在处理一个“表面正常、实际已经沦陷”的 NAS 系统时,我从固件镜像里提取到了一份定向攻击样本。样本的核心不是某个下载者,也不只是反弹 shell,而是一个完成度非常高的内核级后门。它通过内核模块实现了进程、文件和端口三方面的隐藏,又在系统启动层、用户态服务层、PAM 预加载层分别埋下了持久化逻辑,配合双进程看门狗,真正做到“删不掉、杀不死、藏得深”。这篇文章我会沿着“样本定性—逆向还原—内核行为拆解—持久化链还原—C2 分析—检测处置”这条线,把整次分析的思路和坑位都摊开。适合正在做应急响应、NAS 固件维护或恶意软件逆向的同行参考。

1. 定性与环境准备:样本到手时我先做了什么

1.1 设备背景与事件先兆

先交代一下背景。该设备为某 NAS 系统,也就是我们常说的“存储一体机”,架构是 x86_64,跑的是 Linux 内核 4.x 系列的系统,对外只开放了 443 管理端口。现场人员给出的说法是,某天下午系统突然重启了一次,重启后 Web 管理界面变得不稳定,经常出现无法保存配置的情况。他们一开始以为是硬件问题,后来查日志才发现/usr/lib/下多了一个不认识的动态库,系统进程列表里也出现了一个从未见过的名字,这才决定把整个磁盘镜像导出送检。

这类设备之所以会成为定向攻击的目标,原因其实很直白:NAS 系统内部往往同时存放着业务文件、数据库备份、运维脚本和大量敏感文档,而且为了便于维护,很多 NAS 系统会开启 SSH、Web 管理端口,甚至做了端口映射到公网。攻击者只要突破 NAS 这一层,就等于在内网有了一台起始跳板。再加上 NAS 系统的安全补丁更新周期通常不如通用服务器那么及时,固件又是厂商定制过的,通用杀软往往测不到,因此攻击者特别偏爱这类目标。从这次事件来看,整个过程明显不是自动化扫描的“误打误撞”,而是一次有明确侦查和针对性投递的定向攻击样本。

1.2 固件镜像解包与动态分析环境搭建

现场交付的是一个整盘镜像文件,文件名我做了脱敏处理,就叫fw_backup_20251011.img,约 64GB。这类镜像不能直接扔进杀软扫一遍了事,我第一步是计算哈希值,保证后面任何分析动作都能对应到同一个对象:

$ sha256sum fw_backup_20251011.img 3d5f6a9c1b7e4f2a8c3d9e0b1a2f4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2

接着用file和binwalk对镜像做初步摸底。虽然整盘镜像通过file通常只会给出data,但binwalk可以快速把分区结构和固件头部剥出来,这一步主要是为了确认文件系统类型、启动分区偏移量以及是否有额外隐藏分区:

$ file fw_backup_20251011.img fw_backup_20251011.img: data $ binwalk fw_backup_20251011.img DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 TRX firmware header, little endian, ...

用binwalk -e解包后,完整的根文件系统目录就出来了,包括/usr/lib、/etc/init.d、/lib/modules等关键目录都可以直接查看。为了在受控条件下观察样本的真实运行行为,我直接把解包后的根文件系统做成虚拟机镜像启动,通过网络隔离的方式模拟真实设备,并在启动阶段挂上strace、ltrace和auditd,重点监控进程创建、文件读写和网络连接。这一步的核心作用是先把系统“正常基线”和“异常行为”拉开,后面做动态分析才有参照。

这里有一个很实际的提醒:如果条件允许,尽量用和现场一致版本的固件做动态分析。版本差异会导致样本在加载阶段直接“自毁”,或者因为缺少某个依赖库而报错,让你漏掉关键的启动行为。

2. 静态逆向还原:加壳变形与核心模块提取

2.1 自定义加壳特征的快速识别

解包之后,真正引起我注意的有两个文件:一个 ELF 可执行文件,先叫它fw_hook,另一个是内核模块fw_hook.ko。前者被一个自定义加壳器保护,后者则比较直白地存在于 rootfs 中。这里有个值得展开的点:为什么不直接用 UPX 这类常见壳?因为常见壳一旦被识别,分析人员可以直接用现成脱壳工具,攻击者的混淆成本就白花了。而这个壳带有多态加载器和反调试逻辑,至少对这批样本来说,攻击者在隐藏上是花了成本的。

从脱壳角度来说,这类自定义壳通常会分层解密:第一层负责反调试,第二层把真正的代码段逐步释放到内存,第三层才是真正的业务代码。我一般会在入口点位置下断,观察/proc/<pid>/maps的内存布局变化,然后在内存中出现新页的区域做内存转储,最后把 dump 出来的镜像交给逆向工具重建函数。这个过程不一定一次成功,但配合ptrace的反调试反馈,可以快速定位到反调试分支。核心思路不是“硬脱”,而是“让它在自己执行过程中把壳解开”,然后在内存里抓它的真实模样。

2.2 内存转储与关键字符串还原

脱壳后的主程序逻辑并不复杂,它本质上是一个用户态加载器,负责释放内核模块、加载隐藏的动态库、连接 C2 服务器,以及维护整个驻留进程。从内存转储里提取到的关键路径如下:

/tmp/.X11-unix/.mcache /var/tmp/.journal /etc/init.d/S99svc /lib/modules/4.14.xxx/fw_hook.ko http://update.patchsvc[.]top/check

这里的域名、路径、哈希全都做了脱敏,但不影响分析思路复现。很多分析师一上来就盯着 ELF 的导入表看,忽略了这些“落地路径”字符串的价值。实际上,这些路径每一处都对应一个持久化机制:/tmp/.X11-unix是常见的藏文件目录,/etc/init.d/S99svc是开机启动脚本,patchsvc[.]top是 C2 域名。把用户态加载器和内核模块分开看,逻辑会更清晰:用户态负责外部表现,内核态负责内部隐藏,两者配合后,进程、文件、端口三方面的痕迹都能做到很难查。

3. 内嵌内核后门的行为拆解:模块隐藏与系统调用劫持

3.1 内核模块进入内存的方式与符号解析前提

内核模块要进入内核,优先走init_module或finit_module系统调用。我在排查时通过审计日志记录到了其中一条调用链:某个安装脚本直接以 root 身份调用finit_module,把fw_hook.ko装入内核。NAS 系统里 root 权限一旦拿到,之后的加载动作基本没有阻力。那 root 权限是怎么来的?原因在 Web 管理界面存在一个认证绕过与远程命令执行漏洞,攻击者通过管理接口直接以 root 身份落地了。这一点非常值得注意:内核后门本身不一定需要高超的内核级提权技巧,很多时候前置应用层的漏洞才是整个攻击链里最薄弱的环节。

模块加载之后,内核符号的解析方式也很典型。攻击者会先尝试从/boot/System.map读取符号;读取不到就去/proc/kallsyms尝试;这两个都不行,就在内存范围中扫描系统调用表的特征。本次样本用的是第二种方式,先定位sys_call_table的地址,再解析出需要挂钩的符号和函数偏移。为什么这一步很关键?因为后面所有隐藏能力,都要建立在替换系统调用入口的基础上。

3.2 sys_call_table 挂钩后的隐藏战力

本次样本主要挂钩了sys_getdents64、sys_read、sys_kill这几个系统调用点。先把 x86-64 下对应的系统调用号摆出来方便对照:getdents64对应 217,kill对应 62,read对应 0。样本没有粗暴地覆盖整个系统调用表,而是在每个目标位置写入自己构造的函数地址,原地址被保存下来,用来在执行完过滤逻辑后重放原始行为——这是内嵌 rootkit 里非常标准的做法。

我把它的行为拆开看,实际效果会更清楚:

  • 挂钩getdents64后,凡是遍历目录时遇到/tmp/.X11-unix/.mcache、/var/tmp/.journal等路径,直接不返回记录,应用层看起来这些文件从未存在。
  • 挂钩read后,不但可以隐藏特定日志,还能在用户态读取某个特定文件句柄时返回样本预设的“干净内容”。这意味着你可以去cat一个文件,但读到的东西可能已经被替换过。
  • 挂钩kill后,它成了一个“内部信号开关”:当样本收到特定信号值时,会触发反向 shell 或拉取下载器进程,正常调用kill的行为仍然放行。

用户态加载器还会配合这些隐藏能力:当管理员通过 shell 执行ls /tmp、cat /proc/modules、ps aux | grep xxx时,rootkit 会返回一份预先处理好的输出。于是表面上一切正常,实际的状态却完全被粉饰过。这也是很多安全人员第一次遇到内核级 rootkit 时最迷惑的地方——查什么都查不到异常,直到你把系统各层级的状态与基线做严格比对,才会发现矛盾。

3.3 模块自隐藏与卸载对抗细节

rootkit 的自我保护也做得很足。常规的lsmod和/proc/modules看不到这个模块,原因是模块加载完成后,样本会把自己从内核模块链表中摘除,并修改引用计数,让它在模块遍历中不可见。同时,它还拦截了模块卸载相关的路径,如果用户态直接执行rmmod,会收到“Module not found”的结果,而不是正常的卸载动作。这里的对抗逻辑很直接——先让你找不到,再让你卸不掉。

补充一个判断技巧:如果lsmod输出和/proc/modules输出存在出入,或者模块地址区间明显对不上,都可以视为 rootkit 的线索。真正完美的内核级隐藏并不存在,只要我们按照“从用户态到内核态逐层对比”的方式,通常能在某一层发现不一致。关键是不要只信一个来源,比如只看进程列表或者只看文件列表,而是要交叉验证。

4. 持久化控制链路还原:异常启动路径与故障自愈机制

4.1 四处持久化落点:为什么只删一处没用

持久化是定向攻击最看重的部分。攻击者很清楚,NAS 设备随时可能因为断电重启、版本升级、系统重置等原因回到初始状态,所以单独一个 cron 或 systemd 服务是很脆弱的。本次样本至少在四个地方做了落点:

落点位置具体文件/配置作用
启动脚本/etc/init.d/S99svc每次开机自然启动用户态加载器
systemd 服务/etc/systemd/system/guard.service在 init 层面提供受控方式驻留
内核模块/lib/modules/4.14.xxx/fw_hook.ko在系统内核层维持隐藏能力
动态库/usr/lib/libpam_hook.so通过 PAM 机制实现在登录时重新拉起

最后一项值得多说一句。攻击者没有直接替换系统 PAM 模块,而是往动态库预加载路径中加入了这个.so,效果是:任何新起的程序在启动时都会先把这个库载入内存,攻击者由此可以在用户登录、服务启动等回调点里做手脚。只要系统还在运行,任何一个触发动作都可能把整个后门链路重新拉起来。单独删除某一个文件,系统在下一个事件驱动点又把它拉回来,这种设计明显是为了对抗管理员的第一轮清理。

4.2 双进程看门狗与自愈过程的观察

另一个让我印象深刻的点是双进程看门狗机制。样本内包含两个可执行文件,用户态加载器启动后,这两个进程会互相监测对方是否存活。一旦发现对方退出,存活的一方会直接重建对方的可执行文件和数据段,必要时还会重新加载内核模块。我在虚拟机里做了测试:停掉其中一个进程,30 秒内另一个就会把它重新拉起;如果同时停掉两个,开机启动脚本在一分钟后又会把整套链条拉起来。这种互相拉起机制在定向攻击样本里并不罕见,但在 NAS 这种硬件相对固定的环境里,显得特别难缠。

它们还把持久化文件复制到了多个分区。除了/etc和/usr/lib,在/data分区也存了一份,并且伪装成备份文件放在/var/backups目录里。如果运维团队只做文件层面的手工清理,很容易漏掉备份分区里的副本。实际处置中很多“清完又复发”的案例,往往不是因为样本有高深的能力,而是因为原始清理动作不够彻底。

5. C2通信协议解析:加密通道与上线行为特征

5.1 伪造 HTTPS 的破绽:SNI、指纹与固定周期

C2 通信方面,样本采用了 HTTPS 伪装。它解析出一个类似update.patchsvc[.]top/check的上线地址,实际使用 TLS 连接,并在 HTTP header 里附带看起来像升级包查询的参数。这种做法的目的很明确:让流量看起来像固件升级检测,因为 NAS 系统本身就会定期检查更新,运维人员如果只粗略看一眼访问日志,很容易忽略。

但从流量特征看,破绽相当明显。第一,TLS 证书并不是正规 CA 签发的,而是攻击者自己生成的,证书有效期只有 30 天;第二,TLS 指纹与官方浏览器或者系统自带升级客户端完全不一致;第三,beacon 周期非常固定,每隔 5 分钟一次,而且每次加密负载的长度呈规律性波动。对于流量监控系统来说,固定周期性是一个很强的告警信号。

样本载荷本身做了二层加密:外层是 TLS,内层使用自定义异或加 Base64 编码。虽然不能直接在解密前确定全部协议细节,但从长度字段和填充习惯可以推断,它定义一个固定格式的二进制头部,包括 4 字节魔数、4 字节命令类型、4 字节长度和若干字节序列号。这类固定结构的报文,通常是判断协议类型和指令含义的关键切入点。

5.2 指令码映射与内网横向移动逻辑

我梳理了样本的指令码和对应行为,大致如下:

指令码行为
0x01执行 shell 命令并回传输出
0x02上传 / 下载文件
0x03开启 TCP 隧道,将内网端口映射到 C2
0x04清理日志与痕迹
0x05更新自身并重新持久化

在内网横向移动方面,样本的模块化程度也很高。它会读取/etc/hosts、SSH 的 known_hosts、用户目录下的私钥文件,还会扫描 ARP 缓存和暴露的 SMB/SSH 端口。一旦发现其他主机开放了相同弱口令,就会尝试用已有凭据登录,并把自身投递过去。可以看到,这类攻击并不一定靠某个特别厉害的漏洞,更多是“入口点 + 横向移动 + 持久化”三板斧。边缘设备一旦被突破,内网中其他主机暴露的凭据和端口就会成为接下来的突破口。

6. 检测与清除实战:处置流程、指标与加固清单

6.1 直接可用的检测指标(IOC)

先说检测指标。拿到一批样本或疑似感染设备后,我一般按文件、进程、网络三个层面整理检测指标,下面是可以直接对照的清单:

层面指标
文件/tmp/.X11-unix/.mcache、/var/tmp/.journal、/usr/lib/libpam_hook.so、/lib/modules/4.14.xxx/fw_hook.ko
进程两个互为看门狗的可疑进程,无对应可执行文件路径或路径位于 /tmp 下
网络固定 5 分钟周期的 TLS 回连包,域名类似update.patchsvc[.]top

对应检测命令可以这样写:

ls -la /tmp/.X11-unix/ /var/tmp/.journal grep -rn "update.patchsvc" /etc/hosts /etc/cron* /etc/systemd 2>/dev/null cat /proc/modules | grep -i fw_hook ss -antp | grep -i "tls" | grep -v "127.0.0.1"

需要强调一点:一次性的文件检查往往不够,因为很多隐藏行为只有在内核 hook 生效之后才会体现。建议先把整个系统的内存镜像转储下来,再在离线环境下做字符串提取和文件比对,避免在活体系统上被 rootkit 的输出“骗过”。活体上看到的命令结果,并不等于真实状态。

6.2 恢复流程与后续加固清单

处置流程上,我的建议分三层。第一层是隔离:立刻把 NAS 系统从业务网络断开,并在上层防火墙上封禁 C2 域名和关联 IP。第二层是取证:完整导出系统镜像和内存镜像,保存现场,不做任何破坏性清理。第三层才是恢复和加固:在确认现场数据可以完整保存的前提下,全量刷写系统固件,覆盖引导分区、内核分区和系统分区;修改所有设备的管理密码和 SSH 密钥;关掉公网暴露面,只允许从内网访问管理界面。

根据我之前的经验,一定不要为了图省事,只删除几个可疑文件就把设备重新上线。只要启动脚本、预加载库和内核模块三处里任何一处有残留,系统重启后通常又会把整个链路带回来。最稳妥的做法是,把设备恢复到出厂状态,再从可信备份中恢复数据,同时确认备份本身没有被样本污染。所谓“清完又复发”,绝大多数都是清理不彻底,而不是样本真的无法清除。

加固层面,我列几个关键动作:关闭不必要的端口映射;定期更新 NAS 固件;开启系统级审计和日志转存;不要使用默认密码;对 Web 管理接口做严格的来源 IP 限制;对关键目录设置写权限控制,并对系统库和内核模块做哈希校验。运维团队还应该建立一套“异常行为上报”的习惯,像无故重启、日志空白这类现象,往往是攻击链最早暴露出来的信号。

这次分析给我最深的体会是,很多人以为部署了防火墙、装了杀毒软件,NAS 系统就安全了。但面对一个把后门放在内核里、把持久化分散在多个目录的定向样本,任何单一防护层都不够用。真正最可靠的,其实是清晰的系统基线和异常行为敏感性。把“定期比对基线、关注异常细节”这件事固化到日常运维里,比临时去找某个 rootkit 的清除工具要有效得多。

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

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

立即咨询