美文网首页
【kernel exploit】CVE-2016-9793 错误

【kernel exploit】CVE-2016-9793 错误

作者: bsauce | 来源:发表于2021-09-07 18:25 被阅读0次

    影响版本:Linux v4.8.14 以前。v4.8.14已修补,v4.8.13未修补。 7.8分。

    测试版本:Linux-4.8.13 exploit及测试环境下载地址https://github.com/bsauce/kernel-exploit-factory

    编译选项CONFIG_SLAB=y

    General setup ---> Choose SLAB allocator (SLUB (Unqueued Allocator)) ---> SLAB

    在编译时将.config中的CONFIG_E1000CONFIG_E1000E,变更为=y。参考

    $ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v4.x/linux-4.8.13.tar.xz
    $ tar -xvf linux-4.8.13.tar.xz
    # KASAN: 设置 make menuconfig 设置"Kernel hacking" ->"Memory Debugging" -> "KASan: runtime memory debugger"。
    $ make -j32
    $ make all
    $ make modules
    # 编译出的bzImage目录:/arch/x86/boot/bzImage。
    

    漏洞描述net/core/sock.c中的 sock_setsockopt() 函数错误处理负值,导致 sk_sndbufsk_rcvbuf取值为负。调用write时将skb->headskb->end设置错误,最后调用close释放时会访问用户空间报错。

    注意,前提是有CAP_NET_ADMIN权限,才能在构造setsockopt系统调用时加上 SO_SNDBUFFORCE 。配置环境时需在文件系统中包含setcap程序,这样才能给exp设置权限。

    补丁patch

    
    diff --git a/net/core/sock.c b/net/core/sock.c
    index 5e3ca414357e2..00a074dbfe9bf 100644
    --- a/net/core/sock.c
    +++ b/net/core/sock.c
    @@ -715,7 +715,7 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
            val = min_t(u32, val, sysctl_wmem_max);
     set_sndbuf:
            sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
    -       sk->sk_sndbuf = max_t(u32, val * 2, SOCK_MIN_SNDBUF);
    +       sk->sk_sndbuf = max_t(int, val * 2, SOCK_MIN_SNDBUF);
            /* Wake up sending tasks if we upped the value. */
            sk->sk_write_space(sk);
            break;
    @@ -751,7 +751,7 @@ set_rcvbuf:
             * returning the value we actually used in getsockopt
             * is the most desirable behavior.
             */
    -       sk->sk_rcvbuf = max_t(u32, val * 2, SOCK_MIN_RCVBUF);
    +       sk->sk_rcvbuf = max_t(int, val * 2, SOCK_MIN_RCVBUF);
            break;
     
        case SO_RCVBUFFORCE:
    

    保护机制:未开 KASLR/SMAP/SMEP。伪造的skb_shared_info结构在用户空间,显然不能绕过SMAP。

    利用总结

    • (1)首先在地址0xfffffed0处伪造skb_shared_info结构, skb_shared_info)->destructor_arg->callback 指向提权函数。
    • (2)调用socketpair新建两个socket,用于发送和接收数据。
    • (3)调用setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUFFORCE,&sndbuf, sizeof(sndbuf))sk->sk_sndbuf 设置为 0xFFFFFE00。
    • (4)调用write(sockets[1], "\x5c", 1),将skb->end设置为0xfffffec0,skb->head设置为0x10。
    • (5)调用close(sockets[0])触发调用callback函数,劫持控制流。

    1. 漏洞分析

    漏洞sock_setsockopt() 中错误处理负值,导致 sk->sk_sndbufsk->sk_rcvbuf 取值过大。

    int sock_setsockopt(struct socket *sock, int level, int optname,
                char __user *optval, unsigned int optlen)
    {
        struct sock *sk = sock->sk;
        int val;
        int valbool;
        struct linger ling;
        int ret = 0;
    
        if (optname == SO_BINDTODEVICE)
            return sock_setbindtodevice(sk, optval, optlen);
    
        if (optlen < sizeof(int))
            return -EINVAL;
    
        if (get_user(val, (int __user *)optval))
            return -EFAULT;
    
        valbool = val ? 1 : 0;
    
        lock_sock(sk);
    
        switch (optname) {
        ... ...
        case SO_SNDBUF:
            val = min_t(u32, val, sysctl_wmem_max);                 // [1]
    set_sndbuf:
            sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
            sk->sk_sndbuf = max_t(u32, val * 2, SOCK_MIN_SNDBUF);   // <----- max_t() 表示以u32类型比较 val*2 和 SOCK_MIN_SNDBUF 的大小,如果val是负数,如-1>100,导致 sk->sk_sndbuf 等于一个较大的值
            /* Wake up sending tasks if we upped the value. */
            sk->sk_write_space(sk);
            break;
        case SO_SNDBUFFORCE:
            if (!capable(CAP_NET_ADMIN)) {
                ret = -EPERM;
                break;
            }
            goto set_sndbuf;                                        // [2]
        case SO_RCVBUF:
            val = min_t(u32, val, sysctl_rmem_max);
    set_rcvbuf:
            sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
            sk->sk_rcvbuf = max_t(u32, val * 2, SOCK_MIN_RCVBUF);   // <----- 同理
            break;
        ... ...
    
    #define max_t(type, x, y) ({   \
     type __max1 = (x);   \
     type __max2 = (y);   \
     __max1 > __max2 ? __max1: __max2; })
            
    // SOCK_MIN_SNDBUF 取值
    #define SOCK_MIN_SNDBUF     (TCP_SKB_MIN_TRUESIZE * 2)
    #define TCP_SKB_MIN_TRUESIZE    (2048 + SKB_DATA_ALIGN(sizeof(struct sk_buff)))
    

    注意:如果直接通过参数SO_SNDBUF去设置k->sk_sndbuf,则必然经过[1],导致val不会取传入的0xffffff00,无法将k->sk_sndbuf设置为较大的数;所以需通过参数SO_SNDBUFFORCE去设置k->sk_sndbuf,跳过[1]k->sk_sndbuf = 0xffffff00*2 = 0xFFFFFE00

    2. 漏洞跟踪

    write调用链Sys_write -> vfs_write() -> __vfs_write() -> sock_write_iter() -> sock_sendmsg() -> unix_stream_sendmsg() -> sock_alloc_send_pskb() -> alloc_skb_with_frags() -> alloc_skb() -> __alloc_skb()

    • unix_stream_sendmsg():将sk_sndbuf与其他值进行比较后,将size - data_len = 0xFFFFFEC0作为参数传递给sock_alloc_send_pskb() —— 参数是header_len=0xFFFFFEC0,最终传递给 __alloc_skb(),参数是size=0xFFFFFEC0
    • __alloc_skb():申请sk_buff结构,并对headendtail等成员赋值。注意,对size进行对齐,并加上skb->tail赋值给skb->end
    static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg,
                       size_t len)
    {
        struct sock *sk = sock->sk;
        struct sock *other = NULL;
        int err, size;
        struct sk_buff *skb;
        int sent = 0;
        struct scm_cookie scm;
        bool fds_sent = false;
        int max_level;
        int data_len;
    
        wait_for_unix_gc();
        err = scm_send(sock, msg, &scm, false);
        if (err < 0)
            return err;
        ... ...
        while (sent < len) {
            size = len - sent;
    
            /* Keep two messages in the pipe so it schedules better */
            size = min_t(int, size, (sk->sk_sndbuf >> 1) - 64);             // 0xFFFFFE00 >> 1 -0x40 = 0xFFFFFEC0   sk->sk_sndbuf 是int类型,所以右移一位符号位不变, size = 0xFFFFFEC0
    
            /* allow fallback to order-0 allocations */
            size = min_t(int, size, SKB_MAX_HEAD(0) + UNIX_SKB_FRAGS_SZ);   // size = 0xFFFFFEC0
    
            data_len = max_t(int, 0, size - SKB_MAX_HEAD(0));               // data_len = max(0, 0xfffffec0-0xec0) = 0
    
            data_len = min_t(size_t, size, PAGE_ALIGN(data_len));           // data_len = min_t(0xfffffffffffffec0, 0) = 0
    
            skb = sock_alloc_send_pskb(sk, size - data_len, data_len,       // size - data_len = 0xFFFFFEC0
                           msg->msg_flags & MSG_DONTWAIT, &err,
                           get_order(UNIX_SKB_FRAGS_SZ));
            ... ...
        }
    }
    
    struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,          // size = 0xFFFFFEC0
                    int flags, int node)
    {
        struct kmem_cache *cache;
        struct skb_shared_info *shinfo;
        struct sk_buff *skb;
        u8 *data;
        bool pfmemalloc;
    
        skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
    
        size = SKB_DATA_ALIGN(size);                            // [1] size = SKB_DATA_ALIGN(0xfffffec0) = 0xfffffec0
        size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); // size += 0x140 = 0
        data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);  // data = 0x10
        if (!data)
            goto nodata;
                                                                // ksize(0x10) = 0
        size = SKB_WITH_OVERHEAD(ksize(data));                  // size = 0 - sizeof(struct skb_shared_info) = 0xfffffec0
        prefetchw(data + size);
    
        memset(skb, 0, offsetof(struct sk_buff, tail));
        /* Account for allocated memory : skb + skb->head */
        skb->truesize = SKB_TRUESIZE(size);
        skb->pfmemalloc = pfmemalloc;
        atomic_set(&skb->users, 1);
        skb->head = data;                                       // [2] skb->head = 0x10
        skb->data = data;
        skb_reset_tail_pointer(skb);
        skb->end = skb->tail + size;                            // [3] skb->end = 0xfffffec0
        skb->mac_header = (typeof(skb->mac_header))~0U;
        skb->transport_header = (typeof(skb->transport_header))~0U;
    
        /* make sure we initialize shinfo sequentially */
        shinfo = skb_shinfo(skb);
        memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
        atomic_set(&shinfo->dataref, 1);
        kmemcheck_annotate_variable(shinfo->destructor_arg);
    
        if (flags & SKB_ALLOC_FCLONE) {
            struct sk_buff_fclones *fclones;
    
            fclones = container_of(skb, struct sk_buff_fclones, skb1);
    
            kmemcheck_annotate_bitfield(&fclones->skb2, flags1);
            skb->fclone = SKB_FCLONE_ORIG;
            atomic_set(&fclones->fclone_ref, 1);
    
            fclones->skb2.fclone = SKB_FCLONE_CLONE;
            fclones->skb2.pfmemalloc = pfmemalloc;
        }
    out:
        return skb;
    nodata:
        kmem_cache_free(cache, skb);
        skb = NULL;
        goto out;
    }
    EXPORT_SYMBOL(__alloc_skb);
    

    3.控制流劫持

    close调用链skb_release_data() 最后会调用 skb_shared_info->destructor_arg->callback 函数,destructor_arg指向ubuf_info结构,可劫持该函数即可提权。

    static void skb_release_data(struct sk_buff *skb)
    {
        struct skb_shared_info *shinfo = skb_shinfo(skb);               // shinfo = skb->head + skb->end = 0x10 + 0xfffffec0 = 0xfffffed0
        int i;
    
        if (skb->cloned &&
            atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1,
                      &shinfo->dataref))
            return;
    
        for (i = 0; i < shinfo->nr_frags; i++)
            __skb_frag_unref(&shinfo->frags[i]);
    
        /*
         * If skb buf is from userspace, we need to notify the caller
         * the lower device DMA has done;
         */
        if (shinfo->tx_flags & SKBTX_DEV_ZEROCOPY) {
            struct ubuf_info *uarg;
    
            uarg = shinfo->destructor_arg;
            if (uarg->callback)
                uarg->callback(uarg, true);
        }
    
        if (shinfo->frag_list)
            kfree_skb_list(shinfo->frag_list);
    
        skb_free_head(skb);
    }
    // shinfo偏移的计算方法
    #define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
    
    #ifdef NET_SKBUFF_DATA_USES_OFFSET
    static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)
    {
        return skb->head + skb->end;
    }
    // skb_shared_info 结构
    struct skb_shared_info {
        unsigned char   nr_frags;
        __u8        tx_flags;
        unsigned short  gso_size;
        /* Warning: this field is not always filled in (UFO)! */
        unsigned short  gso_segs;
        unsigned short  gso_type;
        struct sk_buff  *frag_list;
        struct skb_shared_hwtstamps hwtstamps;
        u32     tskey;
        __be32          ip6_frag_id;
    
        /*
         * Warning : all fields before dataref are cleared in __alloc_skb()
         */
        atomic_t    dataref;
    
        /* Intermediate layers must ensure that destructor_arg
         * remains valid until skb destructor */
        void *      destructor_arg;                                 // 指向 ubuf_info 结构
    
        /* must be last field, see pskb_expand_head() */
        skb_frag_t  frags[MAX_SKB_FRAGS];
    };
    
    struct ubuf_info {
        void (*callback)(struct ubuf_info *, bool zerocopy_success);
        void *ctx;
        unsigned long desc;
    };
    // destructor_arg偏移
    gef➤  p/x &(*(struct skb_shared_info *)0)->destructor_arg
    $6 = 0x28
    

    参考:

    https://nvd.nist.gov/vuln/detail/CVE-2016-9793

    nuoye-CVE-2016-9793漏洞分析与利用

    相关文章

      网友评论

          本文标题:【kernel exploit】CVE-2016-9793 错误

          本文链接:https://www.haomeiwen.com/subject/vkypwltx.html