68.3 Windows SEH

68.3.1 让我们先忘了MSVC

在Windows,SEH(Structured Exception Handling(结构化异常处理))是异常处理的一种机制。然而,它是语言无关的,不管是C++或者其它OOP语言。我们可以看到SEH(从C++和MSVC扩展)是独立实现的。

每个运行的进程都有一个SEH处理链,TIB有它最后的处理程序的地址。当异常发生时(除零,错误的地址访问,用户通过调用RaiseException()函数引发异常),操作系统在TIB找到最后的处理程序并调用它,获取异常时CPU的状态信息(如寄存器的值等等)。处理程序当前的异常能否修复,如果能,则修复该异常。如果不能,它通知操作系统无法处理它并由操作系统调用异常处理链中的下一个处理程序,直到处理程序能够处理的异常被发现。

在异常处理链的结尾处有一个标准的处理程序,它显示一个对话框用于通知用户进程崩溃,然后把一些崩溃时CPU的状态信息,收集起来并将其发送给微软开发商。

Figure 68.2: Windows XP

Figure 68.2: Windows XP

Figure 68.2: Windows XP

Figure 68.3: Windows XP

Figure 68.3: Windows XP

Figure 68.3: Windows XP

Figure 68.4: Windows 7

Figure 68.4: Windows 7

Figure 68.4: Windows 7

Figure 68.5: Windows 8.1

Figure 68.5: Windows 8.1

Figure 68.5: Windows 8.1

早些时候,这个处理程序被称为Dr.Watson。

顺便说一句,有些开发人员会在自己的处理程序发送程序崩溃的信息。通过SetUnhandledExceptionFilter()函数注册异常处理程序,如果操作系统没有任何其它方式处理异常,则调用它。一个例子是Oracle RDBMS,它保存了CPU所有可能有用的信息和内存状态的巨大转储文件。

让我们写一个自己的primitive exception handler:

  1. #include <windows.h>
  2. #include <stdio.h>
  3. DWORD new_value=1234;
  4. EXCEPTION_DISPOSITION __cdecl except_handler(
  5. struct _EXCEPTION_RECORD *ExceptionRecord,
  6. void * EstablisherFrame,
  7. struct _CONTEXT *ContextRecord,
  8. void * DispatcherContext )
  9. {
  10. unsigned i;
  11. printf ("%s\n", __FUNCTION__);
  12. printf ("ExceptionRecord->ExceptionCode=0x%p\n", ExceptionRecord->ExceptionCode);
  13. printf ("ExceptionRecord->ExceptionFlags=0x%p\n", ExceptionRecord->ExceptionFlags);
  14. printf ("ExceptionRecord->ExceptionAddress=0x%p\n", ExceptionRecord->ExceptionAddress);
  15. if (ExceptionRecord->ExceptionCode==0xE1223344)
  16. {
  17. printf ("That's for us\n");
  18. // yes, we "handled" the exception
  19. return ExceptionContinueExecution;
  20. }
  21. else if (ExceptionRecord->ExceptionCode==EXCEPTION_ACCESS_VIOLATION)
  22. {
  23. printf ("ContextRecord->Eax=0x%08X\n", ContextRecord->Eax);
  24. // will it be possible to 'fix' it?
  25. printf ("Trying to fix wrong pointer address\n");
  26. ContextRecord->Eax=(DWORD)&new_value;
  27. // yes, we "handled" the exception
  28. return ExceptionContinueExecution;
  29. }
  30. else
  31. {
  32. printf ("We do not handle this\n");
  33. // someone else's problem
  34. return ExceptionContinueSearch;
  35. };
  36. }
  37. int main()
  38. {
  39. DWORD handler = (DWORD)except_handler; // take a pointer to our handler
  40. // install exception handler
  41. __asm
  42. { // make EXCEPTION_REGISTRATION record:
  43. push handler // address of handler function
  44. push FS:[0] // address of previous handler
  45. mov FS:[0],ESP // add new EXECEPTION_REGISTRATION
  46. }
  47. RaiseException (0xE1223344, 0, 0, NULL);
  48. // now do something very bad
  49. int* ptr=NULL;
  50. int val=0;
  51. val=*ptr;
  52. printf ("val=%d\n", val);
  53. // deinstall exception handler
  54. __asm
  55. { // remove our EXECEPTION_REGISTRATION record
  56. mov eax,[ESP] // get pointer to previous record
  57. mov FS:[0], EAX // install previous record
  58. add esp, 8 // clean our EXECEPTION_REGISTRATION off stack
  59. }
  60. return 0;
  61. }

FS段寄存器:在Win32指向TIB。在TIB的第一个元素是指向异常处理指针链中的最后一个处理程序,我们将自己的异常处理程序的地址保存在这里。异常处理链的结点结构体名字是_EXCEPTION_REGISTRATION,这是一个单链表实现的栈容器。

Listing 68.1: MSVC/VC/crt/src/exsup.inc

  1. \_EXCEPTION\_REGISTRATION struc
  2. prev dd ?
  3. handler dd ?
  4. \_EXCEPTION\_REGISTRATION ends

每个结点的handler字段指向一个异常处理程序,每个结点的prev字段指向在栈中的上一个结点。最后一个结点的prev指向0xFFFFFFFF(-1)。

Figure 68.5: Windows 8.1

Figure 68.5: Windows 8.1

我们的处理程序安装后,我们调用RaiseException()。这是一个用户异常。处理程序检查异常代码,如果异常代码是0xE1223344,它返回ExceptionContinueExecution。这意味着处理程序修复了CPU的状态(通常是EIP/ESP寄存器),操作系统可以恢复运行。如果你稍微修改一下代码,处理程序返回ExceptionContinueSearch,那么操作系统将调用下一个处理程序,如果没有找到处理程序(因为没人捕获该异常),你会看到标准的Windows进程崩溃对话框。

系统异常和用户异常之间的区别是什么?这里有系统的:

as defined in WinBase.h
EXCEPTION_ACCESS_VIOLATION
EXCEPTION_DATATYPE_MISALIGNMENT
EXCEPTION_BREAKPOINT
EXCEPTION_SINGLE_STEP
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
EXCEPTION_FLT_DENORMAL_OPERAND
EXCEPTION_FLT_DIVIDE_BY_ZERO
EXCEPTION_FLT_INEXACT_RESULT
EXCEPTION_FLT_INVALID_OPERATION
EXCEPTION_FLT_OVERFLOW
EXCEPTION_FLT_STACK_CHECK
EXCEPTION_FLT_UNDERFLOW
EXCEPTION_INT_DIVIDE_BY_ZERO
EXCEPTION_INT_OVERFLOW
EXCEPTION_PRIV_INSTRUCTION
EXCEPTION_IN_PAGE_ERROR
EXCEPTION_ILLEGAL_INSTRUCTION
EXCEPTION_NONCONTINUABLE_EXCEPTION
EXCEPTION_STACK_OVERFLOW
EXCEPTION_INVALID_DISPOSITION
EXCEPTION_GUARD_PAGE
EXCEPTION_INVALID_HANDLE
EXCEPTION_POSSIBLE_DEADLOCK
CONTROL_C_EXIT

这些异常码的定义规则是:

31
S

S是一个基本代码: 11—error; 10—warning; 01—informational; 00—success;U表示是否是用户代码。

这就是为什么我选择了0xE1223344,0xE(1110b)意味着1)user exception(用户异常);2)error(错误)。

当我们尝试读取地址为0的内存时。因为这个地址在win32中并不被使用,所以会引发一个异常。通过检查异常码是否等于EXCEPTION_ACCESS_VIOLATION常量。

读0地址内存的代码看起来像这样:

Listing 68.2: MSVC 2010

  1. ...
  2. xor eax, eax
  3. mov eax, DWORD PTR [eax] ; exception will occur here
  4. push eax
  5. push OFFSET msg
  6. call _printf
  7. add esp, 8
  8. ...

能否修复“on the fly”这个错误然后继续执行程序?当然,我们的异常处理程序可以修复EAX值然后让操作系统继续执行下去。这是我们该做的。printf()将打印1234,因为我们的处理程序执行后EAX不是0,而是全局变量new_value的地址。

若内存管理器有一个关于CPU的错误信号,CPU会暂停线程,在Windows内核查找异常处理程序,然后一个一个调用SEH链的handler。

我在这里使用MSVC 2010,当然,没有任何保证EAX将用于这个指针。

这个地址替换的技巧非常的漂亮,我经常使用它插入到SEH内部中。不过,我忘记了在哪里用它修复“on the fly”错误。

为什么SHE相关的记录存储在栈上而不是其它地方?据说这是因为操作系统不需要在函数执行完成之后关心这些信息。但我不能100%肯定。这有点类似alloca()。

68.3.2 现在让我们回到MSVC

据说,微软的程序员需要在C语言而不是C++上使用异常,所以它们在MSVC上添加了一个非标准的C扩展。它与C++的异常没有任何关联。

  1. __try
  2. {
  3. ...
  4. }
  5. __except(filter code)
  6. {
  7. handler code
  8. }

“Finally”块也许能代替handler code:

  1. __try
  2. {
  3. ...
  4. }
  5. __finally
  6. {
  7. ...
  8. }

filte code是一个表达式,告诉handler code是否对应引发的异常。如果你的filte code太大而无法使用一个表达式,可以定义一个单独的filte函数。

在Windows内核有很多这样的结构,下面是几个例子(WRK(Windows Research Kernel)):

Listing 68.3: WRK-v1.2/base/ntos/ob/obwait.c

  1. try {
  2. KeReleaseMutant( (PKMUTANT)SignalObject,
  3. MUTANT_INCREMENT,
  4. FALSE,
  5. TRUE );
  6. } except((GetExceptionCode () == STATUS_ABANDONED ||
  7. GetExceptionCode () == STATUS_MUTANT_NOT_OWNED)?
  8. EXCEPTION_EXECUTE_HANDLER :
  9. EXCEPTION_CONTINUE_SEARCH) {
  10. Status = GetExceptionCode();
  11. goto WaitExit;
  12. }

Listing 68.4: WRK-v1.2/base/ntos/cache/cachesub.c

  1. try {
  2. RtlCopyBytes( (PVOID)((PCHAR)CacheBuffer + PageOffset),
  3. UserBuffer,
  4. MorePages ?
  5. (PAGE_SIZE - PageOffset) :
  6. (ReceivedLength - PageOffset) );
  7. } except( CcCopyReadExceptionFilter( GetExceptionInformation(), Status ) ) {

这里是一个filter code的例子:

Listing 68.5: WRK-v1.2/base/ntos/cache/copysup.c

  1. LONG
  2. CcCopyReadExceptionFilter(
  3. IN PEXCEPTION_POINTERS ExceptionPointer,
  4. IN PNTSTATUS ExceptionCode
  5. )
  6. /*++
  7. Routine Description:
  8. This routine serves as a exception filter and has the special job of
  9. extracting the "real" I/O error when Mm raises STATUS_IN_PAGE_ERROR
  10. beneath us.
  11. Arguments:
  12. ExceptionPointer - A pointer to the exception record that contains
  13. the real Io Status.
  14. ExceptionCode - A pointer to an NTSTATUS that is to receive the real
  15. status.
  16. Return Value:
  17. EXCEPTION_EXECUTE_HANDLER
  18. --*/
  19. {
  20. *ExceptionCode = ExceptionPointer->ExceptionRecord->ExceptionCode;
  21. if ( (*ExceptionCode == STATUS_IN_PAGE_ERROR) &&
  22. (ExceptionPointer->ExceptionRecord->NumberParameters >= 3) ) {
  23. *ExceptionCode = (NTSTATUS) ExceptionPointer->ExceptionRecord->ExceptionInformation[2];
  24. }
  25. ASSERT( !NT_SUCCESS(*ExceptionCode) );
  26. return EXCEPTION_EXECUTE_HANDLER;
  27. }

在内部,SEH是操作系统支持的异常扩展。但是处理函数是_except_handler3(对于SEH3)或_except_handler4(对于SEH4)。 这个处理函数的代码是与MSVC相关的,它位于它的库或在msvcr*.dll文件。其他的Win32编译器可以提供与之完全不同的机制。

SEH3

SEH3有一个_except_handler3处理函数,而且扩展了_EXCEPTION_REGISTRATION表,并添加了一个指向scope table和previous try level变量。SEH4扩展了scope table缓冲溢出保护。

scope table是一个表,包含了指向filter和handler code的块和每个try/except嵌套。

scopetable

scopetable

再者,操作系统只关心prev/handle字段。excepthandler3函数的工作是读取其他字段和scope table,并决定由哪些处理程序来执行。

excepthandler3函数的源代码是闭源的。然而,Sanos操作系统的win32兼容性层重新实现相同的功能。其它类似的实现有Wine和ReactOS。

如果filter指针为NULL,handler指针则指向finally代码块。

执行期间,栈中的previous try level变量发生变化,所以_except_handler3可以获取当前嵌套级的信息,才知道要使用scope table哪一表项。

SEH3: 一个try/except块例子

  1. #include <stdio.h>
  2. #include <windows.h>
  3. #include <excpt.h>
  4. int main()
  5. {
  6. int* p = NULL;
  7. __try
  8. {
  9. printf("hello #1!\n");
  10. *p = 13; // causes an access violation exception;
  11. printf("hello #2!\n");
  12. }
  13. __except(GetExceptionCode()==EXCEPTION_ACCESS_VIOLATION ?
  14. EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
  15. {
  16. printf("access violation, can't recover\n");
  17. }
  18. }

Listing 68.6: MSVC 2003

  1. $SG74605 DB 'hello #1!', 0aH, 00H
  2. $SG74606 DB 'hello #2!', 0aH, 00H
  3. $SG74608 DB 'access violation, can''t recover', 0aH, 00H
  4. _DATA ENDS
  5. ; scope table
  6. CONST SEGMENT
  7. $T74622 DD 0ffffffffH ; previous try level
  8. DD FLAT:$L74617 ; filter
  9. DD FLAT:$L74618 ; handler
  10. CONST ENDS
  11. _TEXT SEGMENT
  12. $T74621 = -32 ; size = 4
  13. _p$ = -28 ; size = 4
  14. __$SEHRec$ = -24 ; size = 24
  15. _main PROC NEAR
  16. push ebp
  17. mov ebp, esp
  18. push -1 ; previous try level
  19. push OFFSET FLAT:$T74622 ; scope table
  20. push OFFSET FLAT:__except_handler3 ; handler
  21. mov eax, DWORD PTR fs:__except_list
  22. push eax ; prev
  23. mov DWORD PTR fs:__except_list, esp
  24. add esp, -16
  25. push ebx ; saved 3 registers
  26. push esi ; saved 3 registers
  27. push edi ; saved 3 registers
  28. mov DWORD PTR __$SEHRec$[ebp], esp
  29. mov DWORD PTR _p$[ebp], 0
  30. mov DWORD PTR __$SEHRec$[ebp+20], 0 ; previous try level
  31. push OFFSET FLAT:$SG74605 ; 'hello #1!'
  32. call _printf
  33. add esp, 4
  34. mov eax, DWORD PTR _p$[ebp]
  35. mov DWORD PTR [eax], 13
  36. push OFFSET FLAT:$SG74606 ; 'hello #2!'
  37. call _printf
  38. add esp, 4
  39. mov DWORD PTR __$SEHRec$[ebp+20], -1 ; previous try level
  40. jmp SHORT $L74616
  41. ; filter code
  42. $L74617:
  43. $L74627:
  44. mov ecx, DWORD PTR __$SEHRec$[ebp+4]
  45. mov edx, DWORD PTR [ecx]
  46. mov eax, DWORD PTR [edx]
  47. mov DWORD PTR $T74621[ebp], eax
  48. mov eax, DWORD PTR $T74621[ebp]
  49. sub eax, -1073741819; c0000005H
  50. neg eax
  51. sbb eax, eax
  52. inc eax
  53. $L74619:
  54. $L74626:
  55. ret 0
  56. ; handler code
  57. $L74618:
  58. mov esp, DWORD PTR __$SEHRec$[ebp]
  59. push OFFSET FLAT:$SG74608 ; 'access violation, can''t recover'
  60. call _printf
  61. add esp, 4
  62. mov DWORD PTR __$SEHRec$[ebp+20], -1 ; setting previous try level back to -1
  63. $L74616:
  64. xor eax, eax
  65. mov ecx, DWORD PTR __$SEHRec$[ebp+8]
  66. mov DWORD PTR fs:__except_list, ecx
  67. pop edi
  68. pop esi
  69. pop ebx
  70. mov esp, ebp
  71. pop ebp
  72. ret 0
  73. _main ENDP
  74. _TEXT ENDS
  75. END

在这里我们可以看到SEH帧是如果在栈中构建出来的,scope table位于CONST segment-事实上,这些字段是不被改变的。一件有趣的事情是如何改变previous try level变量。它的初始化值是0xFFFFFFFF(-1)。当进入try语句块的时候,变量赋值为0。当try语句块结束的时候,写回-1。我们还能看到filter和handler code的地址。因此,我们可以很容易在函数里看到try/except是如何构造的。

由于函数序言的SEH安装代码被多个函数共享,有时候编译器会在函数序言插入调用SEH_prolog()函数,这就完成了这个任务。该SEH回收代码是SEH_epilog()函数。

让我们尝试用tracer运行这个例子:

  1. tracer.exe -l:2.exe --dump-seh

Listing 68.7: tracer.exe output

  1. EXCEPTION_ACCESS_VIOLATION at 2.exe!main+0x44 (0x401054) ExceptionInformation[0]=1
  2. EAX=0x00000000 EBX=0x7efde000 ECX=0x0040cbc8 EDX=0x0008e3c8
  3. ESI=0x00001db1 EDI=0x00000000 EBP=0x0018feac ESP=0x0018fe80
  4. EIP=0x00401054
  5. FLAGS=AF IF RF
  6. * SEH frame at 0x18fe9c prev=0x18ff78 handler=0x401204 (2.exe!_except_handler
  7. SEH3 frame. previous trylevel=0
  8. scopetable entry[0]. previous try level=-1, filter=0x401070 (2.exe!main+0x60) handler=0x401088 (2.exe!main+0x78)
  9. * SEH frame at 0x18ff78 prev=0x18ffc4 handler=0x401204 (2.exe!_except_handler3)
  10. SEH3 frame. previous trylevel=0
  11. scopetable entry[0]. previous try level=-1, filter=0x401531 (2.exe!mainCRTStartup+0x18d) handler=0x401545 (2.exe!mainCRTStartup+0x1a1)
  12. * SEH frame at 0x18ffc4 prev=0x18ffe4 handler=0x771f71f5 (ntdll.dll!__except_handler4)
  13. SEH4 frame. previous trylevel=0
  14. SEH4 header: GSCookieOffset=0xfffffffe GSCookieXOROffset=0x0
  15. EHCookieOffset=0xffffffcc EHCookieXOROffset=0x0
  16. scopetable entry[0]. previous try level=-2, filter=0x771f74d0 (ntdll.dll!___safe_se_handler_table+0x20) handler=0x771f90eb (ntdll.dll!_TppTerminateProcess@4+0x43)
  17. * SEH frame at 0x18ffe4 prev=0xffffffff handler=0x77247428 (ntdll.dll!_FinalExceptionHandler@16)

我们看到,SEH链包含4个handler。

前面两个是我们的例子。两个?但是我们只有一个?是的,一个是CRT的_mainCRTStartup()函数设置的。并至少作为FPU异常的处理。它的源码可以在MSVC的安装目录找到:crt/src/winxfltr.c。

第三个是ntdll.dll的SEH4,第四个handler也位于ntdll.dll,跟MSVC没什么关系,它有一个自描述函数名。

正如你所见,在一个链中有三种类型的处理函数:一个跟MSVC(最后一个)没什么关系和两个与MSVC关联的:SEH3和SEH4。

SEH3: 两个try/except块例子

  1. #include <stdio.h>
  2. #include <windows.h>
  3. #include <excpt.h>
  4. int filter_user_exceptions (unsigned int code, struct _EXCEPTION_POINTERS *ep)
  5. {
  6. printf("in filter. code=0x%08X\n", code);
  7. if (code == 0x112233)
  8. {
  9. printf("yes, that is our exception\n");
  10. return EXCEPTION_EXECUTE_HANDLER;
  11. }
  12. else
  13. {
  14. printf("not our exception\n");
  15. return EXCEPTION_CONTINUE_SEARCH;
  16. };
  17. }
  18. int main()
  19. {
  20. int* p = NULL;
  21. __try
  22. {
  23. __try
  24. {
  25. printf ("hello!\n");
  26. RaiseException (0x112233, 0, 0, NULL);
  27. printf ("0x112233 raised. now let's crash\n");
  28. *p = 13; // causes an access violation exception;
  29. }
  30. __except(GetExceptionCode()==EXCEPTION_ACCESS_VIOLATION ?
  31. EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
  32. {
  33. printf("access violation, can't recover\n");
  34. }
  35. }
  36. __except(filter_user_exceptions(GetExceptionCode(), GetExceptionInformation()))
  37. {
  38. // the filter_user_exceptions() function answering to the question
  39. // "is this exception belongs to this block?"
  40. // if yes, do the follow:
  41. printf("user exception caught\n");
  42. }
  43. }

现在有两个try块,所以scope table现在有两个元素,每个块占用一个。Previous try level随着try块的进入或退出而改变。

Listing 68.8: MSVC 2003

  1. $SG74606 DB 'in filter. code=0x%08X', 0aH, 00H
  2. $SG74608 DB 'yes, that is our exception', 0aH, 00H
  3. $SG74610 DB 'not our exception', 0aH, 00H
  4. $SG74617 DB 'hello!', 0aH, 00H
  5. $SG74619 DB '0x112233 raised. now let''s crash', 0aH, 00H
  6. $SG74621 DB 'access violation, can''t recover', 0aH, 00H
  7. $SG74623 DB 'user exception caught', 0aH, 00H
  8. _code$ = 8 ; size = 4
  9. _ep$ = 12 ; size = 4
  10. _filter_user_exceptions PROC NEAR
  11. push ebp
  12. mov ebp, esp
  13. mov eax, DWORD PTR _code$[ebp]
  14. push eax
  15. push OFFSET FLAT:$SG74606 ; 'in filter. code=0x%08X'
  16. call _printf
  17. add esp, 8
  18. cmp DWORD PTR _code$[ebp], 1122867; 00112233H
  19. jne SHORT $L74607
  20. push OFFSET FLAT:$SG74608 ; 'yes, that is our exception'
  21. call _printf
  22. add esp, 4
  23. mov eax, 1
  24. jmp SHORT $L74605
  25. $L74607:
  26. push OFFSET FLAT:$SG74610 ; 'not our exception'
  27. call _printf
  28. add esp, 4
  29. xor eax, eax
  30. $L74605:
  31. pop ebp
  32. ret 0
  33. _filter_user_exceptions ENDP
  34. ; scope table
  35. CONST SEGMENT
  36. $T74644 DD 0ffffffffH ; previous try level for outer block
  37. DD FLAT:$L74634 ; outer block filter
  38. DD FLAT:$L74635 ; outer block handler
  39. DD 00H ; previous try level for inner block
  40. DD FLAT:$L74638 ; inner block filter
  41. DD FLAT:$L74639 ; inner block handler
  42. CONST ENDS
  43. $T74643 = -36 ; size = 4
  44. $T74642 = -32 ; size = 4
  45. _p$ = -28 ; size = 4
  46. __$SEHRec$ = -24 ; size = 24
  47. _main PROC NEAR
  48. push ebp
  49. mov ebp, esp
  50. push -1 ; previous try level
  51. push OFFSET FLAT:$T74644
  52. push OFFSET FLAT:__except_handler3
  53. mov eax, DWORD PTR fs:__except_list
  54. push eax
  55. mov DWORD PTR fs:__except_list, esp
  56. add esp, -20
  57. push ebx
  58. push esi
  59. push edi
  60. mov DWORD PTR __$SEHRec$[ebp], esp
  61. mov DWORD PTR _p$[ebp], 0
  62. mov DWORD PTR __$SEHRec$[ebp+20], 0 ; outer try block entered. set previous try level to 0
  63. mov DWORD PTR __$SEHRec$[ebp+20], 1 ; inner try block entered. set previous try level to 1
  64. push OFFSET FLAT:$SG74617 ; 'hello!'
  65. call _printf
  66. add esp, 4
  67. push 0
  68. push 0
  69. push 0
  70. push 1122867 ; 00112233H
  71. call DWORD PTR __imp__RaiseException@16
  72. push OFFSET FLAT:$SG74619 ; '0x112233 raised. now let''s crash'
  73. call _printf
  74. add esp, 4
  75. mov eax, DWORD PTR _p$[ebp]
  76. mov DWORD PTR [eax], 13
  77. mov DWORD PTR __$SEHRec$[ebp+20], 0 ; inner try block exited. set previous try level back to 0
  78. jmp SHORT $L74615
  79. ; inner block filter
  80. $L74638:
  81. $L74650:
  82. mov ecx, DWORD PTR __$SEHRec$[ebp+4]
  83. mov edx, DWORD PTR [ecx]
  84. mov eax, DWORD PTR [edx]
  85. mov DWORD PTR $T74643[ebp], eax
  86. mov eax, DWORD PTR $T74643[ebp]
  87. sub eax, -1073741819; c0000005H
  88. neg eax
  89. sbb eax, eax
  90. inc eax
  91. $L74640:
  92. $L74648:
  93. ret 0
  94. ; inner block handler
  95. $L74639:
  96. mov esp, DWORD PTR __$SEHRec$[ebp]
  97. push OFFSET FLAT:$SG74621 ; 'access violation, can''t recover'
  98. call _printf
  99. add esp, 4
  100. mov DWORD PTR __$SEHRec$[ebp+20], 0 ; inner try block exited. set previous try level back to 0
  101. $L74615:
  102. mov DWORD PTR __$SEHRec$[ebp+20], -1 ; outer try block exited, set previous try level back to -1
  103. jmp SHORT $L74633
  104. ; outer block filter
  105. $L74634:
  106. $L74651:
  107. mov ecx, DWORD PTR __$SEHRec$[ebp+4]
  108. mov edx, DWORD PTR [ecx]
  109. mov eax, DWORD PTR [edx]
  110. mov DWORD PTR $T74642[ebp], eax
  111. mov ecx, DWORD PTR __$SEHRec$[ebp+4]
  112. push ecx
  113. mov edx, DWORD PTR $T74642[ebp]
  114. push edx
  115. call _filter_user_exceptions
  116. add esp, 8
  117. $L74636:
  118. $L74649:
  119. ret 0
  120. ; outer block handler
  121. $L74635:
  122. mov esp, DWORD PTR __$SEHRec$[ebp]
  123. push OFFSET FLAT:$SG74623 ; 'user exception caught'
  124. call _printf
  125. add esp, 4
  126. mov DWORD PTR __$SEHRec$[ebp+20], -1 ; both try blocks exited. set previous try level back to -1
  127. $L74633:
  128. xor eax, eax
  129. mov ecx, DWORD PTR __$SEHRec$[ebp+8]
  130. mov DWORD PTR fs:__except_list, ecx
  131. pop edi
  132. pop esi
  133. pop ebx
  134. mov esp, ebp
  135. pop ebp
  136. ret 0
  137. _main ENDP

如果我们在handler中调用的printf()函数设置一个断点,可以看到另一个SEH handler如何被添加。同样,我们还可以看到scope table包含两个元素。

  1. tracer.exe -l:3.exe bpx=3.exe!printf --dump-seh

Listing 68.9: tracer.exe output

  1. (0) 3.exe!printf
  2. EAX=0x0000001b EBX=0x00000000 ECX=0x0040cc58 EDX=0x0008e3c8
  3. ESI=0x00000000 EDI=0x00000000 EBP=0x0018f840 ESP=0x0018f838
  4. EIP=0x004011b6
  5. FLAGS=PF ZF IF
  6. * SEH frame at 0x18f88c prev=0x18fe9c handler=0x771db4ad (ntdll.dll!ExecuteHandler2@20+0x3a)
  7. * SEH frame at 0x18fe9c prev=0x18ff78 handler=0x4012e0 (3.exe!_except_handler3)
  8. SEH3 frame. previous trylevel=1
  9. scopetable entry[0]. previous try level=-1, filter=0x401120 (3.exe!main+0xb0) handler=0x40113b (3.exe!main+0xcb)
  10. scopetable entry[1]. previous try level=0, filter=0x4010e8 (3.exe!main+0x78) handler=0x401100 (3.exe!main+0x90)
  11. * SEH frame at 0x18ff78 prev=0x18ffc4 handler=0x4012e0 (3.exe!_except_handler3)
  12. SEH3 frame. previous trylevel=0
  13. scopetable entry[0]. previous try level=-1, filter=0x40160d (3.exe!mainCRTStartup+0x18d) handler=0x401621 (3.exe!mainCRTStartup+0x1a1
  14. * SEH frame at 0x18ffc4 prev=0x18ffe4 handler=0x771f71f5 (ntdll.dll!__except_handler4)
  15. SEH4 frame. previous trylevel=0
  16. SEH4 header: GSCookieOffset=0xfffffffe GSCookieXOROffset=0x0
  17. EHCookieOffset=0xffffffcc EHCookieXOROffset=0x0
  18. scopetable entry[0]. previous try level=-2, filter=0x771f74d0 (ntdll.dll!___safe_se_handler_table+0x20) handler=0x771f90eb (ntdll.dll!_TppTerminateProcess@4+0x43)
  19. * SEH frame at 0x18ffe4 prev=0xffffffff handler=0x77247428 (ntdll.dll!_FinalExceptionHandler@16)

SEH4

在缓冲区攻击期间(18.2章),scope table的地址可以被重写。所以从MSVC 2005开始,SEH3升级到SEH4后有了缓冲区溢出保护。现在scope table指针与一个security cookie(一个随机值)做异或运算。scope table扩展了包含两个指向security cookie指针的头部。每个元素都有另一个栈内偏移值:栈帧的地址(EBP)与security_cookie异或。该值将在异常处理过程中读取并检查其正确性。栈中的security cookie每次都是随机的,所以远程攻击者无法预测到它。

SEH4的previous try level初始化值是-2而不是-1。

seh4

seh4

这里有两个使用MSVC编译的SEH4例子:

Listing 68.10: MSVC 2012: one try block example

  1. $SG85485 DB 'hello #1!', 0aH, 00H
  2. $SG85486 DB 'hello #2!', 0aH, 00H
  3. $SG85488 DB 'access violation, can''t recover', 0aH, 00H
  4. ; scope table:
  5. xdata$x SEGMENT
  6. __sehtable$_main DD 0fffffffeH ; GS Cookie Offset
  7. DD 00H ; GS Cookie XOR Offset
  8. DD 0ffffffccH ; EH Cookie Offset
  9. DD 00H ; EH Cookie XOR Offset
  10. DD 0fffffffeH ; previous try level
  11. DD FLAT:$LN12@main ; filter
  12. DD FLAT:$LN8@main ; handler
  13. xdata$x ENDS
  14. $T2 = -36 ; size = 4
  15. _p$ = -32 ; size = 4
  16. tv68 = -28 ; size = 4
  17. __$SEHRec$ = -24 ; size = 24
  18. _main PROC
  19. push ebp
  20. mov ebp, esp
  21. push -2
  22. push OFFSET __sehtable$_main
  23. push OFFSET __except_handler4
  24. mov eax, DWORD PTR fs:0
  25. push eax
  26. add esp, -20
  27. push ebx
  28. push esi
  29. push edi
  30. mov eax, DWORD PTR ___security_cookie
  31. xor DWORD PTR __$SEHRec$[ebp+16], eax ; xored pointer to scope table
  32. xor eax, ebp
  33. push eax ; ebp ^ security_cookie
  34. lea eax, DWORD PTR __$SEHRec$[ebp+8] ; pointer to VC_EXCEPTION_REGISTRATION_RECORD
  35. mov DWORD PTR fs:0, eax
  36. mov DWORD PTR __$SEHRec$[ebp], esp
  37. mov DWORD PTR _p$[ebp], 0
  38. mov DWORD PTR __$SEHRec$[ebp+20], 0 ; previous try level
  39. push OFFSET $SG85485 ; 'hello #1!'
  40. call _printf
  41. add esp, 4
  42. mov eax, DWORD PTR _p$[ebp]
  43. mov DWORD PTR [eax], 13
  44. push OFFSET $SG85486 ; 'hello #2!'
  45. call _printf
  46. add esp, 4
  47. mov DWORD PTR __$SEHRec$[ebp+20], -2 ; previous try level
  48. jmp SHORT $LN6@main
  49. ; filter:
  50. $LN7@main:
  51. $LN12@main:
  52. mov ecx, DWORD PTR __$SEHRec$[ebp+4]
  53. mov edx, DWORD PTR [ecx]
  54. mov eax, DWORD PTR [edx]
  55. mov DWORD PTR $T2[ebp], eax
  56. cmp DWORD PTR $T2[ebp], -1073741819 ; c0000005H
  57. jne SHORT $LN4@main
  58. mov DWORD PTR tv68[ebp], 1
  59. jmp SHORT $LN5@main
  60. $LN4@main:
  61. mov DWORD PTR tv68[ebp], 0
  62. $LN5@main:
  63. mov eax, DWORD PTR tv68[ebp]
  64. $LN9@main:
  65. $LN11@main:
  66. ret 0
  67. ; handler:
  68. $LN8@main:
  69. mov esp, DWORD PTR __$SEHRec$[ebp]
  70. push OFFSET $SG85488 ; 'access violation, can''t recover'
  71. call _printf
  72. add esp, 4
  73. mov DWORD PTR __$SEHRec$[ebp+20], -2 ; previous try level
  74. $LN6@main:
  75. xor eax, eax
  76. mov ecx, DWORD PTR __$SEHRec$[ebp+8]
  77. mov DWORD PTR fs:0, ecx
  78. pop ecx
  79. pop edi
  80. pop esi
  81. pop ebx
  82. mov esp, ebp
  83. pop ebp
  84. ret 0
  85. _main ENDP

Listing 68.11: MSVC 2012: two try blocks example

  1. $SG85486 DB 'in filter. code=0x%08X', 0aH, 00H
  2. $SG85488 DB 'yes, that is our exception', 0aH, 00H
  3. $SG85490 DB 'not our exception', 0aH, 00H
  4. $SG85497 DB 'hello!', 0aH, 00H
  5. $SG85499 DB '0x112233 raised. now let''s crash', 0aH, 00H
  6. $SG85501 DB 'access violation, can''t recover', 0aH, 00H
  7. $SG85503 DB 'user exception caught', 0aH, 00H
  8. xdata$x SEGMENT
  9. __sehtable$_main DD 0fffffffeH ; GS Cookie Offset
  10. DD 00H ; GS Cookie XOR Offset
  11. DD 0ffffffc8H ; EH Cookie Offset
  12. DD 00H ; EH Cookie Offset
  13. DD 0fffffffeH ; previous try level for outer block
  14. DD FLAT:$LN19@main ; outer block filter
  15. DD FLAT:$LN9@main ; outer block handler
  16. DD 00H ; previous try level for inner block
  17. DD FLAT:$LN18@main ; inner block filter
  18. DD FLAT:$LN13@main ; inner block handler
  19. xdata$x ENDS
  20. $T2 = -40 ; size = 4
  21. $T3 = -36 ; size = 4
  22. _p$ = -32 ; size = 4
  23. tv72 = -28 ; size = 4
  24. __$SEHRec$ = -24 ; size = 24
  25. _main PROC
  26. push ebp
  27. mov ebp, esp
  28. push -2 ; initial previous try level
  29. push OFFSET __sehtable$_main
  30. push OFFSET __except_handler4
  31. mov eax, DWORD PTR fs:0
  32. push eax ; prev
  33. add esp, -24
  34. push ebx
  35. push esi
  36. push edi
  37. mov eax, DWORD PTR ___security_cookie
  38. xor DWORD PTR __$SEHRec$[ebp+16], eax ; xored pointer to scope table
  39. xor eax, ebp ; ebp ^ security_cookie
  40. push eax
  41. lea eax, DWORD PTR __$SEHRec$[ebp+8] ; pointer to VC_EXCEPTION_REGISTRATION_RECORD
  42. mov DWORD PTR fs:0, eax
  43. mov DWORD PTR __$SEHRec$[ebp], esp
  44. mov DWORD PTR _p$[ebp], 0
  45. mov DWORD PTR __$SEHRec$[ebp+20], 0 ; entering outer try block, setting previous try level=0
  46. mov DWORD PTR __$SEHRec$[ebp+20], 1 ; entering inner try block, setting previous try level=1
  47. push OFFSET $SG85497 ; 'hello!'
  48. call _printf
  49. add esp, 4
  50. push 0
  51. push 0
  52. push 0
  53. push 1122867 ; 00112233H
  54. call DWORD PTR __imp__RaiseException@16
  55. push OFFSET $SG85499 ; '0x112233 raised. now let''s crash'
  56. call _printf
  57. add esp, 4
  58. mov eax, DWORD PTR _p$[ebp]
  59. mov DWORD PTR [eax], 13
  60. mov DWORD PTR __$SEHRec$[ebp+20], 0 ; exiting inner try block, set previous try level back to 0
  61. jmp SHORT $LN2@main
  62. ; inner block filter:
  63. $LN12@main:
  64. $LN18@main:
  65. mov ecx, DWORD PTR __$SEHRec$[ebp+4]
  66. mov edx, DWORD PTR [ecx]
  67. mov eax, DWORD PTR [edx]
  68. mov DWORD PTR $T3[ebp], eax
  69. cmp DWORD PTR $T3[ebp], -1073741819 ; c0000005H
  70. jne SHORT $LN5@main
  71. mov DWORD PTR tv72[ebp], 1
  72. jmp SHORT $LN6@main
  73. $LN5@main:
  74. mov DWORD PTR tv72[ebp], 0
  75. $LN6@main:
  76. mov eax, DWORD PTR tv72[ebp]
  77. $LN14@main:
  78. $LN16@main:
  79. ret 0
  80. ; inner block handler:
  81. $LN13@main:
  82. mov esp, DWORD PTR __$SEHRec$[ebp]
  83. push OFFSET $SG85501 ; 'access violation, can''t recover'
  84. call _printf
  85. add esp, 4
  86. mov DWORD PTR __$SEHRec$[ebp+20], 0 ; exiting inner try block, setting previous try level back to 0
  87. $LN2@main:
  88. mov DWORD PTR __$SEHRec$[ebp+20], -2 ; exiting both blocks, setting previous try level back to -2
  89. jmp SHORT $LN7@main
  90. ; outer block filter:
  91. $LN8@main:
  92. $LN19@main:
  93. mov ecx, DWORD PTR __$SEHRec$[ebp+4]
  94. mov edx, DWORD PTR [ecx]
  95. mov eax, DWORD PTR [edx]
  96. mov DWORD PTR $T2[ebp], eax
  97. mov ecx, DWORD PTR __$SEHRec$[ebp+4]
  98. push ecx
  99. mov edx, DWORD PTR $T2[ebp]
  100. push edx
  101. call _filter_user_exceptions
  102. add esp, 8
  103. $LN10@main:
  104. $LN17@main:
  105. ret 0
  106. ; outer block handler:
  107. $LN9@main:
  108. mov esp, DWORD PTR __$SEHRec$[ebp]
  109. push OFFSET $SG85503 ; 'user exception caught'
  110. call _printf
  111. add esp, 4
  112. mov DWORD PTR __$SEHRec$[ebp+20], -2 ; exiting both blocks, setting previous try level back to -2
  113. $LN7@main:
  114. xor eax, eax
  115. mov ecx, DWORD PTR __$SEHRec$[ebp+8]
  116. mov DWORD PTR fs:0, ecx
  117. pop ecx
  118. pop edi
  119. pop esi
  120. pop ebx
  121. mov esp, ebp
  122. pop ebp
  123. ret 0
  124. _main ENDP
  125. _code$ = 8 ; size = 4
  126. _ep$ = 12 ; size = 4
  127. _filter_user_exceptions PROC
  128. push ebp
  129. mov ebp, esp
  130. mov eax, DWORD PTR _code$[ebp]
  131. push eax
  132. push OFFSET $SG85486 ; 'in filter. code=0x%08X'
  133. call _printf
  134. add esp, 8
  135. cmp DWORD PTR _code$[ebp], 1122867 ; 00112233H
  136. jne SHORT $LN2@filter_use
  137. push OFFSET $SG85488 ; 'yes, that is our exception'
  138. call _printf
  139. add esp, 4
  140. mov eax, 1
  141. jmp SHORT $LN3@filter_use
  142. jmp SHORT $LN3@filter_use
  143. $LN2@filter_use:
  144. push OFFSET $SG85490 ; 'not our exception'
  145. call _printf
  146. add esp, 4
  147. xor eax, eax
  148. $LN3@filter_use:
  149. pop ebp
  150. ret 0
  151. _filter_user_exceptions ENDP

这里是cookie的含义:Cookie Offset用于区分栈中saved_EBP的地址和EBP⊕security_cookie。附加的Cookie XOR Offset用于区分EBP⊕security_cookie是否保存在栈中。如果这个等式不为true,会由于栈受到破坏而停止这个过程。

security_cookie⊕(Cookie XOR Offset+address_of_saved_EBP) == stack[address_of_saved_EBP + CookieOffset]

如果Cookie Offset为-2,这意味着它不存在。

在我的tracer工具也实现了Cookie检查,具体请看Github

MSVC 2005之后的编译器开启/GS选项仍可能会回滚到SEH3。不过,CRT的代码总是使用SEH4。

68.3.3 Windows x64

正如你所认为的,每个函数序言在设置SEH帧效率不高。另一个性能问题是,函数执行期间多次尝试改变previous try level。这种情况在x64完全改变了:现在所有指向try块,filter和handler函数都保存在PE文件的.pdata段,由它提供给操作系统异常处理所需信息。

这里有两个使用x64编译的例子:

Listing 68.12: MSVC 2012

  1. $SG86276 DB 'hello #1!', 0aH, 00H
  2. $SG86277 DB 'hello #2!', 0aH, 00H
  3. $SG86279 DB 'access violation, can''t recover', 0aH, 00H
  4. pdata SEGMENT
  5. $pdata$main DD imagerel $LN9
  6. DD imagerel $LN9+61
  7. DD imagerel $unwind$main
  8. pdata ENDS
  9. pdata SEGMENT
  10. $pdata$main$filt$0 DD imagerel main$filt$0
  11. DD imagerel main$filt$0+32
  12. DD imagerel $unwind$main$filt$0
  13. pdata ENDS
  14. xdata SEGMENT
  15. $unwind$main DD 020609H
  16. DD 030023206H
  17. DD imagerel __C_specific_handler
  18. DD 01H
  19. DD imagerel $LN9+8
  20. DD imagerel $LN9+40
  21. DD imagerel main$filt$0
  22. DD imagerel $LN9+40
  23. $unwind$main$filt$0 DD 020601H
  24. DD 050023206H
  25. xdata ENDS
  26. _TEXT SEGMENT
  27. main PROC
  28. $LN9:
  29. push rbx
  30. sub rsp, 32
  31. xor ebx, ebx
  32. lea rcx, OFFSET FLAT:$SG86276 ; 'hello #1!'
  33. call printf
  34. mov DWORD PTR [rbx], 13
  35. lea rcx, OFFSET FLAT:$SG86277 ; 'hello #2!'
  36. call printf
  37. jmp SHORT $LN8@main
  38. $LN6@main:
  39. lea rcx, OFFSET FLAT:$SG86279 ; 'access violation, can''t recover'
  40. call printf
  41. npad 1 ; align next label
  42. $LN8@main:
  43. xor eax, eax
  44. add rsp, 32
  45. pop rbx
  46. ret 0
  47. main ENDP
  48. _TEXT ENDS
  49. text$x SEGMENT
  50. main$filt$0 PROC
  51. push rbp
  52. sub rsp, 32
  53. mov rbp, rdx
  54. $LN5@main$filt$:
  55. mov rax, QWORD PTR [rcx]
  56. xor ecx, ecx
  57. cmp DWORD PTR [rax], -1073741819; c0000005H
  58. sete cl
  59. mov eax, ecx
  60. $LN7@main$filt$:
  61. add rsp, 32
  62. pop rbp
  63. ret 0
  64. int 3
  65. main$filt$0 ENDP
  66. text$x ENDS

Listing 68.13: MSVC 2012

  1. $SG86277 DB 'in filter. code=0x%08X', 0aH, 00H
  2. $SG86279 DB 'yes, that is our exception', 0aH, 00H
  3. $SG86281 DB 'not our exception', 0aH, 00H
  4. $SG86288 DB 'hello!', 0aH, 00H
  5. $SG86290 DB '0x112233 raised. now let''s crash', 0aH, 00H
  6. $SG86292 DB 'access violation, can''t recover', 0aH, 00H
  7. $SG86294 DB 'user exception caught', 0aH, 00H
  8. pdata SEGMENT
  9. $pdata$filter_user_exceptions DD imagerel $LN6
  10. DD imagerel $LN6+73
  11. DD imagerel $unwind$filter_user_exceptions
  12. $pdata$main DD imagerel $LN14
  13. DD imagerel $LN14+95
  14. DD imagerel $unwind$main
  15. pdata ENDS
  16. pdata SEGMENT
  17. $pdata$main$filt$0 DD imagerel main$filt$0
  18. DD imagerel main$filt$0+32
  19. DD imagerel $unwind$main$filt$0
  20. $pdata$main$filt$1 DD imagerel main$filt$1
  21. DD imagerel main$filt$1+30
  22. DD imagerel $unwind$main$filt$1
  23. pdata ENDS
  24. xdata SEGMENT
  25. $unwind$filter_user_exceptions DD 020601H
  26. DD 030023206H
  27. $unwind$main DD 020609H
  28. DD 030023206H
  29. DD imagerel __C_specific_handler
  30. DD 02H
  31. DD imagerel $LN14+8
  32. DD imagerel $LN14+59
  33. DD imagerel main$filt$0
  34. DD imagerel $LN14+59
  35. DD imagerel $LN14+8
  36. DD imagerel $LN14+74
  37. DD imagerel main$filt$1
  38. DD imagerel $LN14+74
  39. $unwind$main$filt$0 DD 020601H
  40. DD 050023206H
  41. $unwind$main$filt$1 DD 020601H
  42. DD 050023206H
  43. xdata ENDS
  44. _TEXT SEGMENT
  45. main PROC
  46. $LN14:
  47. push rbx
  48. sub rsp, 32
  49. xor ebx, ebx
  50. lea rcx, OFFSET FLAT:$SG86288 ; 'hello!'
  51. call printf
  52. xor r9d, r9d
  53. xor r8d, r8d
  54. xor edx, edx
  55. mov ecx, 1122867 ; 00112233H
  56. call QWORD PTR __imp_RaiseException
  57. lea rcx, OFFSET FLAT:$SG86290 ; '0x112233 raised. now let''s crash'
  58. call printf
  59. mov DWORD PTR [rbx], 13
  60. jmp SHORT $LN13@main
  61. $LN11@main:
  62. lea rcx, OFFSET FLAT:$SG86292 ; 'access violation, can''t recover'
  63. call printf
  64. npad 1 ; align next label
  65. $LN13@main:
  66. jmp SHORT $LN9@main
  67. $LN7@main:
  68. lea rcx, OFFSET FLAT:$SG86294 ; 'user exception caught'
  69. call printf
  70. npad 1 ; align next label
  71. $LN9@main:
  72. xor eax, eax
  73. add rsp, 32
  74. pop rbx
  75. ret 0
  76. main ENDP
  77. text$x SEGMENT
  78. main$filt$0 PROC
  79. push rbp
  80. sub rsp, 32
  81. mov rbp, rdx
  82. $LN10@main$filt$:
  83. mov rax, QWORD PTR [rcx]
  84. xor ecx, ecx
  85. cmp DWORD PTR [rax], -1073741819; c0000005H
  86. sete cl
  87. mov eax, ecx
  88. $LN12@main$filt$:
  89. add rsp, 32
  90. pop rbp
  91. ret 0
  92. int 3
  93. main$filt$0 ENDP
  94. main$filt$1 PROC
  95. push rbp
  96. sub rsp, 32
  97. mov rbp, rdx
  98. $LN6@main$filt$:
  99. mov rax, QWORD PTR [rcx]
  100. mov rdx, rcx
  101. mov ecx, DWORD PTR [rax]
  102. call filter_user_exceptions
  103. npad 1 ; align next label
  104. $LN8@main$filt$:
  105. add rsp, 32
  106. pop rbp
  107. ret 0
  108. int 3
  109. main$filt$1 ENDP
  110. text$x ENDS
  111. _TEXT SEGMENT
  112. code$ = 48
  113. ep$ = 56
  114. filter_user_exceptions PROC
  115. $LN6:
  116. push rbx
  117. sub rsp, 32
  118. mov ebx, ecx
  119. mov edx, ecx
  120. lea rcx, OFFSET FLAT:$SG86277 ; 'in filter. code=0x%08X'
  121. call printf
  122. cmp ebx, 1122867; 00112233H
  123. jne SHORT $LN2@filter_use
  124. lea rcx, OFFSET FLAT:$SG86279 ; 'yes, that is our exception'
  125. call printf
  126. mov eax, 1
  127. add rsp, 32
  128. pop rbx
  129. ret 0
  130. $LN2@filter_use:
  131. lea rcx, OFFSET FLAT:$SG86281 ; 'not our exception'
  132. call printf
  133. xor eax, eax
  134. add rsp, 32
  135. pop rbx
  136. ret 0
  137. filter_user_exceptions ENDP
  138. _TEXT ENDS

Sko12获取更多详细的信息。

除了异常信息,.pdata还包含了几乎所有函数的开始和结束地址,因此它可能对于自动化分析工具有用。

68.3.4 更多关于SEH的信息

Matt Pietrek. “A Crash Course on the Depths of Win32™ Structured Exception Handling”. In: MSDN magazine (). URL: http://go.yurichev.com/17293.

Igor Skochinsky. Compiler Internals: Exceptions and RTTI. Also available as http://go.yurichev.com/ 17294. 2012.