与用户态下的glibc差不多,都是对已经释放的空间未申请就直接使用,[内核的UAF往往是出现在多线程多进程多文件的情况下。]{.mark}即,假如某个用户程序对用一个内核驱动文件打开了两次,有两个文件描述符,它们都指向了该驱动,又因为是在同一个程序里,所以[当我们释放掉其中一个文件描述符后,还可以使用另一个文件描述符来操控驱动。]{.mark}
为了加深理解,我们就以一题为例
ciscn2017_babydriver
我们用IDA分析一下驱动程序,ioctl函数定义了一个交互命令0x10001,作用是释放之前的堆,申请一个用户指定大小的堆。
在单文件的情况下,没有问题,加入我们在程序里,对该驱动程序,打开了两个文件描述符,先利用第一个文件描述符来与驱动交互,申请一个堆。然后关闭第一个文件描述符。在关闭文件描述符时,对应的close函数会被调用
该函数释放了堆。然而,[我们仍然可以使用第二个文件描述符来对这个堆进行读写操作。,这就造成了UAF。]{.mark}
Linux kernel 使用slab/slub来分配内存,与glibc下的ptmalloc相同点是,如**[果在空闲的堆里存在符合申请的大小的堆]{.mark},则直接把这个堆处理后返回给申请方。为了提权,关键就是修改进程的cred结构,而进程的cred结构也是保存在堆里,进程创建时,就会申请cred结构的空间,来存放cred结构。[如果cred结构申请到我们能控制的空间里,那么我们就能自由修改cred结构,实现提权。]{.mark}**
为了实现这个目的,[我们可以申请一个与cred结构大小相等的堆,然后释放掉]{.mark}。这样,如果我们**[接下来fork一个子进程]{.mark}**,那么子进程申请cred结构的空间时,发现空闲堆里有符合的堆,则拿过来用,而这个堆正是我们UAF能够控制的。**利用UAF,把cred结构里的uid、gid等覆盖为0,即可得到root权限。**至于cred的大小如何确定,有两种方法,第一种是查看对应版本的linux内核源码;第二种则是写一个简易的c语言程序,输出cred的大小。
[本题的linux内核版本为4.4.72,cred结构大小为0xA8。]{.mark}
知道了以上的原理后,我们就可以编写exploit.c程序来提权了。
1 |
|