Setup

Overview

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

Kernel-Overflow module

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.

Leak

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;
}

Overflow

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;
}

Last updated