Hxp2020
Congratulations to Kaztebin, ranked 1 in DEFCON CTF29 again.
It reminds me of my first ctf competition with Katzebin: hxp2020 [1]. There are some excellent challenges in this game which I missed out at that time, including some linux kernel exploitations. Recently I started to learn kernel pwn, and I think it’s time to solve these left challenges.
kernel-rop | 6 solved | 667 points
The kernel insmod a bugged module “hackme”, which has open/read/write functions.
In read function, It reads from kernel stack starting from tmp (rbp - 0x20h) with a size no more than 0x1000, and copy to user. However tmp is just 0x20 bytes long, which can cause some memory leaking on kernel stack.
|
|
Same in write function, we can copy really long buffer to the kernel stack, causing stack overflow in kernel space.
|
|
Sounds like quite easy and straightforward, we can leak address to get the kernel image base, compute commit_creds and prepare_kernel_cred function address and using ROP to get root, just like the challenge name “kernel ROP”. Well, no.
If you consider this challenge as a normal KALSR and smep bypass, you will fail for sure.
Firstly of course, we edit the init file to debug the kernel and print out address of commit_creds like below.
|
|
And when we exit and do this again, wierd thing happens.
|
|
The addresses does change, but we know that KASLR adds a random offset aligning to page size which means at least low bits should not change. What’s going on?
After several frustrating hours, I found the reason. In Jun 2020, the patch [2] added one of the most annoying mitigation against kernel exploits: Function Granular Kernel Address Space Layout Randomization (fgkaslr). It rearranges kernel code at load time on a per-function level granularity, which means every function address can be different when loaded.
Seems like this challenge is unexploitable. But suddenly, I found that not all the functions are rearranged, like __ksymtab_commit_creds just printed out above! So are there any relation between __ksymtab_commit_creds and commit_creds?
I found out that every kernel symbol has a structure below, in which value_offset actually stored offset between address of the symbol and specific function. So if we get value_offset of __ksymtab_commit_creds, we can add to it and get the real address of commit_creds!
|
|
Now the first things is to find out exactly which functions are affected by fgkaslr. Redirecting the output into files, I stored all the address of kallsym twice and got two txts.
|
|
Then I searched for kernel text base and wrote a simple script to list all the functions unaffected by fgkaslr
|
|
It turned out that lots of functions are not rearranged, at least all gadgets we need.
|
|
Allright, now we can do ROPs after leaking kernel image base address and canary!
For example, we use gadgets to move value_offset to rax in get_addr(). In leak1(), we get the content of rax and compute address of prepare_kernel_cred.
|
|
The rest is the same, here is the full exploits.
|
|
But this does not end yet. When I was looking for sovles by other players, I found a really elegant method [3]. This technique is trigger call_modprobe() to execute the file which modprobe_path points to by trying to execve() an unrecognisable format file. And by overwriting modprobe_path which is also unaffected by fgkaslr, we can let our script be executed, which causes an arbitrary code execution with root privileges.
|
|
Here is the exploit which works for me.
|
|
Awesome!
|
|
pfoten | 18 solves | 370 points
A challenge containing no bug module or driver.
Note:
The kernel is a standard Linux kernel, we didn’t add any vulnerabilities.
Sounds like problems can only be found in init file
|
|
I didn’t quite familiar with linux bash grammar. After looking for some information, I finally found out what he did.
-
set mask of file mode creation[4] as 111, which means all users can open and read the following files created.
-
add a 10MB size file /swap, filled with zero
-
correlate /dev/loop0 with /swap using losetup[5]
-
construct a swap space in loop0 using kaswap[6] and enable by swapon
But to fully understand the meanings, we should go deep into linux swap files[7]. Shortly, swap files is a cache for physical RAM. When RAM memory is insufficient, kernel will copy some memory to swap files on the hard disk temporarily in order to leave more memory in RAM.
So you might find the bugs here too. Swap files should never be directly writed by users because when the swap operation happens between RAM and swap files, deliberately constructed data writed in swap files will be directly copy to RAM memory!
Let’s see what will happen if we keep mmaping to occupy virtual memory.
|
|
|
|
After a few iterations, swap file will be filled with other things, which is actually some infrequently used memory staff.
|
|
You can see that even the memory of this ELF executable file was dumped into /swap.
According to Mr.2019[8], we can garble the init or exit process of busybox to shellocode, cuz they are running on root privelege. After compiling busybox with symbols, we can find specific sequences which indicate where the functions are and whether the memory is swaped into /swap.
I tried but it didn’t work for me, finding the sequences is a probablistic incident (actually quite rare). And even when I found the sequences after tons of failure, shellcode I write was not triggered at all.
I discussed with my fellow teammate pu1p[9]. He found that edit syscall
function can gain much higher probability to trigger shellcode we write. It turned out the same on my machine.
What’s more, we use exit machine code as a needle to indicate where the exit is and whether it’s swaped.
|
|
Real shellcode is to open sda/fd0
, read and write
|
|
Final exploits
|
|
Excellent! After only two attempts, we get flag!
|
|
It’s really fantastic, with only a slight umask bug we can gain fully control of the system. But as there is something probablistic and unpredictable when Linux using swap files, it takes tons of attempts trying to figure out what’s the best PAGE_SIZE set in exploits to let Linux swap targets into /swap
file more quickly and stably, which is largely depends on the very machine we are attacking.
Download source code of busybox at [10] and compile with symbols by yourself.
Ref
[1] https://2020.ctf.link/internal/
[2] https://lwn.net/Articles/824307/
[4] https://man7.org/linux/man-pages/man2/umask.2.html
[5] https://man7.org/linux/man-pages/man8/losetup.8.html
[6] https://man7.org/linux/man-pages/man8/mkswap.8.html
[7] https://wiki.archlinux.org/title/swap
[8] https://mem2019.github.io/jekyll/update/2020/12/21/hxp2020-pfoten.html