I’ve recently been interested in Linux rootkits, and it turns out that behind that sexy name lay simple C programs. Being curious, I started looking for publicly available rootkits and tried to compile them to see how they work: Too bad ! Most of the rootkits I came across were written for Linux versions 2.x and 3.x, and I am running version 4.8 … That meant that the code and data structures changed heavily since then, and there was little to no documentation on the subject !! So I digged through the sources of the Linux kernel and tried to figure out how things worked.

In this article I’ll explain how you can write a rootkit that hides processes for, you know, fun and profit !

Before we start, I suppose that you’re familiar with writing LKM modules. If not, I highly recommend that you read these excellent articles here and here. I also suppose that you know what a Virtual FileSystem is. If it’s all good, we can start our dive into the depths of the Linux kernel *insert evil laugh*

Ever heard of procfs ? Well, to keep it simple, it is a special filesystem. This means that this filesystem is not associated with any block device and lays exclusively in memory. As its name suggests, this filesystem exports information about processes to the user-land so it can be used by programs such as ps and top. You don’t believe me ? Here, take a look :

strace tells us that ps opens /proc to list its content with the getdents(3) syscall. Once that is done, process-specific information is retrieved using the read(3) syscall on /proc/$PID/stat. Keep that in mind, we’ll get back to it soon.

Entries in procfs are represented by a memory structure called proc_dir_entry, and we can find its definition in fs/proc/internal.h in the Linux kernel source tree :

There’s one interesting field here: struct file_operations *proc_fops. This structure holds pointers to functions that are called when specific operations are to be executed on a proc_dir_entry structure, like listing the contents of /proc. The source file fs/proc/generic.c defines the default behavior of the kernel when it comes to procfs entries :

So far so good. Remember the getdents function that we mentioned earlier ? If we take a look at its definition in fs/readdir.c, we see that it calls the iterate_dir function, which in turn calls the iterate_shared function at some point :

The prototype of the iterate_shared function is as following :

The function takes as a second parameter a pointer to a struct dir_context, which is defined in include/linux/fs.h :

The filldir_t type is nothing but a typedef of a function pointer :

This is the function that actually fetches, formats and prints stuff to the user-land. At first, it seems a little weird having the output function passed as a parameter to iterate_shared, but it makes sense when you think that we may need to have different outputs given the context (which is why the structure is called dir_context, I guess).

Let’s review all the things we dicussed till now (no more Linux source code excerpts from here on, promise) :

  1. A program, let’s say ls, issues the getdents syscall when listing /proc
  2. The getdents syscall function calls the iterate_shared of the VFS
  3. iterate_shared calls a filldir_t function contained in the dir_context passed in its parameters.
  4. The filldir_t function does its magic, and send the output to the user-land.

Our main goal is to hijack the iterate_shared function in order not to show our evil process. Since the filldir_t function is the one responsible for outputting stuff to the user-land, we have to define our own evil function (which will return 0 if the second parameter is the string containing the pid we want to hide), put it in a struct dir_context, and feed it to the original iterated_shared function. All of this will be done by our evil iterated_shared function.

The illustration below summarizes what our rootkit will be doing :

Of course, we need to backup the data structures we are going to hijack in order to restore the initial state of the system when we unload our rootkit :

  • When loading the rootkit :
    1. Fetch the proc_dir_entry we want to hijack
    2. Make a copy of the associated file_operations (backup)
    3. Make a second copy (evil file_ops)
    4. Overwrite the iterate_shared function in evil file_ops
    5. Overwrite the proc_dir_entry’s file_opertations structure with evil file_ops
  • When unloading the rootkit :
    1. Fetch the proc_dir_entry we want to restore
    2. Replace its file_operations with the backup

Easy, isn’t it ? Let’s start with the part where we backup and hijack stuff :

Now to the fun part :

So our rk_iterate_shared function simply backs up the original context, replaces it with our customized context rk_ctx, and calls the original iterate_shared with the new context.

The rk_iterate_shared function simply compares its second parameter with the value stored in the proc_to_hide variable. If they match (in this case: if it’s pid 1337), the function returns 0, and if not, the original filldir_t function is called so that other processes can still be visible to the user.

This is it. It’s all you need to hide processes from kernel-land. Of course, this is a dummy rootkit, so you have to adapt it to fit your own needs 😉 You can find the complete source code in my github repo.