本文是趨勢科技漏洞研究中心一篇漏洞報告的摘錄,漏洞報告中,來自趨勢科技研究團隊的John Simpson和Pengsu Cheng詳細介紹了最近的一個Microsoft Windows .LNK文件中的遠程命令執行(RCE)漏洞。下文是對其中關於CVE-2020-0729部分的摘錄,有一些小的修改。

微軟在2020年2月的“週二補丁日(Patch Tuesday)”上發佈了99個CVE的安全補丁,一個月修復這麼多漏洞實在是難以置信。由於利用廣泛,很多人都在關注其中的 Scripting Engine漏洞 ,但是還有一個“高危”漏洞也十分重要—— CVE-2020-0729 ,這是一個Windows LNK文件(即快捷方式)中的RCE漏洞。這個漏洞之所以引人注目有一部分歷史原因,以前LNK文件中的漏洞通常被用來傳播惡意軟件,例如著名的Stuxnet,並且大多數情況下,只要瀏覽包含該惡意LNK文件的文件夾就足以觸發該漏洞,無論是在本地還是在網絡共享中。所以問題就變成了,這個RCE漏洞還可以像之前的LNK文件漏洞一樣被這樣觸發嗎?由於LNK文件是二進制的格式,而且只包含一些 頂級結構 信息,回答這個問題需要更多的研究。

開始分析

對於我們這些研究團隊來說,微軟的“週二補丁日”意味着我們要開始研究漏洞了,一般要解壓給定Windows平臺的純安全(security only)補丁包,並根據微軟的建議信息,嘗試定位補丁中與漏洞有關的文件。2月份的補丁包裏沒有包含與處理LNK文件有關的常用DLL文件的更新,例如shell32.dll和windows.storage.dll,這就讓我們很難找到問題在哪兒。但是,通過對文件列表的仔細檢查,我們發現了一個特殊的DLL文件:StructuredQuery.dll。這個DLL文件之所以特殊,部分原因是因爲我們之前看到過涉及到StructuredQuery的正式漏洞,例如 CVE-2018-0825 ,只是這次的週二補丁中沒有類似的建議。所以LNK文件和StructuredQuery之間有什麼關係呢?我們在微軟的開發者中心對StructuredQuery進行了搜索,並找到了一份關於頭文件 structuredquery.h 的文檔,該文檔提到這個頭文件被用於Windows Search,而這正是LNK文件與StructuredQuery之間的聯繫。

LNK文件的衆多功能

衆所周知,LNK文件中包含爲文件或文件夾創造快捷方式的二進制結構,但很少有人知道,LNK文件還可以直接包含搜索結果。通常情況下,當用戶在Windows 10中搜索文件時,資源管理器功能區會出現一個“Search”選項卡,用戶可以在這裏優化搜索功能,設置搜索的高級選項,還可以保存當前的搜索結果以供之後使用。該搜索結果是一個XML文件,擴展名爲”.search-ms”,對於這種格式的文件,只有一個 簡單的文檔介紹

除了以這種方式保存搜索結果外,如果你將下圖中地址欄上的搜索結果圖標拖動到另一個文件夾中,會創建一個LNK文件,該文件包含”search-ms” XML文件中數據的序列化結果。

知道了這種方法後,我們用BinDiff看一下補丁中StructuredQuery的變化。

可以看到,只有一個函數有變化——StructuredQuery1::ReadPROPVARIANT()。由於相似度只有81%,函數的改變可以說相當大,對兩者的流程圖進行對比可以證實這一點:

所以說這個函數在LNK文件中做了什麼呢?這就需要對上面提到的LNK文件中的search-ms文件結構進行一番深入研究了。

根據 Shell Link(.LNK)二進制文件格式規範 ,Windows shell link文件包含幾個必要組件和幾個可選組件,每個shell link文件至少需要包含一個Shell Link Header,其格式如下:

除非另有說明,所有的多字節字段都使用小端模式表示。

LinkFlags字段用來設置是否存在可選結構以及其他各種選項,例如shell link文件中的字符串是否使用Unicode編碼。下面是LinkFlags字段的結構分佈:

有一個標誌在大多數情況下都會被設置——HasLinkTargetIDList,在圖中用位置”A”表示,即LinkFlags字段中第一個字節的最低有效位。如果設置了該標誌,在Shell Link Header之後必須跟隨一個LinkTargetIDList結構,該結構定義了鏈接的目標,並具有如下結構:

其中,IDList結構中包含了一個item ID列表。

ItemIDList的功能與文件路徑類似,其中每個ItemID對應於路徑結構中的一項,它可以代表文件系統中真實的文件夾,或者類似“控制面板”或者“保存的搜索”之類的虛擬文件夾,或者其他形式的嵌入式數據,作爲“快捷方式”來執行特定的功能。想了解關於ItemID和ItemIDList的更多信息,請參閱微軟的 Common Explorer Concepts )。對於這次的漏洞來說,重要的是包含有搜索結果的LNK文件中ItemIDList和ItemID的結構。

當用戶創建了一個存有搜索結果的快捷方式時,這個快捷方式會包含一個IDList結構,該結構以Delegate Folder ItemID開頭,後面跟有一個和搜索有關的User Property View ItemID。通常來說,ItemID以下面的結構開頭:

從偏移0×0004開始的兩個字節與ItemSize和ItemType一起用於確定ItemID的類型。例如,如果ItemSize是0×14,ItemType是0x1F,偏移0×0004處的兩個字節大於ItemSize,則可以認爲ItemID的剩餘部分是一個16字節的全局唯一標識符(GUID),LNK文件中的第一個ItemID通常是這種結構,指向一個文件或文件夾。如果ItemSize大於包含GUID所需大小,但是小於偏移0×0004處的兩個字節,則稱GUID後面的剩餘數據爲ExtraDataBlock,這段數據的前兩個字節是一個大小字段,定義了之後數據的字節數。

對於Delegate Folder ItemID來說,該位置也有一個2字節的大小字段,代表剩餘結構的字節數。它具有以下結構:

LNK文件中所有的GUID都以 RPC IDL 的形式存儲,即GUID的前三個字段中,每個字段都是一個整體,以小端模式存儲(可以看做是一個DWORD後面跟兩個WORD),後兩個字段中的每個字節都是獨立的,舉例來說,GUID {01234567-1234-ABCD-9876-0123456789AB}可以用以下的二進制表示:

\x67\x45\x23\x01\x34\x12\xCD\xAB\x98\x76\x01\x23\x45\x67\x89\xAB

並沒有文檔記錄Delegate Folder ItemID的具體作用,但是該項中Item GUID字段指定的類很可能是用來處理接下來的ItemID的,因此整個路徑結構的根命名空間就是這個由Item GUID指定的類。如果LNK文件中包含搜索結果,則Item GUID爲{04731B67-D933-450A-90E6-4ACD2E9408FE},代表CLSID_SearchFolder,是對Windows.Storage.Search.dll文件的引用。

User Property View ItemID跟在Delegate Folder ItemID的後面,其結構與Delegate Folder ItemID類似:

其中的PropertyStoreList字段很重要,該字段中包含一個或多個 序列化PropertyStore 項,每項都具有以下結構:

Property Store Data字段由一系列屬性組成,這些屬性都屬於Property Format GUID字段指定的類,每個屬性都由一個數字ID標識,即屬性ID或者叫PID。PID和Property Format GUID組合在一起就是屬性鍵,即PKEY。如果Property Format GUID字段等於{D5CDD505-2E9C-101B-9397-08002B2CF9AE},那麼PKEY的確定方式會稍有不同,且每個屬性都會成爲“屬性包(Property Bag)”的一部分,具有以下結構:

屬性包中一些元素的Name字段格式爲Key:FMTID或者Key:PID,這些元素就確定了剩餘元素的PKEY,這種情況下,就必須按照順序排列屬性包中的其他元素以使其生效。

如果Property Format GUID字段不等於我們上面提到的GUID值({D5CDD505-2E9C-101B-9397-08002B2CF9AE}),那麼每個屬性就由整數值PID標識,該屬性具有以下結構:

其中TypedPropertyValue字段表示屬性集中一個屬性的類型值,具體可參考 Microsoft Object Linking and Embedding (OLE) Property Set Data Structures 規範第2.15小結。

在Windows SDK的頭文件中定義了很多不同的PKEY,但是很多PKEY都沒有記錄,只能通過檢查相關庫文件的調試符號中的引用來識別。對於包含搜索結果的LNK文件來說,User Property View ItemID中第一個PropertyStore中的Property Format GUID字段爲{1E3EE840-BC2B-476C-8237-2ACD1A839B22},包含一個ID值爲2的屬性,對應於PKEY_FilterInfo。

PKEY_FilterInfo中的TypedPropertyValue結構中,type字段可選值中存在一個VT_STREAM,使用這個值表示TypedPropertyValue結構由0×0042的type值,兩個填充字節,以及一個IndirectPropertyName組成。IndirectPropertyName指一個可選流數據的名稱,該可選流數據可能包含用於簡單屬性集存儲的PropertySetStream包,或者包含用於非簡單屬性集存儲的”CONTENTS”流元素,具體參考 微軟文檔 。IndirectPropertyName由寬字符串”prop”開頭,後面跟一個十進制字符串,該十進制字符串是PropertySet包中的屬性標識符,因爲LNK文件使用的是序列化後VT_STREAM類型的屬性,所以在查看IndirectPropertyName的時候,只檢查它是否以”prop”開頭,後面的值被忽略。這種情況下,TypedPropertyValue的結構如下:

Stream Data字段的內容取決於該流屬性的PKEY值,對於PKEY_FilterInfo來說,Stream Data包含一個PropertyStoreList,其中又包含更多的序列化PropertyStore,其結構如下:

PKEY_FilterInfo中的PropertyStoreList是.search-ms文件中”conditions”標籤的序列化結果,“conditions”標籤的結構如下:

attribute標籤的確切功能並沒有記錄,但是attribute標籤中包含一個對應CONDITION_HISTORY的GUID,以及一個對應StructuredQuery中CConditionHistory類的CLSID,這就意味着,這種嵌套的condition和attribute結構可能表示搜索結果的歷史記錄,而attribute標籤中的chs屬性表示是否存在任何其他的歷史記錄。對上面這個結構進行序列化,並表示爲一個PropertyStore,該PropertyStore放入PKEY_FilterInfo的PropertyStoreList中,這個PropertyStoreList會成爲一個屬性包,其中屬性的Property Format GUID就是我們上面提到過的{D5CDD505-2E9C-101B-9397-08002B2CF9AE}。更具體地說,序列化後的Conditions結構包含在一個VT_STREAM類型的屬性中,該屬性由name字段的“Condition”來標識。綜上,我們得到了一個結構如下的PropertyStore項:

其中,Condition object通常是一個“Leaf Condition”或者包含多個嵌套對象的“Compound Condition”,其中的嵌套對象可以是一個或多個Leaf Condition或者其他Compound Condition。這兩種condition object都以下面的結構開頭:

其中,Leaf Condition的Condition GUID爲{52F15C89-5A17-48E1-BBCD-46A3F89C7CC2},Compound Condition的Condition GUID爲{116F8D13-101E-4FA5-84D4-FF8279381935}。Attributes字段由attribute結構組成,attribute結構的數量由Number of Attributes字段決定,每個attribute結構對應於.search-ms文件中的一個attribute標籤,以如下結構開頭:

Attribute的剩餘結構由AttributeID和Attribute CLSID決定。如果是上面提到過的CONDITION_HISTORY attribute,那麼AttributeID字段設爲{9554087B-CEB6-45AB-99FF-50E8428E860D},Attribute CLSID字段設爲{C64B9B66-E53D-4C56-B9AE-FEDE4EE95DB1},剩餘結構是一個具有以下結構的ConditionHistory對象,注意其中的字段名稱與XML中attribute標籤的屬性名稱相同:

如果字段has_nested_condition的值大於0,那麼CONDITION_HISTORY attribute就有一個嵌套的Conditon object,而這個Conditon object本身也有可能有一個嵌套的Conditon object……

在讀取完頂層的Attributes及其所有嵌套結構後,Compound Condition和Leaf Condition的結構開始不同,Compound Condition的剩餘結構如下,其中的offset是相對Attributes字段結尾的偏移:

numFixedObjects字段決定了後面還跟有多少condition(通常指Leaf Condition)。

Leaf Condition的剩餘結構如下,其中的offset是相對Attributes字段結尾的偏移:

其中,TokenInformationComplete字段是否出現取決於是否設置了上面對應的TokenInformationComplete flag,如果未設置,則該字段不存在,後面緊跟着下一個TokenInformationComplete flag,如果設置了,則緊接如下結構:

綜上,下圖顯示了一個存有搜索結果的LNK文件可能的最簡結構,簡單起見,所有不相關結構都被刪掉了:

只包含一個Leaf Condition的搜索結果就具有上面這種最簡結構,但大多數情況下,存有搜索結果的LNK文件內會有一個Compound Condition以及許多包含Leaf Condition的嵌套結構。

漏洞

既然我們已經瞭解了存有搜索結果的LNK文件的核心結構,那麼現在可以關注漏洞本身了,漏洞在於如何處理Leaf Condition的PropertyVariant字段。

Leaf Condition的PropertyVariant字段基本上是一個 PROPVARIANT結構 ,該結構由一個2字節的類型以及屬於該特定類型的數據組成。需要注意的是,StructuredQuery中的PROPVARIANT結構有一些不同,通常沒有Microsoft規範中定義的填充字節。

還有一點需要注意,如果其中的2字節類型值是0×1000 (VT_VECTOR)與另一個類型值的組合,那麼結構中會有多個該特定類型的值。

PropertyVariant字段的解析是由我們之前提到的有問題的函數StructuredQuery1::ReadPROPVARIANT()完成的。該函數首先讀入2字節的類型值,檢查該值中是否設置了VT_ARRAY位(0×2000),因爲StructuredQuery並不支持該選項:

之後函數會檢查類型是否是VT_UI4(0×0013),如果不是,進入switch語句處理所有其他類型。

漏洞在於如何處理類型爲VT_VARIANT(0x000C)的PropertyVariant。VT_VARIANT類型通常和VT_VECTOR一起使用,形成一系列的PropertyVariant結構。換句話說,這種類型就像是一個數組,數組中的每個元素可以是任何數據類型。

如果PropertyVariant的類型被設置爲VT_VARIANT(0x000C),類型檢查時會檢查VT_VECTOR位是否設置。

如果沒有設置VT_VECTOR位,ReadPROPVARIANT()函數會通過調用CoTaskMemAlloc()分配一個24字節的緩衝區,緩衝區會傳遞到ReadPROPVARIANT()的遞歸調用中,目的是想將緊跟在VT_VARIANT字段後的屬性填充進緩衝區。但是在傳遞到ReadPROPVARIANT()之前,緩衝區並沒有進行初始化(例如將其充滿空字節)。

如果上面這個嵌套的屬性類型爲VT_CF(0X0047),這類屬性包含一個指向剪貼板數據的指針,ReadPROPVARIANT()同樣會檢查VT_VECTOR位,如果沒有設置,函數會嘗試寫入流中接下來的4個字節,寫入位置由之前分配的24字節緩衝區中的一個8字節值指定。

由於緩衝區沒有被初始化,數據會被寫入未定義的內存區域,從而可能導致任意代碼執行。下圖中,WinDBG(啓用Page Heap)顯示的異常以及堆棧跟蹤情況表明程序嘗試進行數據寫入:

如果攻擊者可以正確地操控內存分佈,讓未初始化緩衝區包含攻擊者控制的數值,那麼他們就可以向該數值指向的內存區域一次性寫入任意4字節數據。

總結

不給出解決方案的漏洞分析是不完整的,對於這次的漏洞,解決方案很簡單,將分配的24字節緩衝區初始化爲空字節,確保攻擊者無法利用該內存位置之前留下的數據作爲緩衝區內容。微軟在二月份發佈了他們的 補丁 ,需要指出的是, 三月份 的時候微軟又修復了另一個LNK漏洞,但是三月份的這個漏洞與本漏洞沒有關係。

特別感謝來自趨勢科技研究團隊的John Simpson和Pengsu Cheng對於此次漏洞的詳盡分析,有關於趨勢科技研究中心的更多信息,請訪問 http://go.trendmicro.com/tis/。

在未來,威脅研究團隊會帶來更多漏洞分析報告,在此之前,請繼續關注ZDI團隊,獲取最新的漏洞利用技術以及安全補丁信息。

原文: CVE-2020-0729: REMOTE CODE EXECUTION THROUGH .LNK FILES ,Trend Micro Research Team

*本文作者:s1len0eye,轉載請註明來自FreeBuf.COM

相關文章