ʕ •̀ o •́ ʔ
1 SEH的编译 VC/VS编译器定义了 __try
,__except
和 __finally
这3个扩展关键字,运行C和C++程序使用这套关键字来编写异常处理代码。编译器会将这些关键字编写的异常处理代码与操作系统的SEH机制衔接起来。
VC编译器优化以后的SEH为程序员提供了如下两种功能:
异常处理功能:try-except
,用于接收和处理被保护块中的代码所发生的异常。
终结处理功能:try-finally
,保证终结处理块始终可以得到执行。
概括来说,__try{}__except
结构将手工方法中的函数和嵌入式汇编代码简化成高级语言中的标记符和表达式。具体来说,将被保护块使用 __ty
关键字和大括号包围起来;使用 __except
块将原本写在异常回调函数中的分支判断结构分解成过滤表达式(if
中的异常情况)和与之对应的异常处理块。
1.1 try-except 对应关系如下图:
通过手工方式将 SEH 挂入到 FS:[0]
链条、处理异常、卸载SEH结构的过程在编译器中对应的语法结构为:
1 2 3 4 5 6 7 8 __try { } __except(过滤表达式) { }
从外部行为的角度来看,结构化异常处理的基本规则是,如果被保护体中的代码发生了异常,不论是CPU 级的硬件异常还是软件发起的软件异常,系统都应该评估过滤表达式的内容,也就是执行过滤表达式中的代码。这意味着,程序的执行路线是从被保护体中飞跃到过滤表达式中的。要正确地飞跃到表达式中显然不那么简单,需要准确地知道过滤表达式的位置,还要保持栈的平衡。
过滤表达式的使用:
过滤表达式既可以是常量 ,函数调用 ,也可以是表达式 ,只要表达式的结果为 $-1$ ,$0$,$1$ 这三个值之一,它们的含义如下:
值
名称
含义
-1
EXCEPTION_CONTINUE_EXECUTION
返回出错位置重新执行。
0
EXCEPTION_CONTINUE_SEARCH
本异常处理块不处理该该异常,寻找其他异常处理块 (不是异常处理器)。
1
EXCEPTION_EXECUTE_HANDLER
执行 异常处理块 中的代码 。执行完后会继续执行本异常处理块下面的代码,即except块之后的第一条指令。
Visual C++编译器还提供了两个只能在过滤表达式中使用的宏 来辅助编写异常处理代码:
DWORD GetExceptionCode()
:返回异常代码。
LPEXCEPTION_POINTERS GetExceptionInformation()
:返回一个指向 EXCEPTION_POINTERS
结构的指针。
1.2 try-finally 终结处理的语法结构如下:
1 2 3 4 5 6 7 8 __try { } __finally { }
终结处理由两部分构造,使用 __try
关键字定义的被保护体和使用 __finally
关键字定义的终结处理块。终结处理的目标是只要被保护体被执行,那么终结处理块就也会被执行,除非被保护体中的代码终止了当前线程(比如调用ExitThread
或ExitProcess
退出线程或整个进程)。因为终结处理块的这种特征,终结处理非常适合做状态恢复或资源释放等工作。比如释放被保护块中获得的信号量以防止被保护块内发生意外时因没有释放这个信号量而导致线程死锁的问题。
根据被保护块的执行路线,SEH 把被保护块的退出(执行完毕)分为正常结束和非正常结束两种。
正常结束 :如果被保护块得到自然执行并顺序进入终结处理块,就认为被保护块是正常结束的。
非正常结束 :如果被保护块是因为发生异常或由于return
、 goto
、break
、continue
等流程控制语句离开被保护块的,就认为被保护块是非正常结束的。
一个只能在终结块中使用 的函数知道被保护块的退出方式:
1 BOOL bRet = AbnormalTermination(void );
bRet == TRUE
,非正常结束。
bRet == FALSE
,正常结束。
一个只能在被保护块中使用 的关键字:leave
。该关键字的作用是立即离开(停止执行)被保护块,或者理解为立即跳转到被保护块的末尾(__try
块的右大括号)。
举例说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <cstdio> #include <windows.h> int main () { __try { printf ("开始执行__try中的代码\n" ); printf ("执行__leave之前的代码\n" ); __leave; printf ("执行__leave之后的代码\n" ); } __finally { printf ("执行__finally的代码\n" ); } return 0 ; }
参考:Windows内核学习笔记之异常(下)– 看雪1900 。
1.3 32/64位SEH编译差异 在上一篇文章中 SEH 部分说过关于 SEH 的存放位置 :
栈帧 (stack frame):将异常处理器注册在所在函数的栈帧中。使用这种方式注册的异常处理经常称为基于帧的异常处理(fame based exception handling )。32 位的 Windows 系统(x86) 使用的就是此种方式。
表格 (table):将异常处理器的基本信息以表格的形式存储在可执行文件(PE)的数据段中,这种方式简称为基于表的异常处理(table based exception handling )。64 位 Windows 系统(x64)中的64 位程序 使用了这种方式。
所以编译器在编译 32 bit 程序和 64 bit 程序的时候,针对 try-except
的编译是不同的。就针对 32 位程序的编译来说,VC、VS编译器在编译时实际上差异不大,VS编译器因为加入了一些新的安全选项,所以编译时加入了一些额外的安全检查代码。
64 位基于表的异常处理 :增强异常处理机制安全性的一种更彻底的方法是 x64 系统中的基于表的异常处理(table based exception handling)。其基本思想是将异常处理器的描述和登记信息都以表格的形式存储在可执行文件中,当有异常发生时,系统根据异常的发生位置自动在这些表格中寻找匹配的处理函数,不需要在栈上做任何登记,也不再使用 FS:[0]
链条。基于表的异常处理机制与基于帧的异常处理机制是不兼容的。运行在 x64 CPU 上的 64 位 Windows 使用了基于表的异常处理,编译运行在这样的目标系统中的 64 位应用序时,编译器会自动使用新的编译方式产生合适的代码。
本文主要讨论 32 位程序的编译,因为 32 位程序的 SEH 是注册在线程堆栈中的,可以在写代码时查看反汇编代码就能知道其编译情况。但是 64 位程序应该是需要逆向一下异常的处理框架才能知道怎么使用 SEH 的,try-except
源代码反汇编查看不到 SEH 相关情况。
1.4 try-except汇编 1.4.1 32位SEH编译 编译器不是为每一个 try-except
都注册 SEH,而是一个函数内不管使用了多少次对 try-except
,都只会注册一个异常处理器,并且只是用一个统一的异常处理回调函数 :__except_handler3/__except_handler4
。
每次执行到 __except_handler3
时,该函数会把回调函数的第二个参数 EstablisherFrame
指向的结构EXCEPTION_REGISTRATION_RECORD
(SEH)进行拓展。__except_handler3
回调函数正是依靠扩展的 3 个字段来寻找过滤表达式和异常处理块的。
VC编译器对 Windows 提供的 EXCEPTION_REGISTRATION_RECORD
结构进行了拓展,拓展后的结构如下:
1 2 3 4 5 6 7 8 struct _EXCEPTION_REGISTRATION { struct _EXCEPTION_REGISTRATION * prev ; void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_ REGISTRATION, PCONTEXT, PEXCEPTION_RECORD); struct scopetable_entry * scopetable ; int trylevel; int _ebp; };
字段
作用
prev
指向上一个结构体的地址,上一个异常链
handler
指向异常处理函数
scopetable
范围表的起始地址
trylevel
这个结构对应的__try块的编号
ebp
栈帧的基地址
可以看到 EXCEPTION_REGISTRATION_RECORD
结构是拓展后_EXCEPTION_REGISTRATION
结构的第一个成员(类似于C++类的继承)。
其中前两个字段是操作系统规定的标准登记结构,后三个字段是编译器扩展的。__except_handler3
函数正是依靠这几个扩展字段来寻找过滤表达式和异常处理块。
在一个函数里面 :
不管有多少个 try-except
都只会挂入一个 _EXCEPTION_REGISTRATION
结构,也就是只有一个 excepthandler3
函数(对于递归函数,每一次调用都会创建一个 _EXCEPTION_REGISTRATION
,并挂入线程的异常链表中)。
有多少个 try-except
,scopetable
数组就有几项。
堆栈中的形成的 FS:[0]
链条:
下面用 VC6 将如下代码编译成 32 位程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <Windows.h> void TestSEH () { __try { } __except (1 ) { } } int main (int argc, char * argv[]) { TestSEH(); return 0 ; }
VC6编译函数 TestSEH
汇编代码如下 (Debug版):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 .text:00401020 .text:00401020 .text:00401020 .text:00401020 .text:00401020 sub_401020 proc near .text:00401020 .text:00401020 var_58 = byte ptr -58 h .text:00401020 ms_exc = CPPEH_RECORD ptr -18 h .text:00401020 .text:00401020 .text:00401020 push ebp .text:00401021 mov ebp, esp .text:00401023 push 0F FFFFFFFh .text:00401025 push offset stru_422020 .text:0040102 A push offset __except_handler3 .text:0040102F mov eax, large fs:0 .text:00401035 push eax .text:00401036 mov large fs:0 , esp .text:0040103 D add esp, 0F FFFFFB8h .text:00401040 push ebx .text:00401041 push esi .text:00401042 push edi .text:00401043 mov [ebp+ms_exc.old_esp], esp .text:00401046 lea edi, [ebp+var_58] .text:00401049 mov ecx, 10 h .text:0040104 E mov eax, 0 CCCCCCCCh .text:00401053 rep stosd .text:00401055 .text:00401055 mov [ebp+ms_exc.registration.TryLevel], 0 .text:00401055 .text:0040105 C mov [ebp+ms_exc.registration.TryLevel], 0F FFFFFFFh .text:00401063 jmp short loc_401075 .text:00401065 .text:00401065 .text:00401065 loc_401065: .text:00401065 .text:00401065 mov eax, 1 .text:0040106 A retn .text:0040106B .text:0040106B .text:0040106B loc_40106B: .text:0040106B .text:0040106B mov esp, [ebp+ms_exc.old_esp] .text:0040106 E mov [ebp+ms_exc.registration.TryLevel], 0F FFFFFFFh .text:00401075 .text:00401075 loc_401075: .text:00401075 mov ecx, [ebp+ms_exc.registration.Next] .text:00401078 mov large fs:0 , ecx .text:0040107F pop edi .text:00401080 pop esi .text:00401081 pop ebx .text:00401082 mov esp, ebp .text:00401084 pop ebp .text:00401085 retn .text:00401085 .text:00401085 sub_401020 endp .text:00401085 .text:00401085
首先注册一个 _EXCEPTION_REGISTRATION
结构:
1 2 3 4 5 6 7 8 .text:00401020 push ebp .text:00401021 mov ebp, esp .text:00401023 push 0F FFFFFFFh .text:00401025 push offset stru_422020 .text:0040102 A push offset __except_handler3 .text:0040102F mov eax, large fs:0 .text:00401035 push eax .text:00401036 mov large fs:0 , esp
trylevel = 0xffffffff
scopetable = 0x00422020
handler = __except_handler3
prev = fs:[0]
1.4.2 trylevel 编译器是以函数为单位来登记异常处理器 _EXCEPTION_REGISTRATION
的,在函数的入口处进行登记,在出口处进行注销。一个函数只有一个 。 那么,如何确定导致异常的代码是否在保护块中呢?如果有多个保护块,又如何判断属于哪个保护块呢?答案是对每个 __try
结构进行编号,然后使用 _ EXCEPTION_ REGISTRATION.trylevel
判断属于哪个保护块。
trylevel
用来标识当前的代码处于哪一个 __try
里面,所有的 __except(Filter){}
编译信息都保存在 scopetable
结构数组中。trylevel
将 __try
的保护块与__except(Filter){}
连接起来。这样不管在哪个 __try
中的发生的异常都能定位到对应的过滤表达式和异常处理块。也很好的解决了异常嵌套问题。
__except_handler3
函数根据 trylevel
知道当前异常处于函数的哪一个 __try
,然后使用 scopetable[trylevel]
能对找到改异常块对应的过滤器和异常处理块。
每当进、出每一个 __try
时都需要修改 trylevel
的值 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 1 : #include "stdafx.h" 2 : #include <windows.h> 3 :4 : void Test__try__except (void ) 5: {0040 DF00 push ebp0040 DF01 mov ebp,esp0040 DF03 push 0F Fh0040 DF05 push offset string "trylevel = 0" +14 h (00422f f0)0040 DF0A push offset __except_handler3 (004041 d0)0040 DF0F mov eax,fs:[00000000 ]0040 DF15 push eax0040 DF16 mov dword ptr fs:[0 ],esp0040 DF1D add esp,0B 0h0040 DF20 push ebx0040 DF21 push esi0040 DF22 push edi0040 DF23 mov dword ptr [ebp-18 h],esp0040 DF26 lea edi,[ebp-60 h]0040 DF29 mov ecx,12 h0040 DF2E mov eax,0 CCCCCCCCh0040 DF33 rep stos dword ptr [edi]6 : __try0040 DF35 mov dword ptr [ebp-4 ],0 7 : {8 : printf ("trylevel = 0\n" );0040 DF3C push offset string "trylevel = 0" (00422f dc)0040 DF41 call printf (00401060 ) 0040DF46 add esp,4 9 :10 : __try0040 DF49 mov dword ptr [ebp-4 ],1 11 : {12 : printf ("trylevel = 1\n" );0040 DF50 push offset string "trylevel = 1" (00422f cc)0040 DF55 call printf (00401060 ) 0040DF5A add esp,4 13 : }0040 DF5D mov dword ptr [ebp-4 ],0 0040 DF64 jmp $L16993+11 h (0040 df7d)14 : __except(EXCEPTION_EXECUTE_HANDLER)0040 DF66 mov eax,1 $L16994: 0040 DF6B ret$L16993: 0040 DF6C mov esp,dword ptr [ebp-18 h]15 : {16 : int a = 0 ;0040 DF6F mov dword ptr [a],0 17 : }0040 DF76 mov dword ptr [ebp-4 ],0 18 :19 : }0040 DF7D mov dword ptr [ebp-4 ],0F FFFFFFFh0040 DF84 jmp $L16989+0 Ah (0040 df94)20 : __except(EXCEPTION_CONTINUE_EXECUTION)0040 DF86 or eax,0F Fh$L16990: 0040 DF89 ret$L16989: 0040 DF8A mov esp,dword ptr [ebp-18 h]21 : {}0040 DF8D mov dword ptr [ebp-4 ],0F FFFFFFFh22 :23 : __try0040 DF94 mov dword ptr [ebp-4 ],2 24 : {25 : printf ("trylevel = 2\n" );0040 DF9B push offset string "trylevel = 2\n" (00422f bc)0040 DFA0 call printf (00401060 ) 0040DFA5 add esp,4 26 :27 : __try0040 DFA8 mov dword ptr [ebp-4 ],3 28 : {29 : printf ("trylevel = 3\n" );0040 DFAF push offset string "trylevel = 3\n" (0042201 c)0040 DFB4 call printf (00401060 ) 0040DFB9 add esp,4 30 : }0040 DFBC mov dword ptr [ebp-4 ],2 0040 DFC3 jmp $L17001+0 Ah (0040 dfe9)31 : __except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? EXCEPTION_EXECUTE_HANDLER :\32 : EXCEPTION_CONTINUE_SEARCH)0040 DFC5 mov eax,dword ptr [ebp-14 h]0040 DFC8 mov ecx,dword ptr [eax]0040 DFCA mov edx,dword ptr [ecx]0040 DFCC mov dword ptr [ebp-20 h],edx0040 DFCF mov eax,dword ptr [ebp-20 h]0040 DFD2 xor ecx,ecx0040 DFD4 cmp eax,0 C0000094h0040 DFD9 sete cl0040 DFDC mov eax,ecx$L17002: 0040 DFDE ret$L17001: 0040 DFDF mov esp,dword ptr [ebp-18 h]33 : {}0040 DFE2 mov dword ptr [ebp-4 ],2 34 : }0040 DFE9 mov dword ptr [ebp-4 ],0F FFFFFFFh0040 DFF0 jmp $L16997+0 Ah (0040e002 )35 : __except(EXCEPTION_EXECUTE_HANDLER)0040 DFF2 mov eax,1 $L16998: 0040 DFF7 ret$L16997: 0040 DFF8 mov esp,dword ptr [ebp-18 h]36 : {}0040 DFFB mov dword ptr [ebp-4 ],0F FFFFFFFh37 :38 : return ;39 : }0040E002 mov ecx,dword ptr [ebp-10 h]0040E005 mov dword ptr fs:[0 ],ecx0040E00 C pop edi0040E00 D pop esi0040E00 E pop ebx0040E00 F add esp,60 h0040E012 cmp ebp,esp0040E014 call __chkesp (004010e0 )0040E019 mov esp,ebp0040E01 B pop ebp0040E01 C ret
进入函数时注册异常处理器 _EXCEPTION_REGISTRATION
,即0040DF00~0040DF16
,并将 trylevel
初始化为 0xffffffff
。
进入第一个 __try
的第一句代码就 trylevel = 0
。
1 0040 DF35 mov dword ptr [ebp-4 ],0
进入第二个 __try
时立即修改 trylevel = 1
。
1 0040 DF49 mov dword ptr [ebp-4 ],1
离开第二个 __try
时理解修改 trylevel = 0
。以及离开第一个 __try
时理解修改 trylevel = 0xffffffff
。
1 2 3 0040 DF5D mov dword ptr [ebp-4 ],0 ... 0040 DF7D mov dword ptr [ebp-4 ],0F FFFFFFFh
当最后离开函数时,注销异常处理器 _EXCEPTION_REGISTRATION
。
1 2 0040E002 mov ecx,dword ptr [ebp-10 h]0040E005 mov dword ptr fs:[0 ],ecx
可以看到,在正常的代码执行流当中,并不会执行 __except(Filter){}
的代码 。只有当异常产生才会有机会执行 __except(){}
。
可以看到针对每一个 __except()
括号中过滤器编译时,都会有一个 ret
指令,这是因为编译器将每一个过滤器都当成一个函数来编译 ,并将过滤器的首地址放在 scopetable_entry.lpfnFilter
中。 方便 __except_handler3
找到并执行过滤器然后做判断。
__except
花括号里面的代码没有 ret
,也就是说 __except
执行完成后继续往下执行,并不是返回。
1.4.3 scopetable 为了描述应用程序代码中的 __try{}__except
结构,编译器在编译每个使用此结构的函数时会为其建立一个数组 ,并存储在模块文件的数据区 (通常称为异常处理范围表)中,也叫做范围表 。数组的每个元素是一个 scopetable_entry
结构,用来描述一个 __try{}__except
结构。
1 2 3 4 5 6 struct scopetable_entry { DWORD previousTryLevel PARPROC lpfnFilter PARPROC lpfnHandler };
IpfnFiter
:指向过滤表达式(被编译成的)函数 。
lpfhHlandler
: 异常处理块的起始地址。
如上一节 Test__try__except
函数的范围表 scopetable
地址为 0x00422ff0
:
说明 :在 scopetable
表中,对于有效的 scopetable_entry
项:
lpfnFilter == NULL
,对应于 __try{}__finally
,lpfnHandler
在局部展开中执行。
lpfnFilter != NULL
,对应于 __try{}__except
,lpfnHandler
在异常处理函数中执行。
1.4.4 VC6编译拓展FS:[0]链说明 如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #include "stdafx.h" #include <windows.h> struct scopetable_entry { DWORD previousTryLevel; FARPROC lpfnFilter; FARPROC lpfnHandler; }; typedef struct _EXCEPTION_REGISTRATION { _EXCEPTION_REGISTRATION* prev; FARPROC handler; scopetable_entry * scopetable; DWORD trylevel; DWORD _ebp; }EXCEPTION_REGISTRATION; void ShowExceptionList (void ) { DWORD ExpList = 0 ; __asm { mov eax, fs:[0 ]; mov ExpList, eax; } EXCEPTION_REGISTRATION* pExpList = (EXCEPTION_REGISTRATION*)ExpList; scopetable_entry * pScopeTableEntry = pExpList->scopetable; while (pExpList != (EXCEPTION_REGISTRATION*)0xFFFFFFFF ) { printf ( "Frame: %08X, Prev: %08X, Handler: %08X, Scopetable: %08X, TryLevel: %08X\n" , pExpList, pExpList->prev, pExpList->handler, pExpList->scopetable, pExpList->trylevel ); for (DWORD i = 0 ; i <= pExpList->trylevel; i++ ) { printf ( " scopetable[%u] PrevTryLevel: %08X " "filter: %08X __except: %08X\n" , i, pScopeTableEntry->previousTryLevel, pScopeTableEntry->lpfnFilter, pScopeTableEntry->lpfnHandler ); pScopeTableEntry++; } printf ( "\n" ); pExpList = (EXCEPTION_REGISTRATION*)pExpList->prev; } } void Test__try__except (void ) { __try { printf ("trylevel = 0\n" ); __try { printf ("trylevel = 1\n" ); } __except(EXCEPTION_EXECUTE_HANDLER) { int a = 0 ; } } __except(EXCEPTION_CONTINUE_EXECUTION) {} __try { printf ("trylevel = 2\n" ); __try { ShowExceptionList(); printf ("trylevel = 3\n" ); } __except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? EXCEPTION_EXECUTE_HANDLER :\ EXCEPTION_CONTINUE_SEARCH) {} } __except(EXCEPTION_EXECUTE_HANDLER) {} return ; } int main (int argc, char * argv[]) { Test__try__except(); getchar(); return 0 ; }
结构 _EXCEPTION_REGISTRATION
后三个成员是编译器拓展的,所以这三个成员在不同的时刻所指向的地址空间的值是会变化的,这些地址是可以被其他程序读写。但是前两个成员 prev
、hander
一直是正确的(因为他们由系统维护)。
可以看到,除了函数 Test__try__except
中有 Hander: 0x00401520
(__except_hander3)外,下面还有一个,同时 fs:[0]
链上孩挂着一个 Hander: 0x7c839ac0
异常处理回调函数。
实际上第二个帧来自Visual C++运行时库。Visual C++ 运行时库源代码中的 CRT0.C
文件清楚地表明了对 main
或 WinMain
的调用也被一个__try__except
块封装着。这个 __try
块的过滤器表达式代码可以在 WINXFLTR.C
文件中找到。
注意到最后一个帧的异常处理程序的地址是 0x7c839ac0
,这与其它两个不同。仔细观察一下,你会发现这个地址在 KERNEL32.DLL
中。这个特别的帧就是由 KERNEL32.DLL
中的 BaseProcessStart
函数安装的。(会在未执行异常中讲解)
参考:《Windows异常处理 》。
1.4.5 VS2019编译32位SEH VS 编译器对 Windows 提供的 EXCEPTION_REGISTRATION_RECORD
结构进行了拓展,也在 VC6 编译器基础上做了拓展后:
1 2 3 4 5 6 7 8 struct _EXCEPTION_REGISTRATION { struct _EXCEPTION_REGISTRATION * prev ; void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_ REGISTRATION, PCONTEXT, PEXCEPTION_RECORD); struct VS_scopetable_entry * scopetable ; int trylevel; int _ebp; };
字段
作用
prev
指向上一个结构体的地址,上一个异常链
handler
指向异常处理函数
scopetable
范围表的起始地址
trylevel
这个结构对应的__try块的编号
ebp
栈帧的基地址
但是 VS 编译器在 VC6 编译器上又做了部分拓展,主要有两点:
异常处理回调函数统一入口变成 __except_handler4
函数。
trylevel 初始化为 0xfffffffe
,主要是为了同 FS:[0] == -1
做一个区别,第一个 __try
还是从 0
开始的。
VS 编译器对 scopetable
在 VC6 基础上进行了拓展:
1 2 3 4 5 6 7 8 struct VS_scopetable_entry { DWORD GSCookieOffset; DWORD GSCookieXOROffset; DWORD EHCookieOffset; DWORD EHCookieXOROffset; struct scopetable_entry [_MAX_LENTH ]; };
这里扩展后的 VS_scopetable_entry
名字是我自己取的,在 scopetable_entry
前面的 4 个成员名字是从 IDA 反汇编看到的。
一个函数里如果有多个 __try{}__except
则会记录在 struct scopetable_entry
数组表里。
VS2019编译 1.4.2 示例函数 TestSEH
汇编代码如下 (Debug版):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include "stdio.h" #include <windows.h> void Test__try__except (void ) { __try { printf ("trylevel = 0\n" ); __try { printf ("trylevel = 1\n" ); } __except (EXCEPTION_EXECUTE_HANDLER) { int a = 0 ; } } __except (EXCEPTION_CONTINUE_EXECUTION) { } __try { printf ("trylevel = 2\n" ); __try { printf ("trylevel = 3\n" ); } __except (GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? EXCEPTION_EXECUTE_HANDLER : \ EXCEPTION_CONTINUE_SEARCH) { } } __except (EXCEPTION_EXECUTE_HANDLER) { } return ; } int main (int argc, char * argv[]) { Test__try__except(); getchar(); return 0 ; }
函数 Test__try__except
反汇编代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 .text:00411780 .text:00411780 .text:00411780 .text:00411780 .text:00411780 sub_411780 proc near .text:00411780 .text:00411780 var_F4 = dword ptr -0F 4h .text:00411780 var_EC = dword ptr -0 ECh .text:00411780 var_34 = byte ptr -34 h .text:00411780 var_20 = dword ptr -20 h .text:00411780 ms_exc = CPPEH_RECORD ptr -18 h .text:00411780 .text:00411780 push ebp .text:00411781 mov ebp, esp .text:00411783 push 0F FFFFFFEh .text:00411785 push offset stru_419160 .text:0041178 A push offset SEH_4130C0 .text:0041178F mov eax, large fs:0 .text:00411795 push eax .text:00411796 add esp, 0F FFFFF1Ch .text:0041179 C push ebx .text:0041179 D push esi .text:0041179 E push edi .text:0041179F lea edi, [ebp+var_34] .text:004117 A2 mov ecx, 7 .text:004117 A7 mov eax, 0 CCCCCCCCh .text:004117 AC rep stosd .text:004117 AE mov eax, ___security_cookie .text:004117B 3 xor [ebp+ms_exc.registration.ScopeTable], eax .text:004117B 6 xor eax, ebp .text:004117B 8 push eax .text:004117B 9 lea eax, [ebp+ms_exc.registration] .text:004117B C mov large fs:0 , eax .text:004117 C2 mov [ebp+ms_exc.old_esp], esp .text:004117 C5 mov ecx, offset unk_41C00F .text:004117 CA call sub_411320 .text:004117 CF mov [ebp+ms_exc.registration.TryLevel], 0 .text:004117 D6 push offset aTrylevel0 .text:004117 DB call sub_4110D7 .text:004117E0 add esp, 4 .text:004117E3 mov [ebp+ms_exc.registration.TryLevel], 1 .text:004117 EA push offset aTrylevel1 .text:004117 EF call sub_4110D7 .text:004117F 4 add esp, 4 .text:004117F 7 mov [ebp+ms_exc.registration.TryLevel], 0 .text:004117F E jmp short loc_411817 .text:00411800 .text:00411800 .text:00411800 loc_411800: .text:00411800 mov eax, 1 .text:00411805 retn .text:00411806 .text:00411806 .text:00411806 loc_411806: .text:00411806 mov esp, [ebp+ms_exc.old_esp] .text:00411809 mov [ebp+var_20], 0 .text:00411810 mov [ebp+ms_exc.registration.TryLevel], 0 .text:00411817 .text:00411817 loc_411817: .text:00411817 mov [ebp+ms_exc.registration.TryLevel], 0F FFFFFFEh .text:0041181 E jmp short loc_41182E .text:00411820 .text:00411820 .text:00411820 loc_411820: .text:00411820 or eax, 0F FFFFFFFh .text:00411823 retn .text:00411824 .text:00411824 .text:00411824 loc_411824: .text:00411824 mov esp, [ebp+ms_exc.old_esp] .text:00411827 mov [ebp+ms_exc.registration.TryLevel], 0F FFFFFFEh .text:0041182 E .text:0041182 E loc_41182E: .text:0041182 E mov [ebp+ms_exc.registration.TryLevel], 2 .text:00411835 push offset aTrylevel2 .text:0041183 A call sub_4110D7 .text:0041183F add esp, 4 .text:00411842 mov [ebp+ms_exc.registration.TryLevel], 3 .text:00411849 push offset aTrylevel3 .text:0041184 E call sub_4110D7 .text:00411853 add esp, 4 .text:00411856 mov [ebp+ms_exc.registration.TryLevel], 2 .text:0041185 D jmp short loc_41189F .text:0041185F .text:0041185F .text:0041185F loc_41185F: .text:0041185F mov eax, [ebp+ms_exc.exc_ptr] .text:00411862 mov ecx, [eax] .text:00411864 mov edx, [ecx] .text:00411866 mov [ebp+var_EC], edx .text:0041186 C cmp [ebp+var_EC], 0 C0000094h .text:00411876 jnz short loc_411884 .text:00411878 mov [ebp+var_F4], 1 .text:00411882 jmp short loc_41188E .text:00411884 .text:00411884 .text:00411884 loc_411884: .text:00411884 mov [ebp+var_F4], 0 .text:0041188 E .text:0041188 E loc_41188E: .text:0041188 E mov eax, [ebp+var_F4] .text:00411894 retn .text:00411895 .text:00411895 .text:00411895 loc_411895: .text:00411895 mov esp, [ebp+ms_exc.old_esp] .text:00411898 mov [ebp+ms_exc.registration.TryLevel], 2 .text:0041189F .text:0041189F loc_41189F: .text:0041189F mov [ebp+ms_exc.registration.TryLevel], 0F FFFFFFEh .text:004118 A6 jmp short loc_4118B8 .text:004118 A8 .text:004118 A8 .text:004118 A8 loc_4118A8: .text:004118 A8 mov eax, 1 .text:004118 AD retn .text:004118 AE .text:004118 AE .text:004118 AE loc_4118AE: .text:004118 AE mov esp, [ebp+ms_exc.old_esp] .text:004118B 1 mov [ebp+ms_exc.registration.TryLevel], 0F FFFFFFEh .text:004118B 8 .text:004118B 8 loc_4118B8: .text:004118B 8 mov ecx, [ebp+ms_exc.registration.Next] .text:004118B B mov large fs:0 , ecx .text:004118 C2 pop ecx .text:004118 C3 pop edi .text:004118 C4 pop esi .text:004118 C5 pop ebx .text:004118 C6 add esp, 0F 4h .text:004118 CC cmp ebp, esp .text:004118 CE call sub_411249 .text:004118 D3 mov esp, ebp .text:004118 D5 pop ebp .text:004118 D6 retn .text:004118 D6 sub_411780 endp .text:004118 D6 .text:004118 D6
将 _EXCEPTION_REGISTRATION
结构挂入:
1 2 .text:004117B 9 lea eax, [ebp+ms_exc.registration] .text:004117B C mov large fs:0 , eax
卸载 _EXCEPTION_REGISTRATION
结构:
1 2 .text:004118B 8 mov ecx, [ebp+ms_exc.registration.Next] .text:004118B B mov large fs:0 , ecx
此时的 VS_scopetable_entry
数据如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .rdata:00419160 stru_419160 dd 0F FFFFFFEh ; GSCookieOffset .rdata:00419160 ; DATA XREF: sub_411780+5 ↑o .rdata:00419160 dd 0 ; GSCookieXOROffset ; SEH scope table for function 411780 .rdata:00419160 dd 0F FFFFEFCh ; EHCookieOffset .rdata:00419160 dd 0 ; EHCookieXOROffset .rdata:00419160 dd 0F FFFFFFEh ; ScopeRecord.EnclosingLevel .rdata:00419160 dd offset loc_411820 ; ScopeRecord.FilterFunc .rdata:00419160 dd offset loc_411824 ; ScopeRecord.HandlerFunc .rdata:00419160 dd 0 ; ScopeRecord.EnclosingLevel .rdata:00419160 dd offset loc_411800 ; ScopeRecord.FilterFunc .rdata:00419160 dd offset loc_411806 ; ScopeRecord.HandlerFunc .rdata:00419160 dd 0F FFFFFFEh ; ScopeRecord.EnclosingLevel .rdata:00419160 dd offset loc_4118A8 ; ScopeRecord.FilterFunc .rdata:00419160 dd offset loc_4118AE ; ScopeRecord.HandlerFunc .rdata:00419160 dd 2 ; ScopeRecord.EnclosingLevel .rdata:00419160 dd offset loc_41185F ; ScopeRecord.FilterFunc .rdata:00419160 dd offset loc_411895 ; ScopeRecord.HandlerFunc .rdata:004191 A0 dd 0 .rdata:004191 A4 dd 0
VC6 和 VS2019 编译的 SEH 大同小异。主要不同点:
handler 初始化
VC6:handler = __except_handler3
VS2019:handler = __except_handler4
trylevel 初始化
VC6:trylevel = 0xffffffff
VS2019:trylevel = 0xfffffffe
scopetable_entry
结构拓展:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct scopetable_entry { DWORD previousTryLevel PARPROC lpfnFilter PARPROC lpfnHandler }; struct VS_scopetable_entry { DWORD GSCookieOffset; DWORD GSCookieXOROffset; DWORD EHCookieOffset; DWORD EHCookieXOROffset; struct scopetable_entry [_MAX_LENTH ]; };
1.4.6 VS2019和VC6编译32位SEH区别 它们对 SEH 的具体实现整体上是一致的,VS2019有变化的是:
VS2019 对 scopetable_entry
做了扩展:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct _EXCEPTION_REGISTRATION // VC6 /VS2019 通用{ struct _EXCEPTION_REGISTRATION * prev ; void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_ REGISTRATION, PCONTEXT, PEXCEPTION_RECORD); struct scopetable_entry * scopetable ; int trylevel; int _ebp; }; struct VS_scopetable_entry { DWORD GSCookieOffset; DWORD GSCookieXOROffset; DWORD EHCookieOffset; DWORD EHCookieXOROffset; struct scopetable_entry [_MAX_LENTH ]; };
其中增加了 SeeurityCookie 的相关内容,这是微软为了防止缓冲区溢出而设置的栈验证机制(即从 Visual Studio 2003 开始增加的CS 保护机制),在函数开头会对栈中的 SeopeTable 使用 Cookie 作为密钥进行加密,异常处理函数也变成了 __except _handler4
。在该函数中,除了增加了对 Security Cookie 和 ScopeTable 的验证之外,整体流程与 __except_handler3
完全一致。
VS2019 的 trylevel
初始值为 0xfffffffe
(VC6为 0xffffffff
)。
VS2019 的异常处理统一入口为 __except_hander4
(VC6为 __except_hander3
)。
可以参考《加密与解密第四版8.3.4》。
2 _except_handler3执行过程 2.1 执行流程 虽然编译器扩展了 Windows 的 SEH 处理机制,但是也仅在执行到 __except_handler3
时才会体现出差异。
如前面练习过的除零异常:CPU检测到异常 --> 查中断表执行处理函数 --> CommonDispatchException --> KiDispatchException --> KiUserExceptionDispatcher --> ntdll!RtlDispatchException --> VEH --> SEH --> __except_handler3。
执行 __except_handler3
函数的过程如下:
将 SEH 异常回调函数的第二个参数 EstablisherFrame
强制转化为 _EXCEPTION_REGISTRATION
结构。
将 _EXCEPTION_REGISTRATION
的成员 trylevel
赋值给变量 nTrylevel
,然后取出 scopetable[nTrylevel]
。
判断 scopetable[nTrylevel].lpfnFilter
过滤函数,如果不为空则直接调用之,如果为空则执行下面第 5 步。
判断 lpfnFilter
函数的返回值:
EXCEPTION_EXECUTE_HANDLER (1),执行 lpfnHandler
,执行完后由于 lpfnHandler
里面没有 ret
所以会继续往下执行代码。
EXCEPTION_CONTINUE_EXECUTION (-1),说明异常已经处理了,直接返回即可。
EXCEPTION_CONTINUE_SEARCH (0),执行第 5 步。
判断当前的是否已经是最外层:
previousTryLevel != -1
,则 nTrylevel = previousTryLevel
,并取出 scopetable[nTrylevel]
,去执行第 3 步。
previousTryLevel == -1
,已经是最外层,则执行第 6 步。
__except_handler3
返回 EXCEPTION_CONTINUE_SEARCH
,让 ntdll!RtlDispatchException
继续寻找其他异常处理器。
注意:
上面第 4 步过滤器返回 EXCEPTION_CONTINUE_SEARCH
是说明本异常处理块不处理当前异常,需要寻找其他异常处理块,并不是寻找其他异常处理器 。寻找其他异常处理器是需要循环链条的,尽管链条上的其他 SEH 并不一定可以解决当前异常。
只有当 __except_handler3
处理到 previousTryLevel == -1
时,返回的 EXCEPTION_CONTINUE_SEARCH
才是继续寻找其他异常处理器。
2.2 源代码堆栈分析 如下的 TestSEH
代码进行分析:
源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <Windows.h> void TestSEH () { __try { } __except (GetExceptionInformation() == NULL ?1 :0 ) { int a = 9 ; } } int main (int argc, char * argv[]) { TestSEH(); return 0 ; }
TestSEH
函数反汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 .text:00401020 .text:00401020 .text:00401020 .text:00401020 .text:00401020 sub_401020 proc near .text:00401020 .text:00401020 var_5C = byte ptr -5 Ch .text:00401020 var_1C = dword ptr -1 Ch .text:00401020 ms_exc = CPPEH_RECORD ptr -18 h .text:00401020 .text:00401020 .text:00401020 push ebp .text:00401021 mov ebp, esp .text:00401023 push 0F FFFFFFFh .text:00401025 push offset stru_422020 .text:0040102 A push offset __except_handler3 .text:0040102F mov eax, large fs:0 .text:00401035 push eax .text:00401036 mov large fs:0 , esp .text:0040103 D add esp, 0F FFFFFB4h .text:00401040 push ebx .text:00401041 push esi .text:00401042 push edi .text:00401043 mov [ebp+ms_exc.old_esp], esp .text:00401046 lea edi, [ebp+var_5C] .text:00401049 mov ecx, 11 h .text:0040104 E mov eax, 0 CCCCCCCCh .text:00401053 rep stosd .text:00401055 .text:00401055 mov [ebp+ms_exc.registration.TryLevel], 0 .text:00401055 .text:0040105 C mov [ebp+ms_exc.registration.TryLevel], 0F FFFFFFFh .text:00401063 jmp short loc_40107F .text:00401065 .text:00401065 .text:00401065 loc_401065: .text:00401065 .text:00401065 mov eax, [ebp+ms_exc.exc_ptr] .text:00401068 neg eax .text:0040106 A sbb eax, eax .text:0040106 C inc eax .text:0040106 D retn .text:0040106 E .text:0040106 E .text:0040106 E loc_40106E: .text:0040106 E .text:0040106 E mov esp, [ebp+ms_exc.old_esp] .text:00401071 mov [ebp+var_1C], 9 .text:00401078 mov [ebp+ms_exc.registration.TryLevel], 0F FFFFFFFh .text:0040107F .text:0040107F loc_40107F: .text:0040107F mov ecx, [ebp+ms_exc.registration.Next] .text:00401082 mov large fs:0 , ecx .text:00401089 pop edi .text:0040108 A pop esi .text:0040108B pop ebx .text:0040108 C mov esp, ebp .text:0040108 E pop ebp .text:0040108F retn .text:0040108F .text:0040108F sub_401020 endp .text:0040108F .text:0040108F
TestSEH
函数进入 __try
之前函数堆栈情况如下:
注意 :
ebp-0x18
处将当前 esp
保存起来是必须的,因为从 __except_handler3
中调用 lpfnHandler
进入到 except(){}
中后,由于 except(){}
不会再返回到 __except_handler3
函数,所以要用这里的 esp
恢复当前堆栈:
1 .text:0040106 E mov esp, [ebp+ms_exc.old_esp]
ebp-0x14
指向的 &_EXCEPTION_POINTERS
是由 __except_handler3
函数压栈的,这是为了在产生异常的函数过滤表达式中使用到才压栈的。
1 .text:00401065 mov eax, [ebp+ms_exc.exc_ptr]
2.3 _except_handler3逆向分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 .text:004011 D4 .text:004011 D4 .text:004011 D4 .text:004011 D4 .text:004011 D4 .text:004011 D4 __except_handler3 proc near .text:004011 D4 .text:004011 D4 .text:004011 D4 var_ExceptionRecord= dword ptr -8 .text:004011 D4 var_ContextRecord= dword ptr -4 .text:004011 D4 arg_ExceptionRecord= dword ptr 8 .text:004011 D4 TargetFrame = dword ptr 0 Ch .text:004011 D4 arg_ContextRecord= dword ptr 10 h .text:004011 D4 .text:004011 D4 push ebp .text:004011 D5 mov ebp, esp .text:004011 D7 sub esp, 8 .text:004011 DA push ebx .text:004011 DB push esi .text:004011 DC push edi .text:004011 DD push ebp .text:004011 DE cld .text:004011 DF mov ebx, [ebp+TargetFrame] .text:004011E2 mov eax, [ebp+arg_ExceptionRecord] .text:004011E5 test [eax+_EXCEPTION_RECORD.ExceptionFlags], 6 .text:004011 EC jnz _lh_unwinding .text:004011F 2 mov [ebp+var_ExceptionRecord], eax .text:004011F 5 mov eax, [ebp+arg_ContextRecord] .text:004011F 8 mov [ebp+var_ContextRecord], eax .text:004011F B lea eax, [ebp+var_ExceptionRecord] .text:004011F B .text:004011F B .text:004011F B .text:004011F B .text:004011F B .text:004011F B .text:004011F E mov [ebx-4 ], eax .text:00401201 mov esi, [ebx+_EXCEPTION_REGISTRATION.trylevel] .text:00401204 mov edi, [ebx+_EXCEPTION_REGISTRATION.scopetable] .text:00401207 .text:00401207 _lh_top: .text:00401207 cmp esi, 0F FFFFFFFh .text:0040120 A jz short _lh_bagit .text:0040120 C lea ecx, [esi+esi*2 ] .text:0040120F cmp [edi+ecx*4 +scopetable_entry.lpfnFilter], 0 .text:00401214 jz short _lh_continue .text:00401216 push esi .text:00401217 push ebp .text:00401218 lea ebp, [ebx+_EXCEPTION_REGISTRATION._ebp] .text:0040121B call [edi+ecx*4 +scopetable_entry.lpfnFilter] .text:0040121F pop ebp .text:00401220 pop esi .text:00401221 mov ebx, [ebp+TargetFrame] .text:00401224 or eax, eax .text:00401226 jz short _lh_continue .text:00401228 js short _lh_dismiss .text:0040122 A mov edi, [ebx+_EXCEPTION_REGISTRATION.scopetable] .text:0040122 D push ebx .text:0040122 E call __global_unwind2 .text:00401233 add esp, 4 .text:00401236 lea ebp, [ebx+10 h] .text:00401239 push esi .text:0040123 A push ebx .text:0040123B call __local_unwind2 .text:00401240 add esp, 8 .text:00401243 lea ecx, [esi+esi*2 ] .text:00401246 push 1 .text:00401248 mov eax, [edi+ecx*4 +8 ] .text:0040124 C call __NLG_Notify .text:00401251 mov eax, [edi+ecx*4 ] .text:00401254 mov [ebx+0 Ch], eax .text:00401257 call [edi+ecx*4 +scopetable_entry.lpfnHandler] .text:0040125B .text:0040125B _lh_continue: .text:0040125B .text:0040125B mov edi, [ebx+_EXCEPTION_REGISTRATION.scopetable] .text:0040125 E lea ecx, [esi+esi*2 ] .text:00401261 mov esi, [edi+ecx*4 +scopetable_entry.previousTryLevel] .text:00401264 jmp short _lh_top .text:00401266 .text:00401266 .text:00401266 _lh_dismiss: .text:00401266 mov eax, 0 .text:0040126B jmp short _lh_return .text:0040126 D .text:0040126 D .text:0040126 D _lh_bagit: .text:0040126 D mov eax, 1 .text:00401272 jmp short _lh_return .text:00401274 .text:00401274 .text:00401274 _lh_unwinding: .text:00401274 push ebp .text:00401275 lea ebp, [ebx+10 h] .text:00401278 push 0F FFFFFFFh .text:0040127 A push ebx .text:0040127B call __local_unwind2 .text:00401280 add esp, 8 .text:00401283 pop ebp .text:00401284 mov eax, 1 .text:00401289 .text:00401289 _lh_return: .text:00401289 .text:00401289 pop ebp .text:0040128 A pop edi .text:0040128B pop esi .text:0040128 C pop ebx .text:0040128 D mov esp, ebp .text:0040128F pop ebp .text:00401290 retn .text:00401290 __except_handler3 endp
这里主要分析几点:
在函数堆栈中构建一个 _EXCEPTION_POINTERS
结构,然后将这个结构的起始地址放到 TestSEH
函数堆栈 ebp-0x14
的位置。
1 2 3 .text:004011 DF mov ebx, [ebp+TargetFrame] ... .text:004011F E mov [ebx-4 ], eax
此时的 ebx
指向回调函数 _except_handler3
第二个参数TargetFrame
,VC6编译器将其转换为扩展的 _EXCEPTION_REGISTRATION
结构。
在 Windows 中,当异常产生的时候,会将当前堆栈中的 EXCEPTION_REGISTR_RECORD
结构地址传给异常处理回调函数的第二个参数。所以在这里__except_handler3
第二个参数TargetFrame
也就是本例中 _EXCEPTION_REGISTRATION
的起始地址,如下图可以知道这里的 ebx-4
即为产生异常那个函数 ebp-0x14
的位置。
对于过滤器如何过滤已经在 2.1 讲过,参照代码看看即可。
当 __except_hander3
开始调用 lpfnHander
后就不再返回到当前函数了,就会从产生异常的函数继续往下执行。
在产生异常的函数 TestSEH
中 :ebp-0x18
处将当前 esp
保存起来是必须的,因为从 __except_handler3
中调用 lpfnHandler
进入到 except(){}
中后,由于 except(){}
不会再返回到 __except_handler3
函数,所以要用这里的 esp
恢复当前堆栈:
1 .text:0040106 E mov esp, [ebp+ms_exc.old_esp]
调用过滤器函数时,会将 TestSEH
函数的 ebp
当参数传进去,是为了寻址用。
1 2 .text:00401218 lea ebp, [ebx+_EXCEPTION_REGISTRATION._ebp] .text:0040121B call [edi+ecx*4 +scopetable_entry.lpfnFilter]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 00761 DC0 55 push ebp 00761 DC1 8B EC mov ebp,esp 00761 DC3 8B 45 08 mov eax,dword ptr [ExceptionRecord] 00761 DC6 8B 08 mov ecx,dword ptr [eax] 00761 DC8 51 push ecx 00761 DC9 E8 C5 F5 FF FF call __filter_x86_sse2_floating_point_exception (0761393 h) 00761 DCE 83 C4 04 add esp,4 00761 DD1 8B 55 08 mov edx,dword ptr [ExceptionRecord] 00761 DD4 89 02 mov dword ptr [edx],eax 00761 DD6 8B 45 14 mov eax,dword ptr [DispatcherContext] 00761 DD9 50 push eax 00761 DDA 8B 4 D 10 mov ecx,dword ptr [ContextRecord] 00761 DDD 51 push ecx 00761 DDE 8B 55 0 C mov edx,dword ptr [EstablisherFrame] 00761 DE1 52 push edx 00761 DE2 8B 45 08 mov eax,dword ptr [ExceptionRecord] 00761 DE5 50 push eax 00761 DE6 68 4F 11 76 00 push offset @__security_check_cookie@4 (076114F h) 00761 DEB 68 04 A0 76 00 push offset __security_cookie (076 A004h) 00761 DF0 E8 FA F3 FF FF call __except_handler4_common (07611 EFh) 00761 DF5 83 C4 18 add esp,18 h 00761 DF8 5 D pop ebp 00761 DF9 C3 ret __except_handler4_common:
3 栈展开 try-finally
结构的终结处理是为了保证 finally
块中的代码可以执行(除非被保护体中的代码终止了当前线程,比如调用ExitThread
或ExitProcess
退出线程或整个进程。)。
局部栈展开的意义 :为了保证 __fianlly
在正常情况下及产生异常进入 __except_hander3
后都能够执行。
全局栈展开的意义 :在当一个函数产生异常时,FS:[0]
链条上可能又挂入了一些新的_EXCEPTION_REGISTR_RECORD
结构,全局展开的意义就在于让这些新挂入的 _EXCEPTION_REGISTR_RECORD
异常处理函数有机会得到执行。
局部展开是为 __finally
服务的,全局展开是让 FS:[0]
链条上新挂入的 _EXCEPTION_REGISTR_RECORD
异常处理函数得到机会执行。
需要注意 :__finally
终结块的代码汇编后有 ret
指令 ,而 __except
异常处理块的代码汇编指令没有 ret
指令 。
全局展开即遍历TEB中异常链表中当前遍历中命中的异常信息记录节点之前的所有节点(遍历中要对每一个节点执行局部展开)。而局部展开是针对某一个具体的异常信息记录节点,即对应一个函数的异常处理信息。它内部可能包含多个try语句,并且try可能进行嵌套。
说明 :在 scopetable
表中,对于有效的 scopetable_entry
项:
lpfnFilter == NULL
,对应于 __try{}__finally
,lpfnHandler
在局部展开中执行。
lpfnFilter != NULL
,对应于 __try{}__except
,lpfnHandler
在异常处理函数中执行。
3.1 局部展开 一、没有使用局部展开
在 __try
中如果没有使用 return
、 goto
、break
、continue
等流程控制语句时,是不会使用到局部展开/全局展开的,如下代码如果将 __try
中的 return
注释掉,则在即将离开 __try
代码时会调用 __finally
的代码,如下 0x0040106E
处的汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 5 : __try00401052 mov dword ptr [ebp-4 ],0 6 : {7 : int a = 6 ;00401059 mov dword ptr [a],6 8 : int b = 9 ;00401060 mov dword ptr [b],9 9 : 10 : }00401067 mov dword ptr [ebp-4 ],0F FFFFFFFh0040106 E call $L544 (00401075 )00401073 jmp $L547 (00401083 )11 : __finally12 : {13 : printf ("This is finally.\n" );00401075 push offset string "This is finally.\n" (0042301 c)0040107 A call printf (00401190 ) 0040107F add esp,4 $L545: 00401082 ret14 : }15 : }
二、使用局部展开
局部展开的意义 :如果在 __try
中如果使用 return
、 goto
、break
、continue
等流程控制语句离开当前保护块时,为了保证 finally
终止块的代码得到执行,就会使用到局部展开 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "stdafx.h" void Test_local_try_finally (void ) { __try { int a = 6 ; int b = 9 ; return ; } __finally { printf ("This is finally.\n" ); } } int main (int argc, char * argv[]) { Test_local_try_finally(); getchar(); return 0 ; }
反汇编代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 .text:00401020 .text:00401020 .text:00401020 .text:00401020 .text:00401020 sub_401020 proc near .text:00401020 .text:00401020 var_60 = byte ptr -60 h .text:00401020 var_20 = dword ptr -20 h .text:00401020 var_1C = dword ptr -1 Ch .text:00401020 var_10 = dword ptr -10 h .text:00401020 var_4 = dword ptr -4 .text:00401020 .text:00401020 .text:00401020 push ebp .text:00401021 mov ebp, esp .text:00401023 push 0F FFFFFFFh .text:00401025 push offset dword_423048 .text:0040102 A push offset __except_handler3 .text:0040102F mov eax, large fs:0 .text:00401035 push eax .text:00401036 mov large fs:0 , esp .text:0040103 D add esp, 0F FFFFFB0h .text:00401040 push ebx .text:00401041 push esi .text:00401042 push edi .text:00401043 lea edi, [ebp+var_60] .text:00401046 mov ecx, 12 h .text:0040104B mov eax, 0 CCCCCCCCh .text:00401050 rep stosd .text:00401052 .text:00401052 mov [ebp+var_4], 0 .text:00401059 mov [ebp+var_1C], 6 .text:00401060 mov [ebp+var_20], 9 .text:00401067 push 0F FFFFFFFh .text:00401069 lea eax, [ebp+var_10] .text:0040106 C push eax .text:0040106 D call __local_unwind2 .text:0040106 D .text:0040106 D .text:00401072 add esp, 8 .text:00401075 jmp short loc_401085 .text:00401077 .text:00401077 .text:00401077 loc_401077: .text:00401077 .text:00401077 push offset aThisIsFinally .text:0040107 C call _printf .text:00401081 add esp, 4 .text:00401084 retn .text:00401085 .text:00401085 .text:00401085 loc_401085: .text:00401085 mov ecx, [ebp+var_10] .text:00401088 mov large fs:0 , ecx .text:0040108F pop edi .text:00401090 pop esi .text:00401091 pop ebx .text:00401092 add esp, 60 h .text:00401095 cmp ebp, esp .text:00401097 call __chkesp .text:0040109 C mov esp, ebp .text:0040109 E pop ebp .text:0040109F retn .text:0040109F .text:0040109F .text:0040109F sub_401020 endp .text:0040109F .text:0040109F
此时的 scopetable
如下:
1 2 3 4 .rdata:00423048 dword_423048 dd 0F FFFFFFFh .rdata:0042304 C dd 0 .rdata:00423050 dd offset loc_401077 .rdata:00423054 dd 0
在离开 __try
之前调用了局部展开函数,如下代码:
1 .text:0040106 D call __local_unwind2
函数原型:
1 2 3 4 void _local_unwind2( PEXCEPTION_REGISTRATION xr, int stop );
__local_unwind2
函数分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 .text:004012 AA .text:004012 AA .text:004012 AA .text:004012 AA .text:004012 AA .text:004012 AA .text:004012 AA .text:004012 AA .text:004012 AA __local_unwind2 proc near .text:004012 AA .text:004012 AA .text:004012 AA var_previousTryLevel= dword ptr -14 h .text:004012 AA arg_Registion = dword ptr 4 .text:004012 AA arg_0xFF = dword ptr 8 .text:004012 AA .text:004012 AA push ebx .text:004012 AB push esi .text:004012 AC push edi .text:004012 AD mov eax, [esp+0 Ch+arg_Registion] .text:004012B 1 push eax .text:004012B 2 push 0F FFFFFFEh .text:004012B 4 push offset __unwind_handler .text:004012B 9 push large dword ptr fs:0 .text:004012 C0 mov large fs:0 , esp .text:004012 C7 .text:004012 C7 _lu_top: .text:004012 C7 mov eax, [esp+1 Ch+arg_Registion] .text:004012 CB mov ebx, [eax+_EXCEPTION_REGISTRATION.scopetable] .text:004012 CE mov esi, [eax+_EXCEPTION_REGISTRATION.trylevel] .text:004012 D1 cmp esi, 0F FFFFFFFh .text:004012 D4 jz short _lu_done .text:004012 D6 cmp esi, [esp+1 Ch+arg_0xFF] .text:004012 DA jz short _lu_done .text:004012 DC lea esi, [esi+esi*2 ] .text:004012 DF mov ecx, [ebx+esi*4 +scopetable_entry.previousTryLevel] .text:004012E2 mov [esp+1 Ch+var_previousTryLevel], ecx .text:004012E6 mov [eax+_EXCEPTION_REGISTRATION.trylevel], ecx .text:004012E9 cmp [ebx+esi*4 +scopetable_entry.lpfnFilter], 0 .text:004012 EE jnz short __NLG_Return2 .text:004012F 0 push 101 h .text:004012F 5 mov eax, [ebx+esi*4 +8 ] .text:004012F 9 call __NLG_Notify .text:004012F E call [ebx+esi*4 +scopetable_entry.lpfnHandler] .text:004012F E .text:004012F E .text:004012F E .text:004012F E .text:004012F E .text:00401302 .text:00401302 __NLG_Return2: .text:00401302 jmp short _lu_top .text:00401304 .text:00401304 .text:00401304 _lu_done: .text:00401304 .text:00401304 pop large dword ptr fs:0 .text:0040130B add esp, 0 Ch .text:0040130 E pop edi .text:0040130F pop esi .text:00401310 pop ebx .text:00401311 retn .text:00401311 __local_unwind2 endp
该函数的功能:
在 FS:[0]
链头挂入一个新的节点,异常处理回调函数为__unwind_handler
,用来解决在 __local_unwind2
中产生的异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 .text:00401288 .text:00401288 .text:00401288 .text:00401288 .text:00401288 __unwind_handler proc near .text:00401288 .text:00401288 .text:00401288 arg_ExceptionRecord= dword ptr 4 .text:00401288 arg_EstablisherFrame= dword ptr 8 .text:00401288 arg_DispatcherContext= dword ptr 10 h .text:00401288 .text:00401288 mov ecx, [esp+arg_ExceptionRecord] .text:0040128 C test [ecx+_EXCEPTION_RECORD.ExceptionFlags], 6 .text:00401293 mov eax, 1 .text:00401298 jz short _uh_return .text:0040129 A mov eax, [esp+arg_EstablisherFrame] .text:0040129 E mov edx, [esp+arg_DispatcherContext] .text:004012 A2 mov [edx+_DISPATCHER_CONTEXT], eax .text:004012 A4 mov eax, 3 .text:004012 A9 .text:004012 A9 _uh_return: .text:004012 A9 retn .text:004012 A9 __unwind_handler endp ============================================================================================ typedef struct _DISPATCHER_CONTEXT { _EXCEPTION_REGISTRATION_RECORD* RegistrationPointer; } DISPATCHER_CONTEXT; typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next ; PEXCEPTION_ROUTINE Handler; } EXCEPTION_REGISTRATION_RECORD;
如果 scopetable_entry.lpfnFilter == NULL
,则直接调用 scopetable_entry.lpfnHandler(__finally)
。
需要注意 :__finally
终结块的代码汇编后有 ret 指令 ,而 __except
异常处理块的代码汇编指令没有 ret 指令 。
3.2 全局展开 全局栈展开的意义 :在当一个函数产生异常时,FS:[0]
链条上可能又挂入了一些新的_EXCEPTION_REGISTR_RECORD
结构,全局展开的意义就在于让这些新挂入的 _EXCEPTION_REGISTR_RECORD
异常处理函数有机会得到执行。
举例如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include "stdafx.h" void Test_try_finally (void ) { __try { __try { int a = 6 ; int b = 0 ; a = a/b; } __finally { printf ("This is finally.\n" ); } } __except(1 ) { printf ("This is except.\n" ); } } int main (int argc, char * argv[]) { Test_try_finally(); getchar(); return 0 ; }
如果 __try
中代码产生异常了,虽然当前函数已经将 _EXCEPTION_REGISTRATION
结构挂在 FS:[0]
链条上了,但是在 __except_handler3
进行异常处理时,链条 FS:[0]
在此之后又挂入了一些新的 _EXCEPTION_REGISTRATION
结构,为了保证这些新挂入的能得到机会执行就设计了全局展开。虽然在全局展开时会判断链条上的结构在当前线程堆栈中,并且异常处理函数不在当前堆栈中时才能够满足执行条件,估计全局展开还有其他什么用途吧。
1、如果在 __try
不发生异常,正常的执行指令流能够执行到 __finally
中的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 12 : int a = 6 ;0040 D7F3 mov dword ptr [a],6 13 : int b = 0 ;0040 D7FA mov dword ptr [b],0 14 : a = a/b;0040 D801 mov eax,dword ptr [a]0040 D804 cdq0040 D805 idiv eax,dword ptr [b]0040 D808 mov dword ptr [a],eax15 : }0040 D80B mov dword ptr [ebp-4 ],0 0040 D812 call $L551 (0040 d819)0040 D817 jmp $L554 (0040 d827)16 : __finally17 : {18 : printf ("This is finally.\n" );0040 D819 push offset string "This is finally.\n" (00422044 )0040 D81E call printf (00401120 ) 0040D823 add esp,4 $L552: 0040 D826 ret19 : }20 : 21 : }0040 D827 mov dword ptr [ebp-4 ],0F FFFFFFFh0040 D82E jmp $L548+17 h (0040 d84d)22 : __except(1 )0040 D830 mov eax,1 $L549: 0040 D835 ret$L548: 0040 D836 mov esp,dword ptr [ebp-18 h]23 : {24 : printf ("This is except.\n" );0040 D839 push offset string "This is except.\n" (0042201 c)0040 D83E call printf (00401120 ) 0040D843 add esp,4 25 : }0040 D846 mov dword ptr [ebp-4 ],0F FFFFFFFh26 : }0040 D84D mov ecx,dword ptr [ebp-10 h]0040 D850 mov dword ptr fs:[0 ],ecx0040 D857 pop edi0040 D858 pop esi0040 D859 pop ebx0040 D85A add esp,60 h0040 D85D cmp ebp,esp0040 D85F call __chkesp (004011 a0)0040 D864 mov esp,ebp0040 D866 pop ebp0040 D867 ret
可以看到如下代码能够执行 __finally
:
1 2 3 4 5 6 7 0040 D812 call $L551 (0040 d819)... 0040 D819 push offset string "This is finally.\n" (00422044 )0040 D81E call printf (00401120 ) 0040D823 add esp,4 $L552: 0040 D826 ret
此时的 scopetable
表:
1 2 3 .rdata:00422F E0 stru_422FE0 _SCOPETABLE_ENTRY <0F FFFFFFFh, offset loc_40D830, offset loc_40D836> .rdata:00422F E0 .rdata:00422F E0 _SCOPETABLE_ENTRY <0 , 0 , offset loc_40D819>
2、如果在 __try
中发生异常,那将会在 __except_handler3
中进行全局展开,使 __finally
的代码有机会执行,其代码参见 2.3,这里主要看的 __global_unwind2
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 .text:004011 D8 ; =============== S U B R O U T I N E ======================================= .text:004011 D8 .text:004011 D8 ; Attributes: library function bp-based frame .text:004011 D8 .text:004011 D8 ; int __cdecl _global_unwind2(PVOID _EXCEPTION_REGISTRATION) .text:004011 D8 __global_unwind2 proc near ; CODE XREF: __except_handler3+5 A↓p .text:004011 D8 .text:004011 D8 _EXCEPTION_REGISTRATION= dword ptr 8 .text:004011 D8 .text:004011 D8 push ebp .text:004011 D9 mov ebp, esp .text:004011 DB push ebx .text:004011 DC push esi .text:004011 DD push edi .text:004011 DE push ebp .text:004011 DF push 0 ; ReturnValue .text:004011E1 push 0 ; ExceptionRecord .text:004011E3 push offset _gu_return ; TargetIp .text:004011E8 push [ebp+_EXCEPTION_REGISTRATION] .text:004011 EB call RtlUnwind .text:004011F 0 .text:004011F 0 _gu_return: .text:004011F 0 pop ebp .text:004011F 1 pop edi .text:004011F 2 pop esi .text:004011F 3 pop ebx .text:004011F 4 mov esp, ebp .text:004011F 6 pop ebp .text:004011F 7 retn .text:004011F 7 __global_unwind2 endp
可以看到,在 __global_unwind2
里面调用了导入函数 kernel32!RtlUnwind-->ntdll!RtlUnwind
。
1 2 3 4 5 6 VOID __stdcall RtlUnwind ( IN PVOID TargetFrame OPTIONAL, IN PVOID TargetIp OPTIONAL, IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL, IN PVOID ReturnValue )
3.3 RtlUnwind分析 RtlUnwind
函数原型:
1 2 3 4 5 6 VOID __stdcall RtlUnwind ( IN PVOID TargetFrame OPTIONAL, IN PVOID TargetIp OPTIONAL, IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL, IN PVOID ReturnValue )
逆向分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 public _RtlUnwind@16 .text:7 C94ABA5 _RtlUnwind@16 proc near .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 var_37C_ExceptionCode= dword ptr -37 Ch .text:7 C94ABA5 var_378_ExceptionFlags= dword ptr -378 h .text:7 C94ABA5 var_374_ExceptionRecord= dword ptr -374 h .text:7 C94ABA5 var_370_ExceptionAddress= dword ptr -370 h .text:7 C94ABA5 var_36C_NumberParameters= dword ptr -36 Ch .text:7 C94ABA5 var_32C = dword ptr -32 Ch .text:7 C94ABA5 var_328 = dword ptr -328 h .text:7 C94ABA5 var_324 = dword ptr -324 h .text:7 C94ABA5 var_31C = dword ptr -31 Ch .text:7 C94ABA5 var_2DC = dword ptr -2 DCh .text:7 C94ABA5 var_StackLimit = dword ptr -2 D8h .text:7 C94ABA5 var_StackBase = dword ptr -2 D4h .text:7 C94ABA5 Context = CONTEXT ptr -2 D0h .text:7 C94ABA5 var_4 = dword ptr -4 .text:7 C94ABA5 EstablisherFrame= dword ptr 8 .text:7 C94ABA5 __global_unwind2_ReturnAddr= dword ptr 0 Ch .text:7 C94ABA5 ExceptionRecord_0= dword ptr 10 h .text:7 C94ABA5 ReturnValue = dword ptr 14 h .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 .text:7 C94ABA5 mov edi, edi .text:7 C94ABA7 push ebp .text:7 C94ABA8 mov ebp, esp .text:7 C94ABAA sub esp, 37 Ch .text:7 C94ABB0 mov eax, ___security_cookie .text:7 C94ABB5 push esi .text:7 C94ABB6 mov esi, [ebp+ExceptionRecord_0] .text:7 C94ABB9 mov [ebp+var_4], eax .text:7 C94ABBC push edi .text:7 C94ABBD lea eax, [ebp+var_StackBase] .text:7 C94ABC3 push eax .text:7 C94ABC4 lea eax, [ebp+var_StackLimit] .text:7 C94ABCA push eax .text:7 C94ABCB call _RtlpGetStackLimits@8 .text:7 C94ABD0 xor edi, edi .text:7 C94ABD2 cmp esi, edi .text:7 C94ABD4 jnz short loc_7C94AC01 .text:7 C94ABD6 mov eax, [ebp+4 ] .text:7 C94ABD9 lea esi, [ebp+var_37C_ExceptionCode] .text:7 C94ABD9 .text:7 C94ABD9 .text:7 C94ABD9 .text:7 C94ABD9 .text:7 C94ABD9 .text:7 C94ABD9 .text:7 C94ABD9 .text:7 C94ABD9 .text:7 C94ABD9 .text:7 C94ABDF mov [ebp+var_37C_ExceptionCode], 0 C0000027h .text:7 C94ABE9 mov [ebp+var_378_ExceptionFlags], edi .text:7 C94ABE9 .text:7 C94ABE9 .text:7 C94ABE9 .text:7 C94ABEF mov [ebp+var_374_ExceptionRecord], edi .text:7 C94ABF5 mov [ebp+var_370_ExceptionAddress], eax .text:7 C94ABFB mov [ebp+var_36C_NumberParameters], edi .text:7 C94AC01 .text:7 C94AC01 loc_7C94AC01: .text:7 C94AC01 cmp [ebp+EstablisherFrame], edi .text:7 C94AC04 jz loc_7C94ACD8 .text:7 C94AC0A or [esi+_EXCEPTION_RECORD.ExceptionFlags], 2 .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0A .text:7 C94AC0E .text:7 C94AC0E loc_7C94AC0E: .text:7 C94AC0E push ebx .text:7 C94AC0F lea eax, [ebp+Context] .text:7 C94AC15 push eax .text:7 C94AC16 mov [ebp+Context.ContextFlags], 10007 h .text:7 C94AC20 call _RtlpCaptureContext@4 .text:7 C94AC25 mov eax, [ebp+ReturnValue] .text:7 C94AC28 add [ebp+Context._Esp], 10 h .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC28 .text:7 C94AC2F mov [ebp+Context._Eax], eax .text:7 C94AC35 call _RtlpGetRegistrationHead@0 .text:7 C94AC35 .text:7 C94AC3A mov ebx, eax .text:7 C94AC3C cmp ebx, 0F FFFFFFFh .text:7 C94AC3F jz loc_7C96E40E .text:7 C94AC3F .text:7 C94AC3F .text:7 C94AC3F .text:7 C94AC3F .text:7 C94AC45 xor edi, edi .text:7 C94AC47 inc edi .text:7 C94AC48 .text:7 C94AC48 loc_7C94AC48: .text:7 C94AC48 cmp ebx, [ebp+EstablisherFrame] .text:7 C94AC4B jz short loc_7C94ACC8 .text:7 C94AC4B .text:7 C94AC4B .text:7 C94AC4B .text:7 C94AC4B .text:7 C94AC4B .text:7 C94AC4D cmp [ebp+EstablisherFrame], 0 .text:7 C94AC51 jz short loc_7C94AC5C .text:7 C94AC53 cmp [ebp+EstablisherFrame], ebx .text:7 C94AC56 jb loc_7C96E373 .text:7 C94AC5C .text:7 C94AC5C loc_7C94AC5C: .text:7 C94AC5C .text:7 C94AC5C cmp ebx, [ebp+var_StackLimit] .text:7 C94AC62 jb loc_7C96E3DE .text:7 C94AC68 lea eax, [ebx+8 ] .text:7 C94AC6B cmp eax, [ebp+var_StackBase] .text:7 C94AC71 ja loc_7C96E3DE .text:7 C94AC77 test bl, 3 .text:7 C94AC7A jnz loc_7C96E3DE .text:7 C94AC80 mov eax, [ebx+4 ] .text:7 C94AC83 cmp eax, [ebp+var_StackLimit] .text:7 C94AC89 jb short loc_7C94AC97 .text:7 C94AC8B cmp eax, [ebp+var_StackBase] .text:7 C94AC91 jb loc_7C96E3DE .text:7 C94AC97 .text:7 C94AC97 loc_7C94AC97: .text:7 C94AC97 push eax .text:7 C94AC98 lea eax, [ebp+var_2DC] .text:7 C94AC9E push eax .text:7 C94AC9F lea eax, [ebp+Context] .text:7 C94ACA5 push eax .text:7 C94ACA6 push ebx .text:7 C94ACA7 push esi .text:7 C94ACA8 call _RtlpExecuteHandlerForUnwind@20 .text:7 C94ACAD dec eax .text:7 C94ACAE jnz loc_7C96E3A1 .text:7 C94ACB4 .text:7 C94ACB4 loc_7C94ACB4: .text:7 C94ACB4 .text:7 C94ACB4 mov eax, ebx .text:7 C94ACB6 mov ebx, [ebx] .text:7 C94ACB8 push eax .text:7 C94ACB9 call _RtlpUnlinkHandler@4 .text:7 C94ACBE .text:7 C94ACBE loc_7C94ACBE: .text:7 C94ACBE cmp ebx, 0F FFFFFFFh .text:7 C94ACC1 jnz short loc_7C94AC48 .text:7 C94ACC3 jmp loc_7C96E40C .text:7 C94ACC8 .text:7 C94ACC8 .text:7 C94ACC8 loc_7C94ACC8: .text:7 C94ACC8 push 0 .text:7 C94ACC8 .text:7 C94ACC8 .text:7 C94ACC8 .text:7 C94ACC8 .text:7 C94ACC8 .text:7 C94ACCA lea eax, [ebp+Context] .text:7 C94ACD0 push eax .text:7 C94ACD1 call _ZwContinue@8 .text:7 C94ACD6 jmp short loc_7C94AC5C .text:7 C94ACD8 .text:7 C94ACD8 .text:7 C94ACD8 loc_7C94ACD8: .text:7 C94ACD8 or [esi+_EXCEPTION_RECORD.ExceptionFlags], 6 .text:7 C94ACDC jmp loc_7C94AC0E .text:7 C94ACDC _RtlUnwind@16 endp .text:7 C94ACDC .text:7 C94ACE1
重点关注的代码在:
1 2 .text:7 C94AC48 loc_7C94AC48: .text:7 C94AC48 cmp ebx, [ebp+EstablisherFrame]
RtlpCaptureContext
函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 .text:7 C92334A .text:7 C92334A .text:7 C92334A .text:7 C92334A .text:7 C92334A .text:7 C92334A _RtlpCaptureContext@4 proc near .text:7 C92334A .text:7 C92334A arg_&CONTEXT = dword ptr 4 .text:7 C92334A .text:7 C92334A push ebx .text:7 C92334B mov ebx, [esp+4 +arg_&CONTEXT] .text:7 C92334F mov [ebx+CONTEXT._Eax], 0 .text:7 C923359 mov [ebx+CONTEXT._Ecx], 0 .text:7 C923363 mov [ebx+CONTEXT._Edx], 0 .text:7 C92336D mov [ebx+CONTEXT._Ebx], 0 .text:7 C923377 mov [ebx+CONTEXT._Esi], 0 .text:7 C923381 mov [ebx+CONTEXT._Edi], 0 .text:7 C92338B .text:7 C92338B loc_7C92338B: .text:7 C92338B mov word ptr [ebx+CONTEXT.SegCs], cs .text:7 C923392 mov word ptr [ebx+CONTEXT.SegDs], ds .text:7 C923399 mov word ptr [ebx+CONTEXT.SegEs], es .text:7 C9233A0 mov word ptr [ebx+CONTEXT.SegFs], fs .text:7 C9233A7 mov word ptr [ebx+CONTEXT.SegGs], gs .text:7 C9233AE mov word ptr [ebx+CONTEXT.SegSs], ss .text:7 C9233B5 pushf .text:7 C9233B6 pop [ebx+CONTEXT.EFlags] .text:7 C9233BC mov eax, [ebp+4 ] .text:7 C9233BF mov [ebx+CONTEXT._Eip], eax .text:7 C9233C5 mov eax, [ebp+0 ] .text:7 C9233C8 mov [ebx+CONTEXT._Ebp], eax .text:7 C9233CE lea eax, [ebp+8 ] .text:7 C9233D1 mov [ebx+CONTEXT._Esp], eax .text:7 C9233D7 pop ebx .text:7 C9233D8 retn 4 .text:7 C9233D8 _RtlpCaptureContext@4 endp
函数 RtlpExecuteHandlerForUnwind
包含 ExecuteHandler
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 .text:7 C92324F .text:7 C92324F .text:7 C92324F .text:7 C92324F .text:7 C92324F _RtlpExecuteHandlerForUnwind@20 proc near .text:7 C92324F .text:7 C92324F mov edx, offset sub_7C9232E3 .text:7 C923254 lea ecx, [ecx] .text:7 C923254 _RtlpExecuteHandlerForUnwind@20 endp .text:7 C923254 .text:7 C923256 .text:7 C923256 .text:7 C923256 .text:7 C923256 .text:7 C923256 .text:7 C923256 .text:7 C923256 .text:7 C923256 .text:7 C923256 .text:7 C923256 .text:7 C923256 ExecuteHandler@20 proc near .text:7 C923256 .text:7 C923256 arg_ExceptionRecord= dword ptr 4 .text:7 C923256 arg_EstablisherFrame= dword ptr 8 .text:7 C923256 arg_ContextRecord= dword ptr 0 Ch .text:7 C923256 arg_OUT_DispatcherContext= dword ptr 10 h .text:7 C923256 arg_ExceptionRoutine= dword ptr 14 h .text:7 C923256 .text:7 C923256 push ebx .text:7 C923257 push esi .text:7 C923258 push edi .text:7 C923259 xor eax, eax .text:7 C92325B xor ebx, ebx .text:7 C92325D xor esi, esi .text:7 C92325F xor edi, edi .text:7 C923261 push [esp+0 Ch+arg_ExceptionRoutine] .text:7 C923265 push [esp+10 h+arg_OUT_DispatcherContext] .text:7 C923269 push [esp+14 h+arg_ContextRecord] .text:7 C92326D push [esp+18 h+arg_EstablisherFrame] .text:7 C923271 push [esp+1 Ch+arg_ExceptionRecord] .text:7 C923275 call ExecuteHandler2@20 .text:7 C923275 .text:7 C923275 .text:7 C923275 .text:7 C923275 .text:7 C92327A pop edi .text:7 C92327B pop esi .text:7 C92327C pop ebx .text:7 C92327D retn 14 h .text:7 C92327D ExecuteHandler@20 endp
RtlpUnlinkHandler
函数如下:
1 2 3 4 5 6 7 8 9 10 .text:7 C92330A ; __stdcall RtlpUnlinkHandler (x) .text:7C92330A _RtlpUnlinkHandler@4 proc near ; CODE XREF: RtlUnwind(x,x,x,x)+114↓p .text:7 C92330A .text:7 C92330A arg_0 = dword ptr 4 .text:7 C92330A .text:7 C92330A mov ecx, [esp+arg_0] .text:7 C92330E mov ecx, [ecx] .text:7 C923310 mov large fs:0 , ecx .text:7 C923317 retn 4 .text:7 C923317 _RtlpUnlinkHandler@4 endp
x86下的源码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 VOID RtlUnwind ( IN PVOID TargetFrame OPTIONAL, IN PVOID TargetIp OPTIONAL, IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL, IN PVOID ReturnValue ) { PCONTEXT ContextRecord; CONTEXT ContextRecord1; DISPATCHER_CONTEXT DispatcherContext; EXCEPTION_DISPOSITION Disposition; PEXCEPTION_REGISTRATION_RECORD RegistrationPointer; PEXCEPTION_REGISTRATION_RECORD PriorPointer; ULONG HighAddress; ULONG HighLimit; ULONG LowLimit; EXCEPTION_RECORD ExceptionRecord1; EXCEPTION_RECORD ExceptionRecord2; RtlpGetStackLimits(&LowLimit, &HighLimit); if (ARGUMENT_PRESENT(ExceptionRecord) == FALSE) { ExceptionRecord = &ExceptionRecord1; ExceptionRecord1.ExceptionCode = STATUS_UNWIND; ExceptionRecord1.ExceptionFlags = 0 ; ExceptionRecord1.ExceptionRecord = NULL ; ExceptionRecord1.ExceptionAddress = _ReturnAddress(); ExceptionRecord1.NumberParameters = 0 ; } if (ARGUMENT_PRESENT(TargetFrame) == TRUE) { ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING; } else { ExceptionRecord->ExceptionFlags |= (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND); } ContextRecord = &ContextRecord1; ContextRecord1.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL | CONTEXT_SEGMENTS; RtlpCaptureContext(ContextRecord); #ifdef STD_CALL ContextRecord->Esp += sizeof (TargetFrame) + sizeof (TargetIp) + sizeof (ExceptionRecord) + sizeof (ReturnValue); #endif ContextRecord->Eax = (ULONG)ReturnValue; RegistrationPointer = RtlpGetRegistrationHead(); while (RegistrationPointer != EXCEPTION_CHAIN_END) { if ((ULONG)RegistrationPointer == (ULONG)TargetFrame) { ZwContinue(ContextRecord, FALSE); } else if ( (ARGUMENT_PRESENT(TargetFrame) == TRUE) && ((ULONG)TargetFrame < (ULONG)RegistrationPointer) ) { ExceptionRecord2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.NumberParameters = 0 ; RtlRaiseException(&ExceptionRecord2); } HighAddress = (ULONG)RegistrationPointer + sizeof (EXCEPTION_REGISTRATION_RECORD); if ( ((ULONG)RegistrationPointer < LowLimit) || (HighAddress > HighLimit) || (((ULONG)RegistrationPointer & 0x3 ) != 0 ) #if !defined(NTOS_KERNEL_RUNTIME) || (((ULONG)RegistrationPointer->Handler >= LowLimit) && ((ULONG)RegistrationPointer->Handler < HighLimit)) #endif ) { #if defined(NTOS_KERNEL_RUNTIME) ULONG TestAddress = (ULONG)RegistrationPointer; if (((TestAddress & 0x3 ) == 0 ) && KeGetCurrentIrql() >= DISPATCH_LEVEL) { PKPRCB Prcb = KeGetCurrentPrcb(); ULONG DpcStack = (ULONG)Prcb->DpcStack; if ((Prcb->DpcRoutineActive) && (HighAddress <= DpcStack) && (TestAddress >= DpcStack - KERNEL_STACK_SIZE)) { HighLimit = DpcStack; LowLimit = DpcStack - KERNEL_STACK_SIZE; continue ; } } #endif ExceptionRecord2.ExceptionCode = STATUS_BAD_STACK; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.NumberParameters = 0 ; RtlRaiseException(&ExceptionRecord2); } else { Disposition = RtlpExecuteHandlerForUnwind( ExceptionRecord, (PVOID)RegistrationPointer, ContextRecord, (PVOID)&DispatcherContext, RegistrationPointer->Handler); switch (Disposition) { case ExceptionContinueSearch : break ; case ExceptionCollidedUnwind : RegistrationPointer = DispatcherContext.RegistrationPointer; break ; default : ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.NumberParameters = 0 ; RtlRaiseException(&ExceptionRecord2); break ; } PriorPointer = RegistrationPointer; RegistrationPointer = RegistrationPointer->Next; RtlpUnlinkHandler(PriorPointer); } } if (TargetFrame == EXCEPTION_CHAIN_END) { ZwContinue(ContextRecord, FALSE); } else { ZwRaiseException(ExceptionRecord, ContextRecord, FALSE); } return ; }
可参阅:
4 异常返回值、错误码 可以参考《加密与解密第四版8.3.4 P344》
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 typedef EXCEPTION_DISPOSITION (__cdecl *PEXCEPTION_ROUTINE) ( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ) #define EXCEPTION_EXECUTE_HANDLER 1 #define EXCEPTION_CONTINUE_SEARCH 0 #define EXCEPTION_CONTINUE_EXECUTION -1 typedef enum _EXCEPTION_DISPOSITION { ExceptionContinueExecution, ExceptionContinueSearch, ExceptionNestedException, ExceptionCollidedUnwind } EXCEPTION_DISPOSITION; #define EXCEPTION_NONCONTINUABLE 0x1 #define EXCEPTION_MAXIMUM_PARAMETERS 15 typedef struct _EXCEPTION_RECORD { NTSTATUS ExceptionCode; ULONG ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord ; PVOID ExceptionAddress; ULONG NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD; #define EXCEPTION_NONCONTINUABLE 0x1 #define EXCEPTION_UNWINDING 0x2 #define EXCEPTION_EXIT_UNWIND 0x4 #define EXCEPTION_STACK_INVALID 0x8 #define EXCEPTION_NESTED_CALL 0x10 #define EXCEPTION_TARGET_UNWIND 0x20 #define EXCEPTION_COLLIDED_UNWIND 0x40 #define EXCEPTION_UNWIND 0x66 #define ExceptionContinueExecution 0x0 #define ExceptionContinueSearch 0x1 #define ExceptionNestedException 0x2 #define ExceptionCollidedUnwind 0x3 #define ErExceptionCode 0x0 #define ErExceptionFlags 0x4 #define ErExceptionRecord 0x8 #define ErExceptionAddress 0xc #define ErNumberParameters 0x10 #define ErExceptionInformation 0x14 #define ExceptionRecordLength 0x50
5 __SEH_prolog 和 __SEH_epilog 因为几乎所有由 MSC 编译生成的 sys、dll、exe 文件都需要使用__except_handle3
异常处理函数,并且都需要进行 SEH 的安装和卸载,所以编译器把这部分代码提取出来,形成了两个独立的函数,分别叫作 __SEH_prolog
和 __SEH_epilog
。它们的主要作用就是把__excep_handler3
安装为 SEH 处理函数及卸载,这也是在反汇编那些使用了 SEH 的系统 API时总会看到如下代码的原因。
如下在 0x7C80B6E3
处:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 .text:7 C80B6DC .text:7 C80B6DC _BaseThreadStart@8 proc near .text:7 C80B6DC .text:7 C80B6DC .text:7 C80B6DC var_20 = dword ptr -20 h .text:7 C80B6DC uExitCode = dword ptr -1 Ch .text:7 C80B6DC ms_exc = CPPEH_RECORD ptr -18 h .text:7 C80B6DC arg_0 = dword ptr 8 .text:7 C80B6DC arg_4 = dword ptr 0 Ch .text:7 C80B6DC .text:7 C80B6DC push 10 h .text:7 C80B6DE push offset stru_7C80B720 .text:7 C80B6E3 call __SEH_prolog .text:7 C80B6E8 .text:7 C80B6E8 and [ebp+ms_exc.registration.TryLevel], 0 .text:7 C80B6EC mov eax, large fs:18 h .text:7 C80B6F2 mov [ebp+var_20], eax .text:7 C80B6F5 cmp dword ptr [eax+10 h], 1E00 h .text:7 C80B6FC jnz short loc_7C80B70D .text:7 C80B6FE cmp _BaseRunningInServerProcess, 0 .text:7 C80B705 jnz short loc_7C80B70D .text:7 C80B707 call ds:__imp__CsrNewThread@0 .text:7 C80B70D .text:7 C80B70D loc_7C80B70D: .text:7 C80B70D .text:7 C80B70D push [ebp+arg_4] .text:7 C80B710 call [ebp+arg_0] .text:7 C80B713 push eax .text:7 C80B714 .text:7 C80B714 loc_7C80B714: .text:7 C80B714 call _ExitThread@4 .text:7 C80B714 .text:7 C80B714 .text:7 C80B714 _BaseThreadStart@8 endp
5.1 __SEH_prolog __SEH_prolog
函数分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 .text:7 C8024D6 .text:7 C8024D6 .text:7 C8024D6 .text:7 C8024D6 .text:7 C8024D6 __SEH_prolog proc near .text:7 C8024D6 .text:7 C8024D6 .text:7 C8024D6 arg_scopetable = dword ptr 8 .text:7 C8024D6 .text:7 C8024D6 push offset __except_handler3 .text:7 C8024DB mov eax, large fs:0 .text:7 C8024E1 push eax .text:7 C8024E2 mov eax, [esp+8 +arg_scopetable] .text:7 C8024E6 mov [esp+8 +arg_scopetable], ebp .text:7 C8024EA lea ebp, [esp+8 +arg_scopetable] .text:7 C8024EE sub esp, eax .text:7 C8024F0 push ebx .text:7 C8024F1 push esi .text:7 C8024F2 push edi .text:7 C8024F3 mov eax, [ebp-8 ] .text:7 C8024F6 mov [ebp-18 h], esp .text:7 C8024F9 push eax .text:7 C8024FA mov eax, [ebp-4 ] .text:7 C8024FD mov dword ptr [ebp-4 ], 0F FFFFFFFh .text:7 C802504 mov [ebp-8 ], eax .text:7 C802507 lea eax, [ebp-10 h] .text:7 C80250A mov large fs:0 , eax .text:7 C802510 retn .text:7 C802510 __SEH_prolog endp
5.2 CPPEH_RECORD与ms_exc 参考本文 2.2、上面对 __SEH_prolog
源代码的分析、对 __SEH_prolog4
源代码的分析,以及《加密与解密第二版 8.3.4 P343》有如下结论。
实际上 __SEH_prolog
函数每次都在栈上建立的是 CPPEH_RECORD
结构,这是编译器的扩展,VS2019也都在用该结构。
C/C++ 编译器扩展 SEH 的异常帧结构为 CPPEH_RECORD
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct CPPEH_RECORD { DWORD old_esp; EXCEPTION_POINTERS *exc_ptr; struct EXCEPTION_REGISTRATION registration ; }; typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; struct _EXCEPTION_REGISTRATION { struct _EXCEPTION_REGISTRATION * prev ; void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_ REGISTRATION, PCONTEXT, PEXCEPTION_RECORD); struct scopetable_entry * scopetable ; int trylevel; int _ebp; };
在 IDA 中经常会在一个函数开头看到这样的局部变量:
1 2 3 4 5 6 7 .text:7 C817044 ms_exc = CPPEH_RECORD ptr -18 h .text:7 C817044 ; __unwind { .text:7 C817044 push 0 Ch .text:7 C817046 push offset stru_7C817070 ; scopetable .text:7 C81704B call __SEH_prolog ...
实际上这里的 ms_exc
就是 CPPEH_RECORD
结构,调用 __SEH_prolog
在当前堆栈构建 CPPEH_RECORD
结构,当 __SEH_prolog
函数返回后,esp
指向 edi
寄存器。
5.3 __SEH_epilog __SEH_epilog
函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .text:7 C802511 .text:7 C802511 .text:7 C802511 .text:7 C802511 .text:7 C802511 __SEH_epilog proc near .text:7 C802511 .text:7 C802511 mov ecx, [ebp-10 h] .text:7 C802514 mov large fs:0 , ecx .text:7 C80251B pop ecx .text:7 C80251C pop edi .text:7 C80251D pop esi .text:7 C80251E pop ebx .text:7 C80251F leave .text:7 C802520 push ecx .text:7 C802521 retn .text:7 C802521 __SEH_epilog endp
6 异常处理相关的流程图