2011年10月8日星期六

[轉載]SEH分析筆記(X86篇)三


二、MSC 提供的EXCEPTION_REGISTRATION::handler 函數
  之前咱們有說過EXCEPTION_REGISTRATION::handler 指向由MSC 編譯器提供的一個函數,這個函數內部負責調用scopetable[?]->lpfnFilter/lpfnHandler。這個函數通常為module!_except_handler?,其中module為模塊名,? 表示某數字。在分析過程中發現,有的模塊完整實現了該函數,有的模塊直接使用了系統提供的nt!_except_handler?。不知道是因為版本的問題,還是編譯選項的問題。我沒有深究。
  我繼續以passhThrough 模塊為例,來說明這個函數。
  首先需要再次說明一下我的註釋習慣:後續列出來的反彙編代碼中,最左邊的:<>符號表示跳轉,是我在分析的過程中為了方便理解流程手寫的。其中< 表示跳轉源,> 是跳轉目標,: 用於組成連接線。
  為了方便對照,我把經過整理的PassThrough!_except_handler4 的原​​型列在這裡:

  EXCEPTION_DISPOSITION _except_handler4 (
      PEXCEPTION_RECORD pExceptionRecord,
      PEXCEPTION_REGISTRATION pExceptionRegistration,
      PCONTEXT,
      PDISPATCHER_CONTEXT
  );

  反彙編代碼:

代碼:
    kd> uf PassThrough!_except_handler4
                  PassThrough!_except_handler4 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 255]:
                    f8720390 mov edi,edi
                    f8720392 push ebp
                    f8720393 mov ebp,esp
                    f8720395 sub esp,14h
                    f8720398 push ebx
                    f8720399 mov ebx,dword ptr [ebp+0Ch] ; ebx = pExceptionRegistration
                    f872039c push esi
                    f872039d mov esi,dword ptr [ebx+8] ; esi = pExceptionRegistration->scopetable
                    f87203a0 xor esi,dword ptr [PassThrough!__security_cookie (f87220b0)] ; 用__security_cookie 對scopetable 解密
                    f87203a6 push edi
                    f87203a7 mov eax,dword ptr [esi]
                    f87203a9 mov byte ptr [ebp-1],0 ; ebp-1 存放的是一個BOOLEAN 值,用來表示是否執行過任何scopetable_entry::lpfnFilter
                    f87203ad mov dword ptr [ebp-8],1 ; ebp-8 被用來存放本函數的返回值,這裡初始化為ExceptionContinueSearch (1)
                    f87203b4 lea edi,[ebx+10h] ; edi = pExceptionRegistration->_ebp
                    f87203b7 cmp eax,0FFFFFFFEh ; 檢查scopetable 中坑的第一個DWORD 值,後續來做相關的安全處理
< f87203ba je PassThrough!_except_handler4+0x39 (f87203c9)
:
: PassThrough!_except_handler4+0x2c [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 315]:
: ; 校驗scopetable 完整性(1)
: f87203bc mov ecx,dword ptr [esi+4]
: f87203bf add ecx,edi
: f87203c1 xor ecx,dword ptr [eax+edi]
: f87203c4 call PassThrough!__security_check_cookie (f8720638)
:
: PassThrough!_except_handler4+0x39 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 315]:
: ; 校驗scopetable 完整性(2)
> f87203c9 mov ecx,dword ptr [esi+0Ch]
                   f87203cc mov eax,dword ptr [esi+8]
                   f87203cf add ecx,edi
                   f87203d1 xor ecx,dword ptr [eax+edi]
                   f87203d4 call PassThrough!__security_check_cookie (f8720638)
                    ; 安全工作處理完畢,開始進入異常處理流程
                   f87203d9 mov eax,dword ptr [ebp+8] ; eax = pExceptionRecord
                   f87203dc test byte ptr [eax+4],66h ; pExceptionRecord->ExceptionFlags & EXCEPTION_UNWIND,判斷是異常處理過程還是展開過程
< f87203e0 jne PassThrough!_except_handler4+0x138 (f87204c8)
:
: PassThrough!_except_handler4+0x56 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 331]:
: ; 異常處理過程
: f87203e6 mov ecx,dword ptr [ebp+10h] ; ecx = pContext
: f87203e9 lea edx,[ebp-14h]
: f87203ec mov dword ptr [ebx-4],edx ; ebx-4 是創建EXCEPTION_REGISTRATION 時候預留的xpointers 的空間,這裡給它賦值
: f87203ef mov ebx,dword ptr [ebx+0Ch] ; ebx = pExceptionRegistration->trylevel
: f87203f2 mov dword ptr [ebp-14h],eax ; [ebp-14] = pExceptionRecord,即xpointers->ExceptionRecord = pExceptionRecord
: f87203f5 mov dword ptr [ebp-10h],ecx ; [ebp-10] = pContext,即xpointers->ContextRecord = pContext
: f87203f8 cmp ebx,0FFFFFFFEh ; cmp pExceptionRegistration->trylevel, TRYLEVEL_INVALID
: < f87203fb je PassThrough!_except_handler4+0xcc (f872045c)
: :
: : PassThrough!_except_handler4+0x6d [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 341]:
: : f87203fd lea ecx,[ecx]
: :
: : PassThrough!_except_handler4+0x70 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 343]:
: : > f8720400 lea eax,[ebx+ebx*2] ; 這裡*3,下面緊接著*4,即*12,實際上是為了跳過x 個scopetable_entry (大小為12個字節)
: : : f8720403 mov ecx,dword ptr [esi+eax*4+14h] ; ecx = scopetable[i].lpfnFilter, 這裡14h是為了跳過10h大小的坑
: : : f8720407 lea eax,[esi+eax*4+10h] ; eax = &scopetable[i]
: : : f872040b mov dword ptr [ebp-0Ch],eax ; ebp-0Ch 存放的是pCurrentScopeTableEntry
: : : f872040e mov eax,dword ptr [eax] ; eax = scopetable[i].previousTryLevel
: : : f8720410 mov dword ptr [ebp+8],eax ; 這裡是將ebp+8 當作局部變量使用, [ebp+8] = scopetable[i].previousTryLevel
: : : f8720413 test ecx,ecx ; 判斷scopetable[i].lpfnFilter 是否為NULL
: : < : f8720415 je PassThrough!_except_handler4+0x9b (f872042b)
: : : :
: : : : PassThrough!_except_handler4+0x87 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 355]:
: : : : ; scopetable[i].lpfnFilter 不為NULL,調用它
: : : : f8720417 mov edx,edi ; edx = pExceptionRegistration->_ebp
: : : : f8720419 call PassThrough!_EH4_CallFilterFunc (f87205d1)
: : : : f872041e mov byte ptr [ebp-1],1 ; [ebp-1] 表示是否執行過lpfnFilter,它是個BOOLEAN 值
: : : : f8720422 test eax,eax
: : : < : f8720424 jl PassThrough!_except_handler4+0xd6 (f8720466) ; 如果是EXCEPTION_CONTINUE_EXECUTION (-1) 就跳
: : : : :
: : : : : PassThrough!_except_handler4+0x96 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 370]:
: : : : < : f8720426 jg PassThrough!_except_handler4+0xdf (f872046f) ; 如果是EXCEPTION_EXECUTE_HANDLER (1) 就跳
: : : : : :
: : : : : : PassThrough!_except_handler4+0x98 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 370]:
: : : : : : ; lpfnFilter 返回EXCEPTION_CONTINUE_SEARCH
: : : : : : f8720428 mov eax,dword ptr [ebp+8] ; eax = scopetable[i].previousTryLevel
: : : : : :
: : : : : : PassThrough!_except_handler4+0x9b [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 341]:
: : > : : : f872042b mov ebx,eax
: : : : : f872042d cmp eax,0FFFFFFFEh ; cmp scopetable[i].previousTryLevel, TRYLEVEL_INVALID
: : : : < f8720430 jne PassThrough!_except_handler4+0x70 (f8720400)
: : : :
: : : : PassThrough!_except_handler4+0xa2 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 478]:
: : : : f8720432 cmp byte ptr [ebp-1],0 ; 沒有執行過lpfnFilter,無需進行安全檢查
: : < : : f8720436 je PassThrough!_except_handler4+0xcc (f872045c)
: : : : :
: : : : : PassThrough!_except_handler4+0xa8 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 486]:
: : : : : ; 執行過lpfnFilter,需要校驗完整性
: : : : : > > f8720438 mov eax,dword ptr [esi]
: : : : : : : f872043a cmp eax,0FFFFFFFEh ; 根據scopetable 坑的第一個DWORD 值判斷是否需要做進一步的安全檢查
: : : : : < : : f872043d je PassThrough!_except_handler4+0xbc (f872044c)
: : : : : : : :
: : : : : : : : PassThrough!_except_handler4+0xaf [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 486]:
: : : : : : : : ; 校驗scopetable 完整性(1)
: : : : : : : : f872043f mov ecx,dword ptr [esi+4]
: : : : : : : : f8720442 add ecx,edi
: : : : : : : : f8720444 xor ecx,dword ptr [eax+edi]
: : : : : : : : f8720447 call PassThrough!__security_check_cookie (f8720638)
: : : : : : : :
: : : : : : : : PassThrough!_except_handler4+0xbc [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 486]:
: : : : : : : : ; 校驗scopetable 完整性(2)
: : : : : > : : f872044c mov ecx,dword ptr [esi+0Ch]
: : : : : : : f872044f mov edx,dword ptr [esi+8]
: : : : : : : f8720452 add ecx,edi
: : : : : : : f8720454 xor ecx,dword ptr [edx+edi] ; 正常情況下[edx+edi] 保存的是__security_cookie 的值
: : : : : : : f8720457 call PassThrough!__security_check_cookie (f8720638)
: : : : : : :
: : : : : : : PassThrough!_except_handler4+0xcc [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 495]:
: > > : : : > : f872045c mov eax,dword ptr [ebp-8]
: : : : : : f872045f pop edi
: : : : : : f8720460 pop esi
: : : : : : f8720461 pop ebx
: : : : : : f8720462 mov esp,ebp
: : : : : : f8720464 pop ebp
: : : : : : f8720465 ret
: : : : : :
: : : : : : PassThrough!_except_handler4+0xd6 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 367]:
: : : : : : ; lpfnFilter 返回EXCEPTION_CONTINUE_EXECUTION
: > : : : : f8720466 mov dword ptr [ebp-8],0 ; [ebp-8] = ExceptionContinueExecution (0)
: : < : : f872046d jmp PassThrough!_except_handler4+0xa8 (f8720438)
: : : :
: : : : PassThrough!_except_handler4+0xdf [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 396]:
: : : : ; lpfnFilter 返回EXCEPTION_EXECUTE_HANDLER
: > : : f872046f mov ecx,dword ptr [ebp+0Ch] ; ecx = pExceptionRegistration
: : : f8720472 call PassThrough!_EH4_GlobalUnwind (f87205fa)
: : : f8720477 mov eax,dword ptr [ebp+0Ch] ; eax = pExceptionRegistration
: : : f872047a cmp dword ptr [eax+0Ch],ebx ; cmp pExceptionRegistration->trylevel
: < : : f872047d je PassThrough!_except_handler4+0x101 (f8720491)
: : : :
: : : : PassThrough!_except_handler4+0xef [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 408]:
: : : : f872047f push offset PassThrough!__security_cookie (f87220b0)
: : : : f8720484 push edi
: : : : f8720485 mov edx,ebx
: : : : f8720487 mov ecx,eax ; ecx = pExceptionRegistration
: : : : f8720489 call PassThrough!_EH4_LocalUnwind (f8720614)
: : : : f872048e mov eax,dword ptr [ebp+0Ch]
: : : :
: : : : PassThrough!_except_handler4+0x101 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 417]:
: > : : f8720491 mov ecx,dword ptr [ebp+8] ; ecx = scopetable[i].previousTryLevel
: : : f8720494 mov dword ptr [eax+0Ch],ecx ; pExceptionRegistration->trylevel = scopetable[i].previousTryLevel
: : : f8720497 mov eax,dword ptr [esi]
: : : f8720499 cmp eax,0FFFFFFFEh
: < : : f872049c je PassThrough!_except_handler4+0x11b (f87204ab)
: : : :
: : : : PassThrough!_except_handler4+0x10e [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 431]:
: : : : ; 校驗scopetable 完整性(1)
: : : : f872049e mov ecx,dword ptr [esi+4]
: : : : f87204a1 add ecx,edi
: : : : f87204a3 xor ecx,dword ptr [eax+edi]
: : : : f87204a6 call PassThrough!__security_check_cookie (f8720638)
: : : :
: : : : PassThrough!_except_handler4+0x11b [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 431]:
: : : : ; 校驗scopetable 完整性(2)
: > : : f87204ab mov ecx,dword ptr [esi+0Ch]
: : : f87204ae mov edx,dword ptr [esi+8]
: : : f87204b1 add ecx,edi
: : : f87204b3 xor ecx,dword ptr [edx+edi]
: : : f87204b6 call PassThrough!__security_check_cookie (f8720638)
: : : ; 調用 lpfnHandler
: : : f87204bb mov eax,dword ptr [ebp-0Ch] ; eax = l_pCurrentScopeTableEntry
: : : f87204be mov ecx,dword ptr [eax+8] ; ecx = l_pCurrentScopeTableEntry->lpfnHandler
: : : f87204c1 mov edx,edi ; edx = pExceptionRegistration->_ebp
: : : f87204c3 call PassThrough!_EH4_TransferToHandler (f87205e8) ; 這裡不會返回! !
: : :
: : : PassThrough!_except_handler4+0x138 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 456]:
> : : f87204c8 mov edx,0FFFFFFFEh
              : : f87204cd cmp dword ptr [ebx+0Ch],edx ; cmp pExceptionRegistration->trylevel, TRYLEVEL_INVALID
              < : f87204d0 je PassThrough!_except_handler4+0xcc (f872045c)
                :
                : PassThrough!_except_handler4+0x142 [d:\5359\minkernel\crts\crtw32\misc\i386\chandler4.c @ 467]:
                : ; pExceptionRegistration->trylevel 不等於TRYLEVEL_INVALID,開始局部展開
                : f87204d2 push offset PassThrough!__security_cookie (f87220b0)
                : f87204d7 push edi ; pExceptionRegistration->_ebp
                : f87204d8 mov ecx,ebx ; ecx = pExceptionRegistration
                : f87204da call PassThrough!_EH4_LocalUnwind (f8720614)
                < f87204df jmp PassThrough!_except_handler4+0xa8 (f8720438)


  這個函數代碼不長。主要分為兩個大分支,一個分支處理異常,一個分支處理展開(參考地址f87203dc 處的test 指令)。
  處理異常的代碼負責遍歷scopetable,依次調用scopetable_entry::lpfnFilter(參考f8720419 處代碼),並針對不同的返回值做出不同的處理:
  1. 返回EXCEPTION_CONTINUE_EXECUTION,則說明異常已經被剛剛調用的lpfnFilter 修復。返回 ExceptionContinueExecution。
  2. 返回EXCEPTION_CONTINUE_SEARCH,則繼續遍歷下一個scopetable_entry。
  3. 返回EXCEPTION_EXECUTE_HANDLER,則說明當前scopetable_entry::lpfnHandler 負責處理該異常。於是調用它。
  對於展開的代碼,則直接開始局部展開,即對scopetable 進行展開。
  更具體的信息,請參考上面反彙編代碼中我附的註釋。

  有幾個小點需要說一下:
  1. 該函數多處使用scopetable 中坑內的數據進行安全檢查。這些操作對理解該函數流程沒有幫助,可以忽略。如果覺得上面代碼不純淨,可以參考我後續的附錄1《Ntfs!_except_handler3 的反彙編代碼》,這個函數流程更清晰簡單。
  2. 一旦有某scopetable_entry::lpfnFilter 返回EXCEPTION_EXECUTE_HANDLER,就會進行全局展開和局部展開。展開結束後會調用該scopetable_entry::lpfnHandler,該函數即為_except 處理域,該函數形式是一個函數,實際上只是一段不返回的代碼。即這段代碼中沒有 ret 指令。執行完整個_except 處理域後,會接著執行其後的指令,並不會返回_except_handler4。
  3. 該函數既啟動展開,又負責展開,於是會出現類似於“重入”的現象。理解的過程中容易擾亂思路。
 
  PassThrough!_except_handler4 在執行過程中可能會調用這幾個函數:
  PassThrough!_EH4_CallFilterFunc、
  PassThrough!_EH4_TransferToHandler、
  PassThrough!_EH4_GlobalUnwind、
  PassThrough!_EH4_LocalUnwind
  這幾個函數名很明白的說明了它們的功能:
  PassThrough!_EH4_CallFilterFunc 負責調用scopetable_entry::lpfnFilter;
  PassThrough!_EH4_TransferToHandler 負責調用scopetable_entry::lpfnFilter;
  PassThrough!_EH4_GlobalUnwind 負責全局展開;
  PassThrough!_EH4_LocalUnwind 負責局部展開。

  來看看_EH4_CallFilterFunc 和_EH4_TransferToHandler 的反彙編代碼,都很短。剩餘兩個展開相關的函數稍後咱們再來分析。


代碼:
    kd> uf PassThrough!_EH4_CallFilterFunc
        PassThrough!_EH4_CallFilterFunc [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 408]:
          f87205d1 push ebp
          f87205d2 push esi
          f87205d3 push edi
          f87205d4 push ebx
          f87205d5 mov ebp,edx
          f87205d7 xor eax,eax
          f87205d9 xor ebx,ebx
          f87205db xor edx,edx
          f87205dd xor esi,esi
          f87205df xor edi,edi
          f87205e1 call ecx ; lpfnFilter();
          f87205e3 pop ebx
          f87205e4 pop edi
          f87205e5 pop esi
          f87205e6 pop ebp
          f87205e7 ret

    kd> uf PassThrough!_EH4_TransferToHandler
        PassThrough!_EH4_TransferToHandler [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 450]:
          f87205e8 mov ebp,edx
          f87205ea mov esi,ecx ; esi = lpfnHandler
          f87205ec mov eax,ecx
          f87205ee xor eax,eax
          f87205f0 xor ebx,ebx
          f87205f2 xor ecx,ecx
          f87205f4 xor edx,edx
          f87205f6 xor edi,edi
          f87205f8 jmp esi ; jmp lpfnHandler



  到這裡,咱們就以PassThrough!_except_handler4 為例分析完了MSC 提供的EXCEPTION_REGISTRATION::handler 函數。這個過程中多次接觸到一個名為“展開”的概念,這就是咱們要講的第三個部分。

2011年10月7日星期五

[轉載]SEH分析筆記(X86篇)二


一、SEH 創建代碼
  這裡我沒有繼續使用上面的SimpleSeh 進行分析,而是新寫了一個簡單的SehTest 函數。

代碼:
        VOID SehTest()
        {
            ULONG ulVal = 0;

            __try // 第一個 __try 域
            {
                ulVal = 0x11111111; // 最後一位為1表示“在__try 代碼塊中”
            }
            __except(Filter_0())
            {
                ulVal = 0x11111110; // 最後一位為0表示“在__except/__finally 代碼塊中”
            }

            __try // 第二個 __try 域
            {
                ulVal = 0x22222222;

                __try // 第三個 __try 域
                {
                    ulVal = 0x33333333;

                    *((ULONG*)NULL) = ulVal; // 觸發異常
                }
                __finally
                {
                    ulVal = 0x33333330;
                }
            }
            __except(Filter_2())
            {
                ulVal = 0x22222220;
            }

            return;
        }

  反彙編代碼如下:
  (需要說明一下我的命名習慣是,"l_"前綴的變量是函數的局部變量,沒有任何前綴的變量是傳入的參數)

引用:
    kd> uf passthrough!SehTest
        ;PassThrough!SehTest [d:\workspace\code\mycode\r0\passthrough\passthrough.c @ 604]:
          f8720040 mov edi,edi
          f8720042 push ebp ; l_ExceptionRegistration->_ebp
          f8720043 mov ebp,esp
          f8720045 push 0FFFFFFFEh ; l_ExceptionRegistration->trylevel = TRYLEVEL_INVALID (-2)
          f8720047 push offset PassThrough!__safe_se_handler_table+0x8 (f8721468) ; l_ExceptionRegistration->scopetable
          f872004c push offset PassThrough!_except_handler4 (f8720390) ; l_ExceptionRegistration->handler
          f8720051 mov eax,dword ptr fs:[00000000h]
          f8720057 push eax ; _EXCEPTION_REGISTRATION::prev
          f8720058 add esp,0FFFFFFF4h ; 這里分配了0xc 字節的棧空間,其中緊貼著l_ExceptionRegistration->prev
                                            ; 的4個字節存放著l_ExceptionRegistration->xpointers
          f872005b push ebx
          f872005c push esi
          f872005d push edi
          f872005e mov eax,dword ptr [PassThrough!__security_cookie (f87220b0)]
          f8720063 xor dword ptr [ebp-8],eax ; 對scopetable 進行異或加密
          f8720066 xor eax,ebp ; 對__security_cookie 進行加密
          f8720068 push eax ; 把加密了的__security_cookie 也壓入棧中,後面用來對scopetable 進行解密
          f8720069 lea eax,[ebp-10h]
          f872006c mov dword ptr fs:[00000000h],eax ; 將l_ExceptionRegistration 掛入線程異常鍊錶中
          f8720072 mov dword ptr [ebp-18h],esp
          f8720075 mov dword ptr [ebp-1Ch],0
          f872007c mov dword ptr [ebp-4],0 ; 進入第一個__try 域,l_ExceptionRegistration->trylevel = 0
          f8720083 mov dword ptr [ebp-1Ch],11111111h
          f872008a mov dword ptr [ebp-4],0FFFFFFFEh ; 離開第一個__try 域,l_ExceptionRegistration->trylevel = TRYLEVEL_NONE (-2)
          f8720091 jmp PassThrough!SehTest+0x6a (f87200aa)
            ; ------------------------------------------------- --------------------------------
            ; 這裡有個空洞,用uf 命令是不會顯示的。範圍是 f8720093 到 f87200a9,彙編碼如下
            ;
            ; PassThrough!SehTest+0x53 [d:\workspace\code\mycode\r0\passthrough\passthrough.c @ 611]:
            ; f8720093 call PassThrough!Filter_0 (f8720010)
            ; f8720098 ret
            ;
            ; f8720099 mov esp,dword ptr [ebp-18h] ; 第一個__except 處理域
            ; f872009c mov dword ptr [ebp-1Ch],11111110h
            ; f87200a3 mov dword ptr [ebp-4],0FFFFFFFEh ; 離開第一個__try 域,l_ExceptionRegistration->trylevel = TRYLEVEL_NONE (-2)
            ; ------------------------------------------------- --------------------------------
        ;PassThrough!SehTest+0x6a [d:\workspace\code\mycode\r0\passthrough\passthrough.c @ 616]:
          f87200aa mov dword ptr [ebp-4],1 ; 進入第二個__try 域,l_ExceptionRegistration->trylevel = 1
          f87200b1 mov dword ptr [ebp-1Ch],22222222h
          f87200b8 mov dword ptr [ebp-4],2 ; 進入第三個__try 域,l_ExceptionRegistration->trylevel = 2
          f87200bf mov dword ptr [ebp-1Ch],33333333h
          f87200c6 mov eax,dword ptr [ebp-1Ch]
          f87200c9 mov dword ptr ds:[00000000h],eax ; 觸發異常
          f87200ce mov dword ptr [ebp-4],1 ; 離開第三個__try 域,l_ExceptionRegistration->trylevel = 1
          f87200d5 call PassThrough!SehTest+0x9c (f87200dc)
          f87200da jmp PassThrough!SehTest+0xa4 (f87200e4)
            ; ------------------------------------------------- --------------------------------
            ; 空洞,範圍是f87200dc 到f87200e3,彙編碼如下
            ;
            ; PassThrough!SehTest+0x9c [d:\workspace\code\mycode\r0\passthrough\passthrough.c @ 628]:
            ; f87200dc c745e430333333 mov dword ptr [ebp-1Ch],33333330h ; __finally 域
            ; ------------------------------------------------- --------------------------------
        ;PassThrough!SehTest+0xa4 [d:\workspace\code\mycode\r0\passthrough\passthrough.c @ 630]:
          f87200e4 mov dword ptr [ebp-4],0FFFFFFFEh ; 離開第二個__try 域,l_ExceptionRegistration->trylevel = TRYLEVEL_NONE (-2)
          f87200eb jmp PassThrough!SehTest+0xc4 (f8720104)
            ; ------------------------------------------------- --------------------------------
            ; 空洞,範圍是f87200ed 到f8720103,彙編碼如下
            ;
            ; PassThrough!SehTest+0xad [d:\workspace\code\mycode\r0\passthrough\passthrough.c @ 631]:
            ; f87200ed e83effffff call PassThrough!Filter_2 (f8720030)
            ; f87200f2 c3 ret
            ;
            ; f87200f3 8b65e8 mov esp,dword ptr [ebp-18h] ; 第二個__except 處理域
            ; f87200f6 c745e420222222 mov dword ptr [ebp-1Ch],22222220h
            ; f87200fd c745fcfeffffff mov dword ptr [ebp-4],0FFFFFFFEh ; 離開第三個__try 域,l_ExceptionRegistration->trylevel = TRYLEVEL_NONE (-2)
            ; ------------------------------------------------- --------------------------------
        ;PassThrough!SehTest+0xc4 [d:\workspace\code\mycode\r0\passthrough\passthrough.c @ 637]:
          f8720104 mov ecx,dword ptr [ebp-10h]
          f8720107 mov dword ptr fs:[0],ecx ; 恢復舊的EXCEPTION_REGISTRATION。即從線程異常鍊錶中摘除l_ExceptionRegistration
          f872010e pop ecx
          f872010f pop edi
          f8720110 pop esi
          f8720111 pop ebx
          f8720112 mov esp,ebp
          f8720114 pop ebp
          f8720115 ret

  來看看 scopetable 的內容:

  kd> dd f8721468
        f8721468 fffffffe 00000000 ffffffd4 00000000 < 16個字節的坑
        f8721478 [fffffffe f8720093 f8720099] [fffffffe
        f8721488 f87200ed f87200f3] [00000001 00000000
        f8721498 f87200dc] 00000000 00000000 00000000
        f87214a8 00000000 00000000 00000000 00000000
        f87214b8 00000000 00000000 00000000 00000000
        f87214c8 00000000 00000000 00000000 00000000
        f87214d8 00000000 00000000 00000000 00000000

  前16個字節是坑,坑的作用晚點再說。之後就是三個scopetable_entry,被我用大括號擴起來了。對照前面的彙編碼可以發現,scopetable_entry::lpfnFilter 和scopetable_entry::lpfnHandler 就是彙編碼中的空洞處的代碼。其中,第三個scopetable_entry::lpfnFilter 是NULL,對照代碼可以發現,是因為這是一個__try & __finally 塊,沒有lpfnFilter。

  彙編代碼很簡單,註釋裡也有詳細的分析過程了,我不再多囉嗦。只提兩點:
  1. EXCEPTION_REGISTRATION::scopetable 指針被用__security_cookie 進行了異或加密。
  2. EXCEPTION_REGISTRATION::scopetable 並不直接指向scopetable_entry 數組,在第一個scopetable_entry 之前有16 個字節的坑。後續分析中會看到,它的主要作用是幫助驗證scopetable 是否被破壞。第三個DWORD,即上文中的ffffffd4 是一個偏移量,後續的分析過程中會看得很清楚。

2011年10月6日星期四

[轉載]SEH分析筆記(X86篇)一


作 者: boxcounter
時 間: 2011-10-05,12:46:17
鏈接: http://bbs.pediy.com/showthread.php?t=140970

[不介意轉載,但請註明出處boxcounter.com
     附件裡有本文的原始稿,一樣的內容,更好的高亮和排版。分別是 html 和 rtl 格式。
     後面的部分代碼可能會因為自動換行變得很亂,需要的朋友手動複製到自己的代碼編輯器就可以正常顯示了]

  這兩天琢磨了下SEH,這裡記錄一下自己琢磨的一些心得。
  SEH 這個概念我就不囉嗦了,任何一個介紹SEH 的資料都有講。我主要記錄一些自己的理解。可能有一些概念理解的不夠清晰,有一些說法比較狹隘,歡迎看到本文的朋友一起討論、修正,非常感謝。
  首先,SEH 是針對於異常的一種處理機制,這個異常分為硬件異常和軟件異常,這裡所說的硬件異常是狹義的異常,也就是CPU 產生的異常。比如除零操作,CPU 執行除零操作時候,會自主啟動異常處理機制。軟件異常,就是程序模擬的異常,比如調用RaiseException 函數。軟件異常是可以隨意觸發的,windows 系統內部遇到問題會觸發,開發人員高興了也可以觸發。
  拋出了問題,就要有解決方案。那這麼多問題和解決方案,如何管理呢? windows 系統當仁不讓的提供了它管理方案—— SEH。我看一些資料有詳細的討論SEH 的確切含義,這裡我不參與討論,而只是簡單的理解為“系統提供的異常處理機制,以及編譯器對其進行增強的部分”。

  來說說系統提供的異常處理機制。
  windows 提供的異常處理機制實際上只是一個簡單的框架,一般情況下開發人員都不會直接用到。咱通常所用的異常處理(比如C++ 的throw、try、catch)都是編譯器在系統提供的異常處理機制上進行加工了的增強版本。這裡先拋開增強版的不提,繼續說原始版本。
  原始版本的機制很簡單:誰都可以觸發異常,誰都可以處理異常(只要它能看得見)。但是不管是觸發還是處理都得先登記。系統把這些登記信息保存在一個鍊錶裡,並且這個鍊錶保存在線程的數據結構裡。也就是說,異常所涉及的一些行為都是線程相關的。比如,線程T1 觸發的異常就只能由線程T1 來處理,其他線程根本就不知道T1 發生了什麼事,更不會狗拿耗子。
  等登記完畢後,線程就可以拋出或處理異常了,系統也可以做相應的管理工作了。 (這裡囉嗦一句,系統提供的SEH 其實是一個針對於“觸發異常-解決異常”的管理機制,系統自身是不提供任何具體異常的解決方案的。解決方案還是要由用戶自身來提供(增強版裡編譯器也會來提供解決方案,來幫“不負責”的程序猿擦屁股,這是後話))
  系統提供的管理工作簡單來說包括(但不限於):找到觸發異常的線程的異常處理鍊錶(前頭登記的那個),然後按照規則(具體的規則後續再說)對該異常進行分發,根據分發後的處理結果再進行下一步的分發或者結束處理。
  系統管理所使用的數據結構和宏:

代碼:
    #define EXCEPTION_CHAIN​​_END ((struct _EXCEPTION_REGISTRATION_RECORD * POINTER_32)-1)

    typedef enum _EXCEPTION_DISPOSITION {
        ExceptionContinueExecution,
        ExceptionContinueSearch,
        ExceptionNestedException,
        ExceptionCollidedUnwind
    } EXCEPTION_DISPOSITION;

    typedef struct _EXCEPTION_RECORD {
        DWORD ExceptionCode;
        DWORD ExceptionFlags;
        struct _EXCEPTION_RECORD *ExceptionRecord;
        PVOID ExceptionAddress;
        DWORD NumberParameters;
        ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
    } EXCEPTION_RECORD;

    typedef EXCEPTION_RECORD *PEXCEPTION_RECORD;

    typedef
    EXCEPTION_DISPOSITION
    (*PEXCEPTION_ROUTINE) (
        IN struct _EXCEPTION_RECORD *ExceptionRecord,
        IN PVOID EstablisherFrame,
        IN OUT struct _CONTEXT *ContextRecord,
        IN OUT PVOID DispatcherContext
        );

    typedef struct _EXCEPTION_REGISTRATION_RECORD {
        struct _EXCEPTION_REGISTRATION_RECORD *Next;
        PEXCEPTION_ROUTINE Handler;
    } EXCEPTION_REGISTRATION_RECORD;

    typedef EXCEPTION_REGISTRATION_RECORD *PEXCEPTION_REGISTRATION_RECORD;


  其中EXCEPTION_REGISTRATION_RECORD 結構就是登記信息。來介紹下它的成員:
  1. EXCEPTION_REGISTRATION_RECORD::Next 域指向下一個EXCEPTION_REGISTRATION_RECORD,由此構成一個異常登記信息(從字面上說,應該叫做“異常註冊記錄”更恰當)鍊錶。鍊錶中的最後一個結點會將Next 置為EXCEPTION_CHAIN​​_END,表示鍊錶到此結束。
  2. EXCEPTION_REGISTRATION_RECORD::Handler 指向異常處理函數。

  前面有簡單的說過原始版本SEH 的管理工作,這裡再根據以上列出的相關數據結構稍微詳細一點說說。
  當接收到異常後,系統找到當前線程(還記不記得,前面有說過,異常是線程相關的。系統接收到的異常就是當前正在運行的線程觸發的。其實這個說法還不准確,DPC 也會觸發異常,而它是線程無關的,這里為了方便理解,先只考慮線程)的異常鍊錶,從鍊錶中的第一個結點開始遍歷,找到一個EXCEPTION_REGISTRATION_RECORD 就調用它的Handler,並把該異常(由第一個類型為EXCEPTION_RECORD 的參數表示)傳遞給該Handler,Handler 處理並返回一個類型為EXCEPTION_DISPOSITION 的枚舉值。該返回值指示系統下一步該做什麼:
  ExceptionContinueExecution 表示:“我已修正了此異常的故障,請你從事發點重新執行,謝謝”。
  ExceptionContinueSearch 表示:“我沒有處理此異常,請你繼續搜索其他的解決方案,抱歉”。
  ExceptionNestedException 和ExceptionCollidedUnwind 這裡先不做解釋,後面會細說。
  這樣系統根據不同的返回值來繼續遍歷異常鍊錶或者回到觸發點繼續執行。

  需要說明一下,本文主要以內核模式下的異常來說,因為相比用戶模式下的異常處理流程,內核模式少了模式切換、棧切換以及反向回調等步驟。
 
  我們現在來看看詳細的內核異常流程。

  首先,CPU 執行的指令觸發了異常,CPU 改執行IDT 中KiTrap??,KiTrap?? 會調用KiDispatchException。該函數原型如下:
  VOID
  KiDispatchException (
      IN PEXCEPTION_RECORD ExceptionRecord,
      IN PKEXCEPTION_FRAME ExceptionFrame,
      IN PKTRAP_FRAME TrapFrame,
      IN KPROCESSOR_MODE PreviousMode,
      IN BOOLEAN FirstChance
      );
  其名稱明白的說明了函數的主要功能:分派異常。其實現可以參考$wrk-v1.2\base\ntos\ke\i386\exceptn.c:1033。我的筆記:
  在當前棧中分配一個CONTEXT,調用KeContextFromKframes 初始化它。
  檢查 ExceptionRecord->ExceptionCode,如果:
    是STATUS_BREAKPOINT,那麼將CONTEXT::Eip 減一;
    是KI_EXCEPTION_ACCESS_VIOLATION,那麼將檢查是否是由AtlThunk 觸發(這個小環節沒有深究),如果是觸發NX(不可執行),那麼將ExceptionRecord->ExceptionInformation [0] 置為0(貌似表示觸發操作的類型,0表示讀、1表示寫);
        如果 PreviousMode 是 KernelMode,那麼,
    如果FirstChance 為TRUE,那麼將該異常傳達給內核調試器,如果內核調試器沒有處理,那麼調用RtlDispatchException 進行處理。
    如果FirstChance 為FALSE,那麼再次將該異常傳達給內核調試器,如果內核調試器沒有處理,那麼BUGCHECK。
  如果 PreviousMode 是 UserMode,那麼,
    如果FirstChance 為TRUE,那麼將該異常傳達給內核調試器,如果內核調試器沒有處理,那麼將異常傳達給應用層調試器。如果仍然沒有處理,那麼將KTRAP_FRAME 和EXCEPTION_RECORD 拷貝到UserMode 的棧中,並設置KTRAP_FRAME::Eip 設置為ntdll!KiUserExceptionDispatcher,返回(將該異常交由應用層異常處理程序進行處理)。
    如果FirstChance 為FALSE,那麼再次將異常傳達給應用層調試器,如果仍然沒有處理,那麼調用ZwTerminateProcess 結束進程,並BUGCHECK。

  拋開應用層異常不說,我們來看PreviousMode 是KernelMode 的情況,其重點是調用RtlDispatchException 的操作。我們來看一下這個函數:
  BOOLEAN
  RtlDispatchException (
          IN PEXCEPTION_RECORD ExceptionRecord,
      IN PCONTEXT ContextRecord
      );
  它的實現可以參考$wrk-v1.2\base\ntos\rtl\i386\exdsptch.c:126。我的筆記:
  遍歷當前線程的異常鍊錶,挨個調用RtlpExecuteHandlerForException,RtlpExecuteHandlerForException 會調用異常處理函數。再根據返回值做出不同的處理:
  對於ExceptionContinueExecution,結束遍歷,返回。 (對於標記為'EXCEPTION_NONCONTINUABLE'的異常,會調用RtlRaiseException。)
  對於ExceptionContinueSearch,繼續遍歷下一個結點。
  對於ExceptionNestedException,則從指定的新異常繼續遍歷。
  只有正確處理ExceptionContinueExecution 才會返回TRUE,其他情況都返回FALSE。

  在繼續講述異常處理機制之前,咱們需要先來認識一下異常鍊錶。
  之前有提到過:系統將異常鍊錶頭保存在線程結構裡。來看看具體的數據結構:
  線程的內核數據結構體現是_ETHREAD,從它開始進入,直到咱們關注的異常鍊錶。

  kd> dt _ETHREAD
  ntdll!_ETHREAD
     +0x000 Tcb : _KTHREAD
     ... 省略之後的成員

  kd> dt _KTHREAD
  ntdll!_KTHREAD
     ... 省略的域成員
     +0x074 Teb : Ptr32 Void
     ... 省略的域成員

  Teb 成員的類型實際是 _TEB,來看看
  kd> dt _TEB
  ntdll!_TEB
     +0x000 NtTib : _NT_TIB
     ... 省略的域成員

  kd> dt _NT_TIB
  ntdll!_NT_TIB
     +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
     +0x004 StackBase : Ptr32 Void
     +0x008 StackLimit : Ptr32 Void
     +0x00c SubSystemTib : Ptr32 Void
     +0x010 FiberData : Ptr32 Void
     +0x010 Version : Uint4B
     +0x014 ArbitraryUserPointer : Ptr32 Void
     +0x018 Self : Ptr32 _NT_TIB
  _NT_TIB 的第一個域成員ExceptionList 就是異常鍊錶頭。

  但是系統不是這麼一步一步找的,而是藉助FS 寄存器來加速尋找。先來說說系統對 FS 的使用。
  在應用層,FS 寄存器“指向”當前執行線程的_TEB 結構體。在內核層,FS 寄存器“指向”另一個跟CPU 相關的結構體:_KPCR,來看看它的結構,
  nt!_KPCR
     +0x000 NtTib : _NT_TIB
     +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
     ... 省略的域成員
  與_TEB 一樣,它的第一個域成員也是_NT_TIB,只不過此時是nt!_NT_TIB,而在應用層是ntdll!_NT_TIB,但它們的結構是一樣的。

  這樣,不論在應用層還是在內核層,系統都可以使用FS:[0] 找到異常鍊錶。

  到這裡,咱們已經聊完了CPU 觸發的異常的處理流程,總結一下它的調用流程:
  CPU 檢測到異常-> KiTrap?? -> KiDispatchException -> RtlDispatchException -> RtlpExecuteHandlerForException

  這是硬件異常,咱們再來看看軟件異常。
  軟件異常跟硬件異常的處理流程非常接近,只有觸發點的不同,調用流程是:
  RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException

  後面兩個被調用的函數咱已經聊過了,主要來看看RtlRaiseException。這個函數從其名字上就能看出是用來觸發異常的。原型如下:
  VOID
  RtlRaiseException (
      IN PEXCEPTION_RECORD ExceptionRecord
  );
  其實現可以參考$wrk-v1.2\base\ntos\rtl\i386\raise.asm:71。我的筆記:
  RtlRaiseException 首先調用RtlDispatchException 分發異常,如果RtlDispatchException 成功分發(有處理函數處理了這個異常),那麼結束本函數。
  如果沒有成功分發,那麼調用ZwRaiseException 再次觸發該異常,這次傳入的異常的FirstChance 被置為FALSE。

  到這裡,系統提供的SEH 機制(本文又稱之為原始版本)大致講解完畢。咱可以回味一下:
  1. 原始版本的實現較簡單,代碼量不大,而且wrk 基本上有所有關鍵函數的實現代碼。
  2. 原始版本的功能過於簡單,實際過程中很難直接使用。整個異常處理過程無非就是遍歷異常鍊錶,挨個調用異常註冊信息的處理函數,如果其中有某個處理函數處理了該異常(返回值為ExceptionContinueExecution),那麼就從異常觸發點(如果是斷點異常,則要回退一個字節的指令(int 3 指令本身))重新執行。否則不管是整個鍊錶中沒有找到合適的處理函數(返回值為ExceptionContinueSearch),或者遍歷過程中出現問題(返回值為ExceptionNestedException),系統都會簡單粗暴的BUGCHECK。而這也帶來一個問題:
     線程運行過程中會調用很多個函數,每個函數都有可能註冊異常處理,它們提供的異常處理函數既可能處理該函數自身觸發的異常,又可能需要處理其子孫函數觸發的異常。前者還好說,自己出了問題,多少還有可能自己修復。而後者就很頭疼了,它無法了解所有其調用的子孫函數內部的實現,要想修復子孫函數觸發的異常,太困難了。而一旦沒有正確處理,或者沒人處理,系統就崩掉。這個後果太嚴重。於是實際上現實程序設計中,基本上沒有直接使用原始版本的SEH,而是使用編譯器提供的增強版本。


  下面咱們就來聊聊編譯器提供的增強版本。

  首先要說明,增強版本有很多個,不同的編譯器提供的的SEH 增強版本或多或少都有不同處。但是,他們一般都是基於windows 系統提供的原始版本進行完善的。一個典型的增強版就是微軟的編譯器(後面簡稱為MSC)裡提供的__try、__finally,__except。咱們接下來就用這個增強版作為目標進行分析。我使用的MSC 是WDK 7600.16385.1,內置的cl 的版本是15.00.30729.207,link 的版本是9.00.30729.207,測試虛擬機系統為32位Win2k3sp1 + wrk。

  咱們先看看增強版的數據結構,跟之前的原始版本有很多相似之處:

代碼:
    typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;
    struct _EXCEPTION_REGISTRATION{
        struct _EXCEPTION_REGISTRATION *prev;
        void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
        struct scopetable_entry *scopetable;
        int trylevel;
        int _ebp;
        PEXCEPTION_POINTERS xpointers;
    };
  這個EXCEPTION_REGISTRATION 在增強版中就相當於原始版本中的EXCEPTION_REGISTRATION_RECORD。可以這麼理解它:

代碼:
    struct _EXCEPTION_REGISTRATION{
        struct _EXCEPTION_REGISTRATION_RECORD ExceptionRegistrationRecord;
        struct scopetable_entry *scopetable;
        int trylevel;
        int _ebp;
        PEXCEPTION_POINTERS xpointers;
    }; // 注:本結構體只用於理解原始版和增強版的區別,實際代碼中並沒有這種形式的定義
  也就是說它沿用了老版本的註冊信息結構,只是在域成員名稱上做了些改動,把Next 改名為prev,把Handler 改為handler。除此之外,在原始版本基礎上增加了4個域成員(scopetable、trylevel、_ebp、xpointers),用來支持它的增強功能。
  需要說明的是,這結構體來源於MSC 的crt 源碼裡的exsup.inc,這個文件使用的是彙編語法,該結構體定義是從該文件的註釋中提取出來。在實際的分析過程中,發現它的定義有一些問題:最後一個域成員xpointers 實際上存放在prev 之前,也就是說,實際中__try 增強版用的結構體是這樣的:

代碼:
  
    typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;
    struct _EXCEPTION_REGISTRATION{
        PEXCEPTION_POINTERS xpointers;
        struct _EXCEPTION_REGISTRATION *prev;
        void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
        struct scopetable_entry *scopetable;
        int trylevel;
        int _ebp;
    };
  相關的宏和結構

代碼:
    TRYLEVEL_NONE equ -1
    TRYLEVEL_INVALID equ -2
  scopetable_entry
     +0x000 previousTryLevel : Uint4B
     +0x004 lpfnFilter : Ptr32 int
     +0x008 lpfnHandler : Ptr32 int

  咱們先來簡單的看一下增強版中出現的幾個新域成員。
  EXCEPTION_REGISTRATION::scopetable 是類型為scopetable_entry 的數組。
  EXCEPTION_REGISTRATION::trylevel 是數組下標,用來索引scopetable 中的數組成員。
  _ebp 是包含該_EXCEPTION_REGISTRATION 結構體的函數的棧幀指針。對於沒有FPO 優化過的函數,一開頭通常有個push ebp 的操作,_ebp 的值就是被壓入的ebp 的值,後續咱們通過代碼就再看實際的應用。
 
  按照原始版本的設計,每一對“觸發異常-處理異常”都會有一個註冊信息即EXCEPTION_REGISTRATION_RECORD。也就是說,如果按照原始的設計,每一個__try/__except(__finally) 都應該對應一個EXCEPTION_REGISTRATION。但是實際的 MSC 實現不是這樣的。
  真正的實現是:
  每個使用__try/__except(__finally) 的函數,不管其內部嵌套或反複使用多少__try/__except(__finally),都只註冊一遍,即只將一個EXCEPTION_REGISTRATION 掛入當前線程的異常鍊錶中(對於遞歸函數,每一次調用都會創建一個EXCEPTION_REGISTRATION,並掛入線程的異常鍊錶中,這是另外一回事)。
  那如何處理函數內部出現的多個__try/__except(__finally) 呢?這多個__except 代碼塊的功能可能大不相同,而註冊信息EXCEPTION_REGISTRATION 中只能提供一個處理函數handler,怎麼辦?
  MSC 的做法是,MSC 提供一個處理函數,即EXCEPTION_REGISTRATION::handler 被設置為MSC 的某個函數,而不是程序猿提供的__except 代碼塊。程序猿提供的多個__except 塊被存儲在EXCEPTION_REGISTRATION::scopetable 數組中。我們看看上面的scopetable_entry 定義,由於我沒有找到它的定義代碼,所以就貼了windbg 中dt 輸出結果。
  其中scopetable_entry::lpfnHandler 就是程序猿提供的__except 異常處理塊代碼。而lpfnFilter 就是__except 的過濾塊代碼。對於__finally 代碼塊,其lpfnFilter 被置為NULL,lpfnHandler 就是其包含的代碼塊。

  下面,我們用一小段簡單的偽代碼來詳細說明。

代碼:
    1 VOID SimpleSeh()
    2 {
    3 __try
    4 {
    5 }
    6 __except(ExceptionFilter_0(...))
    7 {
    8 ExceptCodeBlock_0;
    9 }
    10
    11 __try
    12 {
    13 __try
    14 {
    15 }
    16 __except(ExceptionFilter_1(...))
    17 {
    18 ExceptCodeBlock_1;
    19 }
    20 }
    21 __except(ExceptionFilter_2(...))
    22 {
    23 ExceptCodeBlock_2;
    24 }
    25 }
  編譯時,編譯器會為SimpleSeh 分配一個EXCEPTION_REGISTRATION 和一個擁有3個成員的scopetable 數組,並將EXCEPTION_REGISTRATION::scopetable 指向該數組(請留意:EXCEPTION_REGISTRATION::scopetable 只是一個指針,不是數組)。然後按照__try 關鍵字出現的順序,將對應的__except/__finally 都存入該數組,步驟如下:

  scopetable[0].lpfnFilter = ExceptionFilter_0;
  scopetable[0].lpfnHandler = ExceptCodeBlock_0;

  scopetable[1].lpfnFilter = ExceptionFilter_1;
  scopetable[1].lpfnHandler = ExceptCodeBlock_1;

  scopetable[2].lpfnFilter = ExceptionFilter_2;
  scopetable[2].lpfnHandler = ExceptCodeBlock_2;

  我們假象當前開始執行SimpleSeh 函數,在行14和行15之間觸發了異常。
  根據之前我們的討論的流程:RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException。
  RtlpExecuteHandlerForException 會調用註冊信息中的處理函數,即EXCEPTION_REGISTRATION::handler。該函數是由MSC 提供的,內部會依次調用scopetable 中的lpfnHandler。
  那咱們來模擬執行一下,在14和15行之前觸發異常,那應該先從scopetable[2] 的ExceptionFilter_2 開始執行,假設該函數返回EXCEPTION_CONTINUE_SEARCH。那接下來應該是scopetable[1],假設ExceptionFilter_1 也返回EXCEPTION_CONTINUE_SEARCH。那麼接下來是不是就應該輪到scopetable[0] 了?不是。咱們再看看上面的偽代碼,行14和行15之間的代碼並沒處於第一個__try/__except 的範圍中,該異常輪不到scopetable[0] 來處理。那怎麼辦? SimpleSeh 執行的過程中怎麼知道到scopetable[1] 就應該停止?
 
  MSC 是通過scopetable_entry::previousTryLevel 來解決這個問題的。上面數組的設置,完整的形式其實是這樣:

  scopetable[0].previousTryLevel = TRYLEVEL_NONE;
  scopetable[0].lpfnFilter = ExceptionFilter_0;
  scopetable[0].lpfnHandler = ExceptCodeBlock_0;

  scopetable[1].previousTryLevel = TRYLEVEL_NONE;
  scopetable[1].lpfnFilter = ExceptionFilter_1;
  scopetable[1].lpfnHandler = ExceptCodeBlock_1;

  scopetable[2].previousTryLevel = 1;
  scopetable[2].lpfnFilter = ExceptionFilter_2;
  scopetable[2].lpfnHandler = ExceptCodeBlock_2;

  scopetable_entry::previousTryLevel 包含的意思是“下一個該輪到數組下標為previousTryLevel 的單元了”。當scopetable_entry::previousTryLevel 等於TRYLEVEL_NONE(-1) 時,就會停止遍歷scopetable。

  咱再來模擬執行一遍,當14和15行之間觸發異常時,首先遍歷到scopetable[2],處理完後,找到scopetable[2].previousTryLevel,發現其值為1,那麼遍歷到scopetable[1] ,處理完後,找到scopetable[1].previousTryLevel,發現其值為TRYLEVEL_NONE,於是停止遍歷。
  好像挺圓滿的,是吧。

  咱們再假設下,如果行4和行5之間觸發了同樣的異常,執行流程應該如何。首先,執行scopetable[2],然後在scopetable[1],然後……(省略若干同上字)。停!這次的異常是在第一個__try/__except 中觸發的,輪不到scopetable[2] 來處理,怎麼辦?
  這個時候就輪到EXCEPTION_REGISTRATION::trylevel 出場了~。 EXCEPTION_REGISTRATION::trylevel 的作用就是標識從那個數組單元開始遍歷。
  與scopetable_entry::previousTryLevel 不同,EXCEPTION_REGISTRATION::trylevel 是動態變化的,也就是說,這個值在SimpleSeh 執行過程中是會經常改變的。比如,
  執行到行4和行5之間,該值就會被修改為0;
  執行到第12行,該值被修改為1;
  執行到14行,該值為2。
  這樣,當異常觸發時候,MSC 就能正確的遍歷scopetable 了。
 
  這裡我畫了一幅草圖來幫助理解:
  圖中下方是低地址端,上方是高地址端。
  (boxcounter: 這幅圖是我借助vim 的列操作手繪的,哪位朋友知道有專門畫這類文本圖的工具嗎(除了emacs 的圖操作模式,這玩意太臃腫了,我不太喜歡)?歡迎告知我ns.boxcounter[a]gmail.com。非常感謝。)



       4G |--------------------------| ...
            | ... | |
        --> |--------------------------| |
       / | ret_addr | |
     func1 | _EXCEPTION_REGISTRATION | _EXCEPTION_REGISTRATION / previousTryLevel = TRYLEVEL_NONE \
       \ | ... | | -> scopetable[0] | lpfnFilter = ExceptionFilter_0 |
        --> |--------------------------| | / \ lpfnHandler = ExceptionCodeBlock_0 /
       / | ret_addr | | / / previousTryLevel = TRYLEVEL_NONE \ <-
     func2 | _EXCEPTION_REGISTRATION | _EXCEPTION_REGISTRATION scopetable[1] | lpfnFilter = ExceptionFilter_1 | |
       \ | ... | | \ \ lpfnHandler = ExceptionCodeBlock_1 / |
        --> |--------------------------| | \ / previousTryLevel = 1 \ -^
       / | ret_addr | | -> scopetable[2] | lpfnFilter = ExceptionFilter_2 |
     func3 | _EXCEPTION_REGISTRATION | _EXCEPTION_REGISTRATION \ lpfnHandler = ExceptionCodeBlock_2 /
       \ | ... | |
        --> |--------------------------| |
       / | ret_addr | |
     func4 | _EXCEPTION_REGISTRATION | _EXCEPTION_REGISTRATION
       \ | ... | ^
        --> | | |
            | | FS:[0]
       0 -> |--------------------------|

  這幅圖中的​​函數關係是: func1 -> func2 -> func3 -> func4

  到目前位置,咱們已經熟悉了增強版的概要流程。下面結合真實代碼來分析。代碼分為三塊:SEH 創建代碼、MSC 提供的handler 函數,以及展開函數。
  在開始看分析代碼之前,先把後面分析過程中需要用的宏和結構體列出來:
 
代碼:
    #define EXCEPTION_NONCONTINUABLE 0x1 // Noncontinuable exception
    #define EXCEPTION_UNWINDING 0x2 // Unwind is in progress
    #define EXCEPTION_EXIT_UNWIND 0x4 // Exit unwind is in progress
    #define EXCEPTION_STACK_INVALID 0x8 // Stack out of limits or unaligned
    #define EXCEPTION_NESTED_CALL 0x10 // Nested exception handler call
    #define EXCEPTION_TARGET_UNWIND 0x20 // Target unwind in progress
    #define EXCEPTION_COLLIDED_UNWIND 0x40 // Collided exception handler call

    #define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | \
                              EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND)

    nt!_EXCEPTION_RECORD
       +0x000 ExceptionCode : Int4B
       +0x004 ExceptionFlags : Uint4B
       +0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD
       +0x00c ExceptionAddress : Ptr32 Void
       +0x010 NumberParameters : Uint4B
       +0x014 ExceptionInformation : [15] Uint4B

    typedef enum _EXCEPTION_DISPOSITION {
        ExceptionContinueExecution,
        ExceptionContinueSearch,
        ExceptionNestedException,
        ExceptionCollidedUnwind
    } EXCEPTION_DISPOSITION;

    // scopetable_entry::lpfnFilter 的返回值,也就是__except 過濾塊的返回值
    #define EXCEPTION_EXECUTE_HANDLER 1
    #define EXCEPTION_CONTINUE_SEARCH 0
    #define EXCEPTION_CONTINUE_EXECUTION -1

2011年10月5日星期三

[轉載]飛秋0day


Author: instruder of Code Audit Labs of vulnhunt.com
version:飛秋2.4 修正版

//閃屏振動 數據包


代碼:
00136A70 31 5F 6C 62 74 34 5F 30 23 31 33 31 23 30 30 31 1_lbt4_0#131#001
00136A80 42 32 34 35 37 30 39 42 31 23 30 23 30 23 30 3A B245709B1#0#0#0:
00136A90 31 33 31 32 34 39 31 30 31 39 3A 61 73 6D 3A 41 1312491019:asm:A
00136AA0 53 4D 4B 45 52 4E 45 2D 36 30 44 46 36 46 3A 32 SMKERNE-60DF6F:2
00136AB0 30 39 09

asm:ASMKERNE-60DF6F 是我的主機名,冒號後面是發送的控制命令
將209改成200 即可導致飛秋崩潰


同時控制命令
147
198
199
都有問題

發送200命令後,導致飛秋堆棧溢出了,但是貌似源不可控.


漏洞代碼:

代碼:
00490D66 8B8424 20400000 mov eax, dword ptr [esp+4020]
00490D6D C78424 14400000>mov dword ptr [esp+4014], 0
00490D78 3D 00400000 cmp eax, 4000
00490D7D 7E 18 jle short 00490D97//有符號比較..
00490D7F 8D4C24 04 lea ecx, dword ptr [esp+4]
00490D83 C78424 14400000>mov dword ptr [esp+4014], -1
00490D8E E8 3D3DF7FF call 00404AD0
00490D93 33C0 xor eax, eax
00490D95 EB 63 jmp short 00490DFA
00490D97 8BC8 mov ecx, eax
00490D99 56 push esi
00490D9A 8BB424 20400000 mov esi, dword ptr [esp+4020]
00490DA1 8BD1 mov edx, ecx
00490DA3 8D7C24 10 lea edi, dword ptr [esp+10]
00490DA7 6A 00 push 0
00490DA9 C1E9 02 shr ecx, 2
00490DAC F3:A5 rep movs dword ptr es:[edi], dword ptr [esi]//掛了..
00490DAE 8BCA mov ecx, edx
00490DB0 6A 00 push 0
00490DB2 83E1 03 and ecx, 3
00490DB5 6A 22 push 22
00490DB7 F3:A4 rep movs byte ptr es:[edi], byte ptr [esi]

2011年10月4日星期二

Enigma Virtual Box 3.2 released下載


version 3.20 Build 2011.10.03
- Added option Map Executable Files Using Temporary File for better compatibility with the virtual executable files
- Added Dutch_Flemish localization
- Improved handling few more Windows API functions
- Improved folders deleting for extracted files
- Bug fixed mapping dll files in x64 only
- Bug fixed running boxed file in a root drive folder
- Few minor bugs fixed

Download:
http://enigmaprotector.com/assets/files/enigmavb.exe

2011年10月3日星期一

[轉載]ObRegisterCallbacks返回0xC0000022(拒絕訪問)解決方案


作者:swlilike
最近寫驅動,發現ObRegisterCallbacks這個總是函數調用不成功,在網上查了一下,發現使用這個函數對驅動有特殊的要求,必須使用特殊的簽名才行!否則這個函數會返回0xC0000022(拒絕訪問),逆向此函數可以看到以下是有關是否加簽名的判斷,

PAGE:00000001404AA75D call MmVerifyCallbackFunction
PAGE:00000001404AA762 cmp eax, ebx
PAGE:00000001404AA764 jz short loc_1404AA7E1
PAGE:00000001404AA766
PAGE:00000001404AA766 loc_1404AA766: ; CODE XREF: ObRegisterCallbacks+11B?j
PAGE:00000001404AA766 mov rcx, [rsi+rbp+18h]
PAGE:00000001404AA76B cmp rcx, rbx
PAGE:00000001404AA76E jz short loc_1404AA779
PAGE:00000001404AA770 call MmVerifyCallbackFunction
PAGE:00000001404AA775 cmp eax, ebx
PAGE:00000001404AA777 jz short loc_1404AA7E1

開發調試時可以將
PAGE:00000001404AA764 jz short loc_1404AA7E1
PAGE:00000001404AA76E jz short loc_1404AA779
這兩行改為nop暫時使ObRegisterCallbacks調用成功,方便開發調試和暫時沒簽名的朋友

ObRegisterCallbacks這個函數是在Vista sp1之後的版本中添加的,可用來監控系統中對進線程句柄的操作,如打開進程、複製線程句柄等。

2011年10月2日星期日

ODbgScript源碼學習(二十二)

HelperFunctions.h:

#pragma once

// General functions
string ToLower(string in);//到低位
int searchx(char *SearchBuff, int BuffSize, char *SearchString, int StringLen, char wc);//搜索char
bool GetWildcardBytePositions(string bytestring, vector<int>* wildcardpos);//獲取通配符字節指針
bool RgchContains(char* container, uint containerlen, char* containee, uint containeelen);
bool is_hex(string& s);//判斷hex
bool is_hexwild(string& s);//判斷hexwild
bool is_dec(string &s);//判斷dec
bool is_float(string &s);//判斷float
DWORD rev(DWORD dw);
bool split(vector<string> &vec, const string &str, const char delim);
char GetWildcard(string &s);//獲取通配符
int Str2Rgch(string &s, char* arr, uint size);//轉換字符串到rgch
int Str2RgchWithWC(string &s, char* arr, uint size, char wc);//轉換字符串到rgchWithWC
string trim(const string& sData);//去除前後空格字符
bool UnquoteString(string& s, char cstart, char cend);//結束字符串
void MsgBox(string sMsg, string sTitle);//消息框
void DbgMsg(int n, char* title);//調試消息
void DbgMsg(int n,string title="");
void DbgMsgHex(int n, char* title);
void DbgMsgHex(int n,string title="");
int Str2Hex(string &s, string &dst);//字符串轉換為Hex
int Str2Hex(string &s, string &dst, ulong size);
long double strtof(string &s);
int Int2Hex(DWORD dw, string &dst);//整型到hex
void ReplaceString(string &s, char* what, char* with);//替換字符串
string CleanString(string &s);//清除字符串
string Str2Unicode(char* s,ulong len);//轉換字符串到Unicode
string Str2Unicode(string &s);

bool ESPRun(void);//ESP運行

HWND hwndOllyDbg();//ollydbg句柄
HINSTANCE hinstModule();//模塊句柄
HWND FindHandle(DWORD dwThreadId, string wdwClass, long x, long y);//尋找句柄

// PE Dumper
bool SaveDump(string fileName, DWORD ep);//保存轉儲
bool GetPEInfo(DWORD ep);//獲取pe信息

DWORD  resizeDW(DWORD dw, DWORD size);//重設大小雙字節
void resizeSTR(string &str, DWORD size);//重設大小字符串

string StrLastError(void);//最後錯誤
LARGE_INTEGER MyGetTickCount(ULONGLONG oldValue, bool bUseTickCount=0);//獲取Tick數