Kernel stack cookies
We are using resources from here The kernel module we are exploiting is here The final exploit is here
Overview
Stack cookies exist in the kernel stack just like standard userspace programs. When attempting to clobber the stack through a stack overflow, the kernel will panic and report something similar to the image below:
The method of exploitation is very similar from userspace to kernel space and we will be using the same ideas behind leaking a stack cookie and then replacing it on the stack
We are going to be building a ret2usr
exploit using the kernel-overflow
kernel module. This exploit will require disabling a couple mitigations: smep
smap
kpti
and kaslr
. The launch_stack_cookie.sh
included with these challenges will launch the kernel without these mitigations.
The plan of attack here is as follows: * Leak the stack cookie * Get key kernel addresses * Save some user-mode registers * Build some shellcode * Overwrite the program counter to our shellcode
Leak
When we open and read from the character device, the following kernel code is executed:
Our stack has a variable tmp
on it that should be 32*sizeof(int)
(128) bytes large. The next value on the stack will be our stack cookie, and then some caller saved registers as shown below:
The check on line 9 is insufficient to prevent and overread and consequently passing in a length greater than 128 bytes will leak stack data. We can trigger this leak by opening the character device and reading in more than 128 bytes. The code below will read in 256 bytes and print each as 8 byte reads.
Kernel Addresses
The next step is to get a couple key addresses for our kernel exploit. When we can control execution of the kernel we are interested in calling commit_creds(prepare_kernel_cred(NULL));
which will create a new credential struct with root credentials and then assign our process that new set of credentials!
The classic process of doing this is by reading /proc/kallsyms
. We can open and read this file looking for entries for both those addresses and add them into our exploit. The code below will read kallsyms
looking for commit_creds
and prepare_kernel_cred
and then store the values found into two global variables.
Saving program state
To move from kernel mode execution to user space execution either a sysretq
or iretq
instruction needs to be executed. iretq
is the easier method and requires that the stack has 5 registers available on it for : RIP CS RFLAGS SP SS
. When executing programs there are two sets of these registers in use, one set is used for the kernel and the other is for the usermode program, we need to save off these values so that we can restore them when the kernel executes our shellcode later.
Right from Midas's blog you can use the function below to save off those registers into global variables:
Shellcode
Using the previously gathered prepare_kernel_cred and commit_creds functions addresses, we want to call both of these and then return to userspace. On the return to userspace we want to check our uid and gid to ensure that we are root and then execve /bin/sh.
We can assemble the beginning of our shellcode using gcc's inline asm command. You can reference global variables like our previously saved prepare_kernel_cred value here inline too.
The above code will execute commit_creds(prepare_kernel_cred(NULL));
but won't return us to userspace yet. A normal "ret" instruction here will just panic the kernel since it's expecting to execute another kernel function, not a userspace function.
We need to call the swapgs instruction which will change the GS base register back to it's userspace value, enabling us to execute userspace code. The next step is using either that iretq or sysreq method to return out of kernel space. The full set of shellcode is below:
The rip value set at the end of our shellcode needs to be the next instruction that we want the program to execute. We can set this to a function we want to call with our new set of root credentials.
PC Overwrite
The overflow is present in the device_write function of the kernel module:
Line 16 in device_write()
in the kernel module will overflow that tmp
stack variable which overwrites the stack cookie and saved rbx
and rip
registers.
We can trigger this overflow by opening the character device and writing a buffer larger that 128 bytes. If we place the stack cookie at the end of the 128 bytes, we should be able to reliably overwrite the saved registers. We can trigger a crash with RIP pointing at a location we control with the code below:
At this point, we can exchange the 0x4444444444444444 for our give_me_root function . The flow of our final exploit is as follows:
We can verify our exploit using the existing ctf
user. By changing to the ctf user through su
we can run the exploit and see our ret2usr exploit worked!
Last updated