显示标签为“逆向”的博文。显示所有博文
显示标签为“逆向”的博文。显示所有博文

2011年10月11日星期二

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


附錄2 《nt!__local_unwind2 的反彙編代碼》


代碼:
      kd> uf __local_unwind2
      nt!__local_unwind2 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 205]:
        8087257b push ebx
        8087257c push esi
        8087257d push edi
        8087257e mov eax,dword ptr [esp+10h] ; eax = pExceptionRegistration
        80872582 push ebp
        80872583 push eax
        80872584 push 0FFFFFFFEh
        80872586 push offset nt!__unwind_handler (80872540)
        8087258b push dword ptr fs:[0]
        80872592 mov dword ptr fs:[0],esp ; __local_unwind2 自身也會構建_EXCEPTION_REGISTRATION

      nt!__local_unwind2+0x1e [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 222]:
> 80872599 mov eax,dword ptr [esp+24h] ; eax = pExceptionRegistration
: 8087259d mov ebx,dword ptr [eax+8] ; ebx = pExceptionRegistration->scopetable
: 808725a0 mov esi,dword ptr [eax+0Ch] ; esi = pExceptionRegistration->trylevel
: 808725a3 cmp esi,0FFFFFFFFh ; cmp pExceptionRegistration->trylevel, TRYLEVEL_NONE
:< 808725a6 je nt!_NLG_Return2+0x2 (808725dd)
::
:: nt!__local_unwind2+0x2d [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 228]:
:: 808725a8 cmp dword ptr [esp+28h],0FFFFFFFFh
::< 808725ad je nt!__local_unwind2+0x3a (808725b5)
:::
::: nt!__local_unwind2+0x34 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 230]:
::: 808725af cmp esi,dword ptr [esp+28h]
:::< 808725b3 jbe nt!_NLG_Return2+0x2 (808725dd)
::::
:::: nt!__local_unwind2+0x3a [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 234]:
::>: 808725b5 lea esi,[esi+esi*2]
:: : 808725b8 mov ecx,dword ptr [ebx+esi*4] ; move ecx, [pExceptionRegistration->scopetable[i].previousTryLevel]
:: : 808725bb mov dword ptr [esp+8],ecx ; 這個esp+8 只寫沒讀,什麼情況?
:: : 808725bf mov dword ptr [eax+0Ch],ecx ; pExceptionRegistration->trylevel = ecx
:: : 808725c2 cmp dword ptr [ebx+esi*4+4],0 ; cmp pExceptionRegistration->scopetable->lpfnFilter, NULL
:: :< 808725c7 jne nt!_NLG_Return2 (808725db)
:: ::
:: :: nt!__local_unwind2+0x4e [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 243]:
:: :: ; RegistrationPointer->scopetable->lpfnFilter 等於NULL,即這裡是__try & __finally 的組合
:: :: 808725c9 push 101h
:: :: 808725ce mov eax,dword ptr [ebx+esi*4+8]
:: :: 808725d2 call nt!_NLG_Notify (80872617) ; 這個函數對理解SEH 不重要,可以暫時忽略
:: :: 808725d7 call dword ptr [ebx+esi*4+8] ; pExceptionRegistration->scopetable->lpfnHandler()
:: ::
:: :: nt!_NLG_Return2 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 251]:
<: :> 808725db jmp nt!__local_unwind2+0x1e (80872599) ; 循環
 : :
 : : nt!_NLG_Return2+0x2 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup.asm @ 253]:
 > > 808725dd pop dword ptr fs:[0]
        808725e4 add esp,10h
        808725e7 pop edi
        808725e8 pop esi
        808725e9 pop ebx
        808725ea ret

2011年10月10日星期一

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


附錄1 《Ntfs!_except_handler3 的反彙編代碼》


代碼:
         kd> uf Ntfs!_except_handler3
         ;nt!_except_handler3 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 172]:
           80872c00 push ebp
           80872c01 mov ebp,esp
           80872c03 sub esp,8
           80872c06 push ebx
           80872c07 push esi
           80872c08 push edi
           80872c09 push ebp
           80872c0a cld
           80872c0b mov ebx,dword ptr [ebp+0Ch] ; pExceptionRegistration
           80872c0e mov eax,dword ptr [ebp+8] ; pExceptionRecord
           80872c11 test dword ptr [eax+4],6 ; test pExceptionRecord->ExceptionFlags, (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)
< 80872c18 jne nt!_except_handler3+0xc9 (80872cc9)
:
: ;nt!_except_handler3+0x1e [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 202]:
: ; ebp-8 和ebp-4 是一個類型為PEXCEPTION_POINTERS 的結構體,稱之為l_ExceptionPointers
: 80872c1e mov dword ptr [ebp-8],eax ; l_ExceptionPointers->ExceptionRecord = pExceptionRecord
: 80872c21 mov eax,dword ptr [ebp+10h] ; eax = pContext
: 80872c24 mov dword ptr [ebp-4],eax ; l_ExceptionPointers->ContextRecord = pContext
: 80872c27 lea eax,[ebp-8] ; eax = lException
: 80872c2a mov dword ptr [ebx-4],eax ; ebx-4 指向pExceptionRegistration 所在棧上類型為PEXCEPTION_POINTERS 的變量
: ; 具體棧的構造形式請參考當時建立pExceptionRegistration 的代碼
: ; 這裡是賦值給該PEXCEPTION_POINTERS 變量,以提供給GetExceptionInformation 和GetExceptionCode 使用
: 80872c2d mov esi,dword ptr [ebx+0Ch] ; esi = pExceptionRegistration->trylevel
: 80872c30 mov edi,dword ptr [ebx+8] ; edi = pExceptionRegistration->scopetable
: 80872c33 push ebx
: 80872c34 call nt!_ValidateEH3RN (8087cde8)
: 80872c39 add esp,4
: 80872c3c or eax,eax
:< 80872c3e je nt!_except_handler3+0xbb (80872cbb)
::
:: ;nt!_except_handler3+0x40 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 218]:
:: > 80872c40 cmp esi,0FFFFFFFFh ; cmp pExceptionRegistration->trylevel, TRYLEVEL_NONE
::< : 80872c43 je nt!_except_handler3+0xc2 (80872cc2)
::: :
::: : ;nt!_except_handler3+0x45 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 220]:
::: : 80872c45 lea ecx,[esi+esi*2] ; esi *= 3; 下面要將eis*4,總共esi*12,這是因為scopetable_entry 大小是12
::: : 80872c48 mov eax,dword ptr [edi+ecx*4+4] ; eax = pExceptionRegistration->scopetable[i].lpfnFilter
::: : 80872c4c or eax,eax
:::< : 80872c4e je nt!_except_handler3+0xa9 (80872ca9) ; lpfnFilter 為NULL 則跳轉
:::: :
:::: : ;nt!_except_handler3+0x50 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 226]:
:::: : 80872c50 push esi
:::: : 80872c51 push ebp
:::: : 80872c52 lea ebp,[ebx+10h] ; ebp = pExceptionRegistration->_ebp
:::: : 80872c55 xor ebx,ebx
:::: : 80872c57 xor ecx,ecx
:::: : 80872c59 xor edx,edx
:::: : 80872c5b xor esi,esi
:::: : 80872c5d xor edi,edi
:::: : 80872c5f call eax ; pExceptionRegistration->scopetable[i].lpfnFilter()
:::: : 80872c61 pop ebp
:::: : 80872c62 pop esi
:::: : 80872c63 mov ebx,dword ptr [ebp+0Ch] ; ebx = pExceptionRegistration
:::: : 80872c66 or eax,eax
::::< : 80872c68 je nt!_except_handler3+0xa9 (80872ca9) ; EXCEPTION_CONTINUE_SEARCH
::::: :
::::: : ;nt!_except_handler3+0x6a [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 245]:
::::: : ; 如果lpfnFilter 返回EXCEPTION_CONTINUE_EXECUTION,跳過下面的展開操作
:::::<: 80872c6a js nt!_except_handler3+0xb4 (80872cb4) ; EXCEPTION_CONTINUE_EXECUTION
:::::::
::::::: ;nt!_except_handler3+0x6c [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 249]:
::::::: ; lpfnFilter 返回EXCEPTION_EXECUTE_HANDLER,開始展開
::::::: 80872c6c mov edi,dword ptr [ebx+8] ; edi = pExceptionRegistration->scopetable
::::::: 80872c6f push ebx
::::::: 80872c70 call nt!__global_unwind2 (80872520)
::::::: 80872c75 add esp,4
:::::::
::::::: 80872c78 lea ebp,[ebx+10h] ; ebp = pExceptionRegistration->_ebp
::::::: 80872c7b push esi ; 展開到當前trylevel 為止(不包含本scopetable_entry)
::::::: 80872c7c push ebx
::::::: 80872c7d call nt!__local_unwind2 (8087257b)
::::::: 80872c82 add esp,8
:::::::
::::::: 80872c85 lea ecx,[esi+esi*2]
::::::: 80872c88 push 1
::::::: 80872c8a mov eax,dword ptr [edi+ecx*4+8] ; pExceptionRegistration->scopetable[i].lpfnHandler
::::::: 80872c8e call nt!_NLG_Notify (80872617)
::::::: 80872c93 mov eax,dword ptr [edi+ecx*4] ;
::::::: 80872c96 mov dword ptr [ebx+0Ch],eax ; pExceptionRegistration->trylevel = RegistrationPointer->scopetable[i].previousTryLevel
::::::: 80872c99 mov eax,dword ptr [edi+ecx*4+8] ; pExceptionRegistration->scopetable[i].lpfnHandler
::::::: 80872c9d xor ebx,ebx
::::::: 80872c9f xor ecx,ecx
::::::: 80872ca1 xor edx,edx
::::::: 80872ca3 xor esi,esi
::::::: 80872ca5 xor edi,edi
::::::: 80872ca7 call eax ; pExceptionRegistration->scopetable[i].lpfnHandler(); 這裡不會返回的! !
:::::::
::::::: ;nt!_except_handler3+0xa9 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 285]:
::::::: ; 找到scopetable 中的下一個scopetable_entry,繼續循環
:::>>:: 80872ca9 mov edi,dword ptr [ebx+8]
::: :: 80872cac lea ecx,[esi+esi*2]
::: :: 80872caf mov esi,dword ptr [edi+ecx*4]
::: :< 80872cb2 jmp nt!_except_handler3+0x40 (80872c40)
::: :
::: : ;nt!_except_handler3+0xb4 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 291]:
::: > 80872cb4 mov eax,0 ; eax = ExceptionContinueExecution (0)
::: < 80872cb9 jmp nt!_except_handler3+0xde (80872cde)
::: :
::: : ;nt!_except_handler3+0xbb [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 295]:
:>: : 80872cbb mov eax,dword ptr [ebp+8]
: : : 80872cbe or dword ptr [eax+4],8
: : :
: : : ;nt!_except_handler3+0xc2 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 298]:
: > : 80872cc2 mov eax,1 ; eax = ExceptionContinueSearch (1)
: :< 80872cc7 jmp nt!_except_handler3+0xde (80872cde)
: ::
> :: ;nt!_except_handler3+0xc9 [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 302]:
      :: ; 設置了(EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND),開始展開
      :: 80872cc9 push ebp
      :: 80872cca lea ebp,[ebx+10h] ;ebp = pExceptionRegistration->_ebp
      :: 80872ccd push 0FFFFFFFFh
      :: 80872ccf push ebx
      :: 80872cd0 call nt!__local_unwind2 (8087257b)
      :: 80872cd5 add esp,8
      :: 80872cd8 pop ebp
      :: 80872cd9 mov eax,1 eax = ExceptionContinueSearch (1)
      ::
      :: ;nt!_except_handler3+0xde [d:\dnsrv\base\crts\crtw32\misc\i386\exsup3.asm @ 313]:
      >> 80872cde pop ebp
           80872cdf pop edi
           80872ce0 pop esi
           80872ce1 pop ebx
           80872ce2 mov esp,ebp
           80872ce4 pop ebp
           80872ce5 ret

2011年10月9日星期日

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


三、展開 (unwind)
  為了說明這個概念,需要先回顧下異常發生後的處理流程。

  我們假設一系列使用SEH 的函數調用流程:
  func1 -> func2 -> func3。在 func3 執行的過程中觸發了異常。

  看看分發異常流程RtlRaiseException -> RtlDispatchException -> RtlpExecuteHandlerForException
  RtlDispatchException 會遍歷異常鍊錶,對每個EXCEPTION_REGISTRATION 都調用RtlpExecuteHandlerForException。
  RtlpExecuteHandlerForException 會調用EXCEPTION_REGISTRATION::handler,也就是PassThrough!_except_handler4。如咱們上面分析,該函數內部遍歷EXCEPTION_REGISTRATION::scopetable,如果遇到有scopetable_entry::lpfnFilter 返回EXCEPTION_EXECUTE_HANDLER,那麼scopetable_entry::lpfnHandler 就會被調用,來處理該異常。
  因為lpfnHandler 不會返回到PassThrough!_except_handler4,於是執行完lpfnHandler 後,就會從lpfnHandler 之後的代碼繼續執行下去。也就是說,假設func3 中觸發了一個異常,該異常被func1 中的__except 處理塊處理了,那__except 處理塊執行完畢後,就從其後的指令繼續執行下去,即異常處理完畢後,接著執行的就是func1 的代碼。不會再回到func2 或者func3,這樣就有個問題,func2 和func3 中佔用的資源怎麼辦?這些資源比如申請的內存是不會自動釋放的,豈不是會有資源洩漏問題?

  這就需要用到“展開”了。
  說白了,所謂“展開”就是進行清理。 (注:這裡的清理主要包含動態分配的資源的清理,棧空間是由func1 的“mov esp,ebp” 這類操作順手清理的。當時我被“誰來清理棧空間”這個問題困擾了很久… …)
 
  那這個展開工作由誰來完成呢?由func1 來完成肯定不合適,畢竟func2 和func3 有沒有申請資源、申請了哪些資源,func1 無從得知。於是這個展開工作還得要交給func2 和func3 自己來完成。

  展開分為兩種:“全局展開”和“局部展開”。
  全局展開是指針對異常鍊錶中的某一段,局部展開針對指定EXCEPTION_REGISTRATION。用上面的例子來講,局部展開就是針對func3 或func2 (某一個函數)內部進行清理,全局展開就是func2 和func3 的局部清理的總和。再歸納一下,局部展開是指具體某一函數內部的清理,而全局展開是指,從異常觸發點(func3)到異常處理點(func1)之間所有函數(包含異常觸發點func3)的局部清理的總和。

  來看反彙編代碼:

代碼:
    kd> uf PassThrough!_EH4_GlobalUnwind
        PassThrough!_EH4_GlobalUnwind [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 485]:
          f87205fa push ebp
          f87205fb mov ebp,esp
          f87205fd push ebx
          f87205fe push esi
          f87205ff push edi
          f8720600 push 0 ; pReturnValue
          f8720602 push 0 ; pExceptionRecord
          f8720604 push offset PassThrough!_EH4_GlobalUnwind+0x15 (f872060f) ; pReturnEip
          f8720609 push ecx ; pExceptionRegistration
          f872060a call PassThrough!RtlUnwind (f8720678)
          f872060f pop edi
          f8720610 pop esi
          f8720611 pop ebx
          f8720612 pop ebp
          f8720613 ret


  RtlUnwind 的原型:
        VOID RtlUnwind(
            PEXCEPTION_REGISTRATION pExceptionRegistration
            PVOID pReturnEip
            PEXCEPTION_RECORD pExceptionRecord,
            PVOID pReturnValue
        );


代碼:
    kd> uf PassThrough!RtlUnwind ; 提示:此函數wrk 提供了實現源碼
                  nt!RtlUnwind [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 423]:
                    80867358 push ebp
                    80867359 mov ebp,esp
                    8086735b sub esp,37Ch
                    80867361 mov eax,dword ptr [nt!__security_cookie (80895388)]
                    80867366 push esi
                    80867367 mov esi,dword ptr [ebp+10h] ; esi = pExceptionRecord
                    8086736a mov dword ptr [ebp-4],eax
                    8086736d push edi
                    8086736e lea eax,[ebp-2D8h] ; [ebp-2D8h] = l_pHighLimit
                    80867374 push eax
                    80867375 lea eax,[ebp-2D4h] ; [ebp-2D4h] = l_pLowLimit
                    8086737b push eax
                    8086737c call nt!RtlpGetStackLimits (80887cdc)
                    80867381 xor edi,edi
                    80867383 cmp esi,edi ; pExceptionRecord 是否為NULL
< 80867385 jne nt!RtlUnwind+0x5a (808673b2)
:
: nt!RtlUnwind+0x2f [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 452]:
: 80867387 mov eax,dword ptr [ebp+4]
: 8086738a lea esi,[ebp-37Ch] ; ebp-37Ch 是局部變量l_ExceptionRecord
: 80867390 mov dword ptr [ebp-37Ch],0C0000027h ; l_ExceptionRecord.ExceptionCode = STATUS_UNWIND
: 8086739a mov dword ptr [ebp-378h],edi ; l_ExceptionRecord.ExceptionFlags = 0
: 808673a0 mov dword ptr [ebp-374h],edi ; l_ExceptionRecord.ExceptionRecord = NULL
: 808673a6 mov dword ptr [ebp-370h],eax ; l_ExceptionRecord.ExceptionAddress = ret_addr (PassThrough!RtlUnwind 執行完畢後的返回地址)
: 808673ac mov dword ptr [ebp-36Ch],edi ; l_ExceptionRecord.ExceptionInformation[0] = 0;
:
: nt!RtlUnwind+0x5a [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 462]:
> 808673b2 cmp dword ptr [ebp+8],edi ; pExceptionRegistration 是否為NULL
 < 808673b5 je nt!RtlUnwind+0x65 (808673bd)
 :
 : nt!RtlUnwind+0x5f [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 463]:
 : 808673b7 or dword ptr [esi+4],2 ; l_ExceptionRecord.ExceptionFlags |= EXCEPTION_UNWINDING (0x2)
 :< 808673bb jmp nt!RtlUnwind+0x69 (808673c1)
 ::
 :: nt!RtlUnwind+0x65 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 466]:
 >: 808673bd or dword ptr [esi+4],6 ; l_ExceptionRecord.ExceptionFlags |= EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND (0x2 | 0x4)
  :
  : nt!RtlUnwind+0x69 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 466]:
  > 808673c1 push ebx
                    808673c2 lea eax,[ebp-2D0h] ; ebp-2D0 是局部變量l_Context
                    808673c8 push eax
                    808673c9 mov dword ptr [ebp-2D0h],10007h ; lContext.ContextFlags = CONTEXT_i386 | CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS (0x10000 | 0x1 | 0x2 | 0x3)

                    808673d3 call nt!RtlpCaptureContext (80887c50)
                    808673d8 mov eax,dword ptr [ebp+14h] ; eax = ReturnValue
                    808673db add dword ptr [ebp-20Ch],10h ; -20C = -2D0+C4, lContext.Esp += 0x10
                    808673e2 mov dword ptr [ebp-220h],eax ; -220 = -2D0+B0, lContext.Eax = ReturnValue
                    808673e8 call nt!RtlpGetRegistrationHead (80887d04)
                    808673ed mov ebx,eax ; ebx = 異常鍊錶頭,這裡暫命名為l_pExceptionRegistration
< 808673ef jmp nt!RtlUnwind+0x1d1 (80867529)
:
: nt!RtlUnwind+0x9c [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 500]:
: > 808673f4 cmp ebx,dword ptr [ebp+8] ; cmp l_pExceptionRegistration,pExceptionRegistration
:< : 808673f7 jne nt!RtlUnwind+0xb0 (80867408)
:: :
:: : nt!RtlUnwind+0xa1 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 501]:
:: : ; pExceptionRegistration 等於l_pExceptionRegistration,說明展開完畢
:: : 808673f9 push edi ; TestAlert = FALSE
:: : 808673fa lea eax,[ebp-2D0h] ; eax = &l_Context
:: : 80867400 push eax
:: : 80867401 call nt!ZwContinue (8082c0b8) ; 這裡不會返回
::< : 80867406 jmp nt!RtlUnwind+0xe6 (8086743e)
::: :
::: : nt!RtlUnwind+0xb0 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 509]:
:>: : 80867408 cmp dword ptr [ebp+8],edi ; cmp pExceptionRegistration, NULL
: :< : 8086740b je nt!RtlUnwind+0xe6 (8086743e)
: :: :
: :: : nt!RtlUnwind+0xb5 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 509]:
: :: : 8086740d cmp dword ptr [ebp+8],ebx ; cmp pExceptionRegistration,l_pExceptionRegistration
: ::< : 80867410 jae nt!RtlUnwind+0xe6 (8086743e)
: ::: :
: ::: : nt!RtlUnwind+0xba [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 514]:
: ::: : ; pExceptionRegistration 在l_pExceptionRegistration 之下,即pExceptionRegistration 超出了異常鍊錶的棧範圍
: ::: : 80867412 lea eax,[ebp-32Ch] ; ebp-32Ch 是局部變量l_ExceptionRecord
: ::: : 80867418 push eax
: ::: : 80867419 mov dword ptr [ebp-32Ch],0C0000029h ; l_ExceptionRecord.ExceptionCode = STATUS_INVALID_UNWIND_TARGET
: ::: : 80867423 mov dword ptr [ebp-328h],1 ; l_ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE (1)
: ::: : 8086742d mov dword ptr [ebp-324h],esi ; l_ExceptionRecord.ExceptionRecord = &l_ExceptionRecord
: ::: : 80867433 mov dword ptr [ebp-31Ch],edi ; l_ExceptionRecord.NumberParameters = 0
: ::: : 80867439 call nt!RtlRaiseException (80887a94)
: ::: :
: ::: : nt!RtlUnwind+0xe6 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 530]:
: >>> : 8086743e cmp ebx,dword ptr [ebp-2D4h] ; cmp l_pExceptionRegistration, l_pLowLimit
: : 80867444 lea edi,[ebx+8] ; edi = l_pExceptionRegistration->scopetable_entry
: < : 80867447 jb nt!RtlUnwind+0x162 (808674ba) ; l_pExceptionRegistration 低於線程棧底,跳到錯誤處理
: : :
: : : nt!RtlUnwind+0xf1 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 530]:
: : : 80867449 cmp edi,dword ptr [ebp-2D8h] ; cmp l_pExceptionRegistration->scopetable_entry,l_pHighLimit
: :< : 8086744f ja nt!RtlUnwind+0x162 (808674ba) ; l_pExceptionRegistration 高於線程棧頂,跳到錯誤處理
: :: :
: :: : nt!RtlUnwind+0xf9 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 530]:
: :: : ; l_pExceptionRegistration 處於合法棧中,檢查它是否正確對齊
: :: : 80867451 test bl,3 ; 檢查l_pExceptionRegistration 是否4字節對齊
: ::< : 80867454 jne nt!RtlUnwind+0x1a2 (808674fa) ; 沒對齊,跳到錯誤處理
: ::: :
: ::: : nt!RtlUnwind+0x102 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 580]:
: ::: : ; 進行局部展開
: ::: : 8086745a push dword ptr [ebx+4] ; l_pExceptionRegistration->Handler
: ::: : 8086745d lea eax,[ebp-2DCh] ; 這裡是局部變量l_Dis​​patchContext
: ::: : 80867463 push eax ;
: ::: : 80867464 lea eax,[ebp-2D0h] ; eax = &l_Context
: ::: : 8086746a push eax ; l_Context
: ::: : 8086746b push ebx ; l_pExceptionRegistration
: ::: : 8086746c push esi ; &l_ExceptionRecord
: ::: : 8086746d call nt!RtlpExecuteHandlerForUnwind (80887b54) ; 內部調用l_pExceptionRegistration->Handler
: ::: : 80867472 dec eax
: :::< : 80867473 je nt!RtlUnwind+0x156 (808674ae) ; 如果返回ExceptionContinueSearch 則跳轉
: :::: :
: :::: : nt!RtlUnwind+0x11d [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 586]:
: :::: : 80867475 dec eax
: :::: : 80867476 dec eax
: :::: < : 80867477 je nt!RtlUnwind+0x150 (808674a8) ; 如果返回ExceptionCollidedUnwind 則跳轉
: :::: : :
: :::: : : nt!RtlUnwind+0x121 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 621]:
: :::: : : ; 返回ExceptionContinueSearch 和ExceptionCollidedUnwind 之外的非法值
: :::: : : 80867479 and dword ptr [ebp-31Ch],0 ; l_ExceptionRecord.NumberParameters = 0
: :::: : : 80867480 lea eax,[ebp-32Ch] ; eax = &l_ExceptionRecord
: :::: : : 80867486 push eax
: :::: : : 80867487 mov dword ptr [ebp-32Ch],0C0000026h ; l_ExceptionRecord.ExceptionCode = STATUS_INVALID_DISPOSITION
: :::: : : 80867491 mov dword ptr [ebp-328h],1 ; l_ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE (1)
: :::: : : 8086749b mov dword ptr [ebp-324h],esi ; l_ExceptionRecord.ExceptionRecord = &l_ExceptionRecord
: :::: : : 808674a1 call nt!RtlRaiseException (80887a94)
: ::::<: : 808674a6 jmp nt!RtlUnwind+0x156 (808674ae)
: :::::: :
: :::::: : nt!RtlUnwind+0x150 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 608]:
: :::::: : ; RtlpExecuteHandlerForUnwind 返回ExceptionCollidedUnwind,從l_Dis​​patchContext.RegistrationPointer 繼續處理
: :::::> : 808674a8 mov ebx,dword ptr [ebp-2DCh] ; l_pExceptionRegistration = l_Dis​​patchContext.RegistrationPointer
: ::::: :
: ::::: : nt!RtlUnwind+0x156 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 630]:
: ::::: : ; 局部展開完畢,從異常鍊錶中摘除l_pExceptionRegistration
: :::>> : 808674ae mov eax,ebx ; eax = l_pExceptionRegistration
: ::: : 808674b0 mov ebx,dword ptr [ebx] ; l_pExceptionRegistration = l_pExceptionRegistration.prev
: ::: : 808674b2 push eax
: ::: : 808674b3 call nt!RtlpUnlinkHandler (80887c10) ; 摘除
: :::< : 808674b8 jmp nt!RtlUnwind+0x1cf (80867527)
: :::: :
: :::: : nt!RtlUnwind+0x162 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 540]:
: :::: : ; l_pExceptionRegistration 並不處在當前線程棧中,這種情況不一定是出錯,有可能當前正在執行DPC。
: :::: : ; 當時還需要檢查是否對齊,不對齊就一定是出錯
: >>:: : 808674ba test bl,3 ; 檢查l_pExceptionRegistration 是否4字節對齊
: ::< : 808674bd jne nt!RtlUnwind+0x1a2 (808674fa)
: ::: :
: ::: : nt!RtlUnwind+0x167 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 540]:
: ::: : ; 如果當前正在執行DPC,那IRQL 一定不得低於DISPATCH_LEVEL(難道不是應該一定等於DISPATCH_LEVEL ?)
: ::: : 808674bf call dword ptr [nt!_imp__KeGetCurrentIrql (8080102c)]
: ::: : 808674c5 cmp al,2 ; DISPATCH_LEVEL (2)
: :::< : 808674c7 jb nt!RtlUnwind+0x1a2 (808674fa) ; 低於DISPATCH_LEVEL,跳到錯誤處理點
: :::: :
: :::: : nt!RtlUnwind+0x171 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 542]:
: :::: : 808674c9 mov eax,dword ptr fs:[00000020h] ; eax = 當前CPU 的KPCR::Prcb
: :::: : 808674cf cmp byte ptr [eax+95Ah],0 ; KPCR::Prcb->DpcRoutineActive (BOOLEAN 類型)
: :::: : 808674d6 mov ecx,dword ptr [eax+948h] ; ecx = KPCR::Prcb->DpcStack
: ::::< : 808674dc je nt!RtlUnwind+0x1a2 (808674fa) ; 當前不是在執行DPC,跳到錯誤處理點
: ::::: :
: ::::: : nt!RtlUnwind+0x186 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 547]:
: ::::: : ; KPCR::Prcb->DpcRoutineActive 為TRUE,當前異常是由DpcRoutine 觸發
: ::::: : 808674de cmp edi,ecx ; 比較l_pExceptionRegistration->scopetable_entry 和DPC 棧頂
: :::::< : 808674e0 ja nt!RtlUnwind+0x1a2 (808674fa) ; 高出DPC 棧頂,跳到錯誤處理點
: :::::: :
: :::::: : nt!RtlUnwind+0x18a [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 547]:
: :::::: : 808674e2 lea eax,[ecx-3000h] ; eax = KPCR::Prcb->DpcStack - KERNEL_STACK_SIZE, 即DPC 棧底
: :::::: : 808674e8 cmp ebx,eax ; 比較l_pExceptionRegistration 和DPC 棧底
: ::::::< : 808674ea jb nt!RtlUnwind+0x1a2 (808674fa) ; 低於DPC 棧底,跳到錯誤處理點
: ::::::: :
: ::::::: : nt!RtlUnwind+0x194 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 555]:
: ::::::: : 808674ec mov dword ptr [ebp-2D8h],ecx ; l_pHighLimit = DPC 棧頂
: ::::::: : 808674f2 mov dword ptr [ebp-2D4h],eax ; l_pLowLimit = DPC 棧底
: :::::::<: 808674f8 jmp nt!RtlUnwind+0x1cf (80867527)
: :::::::::
: ::::::::: nt!RtlUnwind+0x1a2 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 564]:
: ::::::::: ; 棧錯誤
: >:>>>>>:: 808674fa and dword ptr [ebp-31Ch],0
: : :: 80867501 lea eax,[ebp-32Ch] ; eax = &l_ExceptionRecord
: : :: 80867507 push eax
: : :: 80867508 mov dword ptr [ebp-32Ch],0C0000028h ; l_ExceptionRecord.ExceptionCode = STATUS_BAD_STACK
: : :: 80867512 mov dword ptr [ebp-328h],1 ; l_ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE (1)
: : :: 8086751c mov dword ptr [ebp-324h],esi ; l_ExceptionRecord.ExceptionRecord = &l_ExceptionRecord
: : :: 80867522 call nt!RtlRaiseException (80887a94)
: : ::
: : :: nt!RtlUnwind+0x1cf [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 493]:
: > >: 80867527 xor edi,edi
: :
: : nt!RtlUnwind+0x1d1 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 493]:
> : 80867529 cmp ebx,0FFFFFFFFh ; cmp l_pExceptionRegistration, EXCEPTION_CHAIN​​_END
               < 8086752c jne nt!RtlUnwind+0x9c (808673f4)
                
                  nt!RtlUnwind+0x1da [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 647]:
                    80867532 cmp dword ptr [ebp+8],0FFFFFFFFh ; cmp pExceptionRegistration, EXCEPTION_CHAIN​​_END
                    80867536 pop ebx
                    80867537 lea eax,[ebp-2D0h] ; eax = &l_Context
                    8086753d push edi ; bTestAlert = FALSE 或bFirstChance = FALSE
                    8086753e push eax
< 8086753f jne nt!RtlUnwind+0x1f0 (80867548)
:
: nt!RtlUnwind+0x1e9 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 656]:
: ; 展開整個異常鍊錶
: 80867541 call nt!ZwContinue (8082c0b8)
:< 80867546 jmp nt!RtlUnwind+0x1f6 (8086754e)
::
:: nt!RtlUnwind+0x1f0 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 667]:
:: ; pExceptionRegistration 等於EXCEPTION_CHAIN​​_END。
:: ; 沒有理解這裡觸發異常是為什麼,但是不妨礙分析異常處理流程。
>: 80867548 push esi ; &l_ExceptionRecord
 : 80867549 call nt!ZwRaiseException (8082ccd4)
 :
 : nt!RtlUnwind+0x1f6 [c:\wrk\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c @ 671]:
 > 8086754e mov ecx,dword ptr [ebp-4]
                    80867551 pop edi
                    80867552 pop esi
                    80867553 call nt!__security_check_cookie (80874e87)
                    80867558 leave
                    80867559 ret 10h

        kd> uf 80887be9​​ ; RtlpExecuteHandlerForUnwind 的異常處理函數
        nt!ExecuteHandler2+0x61 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 329]:
          80887be9​​ mov ecx,dword ptr [esp+4] ; ecx = pExceptionRecord
          80887bed test dword ptr [ecx+4],6 ; test pExceptionRecord->ExceptionFlags, EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND
          80887bf4 mov eax,1 ; eax = ExceptionContinueSearch
          80887bf9 je nt!ExecuteHandler2+0x85 (80887c0d)

        nt!ExecuteHandler2+0x73 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 341]:
          ; 展開過程中 Handler 觸發異常
          80887bfb mov ecx,dword ptr [esp+8] ; ecx = pExceptionRegistration
          80887bff mov edx,dword ptr [esp+10h] ; edx = pDispatcherContext
          80887c03 mov eax,dword ptr [ecx+8] ; eax = pExceptionRegistration->prev
          80887c06 mov dword ptr [edx],eax ; pDispatcherContext->RegistrationPointer = pExceptionRegistration->prev,
                                                    ; 然後pDispatcherContext->RegistrationPointer 就是展開過程中正在被展開的
                                                    ; 那個 EXCEPTION_REGISTRATION_RECORD
          80887c08 mov eax,3 ; eax = ExceptionCollidedUnwind

        nt!ExecuteHandler2+0x85 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 349]:
          80887c0d ret 10h


    kd> uf RtlpExecuteHandlerForUnwind
        nt!RtlpExecuteHandlerForUnwind [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 142]:
          80887b54 mov edx,offset nt!ExecuteHandler2+0x61 (80887be9​​)
          80887b59 lea ecx,[ecx]
          80887b5c push ebx
          80887b5d push esi
          80887b5e push edi
          80887b5f xor eax,eax
          80887b61 xor ebx,ebx
          80887b63 xor esi,esi
          80887b65 xor edi,edi
          80887b67 push dword ptr [esp+20h] ; pExceptionRoutine
          80887b6b push dword ptr [esp+20h] ; pDispatcherContext
          80887b6f push dword ptr [esp+20h] ; pContext
          80887b73 push dword ptr [esp+20h] ; pExceptionRegistration
          80887b77 push dword ptr [esp+20h] ; pExceptionRecord
          80887b7b call nt!ExecuteHandler2 (80887b88)
          80887b80 pop edi
          80887b81 pop esi
          80887b82 pop ebx
          80887b83 ret 14h

    kd> uf nt!ExecuteHandler2
        nt!ExecuteHandler2 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 188]:
          80887b88 push ebp
          80887b89 mov ebp,esp
          80887b8b push dword ptr [ebp+0Ch] ; pExceptionRegistration
          80887b8e push edx ; _EXCEPTION_REGISTRATION_RECORD::Handler (nt!ExecuteHandler2+0x61 (80887be9​​))
          80887b8f push dword ptr fs:[0] ; _EXCEPTION_REGISTRATION_RECORD::Next
          80887b96 mov dword ptr fs:[0],esp; 注意:這裡使用的是原始版本的異常處理
          80887b9d push dword ptr [ebp+14h] ; pDispatcherContext
          80887ba0 push dword ptr [ebp+10h] ; pContext
          80887ba3 push dword ptr [ebp+0Ch] ; pExceptionRegistration
          80887ba6 push dword ptr [ebp+8] ; pExceptionRecord
          80887ba9 mov ecx,dword ptr [ebp+18h] ; ecx = pExceptionRoutine
          80887bac call ecx ; pExceptionRoutine(...),即pExceptionRegistration->handler(...)
          80887bae mov esp,dword ptr fs:[0]
          80887bb5 pop dword ptr fs:[0] ; 摘掉當前_EXCEPTION_REGISTRATION_RECORD
          80887bbc mov esp,ebp
          80887bbe pop ebp
          80887bbf ret 14h

  代碼不長,主要功能也不復雜:從異常鍊錶頭開始遍歷,一直遍歷到指定EXCEPTION_REGISTRATION_RECORD,對每個遍歷到的EXCEPTION_REGISTRATION_RECORD,執行RtlpExecuteHandlerForUnwind 進行局部展開。
  這段代碼裡有一個細節我沒想明白,或者我想複雜了。在nt!RtlUnwind 地址80867401 處,當展開到指定EXCEPTION_REGISTRATION 後,RtlUnwind 通過ZwContinue 返回,而不是使用ret 指令。通過靜態分析和動態分析我都沒有找到用ZwContinue 的好處,或者不可替代的原因。如果有朋友有不同的結論,請分享一下。
  在分析全局展開時發生一件很囧的事,我反彙編完成,梳理流程的時候,總感覺“這個邏輯怎麼這麼熟悉,貌似在哪見過~ 難道wrk 裡有源碼?”,翻開wrk,果然有…… 不過話說回來,在反彙編分析過程讓我對一些細節理解的更深刻了(也只能這麼安慰自己了……)。

  下面我們來看看局部展開。
  在前面講PassThrough!_except_handler4 時,有提到該函數既負責處理異常也負責局部展開。其區分功能的標誌就是判斷EXCEPTION_RECORD::ExceptionFlags 是否包含EXCEPTION_UNWIND 標誌位。可以參考PassThrough!_except_handler4 中地址為f87203dc 的指令:
  f87203dc test byte ptr [eax+4],66h ; pExceptionRecord->ExceptionFlags & EXCEPTION_UNWIND,判斷是異常處理過程還是展開過程
 
  該標誌是在RtlUnwind 中被設置的,可以參考RtlUnwind 中地址為808673b7 和808673bd 出的指令:
  808673b7 or dword ptr [esi+4],2 ; l_ExceptionRecord.ExceptionFlags |= EXCEPTION_UNWINDING (0x2)
  808673bd or dword ptr [esi+4],6 ; l_ExceptionRecord.ExceptionFlags |= EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND (0x2 | 0x4)

  反彙編代碼:

  首先是我整理的_EH4_LocalUnwind 的原型:
       VOID fastcall _EH4_LocalUnwind(
            PEXCEPTION_REGISTRATION pExceptionRegistartion,
            ULONG ulUntilTryLevel
            )


代碼:
    kd> uf PassThrough!_EH4_LocalUnwind
        PassThrough!_EH4_LocalUnwind [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 529]:
          f8720614 push ebp
          f8720615 mov ebp,dword ptr [esp+8] ; ebp = pExceptionRegistartion->_ebp
          f8720619 push edx ; ulUntilTryLevel
          f872061a push ecx ; pExceptionRegistartion
          f872061b push dword ptr [esp+14h] ; push __security_cookie
          f872061f call PassThrough!_local_unwind4 (f87204ec)
          f8720624 add esp,0Ch
          f8720627 pop ebp
          f8720628 ret 8

    PassThrough!_EH4_LocalUnwind 的原型:
       VOID local_unwind4(
           PEXCEPTION_REGSTRATION pExceptionRegstration,
           ULONG ulUntilTryLevel
           )

    kd> uf PassThrough!_local_unwind4
      PassThrough!_local_unwind4 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 177]:
        f87204ec push ebx
        f87204ed push esi
        f87204ee push edi
        f87204ef mov edx,dword ptr [esp+10h] ; edx = __security_cookie
        f87204f3 mov eax,dword ptr [esp+14h] ; eax = pExceptionRegistartion
        f87204f7 mov ecx,dword ptr [esp+18h] ; ecx = ulUntilTryLevel
        f87204fb push ebp
        f87204fc push edx ; __security_cookie
        f87204fd push eax ; pExceptionRegistartion
        f87204fe push ecx ; ulUntilTryLevel
        f87204ff push ecx ; 這裡壓入的值後續被f8720513 處的指令修改
        f8720500 push offset PassThrough!_unwind_handler4 (f872056f) ; _EXCEPTION_REGISTRATION_RECORD::handler
        f8720505 push dword ptr fs:[0] ; _EXCEPTION_REGISTRATION_RECORD::prev, 注意這裡使用的是原始版本
        f872050c mov eax,dword ptr [PassThrough!__security_cookie (f87220b0)]
        f8720511 xor eax,esp ; 這裡是用esp 來異或,而esp 此時指向新_EXCEPTION_REGISTRATION_RECORD
        f8720513 mov dword ptr [esp+8],eax ; [esp+8] 被賦予加密後的__security_cookie
        f8720517 mov dword ptr fs:[0],esp
    
      PassThrough!_local_unwind4+0x32 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 209]:
   >> f872051e mov eax,dword ptr [esp+30h] ; eax = pExceptionRegistartion
   :: f8720522 mov ebx,dword ptr [eax+8] ; ebx = pExceptionRegistration->scopetable, 將ebx 假稱為l_pCurScopeEntry
   :: f8720525 mov ecx,dword ptr [esp+2Ch] ; ecx = &__security_cookie
   :: f8720529 xor ebx,dword ptr [ecx] ; 解密scopetable
   :: f872052b mov esi,dword ptr [eax+0Ch] ; esi = pExceptionRegistration->trylevel
   :: f872052e cmp esi,0FFFFFFFEh ; cmp pExceptionRegistration->trylevel,TRYLEVEL_NONE
< :: f8720531 je PassThrough!_local_unwind4+0x75 (f8720561) ; pExceptionRegistration->trylevel 等於TRYLEVEL_NONE,無需遍歷,返回
: ::
: :: PassThrough!_local_unwind4+0x47 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 216]:
: :: f8720533 mov edx,dword ptr [esp+34h] ; edx = ulUntilTryLevel
: :: f8720537 cmp edx,0FFFFFFFEh ; cmp ulUntilTryLevel, TRYLEVEL_NONE
:< :: f872053a je PassThrough!_local_unwind4+0x54 (f8720540)
:: ::
:: :: PassThrough!_local_unwind4+0x50 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 219]:
:: :: f872053c cmp esi,edx ; cmp pExceptionRegistration->trylevel, ulUntilTryLevel
::<:: f872053e jbe PassThrough!_local_unwind4+0x75 (f8720561) ; 已經遍歷到指定scopetable_entry 了,返回
:::::
::::: PassThrough!_local_unwind4+0x54 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 221]:
:>::: f8720540 lea esi,[esi+esi*2]
: ::: f8720543 lea ebx,[ebx+esi*4+10h] ; ebx - pExceptionRegistration->scopetable[pExceptionRegistration->trylevel]
: ::: f8720547 mov ecx,dword ptr [ebx] ; ecx = l_pCurScopeEntry->previousTryLevel
: ::: f8720549 mov dword ptr [eax+0Ch],ecx ; pExceptionRegistration->trylevel = ebx->l_pCurScopeEntry->previousTryLevel
: ::: f872054c cmp dword ptr [ebx+4],0 ; cmp pExceptionRegistration->lpfnFilter,NULL
: :<: f8720550 jne PassThrough!_local_unwind4+0x32 (f872051e)
: : :
: : : PassThrough!_local_unwind4+0x66 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 240]:
: : : f8720552 mov ecx,1
: : : f8720557 mov eax,dword ptr [ebx+8] ; eax = pExceptionRegistration->lpfnHandler
: : : f872055a call PassThrough!_NLG_Call (f8720630) ; 調用pExceptionRegistration->lpfnHandler
: : < f872055f jmp PassThrough!_local_unwind4+0x32 (f872051e)
: :
: : PassThrough!_local_unwind4+0x75 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 248]:
> > f8720561 pop dword ptr fs:[0]
        f8720568 add esp,18h
        f872056b pop edi
        f872056c pop esi
        f872056d pop ebx
        f872056e ret
    
        kd> uf PassThrough!_NLG_Call
        PassThrough!_NLG_Call [d:\5359\minkernel\crts\crtw32\misc\i386\nlgsupp.asm @ 120]:
          f8720630 call eax
          f8720632 ret


  PassThrough!_EH4_LocalUnwind 中f8720500 處的指令用到的異常處理函數PassThrough!_unwind_handler4 的反彙編代碼:



代碼:
    kd> uf PassThrough!_unwind_handler4
        PassThrough!_unwind_handler4 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 294]:
          f872056f mov ecx,dword ptr [esp+4] ; ecx = pExceptionRecord
          f8720573 test dword ptr [ecx+4],6 ; test pExceptionRecord->ExceptionFlags, EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND
          f872057a mov eax,1 ; eax = ExceptionContinueSearch (1)
          f872057f je PassThrough!_unwind_handler4+0x45 (f87205b4)

        PassThrough!_unwind_handler4+0x12 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 307]:
          ; 在展開過程中又觸發了異常,那麼處理該新異常(實際操作只是做展開,即清理掉該異常佔用的資源,並沒有真正處理該異常)
          f8720581 mov eax,dword ptr [esp+8] ; eax = pExceptionRegistration
          f8720585 mov ecx,dword ptr [eax+8] ; ecx = __security_cookie, 注意這裡不是__pExceptionRegistration->scopetable
          f8720588 xor ecx,eax ; 這裡用pExceptionRegistration 解密,PassThrough!_local_unwind4 中也正是用它進行加密的
                                    ; 參考 f8720511 處的指令
          f872058a call PassThrough!__security_check_cookie (f8720638)
          f872058f push ebp
          f8720590 mov ebp,dword ptr [eax+18h] ; eax+18h 是被PassThrough!_local_unwind4 壓入棧的ebp,
                                                    ; 參考 f87204fb 處的指令
          f8720593 push dword ptr [eax+0Ch] ; push ulUntilTryLevel
          f8720596 push dword ptr [eax+10h] ; pExceptionRegistartion
          f8720599 push dword ptr [eax+14h] ; __security_cookie
          f872059c call PassThrough!_local_unwind4 (f87204ec)
          f87205a1 add esp,0Ch
          f87205a4 pop ebp
          f87205a5 mov eax,dword ptr [esp+8] ; eax = pExceptionRegistration
          f87205a9 mov edx,dword ptr [esp+10h] ; edx = pDispatcherContext (調整pDispatcherContext,改變遍歷順序)
          f87205ad mov dword ptr [edx],eax ; pDispatcherContext->RegistrationPointer = pExceptionRegistration
          f87205af mov eax,3 ; eax = ExceptionCollidedUnwind (3)

        PassThrough!_unwind_handler4+0x45 [d:\5359\minkernel\crts\crtw32\misc\i386\exsup4.asm @ 336]:
          f87205b4 ret


  到這裡概要流程就講完了。在處理異常和展開過程中多處涉及到遍歷操作,咱們來總結一下這些遍歷操作。
  1. 在異常處理過程中,每個被"捲入是非"的異常都至少會遍歷異常鍊錶兩次(如果發生嵌套異常,比如在展開過程中
EXCEPTION_REGISTRATION_RECORD::Handler 又觸發異常,則會遍歷更多次。不過這也可以算作是一個新異常了。看如何理解。 )。
  一次是在RtlDispatchException 中,遍歷的目的是找到願意處理該異常的_EXCEPTION_REGISTRATION_RECORD。
  另一次是在展開過程中、RtlUnwind 函數內,遍歷的目錄是為了對每個遍歷到的EXCEPTION_REGISTRATION_RECORD 進行局部展開。
  2. 同樣的,每個被"捲入是非"的異常的scopetable 也會被遍歷至少兩次,
  一次是在modulename!_except_handler? 中,遍歷目的也是找到願意處理該異常的scopetable_entry。
  另一次是在展開過程中、_local_unwind4 函數內,遍歷的目的是找到所有指定範圍內的scopetable_entry::lpfnFilter 為NULL 的scopetable_entry,調用它們的lpfnHandler (即__finally 處理塊)。

  在展開過程中,__finally 代碼塊會被執行,在執行過程中有可能觸發新的異常,增強版通過返回ExceptionCollidedUnwind (3) 來標識這種情況(參考PassThrough!_unwind_handler4 中f87205af 處的指令)。咱來回顧下這類返回值:


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


  前面的代碼中已經展示了上述4中的三種:
  ExceptionContinueExecution 表示繼續執行(異常已經被修復);
  ExceptionContinueSearch 表示繼續搜索(當前搜索到的異常註冊信息不處理);
  ExceptionCollidedUnwind 表示在展開過程中再次觸發異常。
  ExceptionNestedException 呢?到目前咱們還沒有遇到過,什麼情況會用到它?
 
  從字面上看ExceptionNestedException 大概意思是“嵌套的異常”,是不是可以理解為“處理異常過程中再次觸發的異常”?比如類似於ExceptionCollidedUnwind,只不過ExceptionCollidedUnwind 是在展開過程。而ExceptionNestedException 是在處理異常過程中?
  咱們順著這個思路去尋找,首先來看PassThrough!_except_handler4,它是異常處理的入口了。處理和展開都是它負責的。可是翻遍了它和它的工具函數的反彙編碼也沒有找到它直接返回或者通過註冊異常處理信息來間接返回。於是我決定繼續向上層即調用者方向搜尋,於是找到瞭如下彙編碼:


代碼:
        kd> uf RtlpExecuteHandlerForException
        nt!RtlpExecuteHandlerForException [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 87]:
          80887b4c mov edx,offset nt!ExecuteHandler2+0x3a (80887bc2)
          80887b51 jmp nt!ExecuteHandler (80887b5c)

        nt!ExecuteHandler [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 160]:
          80887b5c push ebx
          80887b5d push esi
          80887b5e push edi
          80887b5f xor eax,eax
          80887b61 xor ebx,ebx
          80887b63 xor esi,esi
          80887b65 xor edi,edi
          80887b67 push dword ptr [esp+20h]
          80887b6b push dword ptr [esp+20h]
          80887b6f push dword ptr [esp+20h]
          80887b73 push dword ptr [esp+20h]
          80887b77 push dword ptr [esp+20h]
          80887b7b call nt!ExecuteHandler2 (80887b88)
          80887b80 pop edi
          80887b81 pop esi
          80887b82 pop ebx
          80887b83 ret 14h

        kd> uf ExecuteHandler2
        nt!ExecuteHandler2 [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 188]:
          80887b88 push ebp
          80887b89 mov ebp,esp
          80887b8b push dword ptr [ebp+0Ch]
          80887b8e push edx
          80887b8f push dword ptr fs:[0]
          80887b96 mov dword ptr fs:[0],esp ; 註冊新的異常處理結構
          80887b9d push dword ptr [ebp+14h]
          80887ba0 push dword ptr [ebp+10h]
          80887ba3 push dword ptr [ebp+0Ch]
          80887ba6 push dword ptr [ebp+8]
          80887ba9 mov ecx,dword ptr [ebp+18h]
          80887bac call ecx
          80887bae mov esp,dword ptr fs:[0]
          80887bb5 pop dword ptr fs:[0]
          80887bbc mov esp,ebp
          80887bbe pop ebp
          80887bbf ret 14h

        kd> uf 80887bc2 ; ExecuteHandler2 使用的異常處理函數
        nt!ExecuteHandler2+0x3a [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 268]:
          80887bc2 mov ecx,dword ptr [esp+4]
          80887bc6 test dword ptr [ecx+4],6
          80887bcd mov eax,1 ; eax = ExceptionContinueSearch (1)
          80887bd2 jne nt!ExecuteHandler2+0x5e (80887be6)

        nt!ExecuteHandler2+0x4c [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 279]:
          80887bd4 mov ecx,dword ptr [esp+8]
          80887bd8 mov edx,dword ptr [esp+10h]
          80887bdc mov eax,dword ptr [ecx+8]
          80887bdf mov dword ptr [edx],eax
          80887be1 mov eax,2 ; eax = ExceptionNestedException (2)

        nt!ExecuteHandler2+0x5e [C:\wrk\wrk-v1.2\base\ntos\rtl\i386\xcptmisc.asm @ 287]:
          80887be6 ret 10h

  RtlpExecuteHandlerForException 函數是在RtlDispatchException 中被調用。 RtlDispatchException 在遍歷異常鏈過程中並不直接調用EXCEPTION_REGISTRATION_RECORD::Handler,而是通過RtlpExecuteHandlerForException 來間接調用。 RtlpExecuteHandlerForException 通過ExecuteHandler2 建立一個異常處理塊。
  PassThrough!_except_handler4 處理異常的過程中,如果再次觸發異常,則會由地址為80887bc2 的異常處理函數返回ExceptionNestedException。因為這幾個函數都很簡單,而且跟之前分析的函數很類似,不再贅述。

  分析完這些,我有個疑問還是沒有解開,我個人一直很​​奇怪增強版的使用方式為何不能這樣:
        __try
        {
        }
        __except()
        {
        }
        __finally
        {
        }
  其中__except 過濾塊和處理塊可以省略。
  但是很遺憾,MSC 中__except 和__finally 只能選其一。 (當然,咱們可以用雙層__try 來實現同樣的效果,但是總感覺不太爽,特別是會導致代碼塊被迫縮進兩次)
  在分析的過程中我也發現我希望的這種方式更合理一些,__except 負責處理異常,在處理異常代碼中被執行。如果沒有處理異常,那麼在展開過程中__finally 代碼塊被執行,做一些清理操作。這樣兩者都存在,各負責各的,不是更好嗎?不知道是不是什麼歷史原因。

  還有一個地方我也覺得不太完美。我們分析原始版本的時候發現,原始版本自身並沒有直接使用展開,RtlDispatchException 等異常處理函數並沒有直接調用RtlUnwind。後者實際上是在增強版中才用到。我個人感覺這種模型並不完美,原始版本並沒有自成體系,而是與增強版糾結在一起,沒有很好的分層。

  另外,在實際應用中SEH 機制可能會導致很難分析的內存洩漏。我們來看一個例子,
  調用流程:func1 -> func2
  其中,func1 的代碼如下:
        VOID Func1()
        {
            __try
            {
                Func2();
            }
            __except(EXCEPTION_EXECUTE_HANDLER)
            {
                // 一些善後處理
            }
        }
 
  Func2 沒有應用SEH,它申請了一塊內存,對這塊內存進行操作,然後釋放該內存。但是在操作內存時候觸發異常,異常被Func1 處理了,但是因為Func2 沒有__finally 處理塊,於是展開過程中Func2 並沒有機會去釋放這塊內存。結果就是:程序依然“正常”的在運行,但是實際上已經造成內存洩漏。隨著程序執行,洩漏的資源可能越來越多。最後導致嚴重的系統故障。
       遇到這種問題,程序猿通過靜態分析是很難找到洩漏的原因的。

  到這裡差不多就囉嗦完了。最後,來一段總結。

  本文只是我分析x86 下windows 異常處理機製過程的一些筆記的集合。因為興趣的原因,我只分析了內核部分,應用層SEH 我沒有琢磨。感興趣的朋友可以參考《軟件調試》中的相關內容,貌似挺詳細的。
  後續我會抽時間再琢磨琢磨x64 下windows 的異常處理機制,前段時間查閱x64 資料的時候看到其異常處理機制調整了很多,比如EXCEPTION_REGISTRATION 不在是放在棧上,而是做成表。如果內部實現改變的較多,我會再寫一份筆記來分享。 sinister 師傅有過這麼一段話,我很認同:
  “交流都是建立在平等的基礎上,在抱怨氛圍和環境不好的同時應該先想一想自己究竟付出了多少?只知索取不願付出的人也就不用抱怨了,要怪也只能怪自己。發自己心得的人無非是兩種目的,一是引發一些討論,好糾正自己錯誤的認識,以便從中獲取更多的知識使自己進步的更快。二是做一份備忘,當自己遺忘的時候能夠馬上找到相關資料。”
  其中提到的兩種目的我都有 :-)
  還是那句老話,FIXME。

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