Ripple 20:Treck TCP/IP協議漏洞技術分析

合天智匯發表於2020-07-14
本文由“合天智匯”公眾號首發,作者:b1ngo
Ripple 20:Treck TCP/IP協議漏洞技術分析
Ripple20是一系列影響數億臺裝置的0day(19個),是JSOF研究實驗室在Treck TCP/IP協議棧中發現的【1】,該協議棧廣泛應用於嵌入式和物聯網裝置。而由於這些漏洞很底層,並且流傳版本很廣(6.0.1.67以下),通過供應鏈的傳播,使得這些漏洞影響十分廣泛和深遠。這也是這個漏洞代稱Ripple的由來:The damaging effects of a these vulnerabilities has been amplified like a ripple effect to a dramatic extent due to the supply chain factor. (Ripple意思就是漣漪,連鎖作用) 我們在本文中,重點分析了Ripple20中的CVE-2020-11896(CVSS V3 10.0)漏洞原理和利用。
背景知識
IP 分片
IP分片的概念是為了解決資料包最大長度的限制,例如乙太網最大傳輸單元MTU為1500位元組。那麼如果傳送的IP packet超過這個長度,就需要將資料包分為小的片段fragments。分片之後,想要將分片的資料包,在接收端重組,還需要一些標誌位,用於表示接下來是否還有分片,該分片在整個資料包的偏移offset和是否是同一資料包等資訊。在IP頭部有4個位元組(4-8位元組)是用於儲存這些分片資訊的:
Ripple 20:Treck TCP/IP協議漏洞技術分析
其中Flags欄位有3部分:Reverse位、DF(Don't Fragment)和MF(More Fragments)。
IP隧道
IP隧道是將IP報文封裝在另一個IP報文中的技術,如果外層和記憶體協議都是IP協議,那麼我們將之稱為ip-in-ip,結構如下圖所示:
Ripple 20:Treck TCP/IP協議漏洞技術分析
Treck TCP/IP的內部實現
Treck TCP/IP中,通過tsPacket結構表示資料包Packet:
struct tsPacket {
    ttUserPacket pktUserStruct;
    ttSharedDataPtr pktSharedDataPtr; // Point to corresponding sharable ttSharedData 
    struct tsPacket * pktChainNextPtr; // Next packet (head of a new datagram in a queue) 
    struct tsDeviceEntry * pktDeviceEntryPtr; // pointer to network Device struct 
    union anon_union_for_pktPtrUnion pktPtrUnion; 
    tt32Bit pktTcpXmitTime; 
    tt16Bit pktUserFlags; 
    tt16Bit pktFlags; 
    tt16Bit pktFlags2;
    tt16Bit pktMhomeIndex;
    tt8Bit pktTunnelCount; // Number of times this packet has been decapsulated. Initially setto zero.
    tt8Bit pktIpHdrLen; // Number of bytes occupied by the IP header.
    tt8Bit pktNetworkLayer; // Specifies the network layer type of this packet (IPv4, IPv6, ARP, etc).
    tt8Bit pktFiller[1];
};

  


該結構重要欄位包括: tsShareData欄位:指向儲存處理資料包所需資訊的Buffer的指標 pktChainNextPtr欄位:指向下一個Packet pktUserStruct欄位:表示資料包分片 pktUserStruct欄位是另外一個重要的資料結構ttUserPacket(typedef struct tsUserPacket)
struct tsUserPacket {
    void * pktuLinkNextPtr; // Next tsUserPacket for fragmented data 指向下一個分片
    ttUser8BitPtr pktuLinkDataPtr; // Pointer to data 
    ttPktLen pktuLinkDataLength; // Size of data pointed by pktuLinkDataPtr 當前buffer的大小
    ttPktLen pktuChainDataLength; // Total packet length (of chained fragmented data). Valid in first link only. 整個packet的長度,如果沒有分片,等於pktuLinkDataLength
    int pktuLinkExtraCount; // Number of links linked to this one (not including this one). Valid in first link only.
};

  


該結構重要欄位包括: pktuLinkNextPtr欄位:指向下一個分片的指標 pktuLinkDataPtr欄位:指向資料buffer的指標 pktuLinkDataLength欄位:表示當前分片長度 pktuChainDataLength欄位:表示整個Packet長度,當不存在分片的時候,這個長度與pktuLinkDataLength長度相等
資料包在不同層級中進行處理的的時候,需要調整pktuLinkDataPtr指標。例如一個ICMP請求包,該資料包有3層:Ethernet、IPv4和ICMP。在乙太網層處理過程中(tfEtherRecv),pktuLinkDataPtr欄位指向的是乙太網頭部,當下一層處理的時候,該欄位和一些其他欄位將會做以下調整:
Ripple 20:Treck TCP/IP協議漏洞技術分析
在這個例子中,乙太網包頭長度為0xE,所以pktuLinkDataPtr指標向前調整0xE,而長度欄位則減少0xE。 在接下來,IPv4層(tfIpIncomingPacket)開始處理資料包,pktuLinkDatatr此時指向的是IP頭部。Treck協議棧通過在tfIpIncomingPacket呼叫tfIpReassemblePacket函式來重組分片,每接受到一個分片,該函式將該分片插入到連結串列中,連結串列之間通過pktuLinkNextPtr指標連線起來:
Ripple 20:Treck TCP/IP協議漏洞技術分析
如果分片有遺漏,那麼最終返回Null;如果沒有,那麼該函式將會把分片連結串列交給下一層去處理。
CVE-2020-11896漏洞原理
下圖是IP頭部的結構:
Ripple 20:Treck TCP/IP協議漏洞技術分析
其中4位元組到8位元組表示的是IP分片相關資訊,包括表示,Flags和Fragment Offset。 而前4位元組則有兩個重要欄位: IP Header Length:IP頭部長度 Total Length:IP資料包總長度
接著輸入理解一下tfIpIncomingPacket函式的實現,該函式首先對資料包進行一些簡單的check:
Ripple 20:Treck TCP/IP協議漏洞技術分析
接下來檢查IpTotalLength是否小於等於pktuChainDataLength欄位,這意味著實際接收到的資料比IP頭部標識的IP資料包總長度要長,在這種情況下,就對多餘資料進行裁剪:
Ripple 20:Treck TCP/IP協議漏洞技術分析
裁剪(trimming)的方式很簡單,就是將pktuChainDataLength和pktuLinkDataLength設定為ipTotalLength的長度。注意,漏洞就在這個裁剪這裡。 回顧一下先前所說:pktuChainDataLength代表的是整個資料包的長度,pktuLinkDataLength代表的是當前分片的長度,如果上述操作成功,那麼就會出現: pktuLinkDataLength = pktuChainDataLength = IpTotalLength 可是如果這個時候,如果pktuLinkNextPtr還是指向其他的分片呢,那麼就會存在不一致現象,這種不一致的現象將會導致後續處理資料包產生錯誤。但是還是有問題,因為在tfIpIncomingPacket函式處理中,首先進行的是trimming操作,接著呼叫tfIpReassemblePacket對根據pktuChainDataLength大小,建立分片連結串列,而在tfIpReassemblePacket函式中並不會將分片資料包複製到buffer中。也就是說,先進行trimming操作,接著呼叫tfIpReassemblePacket建立分片連結串列,最終將分片連結串列返回給下一層進行處理,而下一層並不會再次呼叫tfIpIncomingPacket。這種情況下,上述不一致現象實際上並不能利用。
使用IP隧道
為了使得分片在IP層被處理,並且可以到達脆弱程式碼位置,我們使用IP隧道。 tfIpIncomingPacket函式在處理內層IP資料包時,將之作為沒有分片的資料包進行處理,也就是說MF標誌位=0,此時將不會呼叫tfIpReassemblePacket函式進行處理。這個tfIpIncomingPacket函式將會在兩個地方被呼叫,一次是在內層的IP packet(沒有分片,只呼叫一次),一次是在外層的ip packet(多次,每個分片呼叫一次)。在這個處理過程中,tfIpIncomingPacket首先將會接收所有的外層ip分片,對於每個分片,都會呼叫tfIpReassemblePacket函式。在接收全部的分片之後,將會進入下一網路層的處理,這裡由於ip-in-ip,tfIncomingPacket函式將會被本身所遞迴呼叫,去處理內層ip資料。ip-in-ip結構如下圖所示:
Ripple 20:Treck TCP/IP協議漏洞技術分析
考慮如下這個場景: 內層IP資料包:IPv4{len=32, proto=17}/UDP{checksum=0, len=12} payload為:’A’*1000 外層IP資料包(分片1):IPv4{frag offset=0, MF=1, proto=4, id=0xabcd} 外層IP資料包(分片2):IPv4{frag offset=40, MF=0, proto=4, id=0xabcd} 整體資料包及分片情況如下圖所示:
Ripple 20:Treck TCP/IP協議漏洞技術分析
當tfIpIncomingPacket函式(該函式被處理外層資料包的tfIpIncomingPacket所遞迴呼叫)處理內層IP資料包的時候,此時已經完成了分片的重組,這兩個ip分片被tsUserPacket->pktuLinkDataPtr所連線起來。 那麼接下來在該函式接下來的流程中,內層IP資料包的total length(32),是小於pktuChainDataLength(1000+ 8 + 20 = 1028)的,進入trimming分支進行裁剪操作,將pktuChainDataLength設定為32。
if ((uint)ipTotalLength <= pkt->pktuChainDataLength) { if ((uint)ipTotalLength != pkt->pktuChainDataLength) { pkt->pktuChainDataLength = (uint)ipTotalLength; pkt->pktuLinkDataLength = (uint)ipTotalLength; } }
現在我們新的問題出現了:如何將這個不一致問題(inconsistency)轉為一個記憶體破壞(memory corruption)?
UDP 2.3.2中的heap overflow
在UDP資料處理中,可以確定的是,至少有一條程式碼路徑是將IP分片複製到自定義的buffer裡面,那麼就存在漏洞的可能性。這個過程需要malloc一個堆空間,堆空間的大小取決於pktuChainDataLength欄位,然後將ip分片複製到heap中。做複製這個事情的函式是tfCopyPacket,該函式邏輯抽象如下:
Ripple 20:Treck TCP/IP協議漏洞技術分析
可以看到,這個memcpy的過程並不考慮長度。而堆塊本身的大小是取決於pktuChainDataLength,這個欄位在先前漏洞trimming被觸發後,實際上是小於實際IP資料包總大小的,heap overflow就這樣出現了。 接下來是一些UDP本身欄位的校驗,這部分就不再詳細敘述了,為了解決接收佇列非空的要求,還需要快速的發資料包保持接受佇列中存在資料包。
CVE-2020-11896漏洞利用
接下來作者利用Digi Connect ME 9210進行了驗證,證明可以做到遠端程式碼執行。這個裝置如下圖所示:
Ripple 20:Treck TCP/IP協議漏洞技術分析
 
這是一個極小的嵌入式裝置,用於完成串列埠到乙太網的轉化,串列埠常包括SPI、I2C和CAN等,裡面有嵌入式的ARM CPU,有一個NET+OS作業系統。本次實驗的目標是通過遠端執行shellcode,將開發板上面的LED燈點亮。
exploit編寫策略
heap overflow的利用大概有兩種方式: 1. 覆蓋堆中的meta-data資訊。所謂meta-data就是堆中的元資訊,包括堆塊的大小、空閒位的標誌等等堆本身自帶的資訊,這種利用方式和libc pwn中的off by one原理類似,溢位到下一個堆塊的重要資訊。 2. 覆蓋堆上面的資料結構。這種攻擊方式是希望堆上面存著一些資料結構,該結構中存著一些函式指標可以被覆蓋。
第二種利用方式與特定應用程式相關,第一種則適用面更加廣泛,本文選擇第一種利用方式。
理解Treck heap的內部實現
Treck中實現了一套自己的堆分配機制,在內部使用的是固定大小的堆分配模式,一個分配單元被稱之為bucket。在Treck實現中,有如下幾種固定大小:
Ripple 20:Treck TCP/IP協議漏洞技術分析
而釋放後的bucket,有自己對應的free buckets list,每次釋放一個bucket後,就將其插入到對應大小list的頭部,這裡可以類比於ptmalloc中的tcache機制。在Treck協議棧中通過tfGetRawBuffer,該函式引數為一個4位元組大小的size,返回一個指向分配記憶體的指標,這個分配的記憶體我們稱之為raw bufferraw buffer的釋放通過tfFreeRawBuffer。分配的過程就是從對應空閒連結串列中取出一個堆塊,然後轉為ttRawBufferPtr型別指標返回給使用者:
Ripple 20:Treck TCP/IP協議漏洞技術分析
如果空閒連結串列中沒有空閒的bucket,那麼就呼叫tfBufferDoubleMalloc分配一個新堆塊返回給使用者:
Ripple 20:Treck TCP/IP協議漏洞技術分析
 
Ripple 20:Treck TCP/IP協議漏洞技術分析
釋放的時候將其插入到對應的空閒連結串列中去
Ripple 20:Treck TCP/IP協議漏洞技術分析
可以看到,空閒bucket插入到空閒連結串列後,將會利用rawNextPtr指標連結起來,rawNextPtr指向上一個空閒bucket,結構如下圖所示:
Ripple 20:Treck TCP/IP協議漏洞技術分析
 
到目前為止,我們搞清楚了Trec中的堆結構,堆的分配和釋放過程,特別是空閒bucket也是類似ptmalloc一樣通過連結串列利用堆中的指標連結起來的。那麼我們自然可以想到,如果可以覆蓋掉rawNexPtr指標為可控值,就可以做到任意地址分配了:
Ripple 20:Treck TCP/IP協議漏洞技術分析
就像上圖所示,通過兩次分配之後,我們就可以在棧上分配一個可控的堆塊。這種攻擊方式,在libc Pwn中是非常常見的,例如fastbin attack打malloc_hook改為one_gadget。 這個嵌入式裝置是ARM v9,並且這個堆實現過程基本沒有check,甚至沒有開NX,最後白皮書中採取的方法也是通過ROP跳到shellcode來做到RCE的。 目前筆者手頭還沒有實際執行Treck的裝置,所以沒法實際動手除錯和寫exp,執行效果可以看JSOF官方的視訊演示效果【1】,啟明星辰ADLab也做了一個視訊【2】。
總結
  1. Ripple 20存在於Treck TCP/IP協議棧中,該協議棧廣泛存在於嵌入式裝置中,波及的行業包括:工業裝置、電力裝置、企業網路裝置、交通、能源等等,而上述設施我們稱之為:關鍵基礎設施
  2. Ripple 20不好修補,首先由於供應鏈的廣泛傳播,漏洞存在形式又很底層,所以排查裝置是否執行Treck TCP/IP協議棧容易疏漏。其次雖然Treck官方已經提供了最新穩定版本6.0.1.67,但是這種基礎協議棧軟體的更新還是比較麻煩的
  3. 從白皮書披露來看,漏洞利用技術並不十分複雜,所以漏洞利用門檻可能不會很高,例如這個漏洞,即使無法做到RCE,至少遠端令裝置crash是比較容易做到的,而關鍵基礎設施的crash也已經算是大問題了
相關實驗:
TCP攻擊例項分析
(通過該實驗瞭解SYN-Flooding攻擊,RST攻擊,TCP會話劫持,並通過會話劫持拿到伺服器shell 許可權。)
宣告:筆者初衷用於分享與普及網路知識,若讀者因此作出任何危害網路安全行為後果自負,與合天智匯及原作者無關!
 

相關文章