<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址: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在思路和技術難點上的指點。

    97av