<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/11329

            Author:[email protected]

            0x00 前言


            IE沙箱逃逸是IE瀏覽器安全研究的一個重要課題,其中有一類漏洞會借助ElevationPolicy設置中的白名單程序的缺陷來完成沙箱逃逸。IE在注冊表中有一個和ElevationPolicy類似的名為DragDrop策略設置,這引起了我們的注意。在本文中,筆者將以一個攻擊者的視角,嘗試各種途徑來突破IE沙箱的這一安全策略,通過分析所遇到的障礙,達到對IE沙箱拖拽安全策略進行詳細解析的目的。

            0x01 IE沙箱的拖拽策略


            IE沙箱逃逸技術中有一類是利用ElevationPolicy中的白名單程序的問題去執行任意代碼,在注冊表中,有一個和ElevationPolicy類似的配置,名為DragDrop,具體注冊表路徑如下:

            HKLM\Software\Microsoft\Internet Explorer\Low Rights\DragDrop
            

            如下圖所示:

            p1

            DragDrop Policy值的含義如下:

            0:目標窗口是無效的DropTarget,拒絕;
            1:目標窗口是有效的DropTarget,但無法復制內容;
            2:彈框詢問用戶,允許后將內容復制到目標窗口;
            3:靜默允許拖拽。
            

            在一個干凈的Windows 8.1系統上,DragDrop目錄下默認有三個程序:iexplore.exe, explorer.exe, notepad.exe,它們的Policy值都是3。當目標程序的Policy值為2時,向目標程序窗口拖拽文件,IE會彈出一個提示框,如下圖所示:

            p2

            0x02 Explorer進程的拖拽問題


            在從IE往Explorer上拖拽文件時,雖然DragDrop Policy值設置為了3,IE不會彈框,但是Explorer進程會會彈一個提示框,如下圖所示:

            p3

            然而,當我們從IE中向Explorer側邊欄的樹形文件夾結構中拖拽文件時,并不會彈框。這應該是Explorer程序實現上的一個疏漏。進一步設想,如果我們能夠在IE沙箱中通過程序模擬鼠標的拖拽操作,那么就能夠利用Explorer的這個問題跨越IE沙箱的安全邊界。

            0x03 不使用鼠標完成OLE拖拽


            OLE拖拽是一種通用的文件拖拽方式,它采用了OLE的接口設計方法來實現拖拽功能,使得拖拽的實現通用且模塊化。OLE拖拽技術包含三個基本接口:

            1. IDropSource接口:表示拖拽操作的源對象,由源對象實現;
            2. IDropTarget接口:表示拖拽操作的目標對象,由目標對象實現;
            3. IDataObject接口:表示拖拽操作中傳輸的數據,由源對象實現。

            下圖描述了一個完整的OLE拖拽操作需要實現的關鍵組件:

            p4

            我們要模擬鼠標拖拽,則只需要實現IDropSourceIDataObject接口。正常的OLE拖拽操作的核心是調用ole32!DoDragDrop函數,該函數原型如下:

            #!c++
            HRESULTDoDragDrop(
            IDataObject*pDataObject,   // Pointer to the data object
            IDropSource *pDropSource,   // Pointer to the source
               DWORD     dwOKEffect,    // Effects allowed by the source
               DWORD     *pdwEffect      // Pointer to effects on the source
            );
            

            DoDragDrop的參數中包含了拖拽源對象和拖拽數據的信息,在DoDragDrop函數內部通過鼠標指針位置來獲取拖拽目標對象的信息。接下來,筆者給出一種不使用鼠標,而是用代碼模擬的方式來完成文件拖拽的方法。

            要通過代碼模擬鼠標拖拽操作,即要將DoDragDrop函數中GUI操作的部分剝離出來,找出真正執行拖拽操作的函數,將所需要的參數直接傳遞給它來完成拖拽操作。這里以Win7上的ole32.dll 6.1.7601.18915為例,說明DoDragDrop內部的實現。

            Ole32!DoDragDrop的主要邏輯如下:

            #!c++
            HRESULT __stdcallDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
            {
            CDragOperationdrgop;
                HRESULT hr;
            
                CDragOperation::CDragOperation(&drgop, pDataObj, pDropSource, dwOKEffects, pdwEffect, &hr);
                if ( hr>= 0 ){
                  while ( CDragOperation::UpdateTarget(&drgop)
                    &&CDragOperation::DragOver(&drgop)
                    &&CDragOperation::HandleMessages(&drgop) )
                    hr = CDragOperation::CompleteDrop(&drgop);
                }
                CDragOperation::~CDragOperation(&drgop);
            
                return hr;
            }
            

            CDragOperation::CDragOperation是構造函數,其中重要的初始化操作包括:

            #!c++
            ole32!GetMarshalledInterfaceBuffer
            ole32!ClipSetCaptureForDrag
                -->ole32!GetPrivateClipboardWindow
            ole32!CreateSharedDragFormats
            

            接下來的While循環判斷拖拽的狀態,最終由CompleteDrop完成拖拽,關鍵的函數調用如下:

            #!c++
            ole32!CDragOperation::UpdateTarget
              ->ole32!CDragOperation::GetDropTarget
                   -->ole32!PrivDragDrop
            ole32!CDragOperation::DragOver
              -->ole32!CDropTarget::DragOver
                   -->ole32!PrivDragDrop
            ole32!CDragOperation::CompleteDrop
              -->ole32!CDropTarget::Drop
                   -->ole32!PrivDragDrop
            

            可以看到,最終實現拖拽操作的函數是ole32!PrivDragDrop,通過使用函數偏移硬編碼函數地址,可以調用到ole32.dll中的內部函數。我們定義了一個DropData函數來模擬鼠標拖拽,輸入參數為目標窗口句柄和被拖拽文件的IDataObject指針,主要邏輯如下:

            #!c++
            auto DropData(HWND hwndDropTarget, IDataObject* pDataObject)
            {
                GetPrivateClipboardWindow(CLIP_CREATEIFNOTTHERE);
                CreateSharedDragFormats(pDataObject);
                void *DOBuffer = nullptr;
                HRESULT result = GetMarshalledInterfaceBuffer(IID_IDataObject, pDataObject, &DOBuffer);
            
                if (SUCCEEDED(result)){
                    DWORD dwEffect = 0;
                    POINTL ptl = { 0, 0 };
                    void *hDDInfo = nullptr;
                    HRESULT result = PrivDragDrop(hwndDropTarget, DRAGOP_ENTER, DOBuffer, pDataObject, MK_LBUTTON, ptl, &dwEffect, 0, &hDDInfo);
                    if (SUCCEEDED(result)){
                        HRESULT result = PrivDragDrop(hwndDropTarget, DRAGOP_OVER, 0, 0, MK_LBUTTON, ptl, &dwEffect, 0, &hDDInfo);
                        if (SUCCEEDED(result)){
                            HWND hClip = GetPrivateClipboardWindow(CLIP_QUERY);
                            HRESULT result = PrivDragDrop(hwndDropTarget, DRAGOP_DROP, DOBuffer, pDataObject, 0, ptl, &dwEffect, hClip, &hDDInfo);
                        }
                    }
                }
                return result;
            }
            

            目標窗口句柄可以通過FindWindow函數獲得,將被拖拽文件封裝成一個DataObject并獲得其IDataObject接口指針的方法有兩種:

            1. 自己編寫C++類實現IDataObject接口;
            2. 使用現有類庫中的實現,如:MFC, Shell32中均有對拖拽接口實現的相關類。

            筆者這里給出使用MFC類庫對文件進行封裝并獲得其IDataObject接口的方法,實現代碼如下:

            #!c++
            auto GetIDataObjectForFile(CStringfilePath)
            {
                COleDataSource* pDataSource = new COleDataSource();
                IDataObject*    pDataObject;
                UINT            uBuffSize = 0;
                HGLOBAL         hgDrop;
                DROPFILES*      pDrop;
                TCHAR*          pszBuff;
                FORMATETC     fmtetc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
            
                uBuffSize = sizeof(DROPFILES) + sizeof(TCHAR) * (lstrlen(filePath) + 2);
                hgDrop = GlobalAlloc(GHND | GMEM_SHARE, uBuffSize);
                if (hgDrop != nullptr){
                    pDrop = (DROPFILES*)GlobalLock(hgDrop);
                    if (pDrop != nullptr){
                        pDrop->pFiles = sizeof(DROPFILES);
            #ifdef _UNICODE
                        pDrop->fWide = TRUE;
            #endif
                        pszBuff = (TCHAR*)(LPBYTE(pDrop) + sizeof(DROPFILES));
                        lstrcpy(pszBuff, (LPCTSTR)filePath);
                        GlobalUnlock(hgDrop);
                        pDataSource->CacheGlobalData(CF_HDROP, hgDrop, &fmtetc);
                        pDataObject = (IDataObject *)pDataSource->GetInterface(&IID_IDataObject); 
                    }else{
                        GlobalFree(pDrop);
                        pDataObject = nullptr;
                    }
                }else{
                    GlobalFree(hgDrop);
                    pDataObject = nullptr;
                }
                return pDataObject;
            }
            

            0x04 IE沙箱的拖拽實現


            當我們在IE沙箱中用鼠標進行拖拽操作時,沙箱內的IE Tab進程會通過ShdocvwBroker將數據轉發給沙箱外的主進程,在主進程中完成拖拽操作。也就是說,真正完成拖拽操作是在沙箱外的IE主進程內。兩個進程的函數調用情況大致如下:

            IE子進程(沙箱中):

            #!c++
            MSHTML!CDoc::DoDrag
            -->MSHTML!CDragDropManager::DoDrag
            -->combase!ObjectStubless
                --> … 發送ALPC消息給IE主進程
            

            IE主進程:

            #!c++
            … 接收IE子進程發來的ALPC消息
                -->RPCRT4!Invoke
            -->IEFRAME!CShdocvwBroker::PerformDoDragDrop
                        -->IEFRAME!CShdocvwBroker::PerformDoDragDropThreadProc
                            -->ole32!DoDragDrop
            

            0x05 IE沙箱對拖拽操作的安全限制


            在IE沙箱中,我們是可以直接調到Broker中的函數的。通過自己創建一個IEUserBroker,再由IEUserBroker創建一個ShdocvwBroker,我們就可以調到主進程中的IEFRAME!CShdocvwBroker::PerformDoDragDrop函數。調用的實現方法大致如下:

            #!c++
            typedef HRESULT(__stdcall *FuncCoCreateUserBroker)(IIEUserBroker **ppBroker);
            IIEUserBrokerPtrCreateIEUserBroker()
            {
                HMODULE hMod = LoadLibraryW(L"iertutil.dll");
                FuncCoCreateUserBrokerCoCreateUserBroker;
                CoCreateUserBroker = (FuncCoCreateUserBroker)GetProcAddress(hMod, (LPCSTR)58);
                if (CoCreateUserBroker)
                {
                    IIEUserBrokerPtr broker;
                    HRESULT ret = CoCreateUserBroker(&broker);
                    return broker;
                }
                return nullptr;
            }
            
            IIEUserBrokerPtr broker = CreateIEUserBroker();
            IShdocvwBroker* shdocvw;
            broker->BrokerCreateKnownObject(clsid_CIERecoveryStore, _uuidof(IRecoveryStore), (IUnknown**)&shdocvw);
            shdocvw->PerformDoDragDrop(HWND__ *,IEDataObjectWrapper *,IEDropSourceWrapper *,ulong,ulong,ulong *,long *);
            

            拖拽功能最終是調用ole32!DoDragDrop函數來實現的,DoDragDrop所需的參數都可以由PerformDoDragDrop函數傳入(參考0x03章節中DoDragDrop函數的參數信息)。至此,我們已經可以從沙箱內直接走到沙箱外的ole32!DoDragDrop函數,且傳入參數可控。而要模擬鼠標拖拽操作,有兩個思路:

            1. 使用0x02章節中所講的直接調用ole32.dll內部函數的方法;
            2. 調用API改變鼠標位置。

            對于第一種方法,由于我們是在沙箱內,只能通過Broker接口的代理才能從沙箱中出來,進入到IE主進程的進程空間。所以我們并不能調到主進程中dll的內部函數,進而這種方法是不可行的。

            第二種方法,如果我們能夠改變鼠標的位置,那么在ole32!DoDragDrop函數內部通過鼠標位置獲取目標窗口信息的步驟就會成功通過,就能夠完成模擬鼠標拖拽的目標。然而實驗過程中,我們發現在IE沙箱中是無法通過API來改變鼠標指針位置的。下面來具體說明這個問題。

            筆者想到的能夠改變鼠標指針位置的方法有兩種:

            1. 通過SendInput函數模擬鼠標動作。SendInput函數從用戶態到內核態的函數調用關系如下所示:

              #!c++
              User32!SendInput
              -->user32!NtUserSendInput
                      -->win32k.sys!NtUserSendInput
                          -->win32k.sys!xxxSendInput
                              -->win32k.sys!xxxMouseEventDirect
              
            2. 通過SetCursorPos函數改變鼠標指針位置。SetCursorPos函數從用戶態到內核態的函數調用關系如下:

              #!c++
              user32!SetCursorPos
                      -->user32!SetPhysicalCursorPos
                          -->user32!NtUserCallTwoParam
                              -->win32k.sys!NtUserCallTwoParam
                                  -->win32k.sys!zzzSetCursorPos
                                      -->win32k.sys!zzzSetCursorPosByType
              

            先來看SendInput,如果在IE沙箱中直接調用SendInput函數來改變鼠標指針位置的話,會返回0x5拒絕訪問錯誤,這是因為IEShims.dll中對SendInput函數做了hook,在hook函數中做了處理。具體做處理的函數位置如下:

            #!c++
            IEShims.dll!NS_InputQueueLowMIC::APIHook_SendInput
                -->IEFRAME!FrameUtilExports::PreSendInput
                    -->ShimHelper::PreSendInput
            

            這個hook很容易繞過,我們直接調用NtUserSendInput即可,不過這個函數沒有導出,需要通過函數偏移硬編碼它的地址。

            直接調用NtUserSendInput,該函數不返回錯誤,但是鼠標指針的位置并沒有改變。究其原因,函數調用的失敗是由于UIPI(User Interface Privilege Isolation)的限制。調用SetCursorPos函數也會出現相同的情況。

            UIPI是從Windows Vista開始系統新加入的一項安全特性,它在Windows內核中實現,具體位置如下:

            win32k!CheckAccessForIntegrityLevel
            

            在Win8.1上,這個函數的邏輯如下:

            #!c++
            signed int __stdcallCheckAccessForIntegrityLevelEx(
              unsigned intCurrentProcessIntegrityLevel, 
            intCurrentIsAppContainer, 
              unsigned intTargetProcessIntegrityLevel, 
            intTargetIsAppContainer)
            {
              signed int result;
              if (gbEnforceUIPI&&CurrentProcessIntegrityLevel<TargetProcessIntegrityLevel )
                result = 0;
            esleif ( gbEnforceUIPI&&CurrentProcessIntegrityLevel == TargetProcessIntegrityLevel )
                result = (CurrentIsAppContainer == TargetIsAppContainer || 
            TargetIsAppContainer == -1 || 
            CurrentIsAppContainer == -1) || 
            SeIsParentOfChildAppContainer(
            gSessionId, 
            CurrentIsAppContainer, 
            TargetIsAppContainer);
              else
                result = 1;
              return result;
            }
            

            這個函數首先判斷源進程和目標進程的Integrity Level,若源IL小于目標IL,則拒絕;若源IL大于目標IL,則允許。接著判斷AppContainer屬性,若源和目標的IL相等,且均運行在AppContainer中,則判斷二者是否滿足SeIsParentOfChildAppContainer函數的約束,滿足則允許,否則拒絕。

            注:ProcessIntegrityLevel和IsAppContainer參數都是從EPROCESS->Win32Process結構中取出來的,這是一個內部結構。SeIsParentOfChildAppContainer是ntoskrnl中的一個內部函數。

            0x06 總結


            本文詳細解析了IE沙箱對于拖拽操作的安全策略,先后分析了IE沙箱的拖拽限制策略、Explorer進程在拖拽限制上存在的問題、ole32.dll實現拖拽的內部原理、IE在沙箱中實現拖拽操作的原理和IE沙箱對拖拽操作進行安全限制的具體位置和實現細節。IE沙箱通過在IEShims.dll中hook特定函數和借助系統的UIPI特性(Windows Vista以上)對拖拽操作進行了有效的安全限制。

            0x07 參考資料


            1. Understanding and Working in Protected Mode Internet Explorer https://msdn.microsoft.com/en-us/library/bb250462
            2. OLE Drag and Drop http://www.catch22.net/tuts/ole-drag-and-drop
            3. How to Implement Drag and Drop between Your Program and Explorer http://www.codeproject.com/Articles/840/How-to-Implement-Drag-and-Drop-Between-Your-Progra
            4. WINDOWS VISTA UIPI https://www.coseinc.com/en/index.php?rt=download&act=publication&file=Vista_UIPI.ppt.pdf

            0x08 致謝


            感謝Wins0n在ole32逆向上的幫助;

            感謝FlowerCode在思路和技術難點上的指點。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线