Author:[email protected]
IE沙箱逃逸是IE瀏覽器安全研究的一個重要課題,其中有一類漏洞會借助ElevationPolicy設置中的白名單程序的缺陷來完成沙箱逃逸。IE在注冊表中有一個和ElevationPolicy類似的名為DragDrop策略設置,這引起了我們的注意。在本文中,筆者將以一個攻擊者的視角,嘗試各種途徑來突破IE沙箱的這一安全策略,通過分析所遇到的障礙,達到對IE沙箱拖拽安全策略進行詳細解析的目的。
IE沙箱逃逸技術中有一類是利用ElevationPolicy中的白名單程序的問題去執行任意代碼,在注冊表中,有一個和ElevationPolicy類似的配置,名為DragDrop,具體注冊表路徑如下:
HKLM\Software\Microsoft\Internet Explorer\Low Rights\DragDrop
如下圖所示:
DragDrop Policy值的含義如下:
0:目標窗口是無效的DropTarget,拒絕;
1:目標窗口是有效的DropTarget,但無法復制內容;
2:彈框詢問用戶,允許后將內容復制到目標窗口;
3:靜默允許拖拽。
在一個干凈的Windows 8.1系統上,DragDrop目錄下默認有三個程序:iexplore.exe, explorer.exe, notepad.exe,它們的Policy值都是3。當目標程序的Policy值為2時,向目標程序窗口拖拽文件,IE會彈出一個提示框,如下圖所示:
在從IE往Explorer上拖拽文件時,雖然DragDrop Policy值設置為了3,IE不會彈框,但是Explorer進程會會彈一個提示框,如下圖所示:
然而,當我們從IE中向Explorer側邊欄的樹形文件夾結構中拖拽文件時,并不會彈框。這應該是Explorer程序實現上的一個疏漏。進一步設想,如果我們能夠在IE沙箱中通過程序模擬鼠標的拖拽操作,那么就能夠利用Explorer的這個問題跨越IE沙箱的安全邊界。
OLE拖拽是一種通用的文件拖拽方式,它采用了OLE的接口設計方法來實現拖拽功能,使得拖拽的實現通用且模塊化。OLE拖拽技術包含三個基本接口:
下圖描述了一個完整的OLE拖拽操作需要實現的關鍵組件:
我們要模擬鼠標拖拽,則只需要實現IDropSource
和IDataObject
接口。正常的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接口指針的方法有兩種:
筆者這里給出使用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;
}
當我們在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
在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函數,且傳入參數可控。而要模擬鼠標拖拽操作,有兩個思路:
對于第一種方法,由于我們是在沙箱內,只能通過Broker接口的代理才能從沙箱中出來,進入到IE主進程的進程空間。所以我們并不能調到主進程中dll的內部函數,進而這種方法是不可行的。
第二種方法,如果我們能夠改變鼠標的位置,那么在ole32!DoDragDrop函數內部通過鼠標位置獲取目標窗口信息的步驟就會成功通過,就能夠完成模擬鼠標拖拽的目標。然而實驗過程中,我們發現在IE沙箱中是無法通過API來改變鼠標指針位置的。下面來具體說明這個問題。
筆者想到的能夠改變鼠標指針位置的方法有兩種:
通過SendInput函數模擬鼠標動作。SendInput函數從用戶態到內核態的函數調用關系如下所示:
#!c++
User32!SendInput
-->user32!NtUserSendInput
-->win32k.sys!NtUserSendInput
-->win32k.sys!xxxSendInput
-->win32k.sys!xxxMouseEventDirect
通過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中的一個內部函數。
本文詳細解析了IE沙箱對于拖拽操作的安全策略,先后分析了IE沙箱的拖拽限制策略、Explorer進程在拖拽限制上存在的問題、ole32.dll實現拖拽的內部原理、IE在沙箱中實現拖拽操作的原理和IE沙箱對拖拽操作進行安全限制的具體位置和實現細節。IE沙箱通過在IEShims.dll中hook特定函數和借助系統的UIPI特性(Windows Vista以上)對拖拽操作進行了有效的安全限制。
感謝Wins0n在ole32逆向上的幫助;
感謝FlowerCode在思路和技術難點上的指點。