Previous Post in Series:
- [Linux Kernel Exploitation 0x0] Debugging the Kernel with QEMU https://blog.k3170makan.com/2020/11/linux-kernel-exploitation-0x0-debugging.html
Hi folks this blog post is part of a series in which I'm running through some of the basics when it comes to kernel exploit development for Linux. I've started off the series with a walk through of how to setup your kernel for debugging and included a simple debug driver to target. The post here carries on from this point and explores some stack security paradigms in the kernel.
We're gonna add some stuff to that driver to make it do a dangerous memcpy and then look at whether we can manipulate memory structures with our input. I initially intended to cover full exploit to a root shell with this post but that proved a bit more challenging than I anticipated so I'm splitting this up into two posts. This one will cover almost everything right up to actually controlling the instruction pointer in the kernel and cover a good amount of detail on kernel memory protections for the stack and how they work. So if you'd like to learn more about that stay tuned!
showing the correlation between crashes that trigger a kernel fault
(everytime ssh connects). One can see the register data includes our
payload!This was actually a heap overflow not a stack based overflow
We're going to progress through this just like any other stack smashing tutorial. I'm going show you some vulnerable code, then we're going to experiment with some payload lengths until we get a crash and take it from there.
To do that we need to make sure we can actually trigger the vulnerable code and that means:
- Having a driver script--that invokes the kernel functionality from userspace.
- Having a kernel hooked up to a debugger in a relaunch-able vm with debug symbols and all the other configs we need - I explain how to achieve this in the previous post.
- Having enough GDB fu to: (1) set a breakpoint, (2) inspect the stack and register values. If you want to follow along like a champ I suggest pausing here and trying to get that done, learn to do those two things and then move on it.
*In the next few paragraphs I show you how to set a breakpoint in the
kernel and inspect stack memory, because I lost this copy of the driver
and I don't want to make multiple copies of the same code please treat
these as tutorials for demonstration and try to recreate them on the
version of the driver that wasn't lost.
Okay so if that's in place, we can setup our debugger just as we did in the previous post except we want a setup this time that is going to allow us to inspect the stack so we can actually see what our input is doing to memory. To achieve this we need to first hit the write functions, then look for a more precise breakpoint location so we can conveniently just peek at the stack without too much effort. Lets start with a break-point set for the beginning of the device write function---the one that does that dangerous copy_from_user*---, for us that's called stacksmash_dev_write, here's a quick reminder of how to get that going:
root@syskaller: cat /proc/modules [grab base address]
(gdb) add-symbol-file [PATH]stacksmash_driver.ko [base]
(gdb) break stacksmash_dev_write
Breakpoint 2 at 0x10: stacksmash_dev_write. (2 locations)
For those who need the recap "add-symbol-file" imports symbols from a specified object file, this is done so we have more semantic information while debugging. Next we set a simple breakpoint at stacksmash_dev_write.
* in case you haven't read the code or don't know what the module does, its a pretty straightforward ioctl module with a write and read operation. Its based on the driver used in the previous post the only difference is the write does a copy_to_user into a stack buffer without checking the incoming length.
Cool now we need to trigger the function, to do that we need to invoke the stacksmash_app.elf binary from our qemu target. So we ssh in to the instance, build the app elf and launch as follows:
$ cd [kernel_dir]/image/; ssh -v -i ./stretch.id_rsa -p 10021 root@localhost -o "StrictHostKeyChecking no"
root@syskaller: cd /home/
root@syskaller: gcc -o stacksmash_app.elf stacksmash_app.c root@syskaller: insmod stacksmash_driver.ko
root@syskaller: ./stacksmash_app.elf "aaaaaaaa" 10
If all is well you should hit a breakpoint like so:
Thread 1 hit Breakpoint 2, stacksmash_dev_write (filep=0xffff88806bc30640,
buffer=0x9 <fixed_percpu_data+9> <error: Cannot access memory at address 0x9>,
Which means we are in a comfortable position to track down better breakpoints now.
To find a breakpoint we disassemble the stacksmash_dev_write function:
At offsets 0xffffffffa0000030 to 0xffffffffa0000039 we can see the driver prepping the arguments for copy_from_user, leaving us with:
- rdx holding the size
- rsi holding the source (our arg string of "a"'s)
- rdi holding the destination
We choose some new breakpoints and set em just before the copy_from_user call and one just after, we don this so we can peek at the stack and find out how much damage the input did. To peek we are checking the address that rdi points to before and after the copy_from_user call:
Okay we have full view of what we're doing here, this is good. Now before we can start building exploits lets talk about some of the Linux Kernel's security protections for stack memory and then check which one's we have enabled, see how they work and craft an exploit around this.
Kernel Stack Memory Protection
- STACKPROTECTOR: Exploiting a stack overflow requires writing past the end of a buffer into the pointers on the stack. The kernel adds stack canaries to be able to detect when the stack was corrupted. This option controls this but it depends on the config variables HAVE_STACKPROTECTOR, which means you need to make sure that is off if you want this one off. Another important thing to note is that this only tags functions when they "have an 8-byte or larger character array on the stack", which means there may be times a function doesn't get a stack protector in a equivalent stack write operation, or perhaps a stack write is imposed by a compiler optimization?
- STACKPROTECTOR_STRONG: This option allows one to widen the heuristics used to add canaries to functions. If this is enabled canaries will be added to functions if they merely have any local variables in an assignment operation among others.
- INIT_STACK_NONE: Given how easily one can leak info from uninitialized memory in the kernel i.e. a module uses an uninitialized memory pointer during an IOCTL, doesn't clear it or set it but writes it back to the user---through some craftable call chain or invocation. The problem is so common that there's a config option with sub-options for making sure __user marked variables used in kernel functions are initialized to 0. One can also mark heap objects like this. It obviously doesn't work out well for anyone making assumptions about non-null uninitialized values, but it certainly does solve a big problem.
- CONFIG_VMAP_STACK: To help detect stack overflows the kernel community introduced something called a guard page---this whole technique is very similar to something called shadow memory which is used to track memory behavior in static/dynamic analysis engines. This page with is allocated by the kernel after the end of the stack region whenever a process is spun up for execution---the page triggers a seg fault when written to due to its page access rights. After this folks figured out a way to skip over the guard page and make memory regions clash as they grow over each other. The method was first employed in an exploit famously known as Stack Clash developed by the folks at Qualys. To address this I believe VMAP_STACK was developed in order to allow the kernel to map stack addresses to the range of virtual memory addresses used by vmalloc. Because vmalloc ranges are physically non-contiguous it meant wrapping guard pages around stack memory became a lot easier and guard pages became bigger so they are harder to skip over.
Destroying Kernel Stack
- Driver: https://gitlab.com/k3170makan/linux-kernel-exploit-development/-/blob/master/stacksmash_driver.c
- Userspace App: https://gitlab.com/k3170makan/linux-kernel-exploit-development/-/blob/master/stacksmash_app.c
Okay now that we can write to memory lets try to make that write count, we need to change a small detail about our driver, namely now instead of just simply making a huge copy_from_user it actually memcpy's the input string to a stack variable like so:
Another important change to mention is that from here on out I turned off KASAN, which can be done by making sure CONFIG_KASAN is not set when you compile your kernel. The reason is pretty simple, KASAN is annoyingly sensitive with memory and triggers panics long before you can actually see what you're doing.
We now need to load up the driver as before but we set some breakpoints that wrap the memcpy like this:
And if we write 17 bytes to the stack for instance we should see the following happen:
*note the stack dump at the end, clearly we're hitting the right memory here.
Which results in the following kernel panic:
- Usually they are tucked into the stack just before all the other stack frames from previous functions are shown.
- Stack canaries almost always occupy a full register size with random looking bytes---this means comparing that position to different runs of the function.
And lastly, when you overwrite them with even one single byte, the kernel gets panicky talking about stack-protector stuff again:
In the above screenshot we can see two separate runs of the stacksmash_driver, the first one writes 16 bytes the second 17, note the difference in the behavior and stack layout. At offset 0xffffc90000015fea8 we can see the stack cookie overwritten by a single 0x41.
Cool we now are well versed at setting breakpoints, finding the stack canary and disassembling the binary so I will try to keep the screenshots a little simple from here on out while showing as much as is needed to make my point. The next step is to explore our options in terms of memory protection now that we can control our payload well and navigate memory structures to some extent.
No Canaries, No Cares
Reading and References