Exploiting Arbitrary Read/Write Linux Kernel

Continuing with my research into Linux kernel exploit dev, I decided to try an exploit that doesn’t involve gaining code execution. The following is a short demonstration of escalating a processes privileges due to an arbitrary read/write vulnerability in the kernel.

This is very similar to the CSAW 2015 ctf challenge. We can grow or shrink the buffer, lets assume we initialised the buffer with a size of 1, and then shrink it by 2, then new_size will equal 0xffffffff. When krealloc is called, new_size+1 = 0, krealloc(buffer, 0) = ZERO_SIZE_PTR, which is defined as (void *)0x10. This means the if statement after krealloc is redundant. After we have shrunk the buffer, g_mem_buffer->data = 0x10, and g_mem_buffer->data_size = MAX_INT.

Now, if we look at what happens when we try and read from the buffer, we can see that we are able to read from any location in kernel space:

The if statement above is unable to stop us from reading arbitrary memory, lets say we have set pos to 0xffffff8800000000, and we want to read PAGE_SIZE bytes, count+pos will be smaller than data_size. The same logic applies to the write method.

The Exploit – A Data Only Attack

Exploiting this vulnerability is actually relatively simple. We don’t need to worry about SMEP, SMAP, KASLR or any other such mitigations. Similar to how many Windows kernel exploits involve stealing a SYSTEM processes token by traversing the doubly linked list of processes, here we can simply scan kernel space memory for our specific task struct, within this structure is a pointer to our processes privileges (in a cred struct), grab this pointer, and write null bytes into each uid field (giving us root).

Luckily for us, if we look at the task struct it contains a comm field, this buffer contains the name of the executable:

We can set this field by calling prctl(PR_SET_NAME, some_string). So step one involves generating a random string and setting comm via prctl. Once we find this string, we can then get a pointer to cred and real_cred.

At this point, if everything has worked as it should, we will have valid pointers to our current process’s creds. The cred structure is defined as such:

One thing to note here is that we only want to overwrite the uid and gid fields. So we have to skip over the usage field (which is sizeof(int) bytes), and then leverage our arbitrary write vulnerability to write null bytes into each field: