Setup
The kernel module used for these exercises is based off
hxpCTF 2020
kernel-rop
. It didn't come with source, so I rewrote it and have it uploaded here. You can build the kernel yourself or use my prebuilt one here.You will need
qemu
,gcc
, and gdb
to follow along with these problems.The
launch.sh
script will rebuild the file system and launch qemu#!/bin/bash
# build root fs
pushd fs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
popd
# launch
/usr/bin/qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,+smap \
-no-reboot \
-kernel linux-5.4/arch/x86/boot/bzImage \
-initrd $PWD/initramfs.cpio.gz \
-fsdev local,security_model=passthrough,id=fsdev0,path=$HOME \
-device virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=hostshare \
-nographic \
-monitor none \
-s \
-append "console=ttyS0 nokaslr nopti nosmep nosmap panic=1"
The
-s
flag will start qemu with gdb debugging enabled on localhost port 1234
. Most gdb extensions like gef
and pwndbg
have trouble debugging kernels and you can disable them with the command: gdb -nx ./bzImage
The module we will be exploiting for most of this series is named
kernel-overflow
and is located here [TODO github link]. The module creates a character device named kernel-overflow
which is accessible at /dev/kernel-overflow
. It supports read and write operations, which ultimately lead to a leak and overflow.The leak happens when
read
is called on the character device with a length greater than 256
bytes. Our stack buffer tmp
is only 256
bytes long and the length check below it is not sufficient to prevent an overread from happening. As long as our read is less than 0x1000
bytes, we can read past tmp
and leak out the stack cookie
and stack saved registers
static ssize_t device_read(struct file *filp, char *buf, size_t len, loff_t *offset)
{
int tmp[32] = {0};
tmp[0] = 0xDEADBEEF;
tmp[31] = 0xCAFEBABE;
memcpy(hackme_buf, tmp, len);
if ( len > 0x1000 )
{
printk("Buffer overflow detected (%d < %lu)!\n", 4096LL, len);
BUG();
}
if ( copy_to_user(buf, hackme_buf, len) ) return -14LL;
return len;
}
The overflow happens when
write
is called on the character device with a length greater than 256
bytes. Our stack buffer tmp
is only 256
bytes long and just like the leak, the length check is not sufficient to prevent a stack overflow.static ssize_t device_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
int tmp[32] = {0};
tmp[0] = 0xDEADBEEF;
tmp[31] = 0xCAFEBABE;
if ( len > 0x1000 )
{
printk("Buffer overflow detected (%d < %lu)!\n", 4096LL, len);
BUG();
}
check_object_size(hackme_buf, len, 0LL);
if ( copy_from_user(hackme_buf, buf, len) ) return -14LL;
memcpy(tmp, hackme_buf, len);
// my gcc is optimizing out the memcpy
// having tmp used after the copy ensures
// that it stays in
printk(KERN_ALERT "After %s",tmp);
return len;
}