Rootkits 101 (2). A Simple Rootkit
In the previous article we had go through the very basics of kernel level rootkits. In this second part we are going to walk through a real rootkit to get a better understanding on how they work. We are going to use the excellent rootkit developed by Matias Fontanini. It was created for educational purpose, what makes it one of the best choices to get introduced to this awesome topic.
Go and fetch the code from github:
NOTE: This rootkit only works with Kernels older than 3.8, but it is still a pretty good introduction to the topic. If you are really interesting on this topic, patching this rootkit will be an excellent exercise.easy. Moreover, the fact that it just do not work out of the box, makes it useless for script kiddies
If you are just reading this text, then just skip this section. Otherwise, if you are planning to make changes to the code and try your own stuff, it would be good to keep reading.
A LKM, once loaded, becomes part of the kernel. When developing user space applications, is normal to make mistakes. Those mistakes, in user space, ends up with a Segmentation Fault and a core dump on the disk. Our application crashes and that's it. All this holds for LKM, but, if your program (your LKM) crashes, two things may happen:
- You get a kenel OOPS. That is quite good, and also very useful for debugging your code.
- Your whole system crashes and you have to do a hard-reset (press the power button).
On top of that, if you start playing with kernel structures (as we will do), there are many chances you end up corrupting your file-system. For all those reasons, it is highly recommended that you do your tests within a virtual machine... Sometimes is not possible, but for our topic here is more than OK.
Also it is useful to execute the sync command before loading your module. This will flush your disk buffers and minimise the risk of system corruption.
Finally, if you really want to try this rootkit (I recommend you to do it), you have to chose a quite old distribution (Ubuntu 12.04 for instance) or recompile a kernel older than 3.8. Alternatively, you can patch the rootkit which is, nevertheless a exciting project.
Let's start analysing the code, and for that, we have to find the module's entry point. For a normal C application, the entry point is the main function (usually). For a LKM it is the function specified by the module_init macro.
Scroll down to the end of rootkit.c file, around line 699, and you will find ir. For this module, the init function is named module_init_proc. You will also see the definition of the exit function (for cleaning up at exit, if need be) and another macro setting the module's license (Had you ever seen a module license taints kernel message?...).
The init function is executed when the module is loaded, using either insmod or modprobe. Analogously, the exit function is executed when the module is unloaded from memory, using rmmod or modprobe -r.
The other thing you need to know about a LKM is that you can pass in some parameters. Those parameters are also defined by special macros. Scroll up to line 94 so find the four parameters defined for this module. In this case the macro you have to use is module_param. There are some other macros you can use, but the ones we've just described are the very minimal you need to know.
So, let's move into the module_init_proc. It is around line 655.
The initialisation function is pretty straightforward. It sequentially calls different functions that hooks into different entries in the /proc pseudo filesystem. Do you remember it?.
The first two functions init_hide_rc and init_hide_proc are only invoked if the module is loaded with some parameters, and they are intended to hide the rootkit file and, apparently, some extra file. You need to specify the path and the file name as parameters. Something like this:
# insmod ./rootkit.ko modname="rootkit.ko" mod_path="path_to_your module"
Then, the module creates an entry on the /proc file system (line 663). This is just a convenient way to get a pointer to /proc (it is the father of all /proc entries). You can see how, once /proc's inode is patched, the entry is destroyed with a call to remove_proc_entry (at line 676).
The pointer to the /proc filesystem is stored in the root variable which is passed as parameter to many different functions intended to manipulate different parts of the /proc folder. All of them works in a similar way, so we will just look into a couple of them.
The final function called towards the end is install_handler (line 673). This function is the one that will allow the attacker to send commands to the rootkit to hide/show different information.
Before going into the code of all those functions called when the module is loaded, it will be beneficial to briefly describe how does this rootkit hides information.
Do you remember the inodes we mentioned in the previous part of this series?. Sure, you do. We said that the inodes are structures that represents filesystem entities like files or directories. Each inode contains, among other information, a list of pointers to the so-called file operations (fops), the actual code used to access those files. Whenever you read, write or change the permissions of a file, those functions in the inode structure gets executed.
In other words, an inode for a file, will have: a pointer to the function used when the file is read, a pointer to the function used when data is written into the file, a pointer to the function used when the file permissions are changed... In a similar way, an inode for a directory will have a pointer to the function used when the directory is read/listed, and so on.
Probably you had already realised how does this rootkit work. OK, it gets the inodes for the relevant proc filesystem entries and folders and changes the functions used to read them. The new functions do the normal operation (read data for instance), and then checks the result, discarding the information that the attacker decided to hide.
In this rootkit, you will find two different types of those functions. One group hooks into the readdir file operation. This is the operation used to list the content of a directory. The second group hooks into the read operation on files, used whenever the file's content is accessed.
Let's see how each of these groups of functions are implemented.
Both groups of function have to hook into the inodes for different /proc entries. The way they process the information is what is different, but the actually hooking process is the same, regardless of the file operation being hooked.
Let's look into the function hook_proc (around line 496), for instance. The first thing the function does is to get a pointer to the relevant inode, using the kern_path system call (the path_lookup is for old kernels and you can just skip that part of the code).
You can see that, for this specific function, the rootkit is getting a pointer to the /proc folder. This will allow the rootkit to hide the /proc/PID directory for a specific PID, effectively making that PID invisible to tools like ps... We will see that in a while.
Once the inode pointer is acquired, the pointer to the readdir function is update. Let's see how this is done:
proc_fops = pinode->i_fop;
proc_original = pinode->i_fop;
proc_fops.readdir = do_readdir_proc;
pinode->i_fop = &proc_fops;
First, the rootkit gets the pointer to the file operations (all of them in a single structure). This is the list of functions we mentioned above and that are used to access files and folders (among other things). Second, the original function is stored for later use (in the global variable proc_original in this case). This is needed because the rootkit will have to call the original function to retrieve the real information, and then select what to show and what to hide.
After that, the actual pointer for the readdir operation is substituted by a new function that will do the rootkit thingy, and the modified file operations structure is wrote back to the inode. From this point on, any attempt to list the contents of /proc will call the do_readdir_proc function provided by the rootkit.
Now, you can check one of the functions that hooks into the read operation. For instance, check ini_ttcp_hidehook. You will find out that it works the same, but updating the read operation instead of the readdir operation.
Now, we know how the rootkit gets its own code executed when a file is accessed or a directory is listed. Next step is to find out what does those functions do to hide information. We will start with the directory entries and then we will look in to the file reading case.
For the directory listing case, let's take a close look to the function do_readdir_proc mentioned above. This is the function that deals with the listing of the content in /proc. The function is at line 464.
The readdir file operation, receives 3 parameters. The interesting one is the last one: fdir. This is a pointer to a function that actually fill the data that will be returned to the user space process who tried to read the file. For instance ps, will list the proc directory to get a list of running processes, and then access each directory to get details for each specific process. The readdir file operation, does the first part, takes the names of the files in the directory and for each one, calls this fdir function to get all the details for that file.
So the hooked function for this case looks like this:
proc_filldir = fdir;
ret = proc_original->readdir(fp, buf, fake_proc_fill_dir);
Once again, the do_readdir_proc function, saves the original filling function, and then calls the readdir operation with its own filling function (fake_proc_fill_dir in this case -line 449-).
The fake_proc_file_dir is now standard C code. Nothing really special. The rootkit keeps an array with a list of PIDs to hide (hidden_pids) (we will see later how to get a PID in there). Then, it checks if the provided buffer (parameter that contains the directory entry to get information about) contains one of those PIDs (it is one of the /proc/PID directories). If so, just return 0, otherwise calls the original filldir function (saved in variable proc_filldir) and returns the data normally.
This is how the content of a directory is hidden. Let's see what it does with the content of a file.
Let's look now into init_module_hide_mod (line 538). This function hides the module from the lsmod command. The lsmod command, basically reads and processes /proc/modules (try to cat /proc/modules, and then strace lsmod).
As you may remember, the root variable holds the pointer to the /proc directory. From that starting point, the rootkit looks for the directory entry modules, the file that contains the list of loaded kernel modules. After that, again, the original read file operation is saved (to be used later) and then, the hiding function is set to substitute the read file operation. In this case, the hiding function is do_read_modules (line 202).
This function just calls the original read to actually read the contents of the file (/proc/modules in this case) and searches for the module name inside the read data. If found, it just removes that line, compacting the memory buffer.
Now you can take a look to other functions hooking into the read file operation. Just below do_read_modules you will find the do_read_tcp. It is a bit more complex as it provides more options to hide TCP connections (destination port, source port). Anyway, this is just standard C, there is nothing really special there.
As we said at the beginning, this rootkit is pretty complete and it provides a commanding interface in order to let an attacker define which item wants hidden. The rootkit allows to hide/show PIDs, TCP connections from/to a defined port, show/hide users, show/hide itself and also give root to any running process.
To do this, it makes use, again, of the /proc filesystem. The function install_handler (line 523 and on) is the one that sets up this interface. It uses the /proc/buddyinfo entry. This is a read-only entry, and the rootkit adds a write file operation to it, that always returns an error. This way, tools using buddyinfo will still work normally (read operation is not modified), but when an attacker sends(access the write operation) commands to this entry, the rootkit will react accordingly. In case you are curious, the buddyinfo entry provides information about kernel memory zones...
The hooking on the write file operation works the same that with readdir and read. In this case the function added is orders_handler (line 412) that basically parses the string being written into /proc/buddyinfo and calls the appropriate function to update its internal data structures. There is nothing special about these functions.
You can figure out the commands and parameters accepted by the rootkit by studying this function.
The last function we have to say a word on, is the one that gives root permissions to any running process. This is implemented by the function makeroot (line 172). This function is pretty different to all the other functions we had seen before, but, in a sense, it is simpler.
The function makes use of get_task_struct_by_pid to access to the kernel task associated to the process identified by the PID passed as parameter. The task structure contains all the information related to the process, including its permissions.
So this little function get the task structure for the process passed as parameter and also for the process with PID 1. That is the init process... the Mother Of All Process... and that is literal. The init process is the process that creates any other process in a UNIX system, and therefore it is all-mighty. So the make_root function just copies the init process credentials into the credentials of the target PID, giving to that PID, root access to the system.
In this article we had covered a complete and functional rootkit that abuses the /proc file system to hide different kinds of information. The process is pretty simple and straightforward, just find the inode for the /proc entry you want to hide, hook your hiding function in the file operation you are interested on and filter out information as needed.
Sorry for the long post. I thought it was better to explain a complete rootkit in one go and get the whole picture of the pretty basics.
Hint for those of you that will try to patch the rootkit for recent kernels. From kernel 3.8 (or 3.11 cannot remember), the readdir file operation was removed from the inode file operations structure. That is the reason why the rootkit cannot even be compiled in recent kernels. To go over this you will have to find the readdir function in the kernel (that had not changed, is just in another place) and hook into that... probably in a slightly different way.