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.

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:

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:

As 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:

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

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:

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.

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:

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:

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):

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:

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:

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.

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:

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:

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)