【Linux内核漏洞利用】CISCN2017-babydriver_UAF


一、代码分析:

    //babyioctl:定义了 0x10001 的命令,可以释放全局变量 babydev_struct 中的 device_buf,再根据用户传递的 size 重新申请一块内存,并设置 device_buf_len。
    void __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
    {
      size_t v3; // rdx
      size_t v4; // rbx
      __int64 v5; // rdx
    
      _fentry__(filp, *(_QWORD *)&command);
      v4 = v3;
      if ( command == 0x10001 )
      {
        kfree(babydev_struct.device_buf);
        babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
        babydev_struct.device_buf_len = v4;
        printk("alloc done\n", 0x24000C0LL, v5);
      }
      else
      {
        printk("\x013defalut:arg is %ld\n", v3, v3);
      }
    }
    
    //babyopen: 申请一块空间,大小为 0x40 字节,地址存储在全局变量 babydev_struct.device_buf 上,并更新 babydev_struct.device_buf_len
    int __fastcall babyopen(inode *inode, file *filp)
    {
      __int64 v2; // rdx
    
      _fentry__(inode, filp);
      babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 0x40LL);
      babydev_struct.device_buf_len = 64LL;
      printk("device open\n", 0x24000C0LL, v2);
      return 0;
    }
    
    //babyread: 先检查长度是否小于 babydev_struct.device_buf_len,然后把 babydev_struct.device_buf 中的数据拷贝到 buffer 中,buffer 和长度都是用户传递的参数
    void __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
    {
      size_t v4; // rdx
    
      _fentry__(filp, buffer);
      if ( babydev_struct.device_buf )
      {
        if ( babydev_struct.device_buf_len > v4 )
          copy_to_user(buffer, babydev_struct.device_buf, v4);
      }
    }
    
    //babywrite: 类似 babyread,不同的是从 buffer 拷贝到全局变量中
    void __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
    {
      size_t v4; // rdx
    
      _fentry__(filp, buffer);
      if ( babydev_struct.device_buf )
      {
        if ( babydev_struct.device_buf_len > v4 )
          copy_from_user(babydev_struct.device_buf, buffer, v4);
      }
    }
    
    //babyrelease: 释放空间,没什么好说的
    int __fastcall babyrelease(inode *inode, file *filp)
    {
      __int64 v2; // rdx
    
      _fentry__(inode, filp);
      kfree(babydev_struct.device_buf);
      printk("device release\n", filp, v2);
      return 0;
    }

二、漏洞分析及利用

漏洞:由于babydev_struct是全局变量,若同时打开两个设备,释放第一个,第二设备的babydev_struct仍指向第一个设备的空间,造成UAF。

利用:之前提到了 cred 结构体,可以修改 cred 来提权到 root。

  1. 打开两次设备,通过 ioctl 更改其大小为 cred 结构体的大小。
  2. 释放其中一个,fork 一个新进程,那么这个新进程的 cred 的空间就会和之前释放的空间重叠。
  3. 同时,我们可以通过另一个文件描述符对这块空间写,只需要将 uid,gid 改为 0,即可以实现提权到 root。
    //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 */
    };

需要确定 cred 结构体的大小,有了源码,大小就很好确定了。计算一下是 0xa8(注意使用相同内核版本的源码)。其实是很难计算出来的,可以写个驱动算出来:

    //简单modules
    #include 
    #include 
    #include 
    #include 
    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);

dmesg输出

[18370.393751] <1> Hello world!
[18370.393753] size of cred : 168
[18610.595999] <1> Bye, cruel world

你可能感兴趣的:(【Linux内核漏洞利用】CISCN2017-babydriver_UAF)