混淆漏洞CVE-2017-0213技術分析
1. 引言
CVE-2017-0213 是一個比較冷門的COM 型別混淆 (Type Confusion)漏洞。巧妙的利用該漏洞,可以實現本地的提權。該漏洞由著名的Google Project zero 發現。漏洞資訊原文可參見【1】
然而原文對漏洞的描述有些過於任性,儘管筆者熟悉好幾國英文J,反覆讀了好幾遍還是覺得雲山霧罩。因此決定親自分析下,和讀者共同分享一下。
2. 技術分析
2.1 DCOM 簡介
這個漏洞要從DCOM 談起了。相信大家對Windows的元件物件模型(COM) 都已經非常熟悉了。而DCOM可能相對來說要陌生一些。DCOM是 分散式的COM, 類似於CORBA, 也就是說呼叫的COM 可以在遠端主機上。
DCOM的詳細資訊可參見(閱讀原文檢視)
https://msdn.microsoft.com/en-us/library/cc226801.aspx
在COM模型中,我們知道所有的COM 介面都要繼承 IUnkown 介面。通過QueryInterface函式,可以查詢任意介面。
而在DCOM模型中,對應於IUnknown的介面為IRemunkown 和IRemUnkown2 兩個遠端介面。
相應的,QueryInterface對應的方法為:
IRemUnknown::RemQueryInterface([in] REFIPIDripid, [in] unsigned long cRefs, [in] unsigned short cIids, [in, size_is(cIids)] IID* iids, [out, size_is(,cIids)] PREMQIRESULT* ppQIResults)
和
IRemUnknown2::RemQueryInterface2([in]REFIPID ripid, [in] unsigned short cIids, [in, size_is(cIids)] IID* iids, [out, size_is(cIids)] HRESULT* phr, [out, size_is(cIids)] PMInterfacePointerInternal* ppMIF)
兩者的主要區別在於返回的物件型別上。
IRemUnknown::RemQueryInterface 通過最後一個引數[out,size_is(,cIids)] PREMQIRESULT* ppQIResults 來返回物件。
看一下PREMQIRESULT的定義
typedef struct tagREMQIRESULT { HRESULT hResult; STDOBJREF std; } REMQIRESULT;typedef [disable_consistency_check]REMQIRESULT* PREMQIRESULT
STDOBJREF 通常包含OXID,IPID, OID 這些資訊。
而 IRemUnknown2::RemQueryInterface2
通過最後又一個引數PMInterfacePointerInternal 來返回物件.
而 PMInterfacePointerInternal 的定義
typedef [disable_consistency_check]MInterfacePointer* PMInterfacePointerInternal;
根據MSDN的解釋 (https://msdn.microsoft.com/en-us/library/cc226826.aspx)
MInterfacePointer is an NDR-marshaled structure that MUST contain a hand-marshaled OBJREF.
MInterfacePointer是一個NDR裝組(marshal)的物件引用。
不難看出,IRemUnknown::RemQueryInterface 只是返回了物件的部分資訊,而IRemUnknown2::RemQueryInterface2返回了整個物件的資訊。
2.2 漏洞分析
CVE-2017-0213的問題出現在 IRemUnknown2::RemQueryInterface2的 程式碼中。
程式碼呼叫CStdMarshal::Finish_RemQIAndUnmarshal2來完成對返回物件解組(Unmarshal)。
對於每一個MInterfacePointer指標,函式 CStdMarshal::UnmarshalInterface 對其進行解組,即從 IStream的資料中解組出相應的介面。問題出現在這裡 ,解組的時候,解組代理是根據IStreamde資料中的OBJREF(IID) 來解組的,而並非 IRemUnknown2::RemQueryInterface2 中指定的 IID 。也就是說,這裡沒有對OBJREF 的IID 和IRemUnknown2::RemQueryInterface2中指定的IID 進行一致性檢查。,如果在 IStream中的IID 和呼叫 IRemUnknown2::RemQueryInterface2 時指定的IID 不一致的時候,就會發生型別混淆。
2.3 漏洞利用
型別混淆的漏洞通常可以通過記憶體損壞的方式來進行利用.然而漏洞發現者在利用時,並未採用記憶體損壞的方式來進行漏洞利用。按照漏洞發現者的說法,記憶體損壞的利用方式需要對記憶體進行精心佈局,即便如此 ,在Windows 10上也可能會觸發CFG(Control Flow Guard)。
漏洞發現者另闢其徑,採用了一種基於LoadTypeLibrary來利用的方法。
背景知識:
如果將COM 介面註冊PSOAInterface或者PSDispatch後,oleaut32.dll 會查詢註冊的Type Library資訊(存放在登錄檔中),如果找到的話,將呼叫LoadTypeLibrary 來載入 Type Library. TypeLibrary在載入的時候,有個很有趣的行為: 首先會按GUID查詢,如果查詢失敗的話,會按檔名來查詢。如果按檔名查詢也失敗的話,這時會按照Moniker 來查詢。這時,我們只需將包含scriptlet的Moniker注入到一個Type Library 檔案中。就可以執行這段scriptlet。漏洞發現者採用的ScriptLet如下圖所示。
現在我們知道,可以利用這個漏洞來成功載入JS, 從而達到執行任意檔案的目的。
那麼如何來利用這個漏洞來進行提權呢? 我們注意到,BITS 服務執行在 SYSTEM 完整性等級(IntegrityLevel)上。如果呼叫其
IBackgroundCopyJob::SetNotifyInterface(IUnknown *pNotifyInterface)
並傳入一個精心構造的COM 介面,引發型別混淆,便可利用該漏洞來載入一個TypeLibrary。 這裡,漏洞利用程式選擇了COM 介面 IID_ITMediaControl(GUID {C445DDE8-5199-4BC7-9807-5FFB92E42E09}),其TypeLibGUID為 {21D6D480-A88B-11D0-83DD-00AA003CCABD}, 註冊的DLL為 tapi3.dll。那麼如何才能達到載入自己定義的tapi3.dll的目的呢?Tapi3.dll位於 %system32%目錄下,覆蓋此檔案顯然不可能。漏洞利用程式使用了這樣一個技巧:利用NtCreateSymbolicLink重定向C: 到當前目錄 。在當前目錄下建立windowssystem32tapi3.dll即可。具體過程可參見漏洞利用原始碼【2 】
最後上一張成功利用的截圖。Windows 7 SP1 下成功彈出一個admin許可權的cmd視窗。
3. 總結
“天下漏洞,唯冷不破”。CVE-2017-0213的無論從挖掘和利用,感覺都有些劍走偏鋒,正屬於這種比較冷門的一類。這種漏洞似乎難以通過fuzzing的方式來發現。通常這種漏洞的發現,需要對Windows的程式碼非常熟悉。而從漏洞的利用的角度來看,思路亦是非常巧妙。從這個漏洞的發現到利用,可見漏洞發現者在Windows 作業系統方面的造詣非同一般。
4. 參考文獻
1.https://bugs.chromium.org/p/project-zero/issues/detail?id=1107
2.https://github.com/WindowsExploits/Exploits/blob/master/CVE-2017-0213/Source/CVE-2017-0213.cpp