異常中的異常——藉助系統異常處理特例實現匪夷所思的漏洞利用

wyzsk發表於2020-08-19
作者: xlab · 2016/04/07 10:11

Author:[email protected]

0x00 簡介


記憶體的讀、寫、執行屬性是系統安全最重要的機制之一。通常,如果要改寫記憶體中的資料,必須先確保這塊記憶體具有可寫屬性,如果要執行一塊記憶體中的程式碼,必須先確保這塊記憶體具有可執行屬性,否則就會引發異常。然而,Windows系統的異常處理流程中存在一些小小的特例,藉助這些特例,就可以知其不可寫而寫,知其不可執行而執行。

0x01 直接改寫只讀記憶體


我在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檔案資源節開的綠燈,就可以寫出非常奇妙的漏洞利用程式碼。

0x02 直接執行不可執行記憶體


我在VARA 2009的演講《漏洞挖掘中的時間維度》中介紹了一種較為少見的模組地址釋放後重用漏洞。比如一個程式中執行緒A呼叫了模組X的函式,模組X又呼叫了模組Y的函式。模組Y的函式由於某種原因,耗時比較長才能返回。在它返回前,如能讓執行緒B將模組X釋放,那麼模組Y的函式返回時,返回地址將是無效的。當時發現在Opera瀏覽器中可以利用Flash模組觸發這種漏洞,一款國產下載工具也有類似問題。

p1

另外還有不少其它型別的漏洞,最終表現也和上述問題一樣,可以執行某個固定的指標,但無法控制該指標的值。在無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。)

0x03 參考


  1. ROPs are for the 99%, CanSecWest 2014
  2. Bypassing Browser Memory Protections
  3. (CVE-2015-2425) “Gifts” From Hacking Team Continue, IE Zero-Day Added to Mix
  4. 《漏洞挖掘中的時間維度》,VARA 2009
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章