简介
在第一部分中,我们研究了 Linux rootkit 的工作原理:它们的演变、分类以及操纵用户空间和内核空间的技术。在第二部分中,我们将讨论探测工程。我们首先说明了为什么静态检测对 Linux rootkit 来说往往不可靠,即使二进制文件只做了微不足道的修改,然后介绍了防御者可以使用的行为和运行时信号。从共享对象滥用和 LKM 加载到 eBPF、io_uring、持久性和防御规避,本文重点介绍在真实环境中检测和调查 rootkit 活动的实用方法。
通过 VirusTotal 进行静态检测
在重点介绍行为检测技术之前,我们不妨先看看传统的静态检测机制对 Linux rootkit 的识别能力如何。为此,我们使用 VirusTotal 作为传统的基于特征码的反病毒检测的代理,进行了一次小型实验。我们从公开的研究论文和开源软件库中收集了十个 Linux rootkit 数据集。每个样本都上传到 VirusTotal 或从现有提交的样本中获取。
对于每个 rootkit,我们都记录了标记原始二进制文件的杀毒引擎数量。随后,我们又进行了两项测试:
- 使用
strip --strip-all创建的剥离二进制文件,删除了符号表和其他非必要元数据。 - 通过在原始文件中添加一个空字节而创建的经过简单修改的二进制文件:这是一种故意进行的不复杂的修改。
我们的目标不是通过高级混淆逃避检测,而是评估静态签名在面对最简单的二进制修改时有多脆弱。
表 1:所分析 rootkit 数据集的技术概览
| Rootkit | 基本检测 | 剥离 | 添加空字节 |
|---|---|---|---|
| 阿萨索 | 36/66 | 19/66 | 21/66 |
| 贝德维尔* | 32/66 | 32/66 | 21/66 |
| BrokePKG | 7/66 | 3/66 | 3/66 |
| 地吗啡 | 33/66 | 8/64 | 22/66 |
| 科威德 | 27/66 | 1/66 | 15/66 |
| 移动工具包 | 29/66 | 6/66 | 17/66 |
| 爬行动物 | 32/66 | 3/66 | 20/66 |
| Snapekit | 30/66 | 3/66 | 19/66 |
| 共生体 | 42/66 | 8/66 | 22/66 |
| TripleCross | 31/66 | 17/66 | 19/66 |
* 默认情况下,Bedevil 已被剥离,因此,基本检测和剥离检测是相同的。
观察结果
不出所料,剥离双星一般会导致探测率急剧下降。在一些情况下,检测结果几乎为零,这表明一些杀毒引擎严重依赖符号信息或其他容易删除的元数据。更能说明问题的是添加一个空字节的影响:这种修改不会改变程序逻辑、执行流程或行为,但仍会显著降低许多样本的检测能力。
这凸显了基于签名的静态检测的一个根本弱点。如果一个字节的变化就能对检测结果产生有意义的影响,那么攻击者就不需要复杂的混淆手段来躲避静态扫描仪。
rootkit 中的混淆技术
有趣的是,该数据集中的大多数 rootkit 几乎没有使用高级静态混淆。在存在混淆的情况下,通常仅限于对字符串或配置数据进行简单的 XOR 编码,或采用轻量级打包技术,稍微改变二进制布局。这些方法实施成本低廉,足以破解许多静态签名。
值得注意的是,这些样本中没有更高级的混淆手段。许多都是开源的概念验证 rootkit,旨在展示技术而非主动逃避检测。然而,即使极少或没有混淆,静态检测也证明是不可靠的。
为什么仅有静态检测是不够的
这一实验强化了一个关键点:仅靠静态检测根本不足以进行可靠的 rootkit 检测。静态签名的脆弱性(尤其是在面对微不足道的修改时)意味着防御者不能依靠基于文件的指标或基于哈希值的检测来发现隐蔽的威胁。
当二进制文件可以在不影响行为的情况下被修改时,唯一剩下的一致信号就是 rootkit 在运行时的行为。因此,本博客的其余部分将重点从静态人工制品转向动态分析和行为检测,研究 rootkit 如何与操作系统交互、操纵执行流以及在执行过程中留下可观察到的痕迹。
这就是检测工程变得更具挑战性和更有效的地方。
动态检测工程
用户地带 rootkit 载入检测技术
用户态域 rootkit 通常会劫持动态链接过程,将恶意共享对象注入目标进程,而无需内核级访问。感染始于共享对象文件的创建。可以通过类似下图的检测规则来检测新创建的共享对象文件:
file where event.action == "creation" and
(file.extension like~ "so" or file.name like~ "*.so.*")
这些文件通常被写入可写路径或短暂路径,如/tmp/,/dev/shm/ 或用户主目录下的隐藏子目录。攻击者可以直接从加载器下载、编译或投放。这一知识可用于上述检测规则,以减少噪音。
例如,在上图所示的遥测中,我们可以看到威胁行为者使用scp 将共享对象文件下载到/tmp 中的一个隐藏子目录,然后将其移动到一个库目录,试图混入其中。我们通过以下途径检测到了这一威胁和类似威胁:
一旦共享对象文件出现在系统中,攻击者有几种激活它的方法。最常滥用的机制是LD_PRELOAD 环境变量、/etc/ld.so.preload 文件和动态链接器配置路径,如/etc/ld.so.conf 。
LD_PRELOAD 环境变量允许攻击者指定一个共享对象,在执行动态链接二进制文件时,该对象将先于其他库加载。这样就可以完全覆盖libc 函数,如execve(),open() 或readdir() 。该方法按进程运行,无需 root 访问权限。
要检测这种技术,需要对LD_PRELOAD 环境变量进行遥测。有了它,就可以编写任何检测逻辑来检测不常见的LD_PRELOAD 值。例如
process where event.type == "start" and event.action == "exec" and
process.env_vars != null
如图 1 所示,这也是攻击者的下一步行动。攻击者将libz.so.1 从/tmp/.X12-unix/libz.so.1 移至/usr/local/lib/libz.so.1 。
为了提高保真度,我们使用new_terms 规则类型实现了这一逻辑,只对LD_PRELOAD 变量中先前未见的共享对象条目进行标记:
当然,如果收集的环境变量不止LD_PRELOAD 和LD_LIBRARY_PATH ,则应修改上述规则,将这两项具体包括在内。为减少噪音,应进行统计分析和/或基线分析。
另一种激活方法是利用/etc/ld.so.preload 文件。如果存在,该文件会强制动态链接器将列出的共享对象注入系统中的每个动态链接二进制文件,从而实现全局注入。
类似的方法还包括更改动态链接器的配置,以优先处理恶意库路径。这可以通过修改/etc/ld.so.conf 或向/etc/ld.so.conf.d/ 添加条目来实现,然后执行ldconfig 更新缓存。这改变了关键库的解析路径,如libc.so.6 。
可以通过监控/etc/ld.so.preload 和/etc/ld.so.conf 文件以及/etc/ld.so.conf.d/ 目录中的创建/修改事件来检测这些情况。利用这些原始遥测数据,可以实施检测规则来标记这些事件:
file where event.action in ("creation", "rename") and
file.path like ("/etc/ld.so.preload", "/etc/ld.so.conf", "/etc/ld.so.conf.d/*")
我们经常会看到这样的链条:先创建一个共享对象,然后修改动态链接器。
我们通过以下检测规则进行检测:
在一台主机上同时出现这两个警报值得调查。
内核空间 rootkit 载入检测技术
手动加载 LKM 通常需要使用内置的命令行实用程序,如modprobe 、insmod 和kmod 。检测这些实用程序的执行情况将检测加载阶段(手动执行时)。
process where event.type == "start" and event.action == "exec" and (
(process.name == "kmod" and process.args == "insmod" and
process.args like~ "*.ko*") or
(process.name == "kmod" and process.args == "modprobe" and
not process.args in ("-r", "--remove")) or
(process.name == "insmod" and process.args like~ "*.ko*") or
(process.name == "modprobe" and not process.args in ("-r", "--remove"))
)
许多开源 rootkit 在发布时都没有加载程序,而是依赖预装的 LKM 加载实用程序。例如,Singularity 提供了一个load_and_persistence.sh 脚本,该脚本会执行若干操作,然后最终调用insmod "$MODULE_DIR/$MODULE_NAME.ko" 。虽然insmod 在命令中被调用,但insmod 实际上是kmod ,insmod 是进程参数。奇点负载实例
可以通过以下检测规则轻松检测到:
不过,这种检测方法远非刀枪不入,因为许多 rootkit 都依赖加载器加载 LKM,从而绕过这些用户态实用程序的执行。
例如,Reptile 的加载器直接调用init_module syscall,内存中的内核 blob 已解密:
#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)
int main(void) {
[...]
do_decrypt(reptile_blob, len, DECRYPT_KEY);
module_image = malloc(len);
memcpy(module_image, reptile_blob, len);
init_module(module_image, len, "");
[...]
}
此外,Reptile 的 kmatryoshka 模块还充当内核链加载器,使用指向sys_init_module 的直接函数指针解密并加载另一个隐藏的 LKM,该 LKM 位于kallsyms_on_each_symbol() 。这进一步掩盖了加载机制在用户界面上的可见性。
因此,了解这些实用程序的功能至关重要;它们只是init_module() 和finit_module() 系统调用的封装程序。因此,有效的检测应侧重于直接跟踪这些系统调用,而不是调用它们的工具。
为了确保加载 LKM 所需的数据源的可用性,可以使用各种安全工具。Auditd 或 Auditd Manager 是合适的选择。为便于收集init_module() 和finit_module 系统调用,可执行后续配置。
-a always,exit -F arch=b64 -S finit_module -S init_module
-a always,exit -F arch=b32 -S finit_module -S init_module
将这些原始遥测数据与检测规则结合起来,在事件发生时发出警报,从而实现强有力的防御。
driver where event.action == "loaded-kernel-module" and
auditd.data.syscall in ("init_module", "finit_module")
无论在加载事件中使用何种工具,这一策略都能检测到内核模块的加载。在下面的示例中,我们看到了对Diamorphinerootkit 的真阳性检测。
该预建规则可在此处获取:
使用 Auditd 的 Linux 检测工程研究 中介绍了通过 Auditd 进行 Linux 检测工程的 其他指导。
树外模块和无符号模块
恶意 LKM 的另一个标志是内核 "污点 "标志的出现。当内核检测到加载的模块不属于官方内核树、缺乏有效签名或使用非许可授权时,内核会将其标记为 "污点"。这是一种内置的完整性机制,表示内核处于潜在的不可信任状态。下图是加载reveng_rtkit 模块的示例:
[ 2853.023215] reveng_rtkit: loading out-of-tree module taints kernel.
[ 2853.023219] reveng_rtkit: module license 'unspecified' taints kernel.
[ 2853.023220] Disabling lock debugging due to kernel taint
[ 2853.023297] reveng_rtkit: module verification failed: signature and/or required key missing - tainting kernel
内核将该模块识别为树外模块,许可证不明,缺少加密验证。这会导致内核被标记为污点。
要检测这种行为,必须对系统和内核日志进行解析和摄取。内核日志遥测一旦可用,简单的模式匹配或基于规则的检测就能标记这些事件。可通过以下方式检测树外模块加载情况:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"loading out-of-tree module taints kernel."
类似的检测逻辑也可用于检测无符号模块加载:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"module verification failed: signature and/or required key missing - tainting kernel"
利用上述检测逻辑,我们在遥测中观察到真阳性,试图加载 Singularity:
这些规则默认在
日志条目将始终显示触发事件的模块名称,以便于分流。在此警报触发的手动检查中,如果系统中不存在 LKM,则可能表明 LKM 正在隐藏自己。
杀死信号
许多(开源)rootkit 利用kill 信号,特别是那些较高、未指定范围(32+)的信号,作为隐蔽的通信渠道或恶意行动的触发器。例如,rootkit 可拦截特定的高编号kill 信号(如kill -64 <pid> )。接收到这一信号后,rootkit 的有效载荷可被配置为提升权限、执行命令、切换隐藏功能或建立后门。
要检测到这一点,我们可以利用 Auditd 创建一条规则,收集所有杀死信号:
-a exit,always -F arch=b64 -S kill -k kill_rule
传给kill() 的参数是kill(pid, sig) 。我们可以查询a1 (信号),标记任何高于 32 的杀死信号。
process where event.action == "killed-pid" and
auditd.data.syscall == "kill" and auditd.data.a1 in (
"21", "22", "23", "24", "25", "26", "27", "28", "29", "2a",
"2b", "2c", "2d", "2e", "2f", "30", "31", "32", "33", "34",
"35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e",
"3f", "40", "41", "42", "43", "44", "45", "46", "47"
)
通过 Auditd 分析kill() syscall 的不寻常信号值,可以有效检测利用这些信号的 rootkit,如 Diamorphine 所用的技术。与杀戮相关的预建规则可在以下网址获取:
分段故障
最后,必须认识到内核空间 rootkit 本身很脆弱。LKM 通常是针对特定内核版本和配置编译的。错误解析的符号或错误对齐的内存写入可能会引发分段故障。虽然这些故障可能不会立即暴露 rootkit 的功能,但它们提供了强有力的取证信号。
要检测到这一点,必须启用原始系统日志收集功能。在此基础上,编写检测规则来标记 segfault 信息,有助于识别恶意行为或内核不稳定,这两种情况都值得调查:
event.dataset:"system.syslog" and process.name:"kernel" and message:"segfault"
该检测规则可作为构件规则直接使用:
将系统调用级模块加载可见性与内核污点、树外信息、杀戮信号检测和 segfault 警报相结合,为分层策略检测基于 LKM 的 rootkit 奠定了基础。
eBPF rootkits
eBPF rootkits 利用 Linux 内核 BPF 子系统的合法功能。可使用bpftool 等实用程序或滥用bpf() 系统调用的自定义加载程序动态加载和附加程序。
检测基于 eBPF 的 rootkit 需要了解bpf() syscalls 和敏感 eBPF 辅助程序的使用情况。涉及的主要指标包括
bpf(BPF_MAP_CREATE, ...)bpf(BPF_MAP_LOOKUP_ELEM, ...)bpf(BPF_MAP_UPDATE_ELEM, ...)bpf(BPF_PROG_LOAD, ...)bpf(BPF_PROG_ATTACH, ...)
利用 Auditd 可创建审计规则,其中a0 可用于指定所关注的特定 BPF 系统调用:
-a always,exit -F arch=b64 -S bpf -F a0=0 -k bpf_map_create
-a always,exit -F arch=b64 -S bpf -F a0=1 -k bpf_map_lookup_elem
-a always,exit -F arch=b64 -S bpf -F a0=2 -k bpf_map_update_elem
-a always,exit -F arch=b64 -S bpf -F a0=5 -k bpf_prog_load
-a always,exit -F arch=b64 -S bpf -F a0=8 -k bpf_prog_attach
必须根据具体环境进行调整,以确保利用 eBPF 的良性程序(如 EDR 或其他可观测性工具)不会产生噪声。另一个重要信号是 eBPF 辅助函数的使用。
bpf_probe_write_user 辅助函数
bpf_probe_write_user 辅助程序允许内核空间 eBPF 程序直接写入用户态内存。虽然该功能用于调试,但可能会被 rootkit 滥用。
检测工作仍然充满挑战,但 Linux 内核通常会记录敏感辅助程序的使用情况,如bpf_probe_write_user 。对这些条目的监控提供了一个检测机会,需要收集原始系统日志和特定的检测规则,例如以下规则:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"bpf_probe_write_user"
如果内核日志记录显示使用了bpf_probe_write_user ,该规则就会发出警报。虽然合法工具可能偶尔会调用它,但意外或频繁使用,特别是与可疑的进程行为一起使用,就需要进行调查。背景信息,如 eBPF 程序的附着点和所涉及的用户地流程,有助于分流。该检测规则可在此处获取:
下面是几个通过这种逻辑检测出真阳性的明显例子:
该规则会触发nysm(一种隐蔽的后开发容器)和boopkit(一种 Linux eBPF 后门)。
io_uring rootkits
ARMO 研究(2025 年)引入了一种新的防御规避技术,利用io_uring 这一异步 I/O 设计,减少可观测的系统调用活动,绕过标准遥测技术。该技术仅限于 5.1 及以上版本的内核,并避免使用钩子。虽然 rootkit 研究人员最近发现了这种方法,但它仍在积极开发中,功能集相对来说还不成熟。RingReaper 就是一个利用这种技术的工具。Rootkits 可通过io_uring_enter() 批量处理文件、网络和其他 I/O 操作。代码示例如下。
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, size, offset);
io_uring_submit(&ring);
这些调用使用io_uring 排队并提交读取请求,绕过了典型的系统调用遥测路径。
与系统调用表挂钩或基于LD_PRELOAD 的注入不同,io_uring 本身并不是一种 rootkit 传播机制,而是提供了一种更隐蔽的手段,可在入侵后与文件系统和设备进行交互。虽然io_uring 无法直接执行二进制文件(因为缺乏类似execve 的功能),但它可以实现文件创建、枚举和数据外泄等恶意行为,同时最大限度地降低可观察性。
检测基于io_uring 的 rootkit 需要了解支撑其运行的系统调用,如io_uring_setup() 、io_uring_enter() 和io_uring_register() 。
虽然 EDR 解决方案可能难以捕捉io_uring 的间接影响,但 Auditd 可以直接跟踪这些系统调用。以下审计规则可捕获相关事件进行分析:
-a always,exit -F arch=b64 -k io_uring
-S io_uring_setup -S io_uring_enter -S io_uring_register
不过,这只会暴露系统调用本身,而不会暴露正在访问的特定文件或对象。io_uring 真正的"神奇" 发生在用户态库中(如liburing ),因此对系统调用参数的分析至关重要。
例如,使用to_submit > 0 监控io_uring_enter() 表示正在对 I/O 操作进行批处理,而交替调用min_complete > 0 则表示正在完成轮询。与进程属性(如 UID=0、不寻常路径,如/dev/shm 、/tmp 或tmpfs-backed 位置)相关联可提高检测效率。
追踪io_uring 活动的一种实用方法是通过 eBPF,使用BCC 等工具,以sys_enter_io_uring_enter 等追踪点为目标。这样,分析人员就可以在io_uring 操作期间监控进程行为和活动文件描述符:
tracepoint:syscalls:sys_enter_io_uring_enter
{
printf("\nPID %d (%s) called io_uring_enter with fd=%d, to_submit=%d, min_complete=%d, flags=%d\n",
pid, comm, args->fd, args->to_submit, args->min_complete, args->flags);
printf("Manually inspect with: ls -l /proc/%d/fd\n", pid);
}
为了说明这一点,我们对 RingReaper 引入的几种技术进行了测试。实时跟踪功能可显示使用中的文件描述符,帮助识别可疑活动,如从/run/utmp 读取,以检测登录的用户:
写入文件的活动,在本例中为/root/test :
或通过ps 读取每个活动 PID 的comm 内容,从而列出进程信息:
虽然系统调用监控能显示io_uring 的使用情况,但如果没有额外的相关性,它并不能直接显示 I/O 的性质。io_uring 是一种相对较新的技术,因此仍具有隐蔽性,但也有一些局限性。io_uring 然而,攻击者可能会滥用文件写入(如 cron 作业、udev 规则)来实现延迟或间接执行,Reptile 和Sedexp恶意软件家族使用的持久性技术就证明了这一点。
Rootkit 持久性技术
无论是用户空间还是内核空间的 Rootkit,都需要某种形式的持久性,才能在重启或用户会话期间保持功能。这些方法因 rootkit 的类型和权限而异,但通常涉及滥用配置文件、服务管理或系统初始化脚本。
用户域 rootkit - 环境变量持久性
当使用LD_PRELOAD 激活用户态 rootkit 时,默认情况下该行为不会持续。为实现持久性,攻击者可能会修改 shell 初始化文件(如~/.bashrc,~/.zshrc 或/etc/profile ),以导出LD_PRELOAD 或LD_LIBRARY_PATH 等环境变量。这些修改可确保每个新 shell 会话自动继承激活 rootkit 所需的环境。值得注意的是,用户和根用户都有这些文件。因此,即使非特权用户也可以引入持久性,劫持其特权级别的执行流。
要检测到这一点,可以使用类似下面显示的规则:
file where event.action in ("rename", "creation") and file.path like (
// system-wide configurations
"/etc/profile", "/etc/profile.d/*", "/etc/bash.bashrc",
"/etc/bash.bash_logout", "/etc/zsh/*", "/etc/csh.cshrc",
"/etc/csh.login", "/etc/fish/config.fish", "/etc/ksh.kshrc",
// root and user configurations
"/home/*/.profile", "/home/*/.bashrc", "/home/*/.bash_login",
"/home/*/.bash_logout", "/home/*/.bash_profile", "/root/.profile",
"/root/.bashrc", "/root/.bash_login", "/root/.bash_logout",
"/root/.bash_profile", "/root/.bash_aliases", "/home/*/.bash_aliases",
"/home/*/.zprofile", "/home/*/.zshrc", "/root/.zprofile", "/root/.zshrc",
"/home/*/.cshrc", "/home/*/.login", "/home/*/.logout", "/root/.cshrc",
"/root/.login", "/root/.logout", "/home/*/.config/fish/config.fish",
"/root/.config/fish/config.fish", "/home/*/.kshrc", "/root/.kshrc"
)
根据环境的不同,这些外壳中可能有几个没有被使用,因此可以创建一个更有针对性的检测规则,例如只针对bash 或zsh 。使用 Elastic Defend 和Elastic 文件完整性监控集成的完整检测逻辑可在此处找到:
如需了解更多信息,请参阅《 Linux 检测工程--持久性机制入门 》( Linux Detection Engineering - A primer on persistence mechanisms )一书,其中全面介绍了这种持久性技术,包括检测其滥用的其他几种方法。
用户地带 rootkit - 基于配置的持久性
修改/etc/ld.so.preload 、/etc/ld.so.conf 或/etc/ld.so.conf.d/ 配置文件可使 rootkit 跨用户和会话全局持久化(有关此持久化载体的更多信息,请参阅《Linux 检测工程--持久化机制续篇》)。一旦写入,动态链接器将继续注入恶意共享对象,除非明确恢复这些配置。这些方法在设计上具有持久性。检测策略与上一节中描述的策略相同,依靠监控这些路径中的文件创建或修改事件。
内核空间 rootkit - LKM 持久性
与用户态 rootkit 类似,LKM 默认情况下也不是持久性的。攻击者必须明确配置系统,以便在启动时重新加载恶意模块。这通常是通过利用合法的内核模块加载机制来实现的:
模块文件: modules
该文件列出了系统启动时应自动加载的内核模块。在此处添加恶意.ko 文件名可确保modprobe 在启动时加载该文件。该文件位于/etc/modules 。
的配置目录 modprobe
该目录包含modprobe 实用程序的配置文件。攻击者可能会使用别名来伪装他们的 rootkit,或在特定内核事件发生时(如设备被探测时)自动加载。这些 modprobe 配置文件位于/etc/modprobe.d/,/run/modprobe.d/,/usr/local/lib/modprobe.d/,/usr/lib/modprobe.d/ 和/lib/modprobe.d/ 。
配置启动时加载的内核模块: modules-load.d
这些配置文件指定在启动过程的早期加载哪些模块,分别位于/etc/modules-load.d/,/run/modules-load.d/,/usr/local/lib/modules-load.d/ 和/usr/lib/modules-load.d/ 。
要检测上面列出的所有持久性技术,可以创建与下面类似的检测规则:
file where event.action in ("rename", "creation") and file.path like (
"/etc/modules",
"/etc/modprobe.d/*",
"/run/modprobe.d/*",
"/usr/local/lib/modprobe.d/*",
"/usr/lib/modprobe.d/*",
"/lib/modprobe.d/*",
"/etc/modules-load.d/*",
"/run/modules-load.d/*",
"/usr/local/lib/modules-load.d/*",
"/usr/lib/modules-load.d/*"
)
此预置规则可将上述所有路径合并为一条检测规则:
使用这种方法自动部署持久性的 rootkit 的一个例子是 Singularity。在部署过程中,会执行以下命令:
read -p "Enter the module name (without .ko): " MODULE_NAME
CONF_DIR="/etc/modules-load.d"
mkdir -p "$CONF_DIR"
echo "[*] Setting up persistence..."
echo "$MODULE_NAME" > "$CONF_DIR/$MODULE_NAME.conf"
默认情况下,这意味着singularity.conf 将作为/etc/modules-load.d/ 下的一个新条目创建。通过遥测技术,我们只需监测新文件的创建情况就能检测到这种技术:
这些目录也用于良性 LKM,因此容易出现误报。另一种持久性方法是使用基于触发器或计划的技术,通过执行加载器来加载内核模块。
基于 Udev 的持久性 - Reptile 示例
一种不太常见但功能强大的持久性方法是滥用 udev,它是一种处理动态设备事件的 Linux 设备管理器。Udev 会在满足特定条件时执行基于规则的脚本。Linux 检测工程--持久性机制续篇》一书中全面介绍了这一技术。Reptile rootkit通过在/etc/udev/rules.d/ 下安装恶意的 udev 规则来演示这种技术:
ACTION=="add", ENV{MAJOR}=="1", ENV{MINOR}=="8", RUN+="/lib/udev/reptile"
Levelblue 发现的Sedexp恶意软件很可能就是受到了这一规则的启发。规则是这样的
ACTION=="add":当系统中添加新设备时触发。ENV{MAJOR}=="1":匹配主要编号为 "1 "的设备,通常是与内存相关的设备,如/dev/mem,/dev/null,/dev/zero, 和/dev/random。ENV{MINOR}=="8":进一步将条件缩小到/dev/random。RUN+="/lib/udev/reptile":当检测到上述设备时,执行 Reptile 载入器二进制文件。
每当加载/dev/random 设备时,该规则都会触发加载器二进制文件的执行,从而建立持久性。作为一种广泛使用的随机数生成器,它对许多系统应用和启动过程都至关重要,因此这种方法非常有效。只有在发生特定设备事件时才会激活,并通过udev daemon 以 root 权限执行。要检测这种技术,可以创建类似下面的检测规则:
file where event.action in ("rename", "creation") and file.extension == "rules" and file.path like (
"/lib/udev/*",
"/etc/udev/rules.d/*",
"/usr/lib/udev/rules.d/*",
"/run/udev/rules.d/*",
"/usr/local/lib/udev/rules.d/*"
)
我们通过以下预置规则来创建和修改这些文件:
一般持久性机制
除了内核模块加载路径外,攻击者还可能依赖更通用的 Linux 持久性方法,通过加载器重新加载用户态或内核空间 rootkit:
Systemd:在任何(如/etc/systemd/system/ )目录下创建或附加服务/定时器,该目录在启动时支持加载程序。
file where event.action in ("rename", "creation") and file.path like (
"/etc/systemd/system/*", "/etc/systemd/user/*",
"/usr/local/lib/systemd/system/*", "/lib/systemd/system/*",
"/usr/lib/systemd/system/*", "/usr/lib/systemd/user/*",
"/home/*.config/systemd/user/*", "/home/*.local/share/systemd/user/*",
"/root/.config/systemd/user/*", "/root/.local/share/systemd/user/*"
) and file.extension in ("service", "timer")
初始化脚本:创建或附加恶意运行控制(/etc/rc.local)、SysVinit(/etc/init.d/) 或Upstart(/etc/init/) 脚本。
file where event.action in ("creation", "rename") and
file.path like (
"/etc/init.d/*", "/etc/init/*", "/etc/rc.local", "/etc/rc.common"
)
Cron 作业:创建或附加 cron 作业,以便重复执行加载程序。
file where event.action in ("rename", "creation") and
file.path like (
"/etc/cron.allow", "/etc/cron.deny", "/etc/cron.d/*",
"/etc/cron.hourly/*", "/etc/cron.daily/*", "/etc/cron.weekly/*",
"/etc/cron.monthly/*", "/etc/crontab", "/var/spool/cron/crontabs/*",
"/var/spool/anacron/*"
)
Sudoers:创建或附加恶意 sudoers 配置作为后门。
file where event.type in ("creation", "change") and
file.path like "/etc/sudoers*"
这些方法使用广泛、灵活,而且通常更容易通过进程线或文件修改遥测进行检测。
用于检测这些持久性技术的预置检测规则列表如下:
- Systemd Service Created
- Systemd Timer Created
- System V Init Script Created
- rc.local/rc.common File Creation
- Cron Job Created or Modified
- Sudoers 文件活动
- 通过文件修改实现持久性
Rootkit 防御规避技术
虽然从定义上讲,rootkit 是一种逃避防御的工具,但许多rootkit 还采用了其他技术,以便在部署期间和之后不被发现。这些方法旨在避免在日志中曝光,躲避端点检测代理,并干扰常见的调查工作流程。下一节概述了现代 Linux rootkit 使用的主要规避技术,并按其运行目标进行了分类。
试图在部署时保持隐蔽性
从取证的角度来看,威胁行为者通常注重隐蔽的执行策略。例如,威胁行为者可以从/dev/shm 共享内存目录中存储和执行有效载荷,因为这是一个完全虚拟的文件系统,因此有效载荷永远不会接触磁盘。从取证的角度看,这很好,但作为行为检测工程师,我们发现这种行为非常可疑,而且不常见。
举例来说,虽然不是实际的威胁行为者,但 Singularity 的作者建议采用以下部署方法:
cd /dev/shm
git clone https://github.com/MatheuZSecurity/Singularity
cd Singularity
sudo bash setup.sh
sudo bash scripts/x.sh
要想以几乎为零的误报率检测到这种行为,需要安装几个绊线,首先要将 GitHub 仓库克隆到/dev/shm 目录中。
sequence by process.entity_id, host.id with maxspan=10s
[process where event.type == "start" and event.action == "exec" and (
(process.name == "git" and process.args == "clone") or
(
process.name in ("wget", "curl") and
process.command_line like~ "*github*"
)
)]
[file where event.type == "creation" and
file.path like ("/tmp/*", "/var/tmp/*", "/dev/shm/*")]
在/tmp 和/var/tmp 中克隆目录很常见,因此在克隆存储库很常见的环境中,可从本规则中删除这些内容。然而,同样的活动在/dev/shm 上并不多见。
加载程序调用setup.sh 脚本,继续编译/dev/shm/ 子目录中的 LKM。真正的威胁行为者一般不会在主机上编译,但这种情况并不少见。
sequence with maxspan=10s
[process where event.type == "start" and event.action == "exec" and
process.name like (
"*gcc*", "*g++*", "c++", "cc", "c99", "c89", "cc1*", "clang*",
"musl-clang", "tcc", "zig", "ccache", "distcc"
)] as event0
[file where event.action == "creation" and file.path like "/dev/shm/*" and
process.name like (
"ld", "ld.*", "lld", "ld.lld", "mold", "collect2", "*-linux-gnu-ld*",
"*-pc-linux-gnu-ld*"
) and
stringcontains~(event0.process.command_line, file.name)]
该端点逻辑会检测编译器的执行情况,随后链接器会在/dev/shm (或子目录)中创建一个文件。
最后,由于它克隆了/dev/shm 中的整个版本库,并执行了setup.sh 和x.sh ,我们将观察到共享内存目录下的进程执行情况,这在大多数环境中并不常见:
process where event.type == "start" and event.action == "exec" and
process.executable like ("/dev/shm/*", "/run/shm/*")
这些规则可在检测-规则和保护-工件库中找到:
伪装成合法程序
为了避免在进程枚举或系统监控过程中受到检查,rootkit 通常会重命名进程和线程,使其与良性系统组件相匹配。常见的伪装包括
kworker,migration, 或rcu_sched(内核线程)sshd,systemd,dbus-daemon, 或bash(用户域守护进程)
选择这些名称是为了与ps 、top 或htop 等工具的输出结果相混淆,从而增加人工检测的难度。利用这种技术的 rootkit 包括 Reptile 和PUMAKIT。Reptile 在初始化时通过kworker 生成异常网络事件:
network where event.type == "start" and event.action == "connection_attempted"
and process.name like~ ("kworker*", "kthreadd") and not (
destination.ip == null or
destination.ip == "0.0.0.0" or
cidrmatch(
destination.ip,
"10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12",
"192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32",
"192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24",
"192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24",
"224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24","198.18.0.0/15",
"198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1",
"FE80::/10", "FF00::/8"
)
)
下面的示例展示了 Reptile 的端口敲击功能,即内核线程分叉、将会话 ID 更改为 0 并建立网络连接:
我们还看到 Reptile 利用相同的kworker 流程来创建文件:
file where event.type == "creation" and
process.name like~ ("kworker*", "kthreadd")
PUMAKIT通过kthreadd 生成内核线程来执行用户态命令,但在其他 rootkit 中,通过kworker 进程也观察到类似活动:
process where event.type == "start" and event.action == "exec" and
process.parent.name like~ ("kworker*", "kthreadd") and
process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and
process.args == "-c"
由于 Linux 内核的内部操作,这些kworker 和kthreadd 规则可能会产生误报。这些参数可以很容易地按环境排除,也可以在逻辑中添加额外的命令行参数。
这些规则可在检测-规则和保护-工件库中找到:
此外,恶意进程(如初始投放器或持久化机制)可能会伪装成内核线程,并利用内置的 shell 函数来实现这一目的。利用exec -a 命令,可以用攻击者选择的名称创建任何进程。内核进程伪装可通过以下检测查询进行检测:
process where event.type == "start" and event.action == "exec" and
process.command_line like "[*]" and process.args_count == 1
这种行为如下所示,其中有几个恶意软件试图伪装成内核工作进程或网络服务进程。
利用黑客之选(THC)工具包的威胁行为者通常也会滥用这种技术,特别是在部署gsocket 时。
与内核伪装和一般通过exec -a 进行伪装有关的规则可在保护-工件库中找到:
在野外和马丸中看到的另一种技术是使用prctl 来踩踏其过程名称。为确保提供这种遥测数据,可创建自定义 Auditd 规则:
-a exit,always -F arch=b64 -S prctl -k prctl_detection
并伴有以下检测逻辑
process where host.os.type == "linux" and auditd.data.syscall == "prctl" and
auditd.data.a0 == "f"
将允许对这一技术进行检测。在下面的截图中,我们可以看到使用这种技术的遥测示例,其中process.executable 是胡言乱语,然后prctl 将被用来在系统中伪装成合法进程。
该规则及其设置说明可在此处获取:
虽然伪装的方法有很多,但这些都是最常见的。
日志和审计清理
许多 rootkit 都包含从日志中清除其安装或活动痕迹的程序。其中一种技术是清除受害者的 Shell 历史记录。这可以通过两种方式检测到。一种方法是检测 shell 历史文件的删除情况:
file where event.type == "deletion" and file.name in (
".bash_history", ".zsh_history", ".sh_history", ".ksh_history",
".history", ".csh_history", ".tcsh_history", "fish_history"
)
第二种方法是检测带有与清除 shell 历史有关的命令行参数的进程执行:
process where event.type == "start" and event.action == "exec" and (
(
process.args in ("rm", "echo") or
(
process.args == "ln" and process.args == "-sf" and
process.args == "/dev/null"
) or
(process.args == "truncate" and process.args == "-s0")
)
and process.command_line like~ (
"*.bash_history*", "*.zsh_history*", "*.sh_history*", "*.ksh_history*",
"*.history*", "*.csh_history*", "*.tcsh_history*", "*fish_history*"
)
) or
(process.name == "history" and process.args == "-c") or
(
process.args == "export" and
process.args like~ ("HISTFILE=/dev/null", "HISTFILESIZE=0")
) or
(process.args == "unset" and process.args like~ "HISTFILE") or
(process.args == "set" and process.args == "history" and process.args == "+o")
同时启用两种检测规则(进程和文件),可以实现更强大的深度防御策略。
加载时,rootkit 可能会玷污内核或生成树外信息,这些信息可在解析 syslog 和内核日志时识别出来。为了消除踪迹,rootkit 可能会删除这些日志文件:
file where event.type == "deletion" and file.path in (
"/var/log/syslog", "/var/log/messages", "/var/log/secure",
"/var/log/auth.log", "/var/log/boot.log", "/var/log/kern.log",
"/var/log/dmesg"
)
或者通过dmesg 清理内核信息缓冲区:
process where event.type == "start" and event.action == "exec" and
process.name == "dmesg" and process.args in ("-c", "--clear")
自动清除dmesg的 rootkit 的一个例子是bds rootkit,它通过执行/opt/bds_elf/bds_start.sh 加载:
清除这些日志的另一种方法是使用journalctl:
process where event.type == "start" and event.action == "exec" and
process.name == "journalctl" and
process.args like ("--vacuum-time=*", "--vacuum-size=*", "--vacuum-files=*")
奇点公司曾使用过这种技术:
Singularity 的加载器脚本采用的另一种技术是,在无法加载或完成加载过程后,删除与 rootkit 相关的所有文件。为了更彻底地删除,作者选择使用shred 而不是rm 。rm (删除)只需删除文件指针,速度快,但允许数据恢复。shred 用随机数据多次覆盖文件数据,确保无法恢复。这使得删除更加永久,但同时从行为检测的角度来看也更加嘈杂,因为shred 在大多数 Linux 系统中并不常用。
process where event.type == "start" and event.action == "exec" and
process.name == "shred" and (
// Any short-flag cluster containing at least one of u/z,
// and containing no extra "-" after the first one
process.args regex~ "-[^-]*[uz][^-]*" or
process.args in ("--remove", "--zero")
) and
not process.parent.name == "logrotate"
上述 regex 可确保通过组合或修改标志来逃避检测的尝试变得更加困难。下面是奇点公司查找与部署有关的文件并将其粉碎的示例:
这些文件和日志删除技术可通过几种开箱即用的检测规则检测到:
rootkit 完成清除其痕迹后,可能会对其更改过的文件进行时间压缩,以确保不留下文件修改痕迹:
process where event.type == "start" and event.action == "exec" and
process.name == "touch" and
process.args like (
"-t*", "-d*", "-a*", "-m*", "-r*", "--date=*", "--reference=*", "--time=*"
)
这里有一个例子,威胁行为者使用/etc/ld.so.conf 文件的时间戳作为/dev/shm 驱动器上文件的参考时间,试图混入其中:
我们通过检测规则和保护工件增加了这种技术的覆盖范围:
尽管还有更多技术我们没有在本研究中讨论,但我们相信,本研究将有助于加深对 Linux rootkit 及其检测工程的理解。
Rootkit 防范技术
防止 Linux rootkit 需要采取分层防御策略,将内核和用户态域加固、严格访问控制和持续监控结合起来。强制性访问控制框架(如 SELinux 和 AppArmor)限制了进程行为和用户域持久性机会。同时,包括锁定模式、KASLR、SMEP/SMAP 和 LKRG 等工具在内的内核加固技术可以降低内核级入侵的风险。通过禁用动态加载或强制模块签名来限制内核模块的使用,可进一步减少部署 rootkit 的常见媒介。
通过对系统调用和文件活动进行 Auditd 和文件完整性监控,以及通过可识别和防止可疑运行时行为的 EDR 解决方案,提高了对恶意行为的可见性。通过seccomp-bpf 、Linux 功能和 landlock LSM 将进程权限降至最低,从而限制系统调用访问和文件系统交互,进一步加强了安全性。
及时更新内核和软件,并在必要时提供实时补丁支持,可在已知漏洞被利用之前将其关闭。此外,文件系统和设备配置应通过使用限制性标志重新挂载敏感文件系统和禁止访问内核内存接口(如/dev/mem 和/proc/kallsyms )来加固。
没有任何一种控制能完全防止 rootkit。结合配置加固、静态和动态检测以及取证准备的分层防御仍然至关重要。
结论
在本系列的第一部分,我们研究了 Linux rootkit 如何在内部运行,探讨了它们的演变、分类以及操纵用户空间和内核空间的技术。在第二部分中,我们将这些知识转化为实用的检测策略,重点关注暴露 rootkit 活动的行为信号和运行时遥测。
Windows 恶意软件仍然是商业安全供应商和威胁研究团体关注的焦点,而 Linux 尽管为全球大多数云基础设施、高性能计算环境和互联网服务提供动力,但其研究相对不足。
我们的分析结果表明,Linux rootkit 正在不断演变。随着 eBPF、io_uring 和容器化 Linux 工作负载等技术被越来越多地采用,引入了新的攻击面,而这些攻击面尚未得到很好的理解或广泛的保护。
我们鼓励安全界:
- 从静态和动态两个角度对以 Linux 为重点的检测工程进行投资。
- 公开分享研究成果、概念验证和检测策略,以加速维护者之间的知识共享。
- 与供应商、学术界和产业界合作,推动 Linux rootkit 防御达到与 Windows 相同的成熟度。
只有通过共同提高可见性、检测和响应能力,防御者才能在这种隐蔽且快速演变的威胁环境中保持领先。
