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:
- Use
timerfd_create
to spray the heap withtimerfd_ctx
structs - Use the TOCTOU to get an OOB read from the heap to userspace to leak kaslr
- Use the arb write to overwrite
modprobe_path
Winning the race with userfaultfd
To make TOCTOU easier I used userfaultfd
.
- Mmap two contiguous pages
- Place the struct in the middle of the two pages
- Register userfaultfd on the first one.
- Before
memcpy(params->buf, kbuf, params->buf_len)
is called (whenrdi
is being loaded), a page fault will be triggered atparams->buf
, thus letting us changebuf_len
.
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}