Linux內核分析 | CVE-2017-1000112(UDP Fragment Offload)

2021-03-02 看雪學院
如果追加數據失敗,則調用 udp_flush_pending_frame 丟棄數據。底層調用 __ip_flush_pending_frames4.4.0內核下 ip_append_data 源碼分析基本流程對於UDP,分片工作主要在 ip_append_data(ip_apepend_page) 中完成。實際上這是比較複雜的一個函數,我們只挑關鍵的說。他的作用主要是將上層下來的數據進行整形,如果是大數據包進行切割,變成多個小於或等於MTU的SKB。如果是小數據包,並且開啟了聚合,就會將若干個數據包整合。https://elixir.bootlin.com/linux/v4.4/source/net/ipv4/ip_output.c#L864ip_append_data的準備工作
int ip_append_data(struct sock *sk, struct flowi4 *fl4,           int getfrag(void *from, char *to, int offset, int len,                   int odd, struct sk_buff *skb),           void *from, int length, int transhdrlen,           struct ipcm_cookie *ipc, struct rtable **rtp,           unsigned int flags){    struct inet_sock *inet = inet_sk(sk);    int err;     if (flags&MSG_PROBE)            return 0;     if (skb_queue_empty(&sk->sk_write_queue)) {        err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);        if (err)            return err;    } else {                transhdrlen = 0;    }     return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,                sk_page_frag(sk), getfrag,                from, length, transhdrlen, flags);}

getfrag(void *from, char *to, int offset,int len, int odd, struct sk_buff *skb) 函數主要是做了將數據賦值到 skb中。而一個skb就是一個sk_buff 結構體的指針。struct sk_buff - socket buffer 就是一個sock的緩衝區。參數 int transhdrlen 是一個代表著傳輸層header的長度,同時也是標誌是否為第一個fragment的標誌。參數 unsigned int flags 則是一個標誌。在本函數中主要用到了 MSG_PROBE(進行MTU路徑探測,而不真正進行數據發送)、MSG_MORE(代表後續還有數據被發送)在 ip_append_data 中首先判斷flags是否開啟了 MSG_PROBE 選項,如果開啟了,那麼就直接返回0。接下來判斷sk_buff隊列是否為空,如果是空的話通過 ip_setup_cork 初始化 cork 變量。如果不空那麼設置 transhdrlen = 0 說明不是第一個fragment。接下來調用:__ip_append_data 也是主要的處理流程。__ip_append_data
static int __ip_append_data(struct sock *sk,                struct flowi4 *fl4,                struct sk_buff_head *queue,                struct inet_cork *cork,                struct page_frag *pfrag,                int getfrag(void *from, char *to, int offset,                    int len, int odd, struct sk_buff *skb),                void *from, int length, int transhdrlen,                ){    struct inet_sock *inet = inet_sk(sk);    struct sk_buff *skb;         struct ip_options *opt = cork->opt;    int hh_len;    int exthdrlen;    int mtu;    int copy;    int err;    int offset = 0;    unsigned int maxfraglen, fragheaderlen, maxnonfragsize;    int csummode = CHECKSUM_NONE;    struct rtable *rt = (struct rtable *)cork->dst;    u32 tskey = 0;     skb = skb_peek_tail(queue);             exthdrlen = !skb ? rt->dst.header_len : 0;    mtu = cork->fragsize;    if (cork->tx_flags & SKBTX_ANY_SW_TSTAMP &&        sk->sk_tsflags & SOF_TIMESTAMPING_OPT_ID)        tskey = sk->sk_tskey++;     hh_len = LL_RESERVED_SPACE(rt->dst.dev);             fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);        maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;            maxnonfragsize = ip_sk_ignore_df(sk) ? 0xFFFF : mtu;                 if (cork->length + length > maxnonfragsize - fragheaderlen) {        ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,                   mtu - (opt ? opt->optlen : 0));        return -EMSGSIZE;    }         if (transhdrlen &&        length + fragheaderlen <= mtu &&        rt->dst.dev->features & NETIF_F_V4_CSUM &&        !(flags & MSG_MORE) &&        !exthdrlen)        csummode = CHECKSUM_PARTIAL;             cork->length += length;                        if (((length > mtu) || (skb && skb_is_gso(skb))) &&        (sk->sk_protocol == IPPROTO_UDP) &&        (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&        (sk->sk_type == SOCK_DGRAM)) {                err = ip_ufo_append_data(sk, queue, getfrag, from, length,                     hh_len, fragheaderlen, transhdrlen,                     maxfraglen, flags);            if (err)            goto error;        return 0;    }              if (!skb)        goto alloc_new_skb;

copy 代表的是最後一個skb的剩餘空間。

skb->len 表示此 SKB 管理的 Data Buffer 中數據的總長度copy < 0:即mtu < skb->len 溢出了。有些數據需要從當前的IP分片中移動到新的片段中copy<0
if (copy <= 0) {            char *data;            unsigned int datalen;            unsigned int fraglen;            unsigned int fraggap;            unsigned int alloclen;            struct sk_buff *skb_prev;alloc_new_skb:            skb_prev = skb;            if (skb_prev)                fraggap = skb_prev->len - maxfraglen;                else                fraggap = 0;                         datalen = length + fraggap;            if (datalen > mtu - fragheaderlen)                datalen = maxfraglen - fragheaderlen;            fraglen = datalen + fragheaderlen;             if ((flags & MSG_MORE) &&                !(rt->dst.dev->features&NETIF_F_SG))                alloclen = mtu;                    else                alloclen = fraglen;              alloclen += exthdrlen;                         if (datalen == length + fraggap)                alloclen += rt->dst.trailer_len;

當我們初步確定了需要分配的新的skb的大小 alloclen 後:
if (transhdrlen) {                               skb = sock_alloc_send_skb(sk,                    alloclen + hh_len + 15,                    (flags & MSG_DONTWAIT), &err);        } else {                skb = NULL;            if (atomic_read(&sk->sk_wmem_alloc) <=                2 * sk->sk_sndbuf)                skb = sock_wmalloc(sk,                           alloclen + hh_len + 15, 1,                           sk->sk_allocation);            if (unlikely(!skb))                err = -ENOBUFS;        }if (!skb)            goto error;        

            skb->ip_summed = csummode;            skb->csum = 0;            skb_reserve(skb, hh_len);

        skb_shinfo(skb)->tx_flags = cork->tx_flags;        cork->tx_flags = 0;        skb_shinfo(skb)->tskey = tskey;        tskey = 0;

                        data = skb_put(skb, fraglen + exthdrlen);            skb_set_network_header(skb, exthdrlen);                 skb->transport_header = (skb->network_header +                         fragheaderlen);            data += fragheaderlen + exthdrlen;             if (fraggap) {                                            skb->csum = skb_copy_and_csum_bits(                    skb_prev, maxfraglen,                    data + transhdrlen, fraggap, 0);                                skb_prev->csum = csum_sub(skb_prev->csum,                              skb->csum);                data += fraggap;                pskb_trim_unique(skb_prev, maxfraglen);            }             copy = datalen - transhdrlen - fraggap;            if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {                err = -EFAULT;                kfree_skb(skb);                goto error;            }             offset += copy;            length -= datalen - fraggap;            transhdrlen = 0;            exthdrlen = 0;            csummode = CHECKSUM_NONE;                         __skb_queue_tail(queue, skb);                continue; }

(2)分配的新skb->len剛好是確切的大小(小於mtu) 在這裡我們只討論最複雜的這種情況(並且也是跟我們漏洞最相關的這種情況),其餘的更加細節的可以看:https://blog.csdn.net/minghe_uestc/article/details/7836920?utm_source=blogxgwz2

poc/exp
poc/exp分析
#define SHINFO_OFFSET 3164 int size = SHINFO_OFFSET + sizeof(struct skb_shared_info);int rv = send(s, buffer, size, MSG_MORE);int val = 1;rv = setsockopt(s, SOL_SOCKET, SO_NO_CHECK, &val, sizeof(val));send(s, buffer, 1, 0);    close(s);

AF_INET 代表TCP/IP協議族,在socket編程中只能是AF_INET。s_addr 代表ip地址,INADDR_LOOPBACK代表綁定地址LOOPBAC, 往往是127.0.0.1, 只能收到127.0.0.1上面的連接請求。htons將其轉換成一個轉換成網絡數據格式的數字。當我們建立好socket並初始化之後,第一次send,帶上標記為MSG_MORE告訴系統我們接下來還有數據要發送。此時走UFO路徑如果要發送的是UDP數據包,且系統支持UFO,並且需要分片(length > mtu),那麼 send() 最終會進入:ip_ufo_append_data
static inline int ip_ufo_append_data(struct sock *sk,            struct sk_buff_head *queue,            int getfrag(void *from, char *to, int offset, int len,                   int odd, struct sk_buff *skb),            void *from, int length, int hh_len, int fragheaderlen,            int transhdrlen, int maxfraglen, unsigned int flags){    struct sk_buff *skb;    int err;         skb = skb_peek_tail(queue);         if (!skb) {        skb = sock_alloc_send_skb(sk,            hh_len + fragheaderlen + transhdrlen + 20,            (flags & MSG_DONTWAIT), &err);         if (!skb)            return err;                 skb_reserve(skb, hh_len);                 skb_put(skb, fragheaderlen + transhdrlen);                 skb_reset_network_header(skb);                 skb->transport_header = skb->network_header + fragheaderlen;         skb->csum = 0;         __skb_queue_tail(queue, skb);    } else if (skb_is_gso(skb)) {        goto append;    }     skb->ip_summed = CHECKSUM_PARTIAL;        skb_shinfo(skb)->gso_size = maxfraglen - fragheaderlen;    skb_shinfo(skb)->gso_type = SKB_GSO_UDP; append:    return skb_append_datato_frags(sk, skb, getfrag, from,                       (length - transhdrlen));}

調用 sock_alloc_send_skb 分配一個新的skb,然後把數據放到新的skb的非線性區域中。(skb_share_info)
struct skb_shared_info {    unsigned char    nr_frags;    __u8        tx_flags;    unsigned short    gso_size;        unsigned short    gso_segs;    unsigned short  gso_type;    struct sk_buff    *frag_list;    struct skb_shared_hwtstamps hwtstamps;    u32        tskey;    __be32          ip6_frag_id;         atomic_t    dataref;         void *        destructor_arg;         skb_frag_t    frags[MAX_SKB_FRAGS];};

注意在本條UFO路徑中我們skb中數據的大小是大於mtu的!通過 skb_shinfo(SKB) 宏也可以看出來skb_shared_info與skb之間的關係。在第二次send之前,我們調用 setsockopt 來設置了 SO_NO_CHECK 標誌,即不校驗checksum。(內核是通過SO_NO_CHECK的標誌來判斷用UFO機制還是non-UFO機制,這一點在剛剛的源碼中並不明顯。請直接看下面漏洞補丁那裡的patch)
#define skb_shinfo(SKB)    ((struct skb_shared_info *)(skb_end_pointer(SKB)))

造成接下來 第二次send的時候越過UFO路徑而執行non-UFO的代碼。在non-UFO中,此時計算的 copy = mtu - skb_len 小於0,此時的skb是直接從skb隊尾取出來的,也就是第一次send時new alloc出來的len > mtu的skb。由於copy < 0,那麼在 non-UFO 路徑上觸發了重新分配skb的操作。
copy = mtu - skb->len; if(copy<0){char *data;unsigned int datalen;unsigned int fraglen;unsigned int fraggap;unsigned int alloclen; struct sk_buff *skb_prev = skb; fraggap = skb_prev->len - maxfraglen; datalen = length + fraggap;  skb_copy_and_csum_bits(skb_prev, maxfraglen,data + transhdrlen, fraggap, 0); }

其中的 skb_copy_and_csum_bits 將舊的 skb_prev 中的數據(UFO路徑中的skb)複製到新分配的sk_buff中(即skb_shared_info->frags[]中的page_frag),從而造成溢出。而對於 skb_shared_info 存在一個成員 void * destructor_arg 他是skb釋放時的在 kfree_skb 中底層對於其產生的一個析構函數的調用。

這裡很有意思的一個處理,直接將一個void 賦給一個 ubuf_info 類型。
struct ubuf_info {    void (*callback)(struct ubuf_info *, bool zerocopy_success);    void *ctx;    unsigned long desc;};

當完成了skb DMA時,通過他調用回調函數做析構,釋放緩衝區。並且此時skb引用計數為0。而ctx負責跟蹤設備上下文。desc負責跟蹤用戶空間的緩衝區索引。zerocopy_success代表是否發生 零拷貝.destructor_arg 就可以實現程序流劫持了。具體的打法有很多:1. 如果不開smep/kaslr,那麼直接ret2usr即可。2. 如果開了smep,可以做內核rop先劫持cr4來關閉smep。然後jmp到payload3. 如果開了kaslr。我看了一下網上最通用的exp,用了一種非常有意思的方式來bypass KASLR。通過使用 klogctl 讀取內核日誌,然後在內核日誌中查找 'Freeing unused' 這個字符串。然後找到與其同一行的ffffff開頭的數字,最後 & 0xffffffffff000000ul 拿到一個地址,由於偏移不變,那麼接下來就有了其他gadgets/函數在內核中的準確地址了。exp地址:https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c
patch:https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=85f1bd9a7b5a79d5baa8bf44af19658f7bf77bfaWhen iteratively building a UDP datagram with MSG_MORE and that
datagram exceeds MTU, consistently choose UFO or fragmentation.Once skb_is_gso, always apply ufo. Conversely, once a datagram is
split across multiple skbs, do not consider ufo.Sendpage already maintains the first invariant, only add the second.
IPv6 does not have a sendpage implementation to modify.A gso skb must have a partial checksum, do not follow sk_no_check_tx
in udp_send_skb.大概就是說,之前的話主要是由於 SO_NO_CHECK 可以控制UFO路徑切換造成問題。但是現在的話一旦我們有了gso(Generic Segmentation Offload一種UFO分片優化,發生在數據送到網卡之前),那麼就會調用ufo,而不是產生路徑切換的隱患。同時作者也說了如果數據報被分片到多個skb中,那麼不要使用ufo了。參考UFO (UDP Fragmentation Offload)Packet fragmentation and segmentation offload in UDP and VXLAN Linux網絡協議棧--ip_append_data函數分析關於網絡編程中MTU、TCP、UDP優化配置的一些總結CVE-2017-1000112-UFO 學習總結

看雪ID:ScUpax0s

https://bbs.pediy.com/user-home-876323.htm

  *本文由看雪論壇 ScUpax0s 原創,轉載請註明來自看雪社區。

相關焦點

  • 當運行 Linux 內核的機器死機時……
    作者 | dog250 責編 | 張文頭圖 | CSDN 下載自視覺中國曾經寫過一個模塊,當運行 Linux 內核的機器死機時,SSH肯定無法登錄了,但只要它還響應中斷,就盡力讓它可以通過網絡帶回一些信息。
  • Linux 內核 TCP MSS 機制詳細分析
    感覺是一個很厲害的Dos漏洞,不過因為有其他事打斷,所以進展的速度比較慢,這期間網上已經有相關的分析文章了。[2][3] 而我在嘗試復現CVE-2019-11477漏洞的過程中,在第一步設置MSS的問題上就遇到問題了,無法達到預期效果,但是目前公開的分析文章卻沒對該部分內容進行詳細分析。所以本文將通過Linux內核源碼對TCP的MSS機制進行詳細分析。
  • 深入作業系統,從內核理解網絡包的接收過程(Linux篇)
    《不為人知的網絡編程(一):淺析TCP協議中的疑難雜症(上篇)》《不為人知的網絡編程(二):淺析TCP協議中的疑難雜症(下篇)》《不為人知的網絡編程(三):關閉TCP連接時為什麼會TIME_WAIT、CLOSE_WAIT》《不為人知的網絡編程(四):深入研究分析
  • 【乾貨】你真的需要了解一下 Linux 系統 UDP 丟包問題分析思路
    之所以沒有分析發送數據流程,一是因為發送流程和接收類似,只是方向相反;另外發送流程報文丟失的概率比接收小,只有在應用程式發送的報文速率大於內核和網卡處理速率時才會發生。本篇文章假定機器只有一個名字為 eth0 的 interface,如果有多個 interface 或者 interface 的名字不是 eth0,請按照實際情況進行分析。
  • 淺談分析Arm linux 內核移植及系統初始化的過程二
    的內核線程,繼續初始化。);//setup processor and machine and destinate some pointers for do_initcalls() s5、淺談分析Arm linux 內核移植及系統初始化的過程 諮詢QQ:313807838// for example init_machine
  • Linux 系統內核的調試
    以試驗使用的kgdb補丁為例,linux內核的版本為linux-2.6.7,補丁版本為kgdb-2.2。下載linux-2.6.9-hsc0.patch.gz,文件的下載地址請參見[8]。下載linux-2.6.9.tar.bz2,文件的下載地址請參見[9]。  現在我們得到了整個的linux-2.6.9原始碼,以及所需的內核補丁。請準備一個有2GB空間的目錄裡來完成以下製作μcLinux內核的過程。
  • Linux 內核學習:環境搭建和內核編譯
    內核學習之一:環境搭建--安裝Debian7.3本系列文章假設讀者已對linux有一定的了解,其實學習linux內核不需要有很深的關於linux的知識,只需要了解以下內容:linux基礎知識及基本shell命令;現代作業系統的基本概念;C語言和gcc基本使用。
  • Linux 內核的測試和調試(6)
    注意不要以附件形式發送補丁,而是以純文本形式粘貼在郵件裡面。確保你的郵件客戶端可以發送純文本信息,你可以試試給自己發送一份補丁郵件來測試你的郵件客戶端的功能。收到自己的郵件後,運行 checkpatch 命令並給自己的內核源碼打上你的補丁。如果這兩部都能通過,你就可以給 Linux 郵箱列表發送補丁了。使用 git send-email 命令是提交補丁最安全的方式,可以避免你的郵箱的兼容性問題。
  • CVE-2020-0423 Android內核提權漏洞分析
    官方給出影響的版本在Android 8-11之間,詳細的英文文章請參考這裡https://blog.longterm.io/cve-2020-0423.html。,就可以做到用戶態可以隨意讀寫內核了,甚至代碼段,拿到root豈不是輕而易舉了。
  • Linux內核源碼do_fork分析
    我們都知道進程是Linux內核中最為重要的一個抽象概念,那麼我們平時在fork一個進程時,該進程究竟是怎麼產生的呢? 本篇推送會淺談一下在進程創建過程中扮演著重要角色的do_fork函數。
  • CVE-2019-2215復現及分析
    id=1942公開的poc拿到了任意內核讀寫權限。後續文章https://hernan.de/blog/2019/10/15/tailoring-cve-2019-2215-to-achieve-root/。這個漏洞比較好用,並且利用公開的漏洞能夠root最新機器。
  • linux配置、編譯內核實用工具
    第一個階段Make會讀取所有變量和分析所有目標的依賴關係,並最終建立一棵依賴關係樹。同時,所有的立即型變量(通過「:=」賦值)在這個過程中被擴展,就像C變量一樣。而在這個階段的最後,所有的延遲型變量才被擴展(通過「=」賦值)。這點需要格外注意。第二個階段Make會根據依賴關係樹執行命令。
  • Linux內核編譯初體驗
    下載內核在ftp://ftp.kernel.org/pub/linux/kernel/下載原版內核本文引用地址:http://www.eepw.com.cn/article/201611/319326.htm此處使用linux-2.6.22.6.tar.bz22.
  • CVE-2017-0263 win32k.sys內核漏洞分析
    CVE-2017-0263是一個win32k的菜單管理組件中的一個UAF漏洞 函數win32k!xxxMNEndMenuState中釋放全局菜單狀態對象的成員域pGlobalPopupMenu。指向的根彈出菜單對象時,沒有將該成員域置零,導致該成員仍然指向已被釋放的內存區域,即有可能再次使用。
  • Linux數據報文的來龍去脈
    一、網卡把報文傳到內核的流程圖圖1. 網卡傳遞數據包到內核的流程圖1. 網卡在啟動時會申請一個接收ring buffer,其條目都會指向一個skb的內存。2. DMA完成數據報文從網卡硬體到內存到拷貝後,網卡發送一個中斷通知CPU。3.
  • SUMAP網絡空間測繪|2021年CVE漏洞趨勢安全分析報告
    對於今天的網際網路安全我們更需要通過模型監測方式來持續觀察漏洞趨勢和影響範圍,才能持續應對漏洞爆發之後的安全趨勢分析評估。 本文主要通過網絡測繪角度手機各種資產協議的版本號信息,通過比對cve漏洞影響範圍中的版本號方式進行安全風險趨勢分析,無任何實際危害網際網路行為。資產在攜帶版本中也會存在修復補丁後版本不變的情況。
  • Ubuntu 發現內核回歸系統崩潰漏洞,需儘快升級
    12 月 13 日,Ubuntu 發布了安全更新 , 修復了系統內核因引入回歸導致數據損壞 , 系統崩潰的重要漏洞。以下是漏洞詳情:漏洞詳情來源:https://ubuntu.com/security/notices/USN-4658-2USN-4658-2: 內核回歸 嚴重程度:高在先前 USN-4658-1(https://ubuntu.com/security/notices/USN-4658-1), 官方修復了 Linux
  • Linux內核啟動-內核解壓縮
    本文引用地址:http://www.eepw.com.cn/article/148792.htm從內核的生成過程來看內核的連結主要有三步:第一步是把內核的原始碼編譯成.o文件,然後連結,這一步,連結的是arch/i386/kernel/head.S,生成的是vmlinux。
  • linux-3.18內核系統調用
    否則,如果某個用戶應用試圖調用這些已經被淘汰的系統調用,所得到的結果,比如打開了一個文件,就會與預期完全不同,這將令人感到非常奇怪。其實,sys_ni_syscall中的"ni"即表示"not implemented(沒有實現)"。
  • linux內核中的IS_ERR
    在看內核源碼的時候,經常會遇到IS_ERR,比如在linux/arch/arm/kernel/sys_arm.c中本文引用地址:http