kernel UAF CISCN2017 - babydriver

本文分析了一个Linux内核驱动babydriver,揭示了一个未初始化的使用(UAF)漏洞。通过查看驱动文件信息,发现无PIE,无canary保护,存在0x10001命令用于内存操作。分析主要函数,如babyioctl、babyopen、babyread、babywrite、babyrelease等,指出在特定条件下可触发UAF。提权思路涉及打开两次设备,调整内存大小,释放内存,通过fork创建新进程,利用UAF修改cred结构体实现权限提升。
摘要由CSDN通过智能技术生成

分析

先解压rootfs.cpio 看一下有什么文件:

mira@ubuntu:~/test/kernel/CISCN2017-babydriver$ ls
babydriver.ko  babydriver.tar  core
mira@ubuntu:~/test/kernel/CISCN2017-babydriver$ tar -xf babydriver.tar 
mira@ubuntu:~/test/kernel/CISCN2017-babydriver$ ls
babydriver.ko  babydriver.tar  boot.sh  bzImage  core  rootfs.cpio
mira@ubuntu:~/test/kernel/CISCN2017-babydriver$ mv ./rootfs.cpio ./core/rootfs.cpio.gz
mira@ubuntu:~/test/kernel/CISCN2017-babydriver$ cd core/
mira@ubuntu:~/test/kernel/CISCN2017-babydriver/core$ ls
rootfs.cpio.gz
mira@ubuntu:~/test/kernel/CISCN2017-babydriver/core$ gunzip rootfs.cpio.gz 
mira@ubuntu:~/test/kernel/CISCN2017-babydriver/core$ ls
rootfs.cpio
mira@ubuntu:~/test/kernel/CISCN2017-babydriver/core$ cpio -idmv < rootfs.cpio 
.
etc
etc/init.d
etc/passwd
etc/group
......
usr/sbin/ether-wake
tmp
linuxrc
home
home/ctf
5556 blocks

mira@ubuntu:~/test/kernel/CISCN2017-babydriver/core$ cat init 
#!/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

根据 init 的内容, 12行加载了 babydriver.ko 这个驱动,根据pwn的一般套路,这个就是漏洞的LKM 了。init的其他命令都是linux常用的命令。

查看驱动文件的信息

在这里插入图片描述
没有开 PIE,无 canary 保护,没有去除符号表
用IDA 打开分析,查看结构体:
在这里插入图片描述

查看主要函数:

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上,

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

babydriver_init() 和 babydriver_exit() 两个函数分别完成了 /dev/babydev 设备的初始化和清理。

思路

没有用户态传统的溢出漏洞等,但存在一个伪条件竞争引发的UAF漏洞。
也就是说如果我们同时打开两个设备,第二次会覆盖第一次分配的空间,因为 babydev_struct 是全局的。同样,如果释放第一个,那么第二个其实是被释放过得,这样就造成了一个 UAF。
那么有了 UAF 要怎么用呢: 之前提到了 cred 结构体,可以修改 cred 来提权到root。
4,472 的 cred 结构体定义如下:

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

那么根据UAF的思想,思路如下:

  1. 打开两次设备,通过ioctl更改其大小为 cred 结构体大小
  2. 释放其中一个,fork 一个新进程,那么这个新进程的 cred 的空间就会和之前释放的空间重叠。
  3. 同时,我们可以通过另一个文件描述符对这块空间写,只需要将uid,gid改为0,即可实现提权到root
    需要确定 cred 结构体的大小,有了源码,大小就很好确定了。计算一下是0xa8(注意使用相同内核版本的源码)

重新打包 initramfs.cpio

find . | cpio -o --format=newc > initramfs.cpio
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值