[Linux Kernel Exploitation 0x1] Smashing Stack Overflows in the Kernel


Previous Post in Series:

  1. [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!

 

gif 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 oops!

 

Getting Setup

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:

  1. Having a driver script--that invokes the kernel functionality from userspace.
  2. 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.
  3. 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:

(gdb) c
Continuing.

Thread 1 hit Breakpoint 2, stacksmash_dev_write (filep=0xffff88806bc30640,
    buffer=0x9 <fixed_percpu_data+9> <error: Cannot access memory at address 0x9>,
    len=140726537215640, offset=0xffffc9000049feb8)
    at drivers/stacksmash//stacksmash_driver.c:96

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. 
 
*Also please note we're going to swap this driver out for another one that is again slightly modified to make exploitation a bit easier, here I made the mistake of not actually involving any stack variables! So please take this as a quick lesson in GDB foo and debugging kernel drivers but if you'd like to follow on please switch to targeting this driver [https://gitlab.com/k3170makan/linux-kernel-exploit-development/-/blob/master/stacksmash_driver.c]. I hope this doesn't make it too hard to follow, I was also too lazy to re-do the screenshots hehe they came out so neat!

Kernel Stack Memory Protection

Lets look at the stuff in the kernel making modern stack exploitation so difficult ---I believe most of these are accessible via .config by appending CONFIG_ to the name, and sing the ./script/config script
  • 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. 

We're not going to be turning any of these off just yet, its important to know what the difficulties of exploitation are like in this state and then slowly remove protections to show how and why they work. Now that we know what a modern kernel stack looks like and what we need to dance around lets see if we can overwrite some structures in memory.

Destroying Kernel Stack

 

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:

*having done this a couple times now, i recommend actually only taking the breakpoint just after memcpy returns. This is more efficient if you know how to search through memory for the payload and other goodies.

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:

We're on the right track! A little worrying here, the kernel is saying stuff about "Kernel stack is corrupted", which means we are overwriting the kernel stack canary value--I mentioned in the section on memory protections. There are two approaches we can take here, 1) get rid of the canary:  We can cheat and contrive an example with no such memory protections and explore how easy it is to exploit or 2) leak the canary: We do another sort of cheating and add a flaw to our driver that leaks the canary value. We're gonna do both! 

Lets make sure we know where the stack canary is though, I've shown a couple examples here---these are taken from the break point just after the memcpy is hit but I believe any breakpoint in the function should work:
 

 





And to boot, we can make 100% sure that we are actually looking at a stack canary value by looking for the following markers:
  • 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

The first thing I'd like to experiment with is no stack protector options for the kernel at all, we can turn them off my issuing the following commands and re-making the kernel:
 



You'll notice that some options don't get turned off, just ignore them for now; there is a way to force them off but it involves scratching around with the Kconfig default values which can turn into a mess really quickly so I'm not going to advise that. Btw this build will take a while as well, obviously because it affects literally everything that runs in the kernel and requires recompiling everything!

Here's the behavior of the stack overflow when I write 17 bytes without CONFIG_STACKPROTECTOR and CONFIG_STACKPROTECTOR_STRONG:
 


See no weird 64bit values that look all strange and random, also when we check the function exit prologues we don't see any compares or checks against the canary. 
 
The next thing we need to do is try to corrupt some return pointers but that requires I actually know a little more about what I'm doing here so I'm gonna need a little break to git guud. Please watch this space for the next post. 
 
Thanks for reading!
 

Reading and References

  1. https://census-labs.com/media/bheu-2011-slides.pdf
  2. https://papers.put.as/papers/macosx/2011/PROTECTING-THE-CORE-KERNEL-EXPLOITATION-MITIGATIONS-bheu-2011-wp.pdf 
  3. https://www.kernel.org/doc/html/latest/security/self-protection.html 
  4. https://www.kernel.org/doc/Documentation/security/self-protection.txt 
  5. https://yoursunny.com/t/2018/one-kernel-module/ 
  6. https://cs.stackexchange.com/questions/45159/can-someone-explain-this-diagram-about-slab-allocation
  7. https://blog.infosectcbr.com.au/2020/02/linux-kernel-stack-smashing.html
  8. https://lwn.net/Articles/692208/  
  9. https://lwn.net/Articles/691631/
  10. https://lwn.net/Articles/725832/ 
  11. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=320b2b8de12698082609ebbc1a17165727f4c893 
  12. https://blog.qualys.com/vulnerabilities-research/2017/06/19/the-stack-clash 
  13. https://googleprojectzero.blogspot.com/2016/06/exploiting-recursion-in-linux-kernel_20.html 
  14. https://blog.aquasec.com/bugs-gone-wild-container-stack-clash-and-cve-2017-1000253 

Comments