Linux LKM Persistence
在八月份,Ruben Groenewoud[1] 发表了两篇关于 Linux 持久化机制的详细[2]文章[3],随后又发布了一个名为 PANIX[4] 的测试/模拟工具,该工具实现了许多这些持久化机制。Ruben 的工作受到了 Pepe Berba[5] 的系列文章[6]和 Eder Ignacio[7] 的工作[8]的影响。Eder 在二月份发表的关于使用 udev 规则实现持久化的文章似乎特别有先见之明,因为 Stroz/AON 在八月份报告了一个使用 udev 规则进行持久化的长期活动[9]。我强烈推荐所有这些工作,说实话,我把这些链接放在这里,是为了我个人在需要时能够方便地找到它们。
总的来说,所有这些工作都集中在使用持久化机制来运行用户空间程序。例如,PANIX 默认设置一个简单的反向 shell(尽管实际的有效载荷可以自定义),而 Stroz/AON 描述的"sedexp"活动使用 udev 规则来触发自定义恶意软件可执行文件。
阅读所有这些材料让我的邪恶思维开始运转,让我思考如果我在使用 Linux 可加载内核模块[10](LKM)类型的 rootkit 时,我该如何处理持久化问题。当然,我可以使用 PANIX 中任何具有 root 权限(或至少具有 CAP_SYS_MODULE 能力)的用户空间持久化机制来调用 modprobe 或 insmod 来加载我的恶意内核模块。但是其他专门用于在启动时加载内核模块的 Linux 机制呢?
Hiks Gerganov[11] 写了一篇有用的文章,总结了如何在启动时加载 Linux 模块[12]。如果你想走传统路线,你总是可以把想要加载的模块名称放入 /etc/modules。但这似乎太明显了,所以我们将使用更灵活的 systemd-modules-load 服务来安装我们的恶意内核模块。
systemd-modules-load 会在多个目录中查找指定要加载模块的配置文件,包括 /etc/modules-load.d、/usr/lib/modules-load.d 和 /usr/local/lib/modules-load.d。systemd-modules-load 也会查看 /run/modules-load.d,但 /run 通常是一个 tmpfs 类型的文件系统,在重启后不会保持。配置文件名必须以".conf"结尾,并且简单地包含要加载的模块名称,每行一个名称。
在本例中,我将使用 Diamorphine[13] LKM rootkit。Diamorphine 最初是一个概念验证 rootkit,但最近在野外发现了 Diamorphine 的变种[14]。Diamorphine 允许你在编译时选择一个"魔术字符串"——任何以该魔术字符串开头的文件或目录名称,在 rootkit 加载到内核后都会被自动隐藏。在我的示例中,我使用的魔术字符串是"zaq123edcx"。
首先,我们需要将 Diamorphine 内核模块(通常编译为 diamorphine.ko)复制到 /usr/lib/modules 目录下,以便 systemd-modules-load 调用的 modprobe 命令能够找到它:
# cp diamorphine.ko /usr/lib/modules/$(uname -r)/kernel/drivers/block/zaq123edcx-diamorphine.ko
# depmod
需要注意的是,/usr/lib/modules 下的目录是特定于内核版本的。你可以将恶意模块放置在 /usr/lib/modules/*/kernel 下的任意位置。通过在文件名中使用魔术字符串,我们依赖 rootkit 本身来隐藏该模块。当然,如果目标机器进行了内核更新,那么旧内核目录中的 Diamorphine 模块将不再被加载,你的恶意行为可能会暴露。
depmod 步骤是必需的,用于更新 /usr/lib/modules/*/modules.dep 和 /usr/lib/modules/*/modules.dep.bin 文件。在这些文件更新之前,modprobe 将无法定位你的内核模块。不幸的是,depmod 会将恶意模块的路径名写入这两个 modules.dep* 文件中。因此,你可能需要选择一个比我在这里使用的更隐蔽的名称(和魔术字符串)。
剩下的唯一步骤就是为 systemd-modules-load 创建一个配置文件:
# echo zaq123edcx-diamorphine >/usr/lib/modules-load.d/zaq123edcx-evil.conf
配置文件只需要一行内容——就是你复制到 /usr/lib/modules 下的恶意模块的名称,但不包含".ko"扩展名。在这里我们再次使用 Diamorphine 的魔术字符串来命名配置文件,这样一旦 rootkit 被加载,该文件就会被隐藏。
这就是所有需要的配置了。通过运行"modprobe zaq123edcx-diamorphine"手动加载 rootkit,然后你就可以放心了,因为每当系统重启时,rootkit 都会自动加载。
寻找恶意痕迹
这些更改会创建哪些痕迹?/usr/lib/modules-load.d 目录和你安装 rootkit 模块的目录的 mtime 将会被更新。除了将你的恶意模块名称写入 modules.dep文件外,depmod 命令还会更新 /usr/lib/modules/ 下的其他几个文件的 mtime:
/usr/lib/modules/.../modules.alias
/usr/lib/modules/.../modules.alias.bin
/usr/lib/modules/.../modules.builtin.alias.bin
/usr/lib/modules/.../modules.builtin.bin
/usr/lib/modules/.../modules.dep
/usr/lib/modules/.../modules.dep.bin
/usr/lib/modules/.../modules.devname
/usr/lib/modules/.../modules.softdep
/usr/lib/modules/.../modules.symbols
/usr/lib/modules/.../modules.symbols.bin
对这些文件和目录进行时间戳篡改[15]可以增加安全分析人员的取证难度。
但是加载 rootkit 也可能会导致内核被["污染"](https://docs.kernel.org/admin-guide/tainted-kernels.html ""污染"")。你可以通过查看 dmesg 输出中的污染警告来验证:
# dmesg | grep taint
[ 8.390098] diamorphine: loading out-of-tree module taints kernel.
[ 8.390112] diamorphine: module verification failed: signature and/or required key missing - tainting kernel
然而,这些日志消息可能会被攻击者删除,或者由于系统正常的日志轮转而消失(如果机器运行时间足够长)。因此,你还应该检查 /proc/sys/kernel/tainted 文件:
# cat /proc/sys/kernel/tainted
12288
任何非零值都表示内核已被污染。为了解析这个值,这里有一个基于 kernel.org 文档的技巧[16],如上文所述:
# taintval=$(cat /proc/sys/kernel/tainted)
# for i in {0..18}; do [[ $(($taintval>>$i & 1)) -eq 1 ]] && echo $i; done
12
13
参考 kernel.org 文档[17],第 12 位被设置表示加载了"树外"(externally built,外部构建)模块。第 13 位表示该模块未签名。注意这些标志与上面 dmesg 输出中的日志消息相对应。
虽然这是一个有用的命令行技巧,但我认为如果能有一个更便携的格式和更详细的输出会更有用。所以我为大家介绍 chktaint.sh[18]:
$ chktaint.sh
externally-built (“out-of-tree”) module was loaded
unsigned module was loaded
默认情况下,chktaint.sh 会从活动系统的 /proc/sys/kernel/tainted 文件中读取值。但在许多情况下,你可能需要离线分析采集的证据。因此,chktaint.sh 还支持指定一个替代的文件路径("chktaint.sh /path/to/evidence/file")或直接使用从 /proc/sys/kernel/tainted 获取的原始数值("chktaint.sh 12288")。
攻击者部署的持久化机制通常是检测系统是否被入侵的最佳方式。如果攻击者使用了 LKM rootkit,检查 /proc/sys/kernel/tainted 文件通常是确定系统是否存在问题的良好起点。这可以与来自 chkrootkit 项目[19] 的工具结合使用,如 chkproc(用于查找隐藏进程)和 chkdirs(用于查找隐藏目录)。
参考资料
Ruben Groenewoud: https://www.linkedin.com/in/ruben-groenewoud/
[2]详细: https://www.elastic.co/security-labs/primer-on-persistence-mechanisms
[3]文章: https://www.elastic.co/security-labs/sequel-on-persistence-mechanisms
[4]PANIX: https://github.com/Aegrah/PANIX
[5]Pepe Berba: https://www.linkedin.com/in/pberba/
[6]系列文章: https://pberba.github.io/posts/
[7]Eder Ignacio: https://www.linkedin.com/in/eperezign/
[8]工作: https://ch4ik0.github.io/en/posts/leveraging-Linux-udev-for-persistence/
[9]使用 udev 规则进行持久化的长期活动: https://www.aon.com/en/insights/cyber-labs/unveiling-sedexp
[10]可加载内核模块: https://www.wiz.io/blog/linux-rootkits-explained-part-2-loadable-kernel-modules
[11]Hiks Gerganov: https://www.baeldung.com/author/hiksgerganov
[12]如何在启动时加载 Linux 模块: https://www.baeldung.com/linux/kernel-module-load-boot
[13]Diamorphine: https://github.com/m0nad/Diamorphine
[14]在野外发现了 Diamorphine 的变种: https://decoded.avast.io/davidalvarez/new-diamorphine-rootkit-variant-seen-undetected-in-the-wild/
[15]时间戳篡改: https://righteousit.com/2024/09/04/more-on-ext4-timestamps-and-timestomping/
[16]基于 kernel.org 文档的技巧: https://docs.kernel.org/admin-guide/tainted-kernels.html#decoding-tainted-state-at-runtime
[17]kernel.org 文档: https://docs.kernel.org/admin-guide/tainted-kernels.html#table-for-decoding-tainted-state
[18]chktaint.sh: https://raw.githubusercontent.com/halpomeranz/dfis/refs/heads/master/chktaint.sh
[19]chkrootkit 项目: https://chkrootkit.org/
原文始发于微信公众号(securitainment):Linux LKM 内核模块持久化
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论