【kernel exploit】CVE-2016-9793 錯(cuò)誤處理負(fù)值導(dǎo)致訪問(wèn)用戶空間

影響版本:Linux v4.8.14 以前。v4.8.14已修補(bǔ),v4.8.13未修補(bǔ)。 7.8分。

測(cè)試版本:Linux-4.8.13 exploit及測(cè)試環(huán)境下載地址https://github.com/bsauce/kernel-exploit-factory

編譯選項(xiàng)CONFIG_SLAB=y

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

在編譯時(shí)將.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: 設(shè)置 make menuconfig 設(shè)置"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() 函數(shù)錯(cuò)誤處理負(fù)值,導(dǎo)致 sk_sndbufsk_rcvbuf取值為負(fù)。調(diào)用write時(shí)將skb->headskb->end設(shè)置錯(cuò)誤,最后調(diào)用close釋放時(shí)會(huì)訪問(wèn)用戶空間報(bào)錯(cuò)。

注意,前提是有CAP_NET_ADMIN權(quán)限,才能在構(gòu)造setsockopt系統(tǒng)調(diào)用時(shí)加上 SO_SNDBUFFORCE 。配置環(huán)境時(shí)需在文件系統(tǒng)中包含setcap程序,這樣才能給exp設(shè)置權(quán)限。

補(bǔ)丁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:

保護(hù)機(jī)制:未開(kāi) KASLR/SMAP/SMEP。偽造的skb_shared_info結(jié)構(gòu)在用戶空間,顯然不能繞過(guò)SMAP。

利用總結(jié)

  • (1)首先在地址0xfffffed0處偽造skb_shared_info結(jié)構(gòu), skb_shared_info)->destructor_arg->callback 指向提權(quán)函數(shù)。
  • (2)調(diào)用socketpair新建兩個(gè)socket,用于發(fā)送和接收數(shù)據(jù)。
  • (3)調(diào)用setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUFFORCE,&sndbuf, sizeof(sndbuf))sk->sk_sndbuf 設(shè)置為 0xFFFFFE00。
  • (4)調(diào)用write(sockets[1], "\x5c", 1),將skb->end設(shè)置為0xfffffec0,skb->head設(shè)置為0x10。
  • (5)調(diào)用close(sockets[0])觸發(fā)調(diào)用callback函數(shù),劫持控制流。

1. 漏洞分析

漏洞sock_setsockopt() 中錯(cuò)誤處理負(fù)值,導(dǎo)致 sk->sk_sndbufsk->sk_rcvbuf 取值過(guò)大。

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() 表示以u(píng)32類型比較 val*2 和 SOCK_MIN_SNDBUF 的大小,如果val是負(fù)數(shù),如-1>100,導(dǎo)致 sk->sk_sndbuf 等于一個(gè)較大的值
        /* 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)))

注意:如果直接通過(guò)參數(shù)SO_SNDBUF去設(shè)置k->sk_sndbuf,則必然經(jīng)過(guò)[1],導(dǎo)致val不會(huì)取傳入的0xffffff00,無(wú)法將k->sk_sndbuf設(shè)置為較大的數(shù);所以需通過(guò)參數(shù)SO_SNDBUFFORCE去設(shè)置k->sk_sndbuf,跳過(guò)[1]k->sk_sndbuf = 0xffffff00*2 = 0xFFFFFE00。

2. 漏洞跟蹤

write調(diào)用鏈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與其他值進(jìn)行比較后,將size - data_len = 0xFFFFFEC0作為參數(shù)傳遞給sock_alloc_send_pskb() —— 參數(shù)是header_len=0xFFFFFEC0,最終傳遞給 __alloc_skb(),參數(shù)是size=0xFFFFFEC0。
  • __alloc_skb():申請(qǐng)sk_buff結(jié)構(gòu),并對(duì)head,end,tail等成員賦值。注意,對(duì)size進(jìn)行對(duì)齊,并加上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類型,所以右移一位符號(hào)位不變, 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調(diào)用鏈skb_release_data() 最后會(huì)調(diào)用 skb_shared_info->destructor_arg->callback 函數(shù),destructor_arg指向ubuf_info結(jié)構(gòu),可劫持該函數(shù)即可提權(quán)。

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偏移的計(jì)算方法
#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 結(jié)構(gòu)
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 結(jié)構(gòu)

    /* 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漏洞分析與利用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,263評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,543評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,323評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,874評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,095評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,551評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,720評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,961評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,358評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,612評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,330評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,690評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容