CSAW Quals 2021 - Krypto
[ pwn , kernel ]

Description:
I've always wanted to get into kernel development, and messing around with the new crypto subsystem seems like a great first idea. I even thought of a really catchy name for it!


Krypto is a linux kernel exploitation challenge from CSAW Qualifier 2021.

In this challenge we are given the LKM source code, krypto.c.
The kernel version is 5.4.0 and smap/smep are disabled.

krypto.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <crypto/rng.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nick Gregory");
MODULE_DESCRIPTION("/dev/krypto");
MODULE_VERSION("0.1");

#define KRYPTO_RNG_CMD 0x1337

struct rng_params {
    char *buf;
    size_t buf_len;
};

static int krypto_open(struct inode *inode, struct file *file)
{
    struct crypto_rng *rng = NULL;

    rng = crypto_alloc_rng("ansi_cprng", 0, 0);
    if (IS_ERR(rng)) {
        return -ENOMEM;
    }

    // fun fact! the kernel docs don't include this so the example doesn't actually work!
    char seed[32] = {0};
    crypto_rng_reset(rng, seed, sizeof(seed));

    file->private_data = rng;

    return 0;
}

static int krypto_rng(struct file *file, struct rng_params *params)
{
    char *kbuf = NULL;
    int ret = 0;
    size_t len = params->buf_len;

    if (len == 0 || len > 0x1000) {
        return -EINVAL;
    

    kbuf = kzalloc(len, GFP_KERNEL);
    if (!kbuf) {
        return -ENOMEM;
    }

    ret = crypto_rng_get_bytes(file->private_data, kbuf, len);

    if (ret < 0) {
        goto out_free;
    }

    memcpy(params->buf, kbuf, params->buf_len);

out_free:
    kfree(kbuf);
    return ret;
}

static long krypto_ioctl(struct file *file, unsigned cmd, unsigned long arg)
{
    switch (cmd) {
    case KRYPTO_RNG_CMD:
        return krypto_rng(file, (struct rng_params *)arg);
    default:
        return -EINVAL;
    }
}

static int krypto_release(struct inode *inode, struct file *file)
{
    crypto_free_rng(file->private_data);
    return 0;
}

static struct file_operations krypto_fops = {
    .owner          = THIS_MODULE,
    .open           = krypto_open,
    .release	    = krypto_release,
    .unlocked_ioctl = krypto_ioctl,
};

static struct miscdevice krypto_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "krypto",
    .fops = &krypto_fops,
    .mode = S_IRUGO,
};

static int __init mod_init(void)
{
    return misc_register(&krypto_device);
}

static void __exit mod_exit(void)
{
    misc_deregister(&krypto_device);
}

module_init(mod_init);
module_exit(mod_exit);

Analyzing the LKM

In short the krypto_rng ioctl allows to write at most 0x1000 bytes to an arb location in memory using crypto_rng_get_bytes.

Because krypto_open seeds the random number generator using the same seed (char seed[32] = {0};) we can predict the bytes produced by crypto_rng_get_bytes.

To communicate with the module we use the rng_params struct:

struct rng_params {
    char *buf;       // Where to write the random bytes
    size_t buf_len;  // How many bytes
};

Finding the vuln

Obviously we have a write primitive in the kernel, but where and what we want to write?
Also there is a TOCTOU in krypto_rng, which we can use with userfaultfd to make the race reliable.

Steps:

Winning the race with userfaultfd

To make TOCTOU easier I used userfaultfd.

Use crypto_rng_get_bytes to overwrite modprobe_path

To predict which bytes crypto_rng_get_bytes will produce I just dumped 0x1000 random bytes and then used a python script.

#!/usr/bin/python3
with open("./dump.raw", "rb") as f:
    data = f.read()

find = b"/tmp/a\x00"
found = []
i, j = 0, 0

data = data[256:] # The first 256 bytes are produced during the leak part 
                  # of the exploit, skip them
for b in data:
    if(len(found) == len(find)):
        break
    if b == find[j]:
        found.append((i, chr(b)))
        j+=1
    i+=1

print(found)
$ python3 predict.py
[(87, '/'), (133, 't'), (758, 'm'), (767, 'p'), (823, '/'), (1021, 'a'), (1124, '\x00')]

Final exploit

link to exploit.c

flag: flag{N3ver_trust_the_user...l@nd}