Author:[email protected]
內存的讀、寫、執行屬性是系統安全最重要的機制之一。通常,如果要改寫內存中的數據,必須先確保這塊內存具有可寫屬性,如果要執行一塊內存中的代碼,必須先確保這塊內存具有可執行屬性,否則就會引發異常。然而,Windows系統的異常處理流程中存在一些小小的特例,借助這些特例,就可以知其不可寫而寫,知其不可執行而執行。
我在CanSecWest 2014的演講《ROPs are for the 99%》中介紹了一種有趣的IE瀏覽器漏洞利用技術:通過修改JavaScript對象中的某些標志,從而關閉安全模式,讓IE可以加載類似WScript.Shell這樣的危險對象,從而執行任意代碼而完全無需考慮DEP。
不過,修改SafeMode標志并非是讓IE可以加載危險對象的唯一方法。
IE瀏覽器的某些界面實際上是用HTML實現的,這些HTML通常存儲在ieframe.dll的資源中,例如:打印預覽是res://ieframe.dll/preview.dlg
,整理收藏夾是res://ieframe.dll/orgfav.dlg
,頁面屬性則是res://ieframe.dll/docppg.ppg
。
IE瀏覽器會為這些HTML創建獨立的渲染實例,以及獨立的JavaScript引擎實例。而為這些HTML創建的JavaScript引擎實例中,SafeMode本身就是關閉的。
所以,只需將JavaScript代碼插入到ieframe.dll的資源中,然后觸發IE的相應功能,被插入的代碼就會被當作IE自身的功能代碼在SafeMode關閉的JavaScript實例下執行。
不過,PE的資源節是只讀的,如果試圖用某個能對任意地址進行寫入的漏洞直接改寫ieframe.dll的資源,會觸發寫訪問違例:
#!bash
eax=00000041 ebx=1e2e31b0 ecx=00000000 edx=00000083 esi=1e2e31b0 edi=68b77fe5
eip=69c6585f esp=0363ac00 ebp=0363ac84 iopl=0 nv up ei pl nz na pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010207
jscript9!Js::JavascriptOperators::OP_SetElementI+0x117:
69c6585f 88040f mov byte ptr [edi+ecx],al ds:002b:68b77fe5=76
0:008> !exchain
0363b0f0: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+1570 (69b421d1)
0363b648: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+1570 (69b421d1)
0363bab8: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+1570 (69b421d1)
0363bb78: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+28c0 (69c71564)
0363bbc0: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+2898 (69c7150f)
0363bc44: jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+276a (69d0dedd)
0363c588: MSHTML!_except_handler4+0 (66495fa4)
CRT scope 0, filter: MSHTML! ... Omitted... (6652bbe8)
func: MSHTML!... Omitted... (6652bbf1)
0363c62c: user32!_except_handler4+0 (7569a61e)
CRT scope 0, func: user32!UserCallWinProcCheckWow+123 (75664456)
0363c68c: user32!_except_handler4+0 (7569a61e)
CRT scope 0, filter: user32!DispatchMessageWorker+15e (756659b7)
func: user32!DispatchMessageWorker+171 (756659ca)
0363f9a8: ntdll!_except_handler4+0 (776a71f5)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+2e (776a74d0)
func: ntdll!__RtlUserThreadStart+63 (776a90eb)
0363f9c8: ntdll!FinalExceptionHandler+0 (776f7428)
在上面的異常處理鏈中,mshtml.dll中的異常處理函數最終會調用kernel32!RaiseFailFastException()。如果g_fFailFastHandlerDisabled標志是false,就會終止當前進程:
#!cpp
int __thiscall RaiseFailFastExceptionFilter(int this) {
signed int **v1; // [email protected]
CONTEXT *v2; // [email protected]
signed int v3; // [email protected]
UINT v4; // [email protected]
HANDLE v5; // [email protected]
v1 = (signed int **)this;
if ( !g_fFailFastHandlerDisabled )
{
v2 = *(CONTEXT **)(this + 4);
g_fFailFastHandlerDisabled = 1;
RaiseFailFastException(*(PEXCEPTION_RECORD *)this, v2, 2u);
v3 = 1653;
if ( *v1 )
v3 = **v1;
v4 = v3;
v5 = GetCurrentProcess();
TerminateProcess(v5, v4);
}
return 0;
}
但是,如果g_fFailFastHandlerDisabled標志為true,異常處理鏈就會執行到kernel32!UnhandledExceptionFilter(),并最終執行kernel32!CheckForReadOnlyResourceFilter():
#!cpp
int __stdcall CheckForReadOnlyResourceFilter(int a1) {
int result; // [email protected]
if ( BasepAllowResourceConversion )
result = CheckForReadOnlyResource(a1, 0);
else
result = 0;
return result;
}
如果BasepAllowResourceConversion 也為true,CheckForReadOnlyResource()函數就會將試圖寫入的那個內存分頁的屬性設為可寫,然后正常返回。
也就是說,如果先將g_fFailFastHandlerDisabled和BasepAllowResourceConversion這兩個標志改寫為true,之后就可以直接修改ieframe.dll的資源,而不必擔心其只讀屬性的問題,操作系統會處理好一切。
另外還有個小問題。如果像上面所說的那樣觸發了一次CheckForReadOnlyResource()中的修改內存屬性的操作,內存屬性的RegionSize也會變成一個內存分頁的大小,通常是0x1000。而IE在以ieframe.dll中的HTML資源創建渲染實例前,mshtml!GetResource()函數會檢查資源所在內存的RegionSize屬性,如果該屬性小于資源的大小,就會返回失敗。然而,只需將要改寫的資源從頭到尾全部改寫一遍, RegionSize就會相應變大,從而繞過這個檢查。
這樣,利用Windows寫訪問異常對PE文件資源節開的綠燈,就可以寫出非常奇妙的漏洞利用代碼。
我在VARA 2009的演講《漏洞挖掘中的時間維度》中介紹了一種較為少見的模塊地址釋放后重用漏洞。比如一個程序中線程A調用了模塊X的函數,模塊X又調用了模塊Y的函數。模塊Y的函數由于某種原因,耗時比較長才能返回。在它返回前,如能讓線程B將模塊X釋放,那么模塊Y的函數返回時,返回地址將是無效的。當時發現在Opera瀏覽器中可以利用Flash模塊觸發這種漏洞,一款國產下載工具也有類似問題。
另外還有不少其它類型的漏洞,最終表現也和上述問題一樣,可以執行某個固定的指針,但無法控制該指針的值。在無DEP環境下,這些漏洞并不難利用,只要噴射代碼到會被執行的地址即可。而在DEP環境下,這些漏洞通常都被認為是不可能利用的。
但如果在預期會被執行到的地址噴射下面這樣的數據:
#!cpp
typedef struct _THUNK3 {
UCHAR MovEdx; // 0xba mov edx, imm32
LONG EdxImmediate;
UCHAR MovEcx; // 0xb9 mov ecx, imm32
LONG EcxImmediate; // <- put your Stack Pivot here
USHORT JmpEcx; // 0xe1ff jmp ecx
} Thunk3;
即使在DEP環境下,盡管堆噴射的內存區域確定無疑不可執行,但你會驚奇地發現系統似乎還是執行了這些指令,跳到ecx所設定的地址去了。只要把ecx設為合適的值,就可以跳往任何地址,繼而執行ROP鏈。
這是因為Windows系統為了兼容某些老版本程序,實現了一套叫ATL thunk emulation的機制。系統內核在處理執行訪問異常時,會檢查異常地址處的代碼是否符合ATL thunk特征。對符合ATL thunk特征的代碼,內核會用KiEmulateAtlThunk()函數去模擬執行它們。
ATL thunk emulation機制會檢查要跳往的地址是否位于PE文件中,在支持CFG的系統上還會確認要跳往的地址能否通過CFG檢查。同時,在Vista之后的Windows默認 DEP policy 下,ATL thunk emulation機制僅對沒有設置 IMAGE_DLLCHARACTERISTICS_NX_COMPAT的程序生效。如果程序編譯時指定了/NXCOMPAT參數,就不再兼容ATL thunk emulation了。不過還是有很多程序支持ATL thunk emulation,例如很多第三方應用程序,以及32 位的 iexplore.exe。所以,類似Hacking Team泄露郵件中的CVE-2015-2425,如能用某種堆噴成功搶占內存,也可借此技巧實現漏洞利用。
這樣,利用系統異常處理流程中的ATL thunk emulation能直接執行不可執行內存的特性,就可以讓一些通常認為無法利用的漏洞起死回生。
(本文大部分內容完成于2014年10月,涉及的模塊地址、符號信息等基于Windows Technical Preview 6.4.9841 x64 with Internet Explorer 11。)