每次我们定义了一个新的SEH异常处理回调函数,EXCEPTION_REGISTRATION结构的prev字段都被要求填写上一个EXCEPTION_REGISTRATION结构的地址,随着应用程序对模块的调用一层层深入下去的时候,那么最后回调函数会形成一个SEH链
当程序中有多个线程在运行的时候,每个线程中都会存在各自的SEH链,这些SEH链中指定了多个回调函数,除他们以外,系统中可能还会存在一个全局性的筛选器,再者如果进程被调试,调试器进程也相当于一个异常处理的程序存在.那么当一个异常发生的时候,系统究竟该听谁的呢?
在这种情况下,系统按一定的步骤选择一个回调函数并执行他,如果这个回调函数可以执行这个异常,那么其他的回调函数都不会执行,否则系统执行下一个回调函数
(1).系统查看产生异常的进程是否正在被调试,如果正在被调试的话,那么向调试器发送EXCEPTION_DEBUG_EVENT事件
(2).如果进程没有被调试或者调试器不去处理这个异常,那么系统 检查异常所处的线程,并在这个线程环境中查看fs:[0]来确定是否安装有SEH异常处理回调函数,如果有则调用它.
(3).回调函数尝试处理这个异常,如果可以正确处理的话,则修正错误并将返回值设置为ExceptionContinueExecution,这时系统将结束整个查找过程
(4).如果回调函数返回ExceptionContinueSearch,告知系统他无法处理这个异常,那么系统将根据SEH链中的prev字段得到上一个回调函数地址,并重复步骤(3)过程,直到链中某个回调函数返回ExceptionContinueExecution为止,查找结束
(5).如果到了SEH链的尾部,却没有一个回调函数愿意处理这个异常,那么系统将再次检测进程是否正在被调试,如果被调试的话,则再次通知调试器
(6).如果调试器还是不去处理这个异常或者进程没有被调试 ,那么系统检查有没有安装筛选器回调函数,如果有,则去调用他,筛选器回调函数返回的时候,系统默认的异常处理程序根据这个返回值将做出相应的动作
(7).如果没有安装筛选器回调函数,系统直接 调用默认的异常处理程序终止进程
一个 比较形象的比喻就是:
Windows拿着一份异常处理的活挨个问每个回调函数
"你干不干" ,"不干","你呢","我也不干"... ... 当问到某一个的时候,
他说:"那我来干好了!"那么Windows就不会在去问其他人了,于是相安无事
有时,问完一圈后,谁都不愿意干(当然是干不了)Windows大怒:"谁都不干,看我炒了你们!"于是就把整个进程终止掉,所有的异常处理回调函数全部完蛋啦
(筛选器-全局的-进程的 SEH-线程的)
完整的异常处理回调函数
- <span style="font-family:Microsoft YaHei;font-size:13px;">_Handler1 proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
- ;C调用方式-调用者自己平衡堆栈
- .if (异常代码 == 0c0000027h) || (异常标志 & EXCEPTION_UNWINDING) || (异常标志 & EXCEPTION_UNWINDING_FOR_EXIT)
- ;进行资源释放等扫尾工作
- mov eax,ExceptionContinueSearch
- .elseif 异常代码 == 可以处理的异常代码
- ;处理异常,对CONTEXT进行修正
- ;进行展开操作
- mov eax,ExceptionContinueExecution
- .else
- ;其他无法处理的异常代码
- mov eax,ExceptionContinueSearch
- .endif
- ret
-
- _Handler1 endp</span>
二.展开操作(Unwinding)
如果把第一个SEH处理的函数的返回值改成ExceptionContinueSearch这个时候函数将会进行循环查找
这个时候回调函数应该进行一些扫尾工作,因为其将被卸载
注册 Unwinding操作可以使用RtlUnwinding函数
invoke RtlUnwinding,lpLastStackFrame,lpCodeLabel,lpExceptionRecord,dwRet
(1).lpLastStackFrame:这个参数设置为NULL,那么他将对所有的SEH链进行展开操作,这时所有的回调函数参数中的异常标志在带有EXCEPTION_UNWINDING的同时也带有EXCEPTION_UNWINDING_FOR_EXIT标志位,这种方式称为展开退出
指定为当前回调函数的EXCEPTION_REGISTRATION结构的地址的话,表示对当前回调函数之后的所有其他回调函数进行展开操作,这样RtlUnwinding函数调用的每一个回调函数时,异常标记位都会带有EXCEPTION_UNWINDING标记位
(2).lpCodeLabel参数指明函数将要返回的位置,如果位NULL,那么RtlUnwinding函数将返回到其后面的一条指令,否则函数直接返回到lpCodeLabel指定的位置
(3).lpExceptionRecord指定一个EXCEPTION_RECORD结构,这个结构将在调用的时候传给每一个回调函数
(4).dwRet参数一般不被使用,它可以指明为NULL
使用RtlUnwinding函数时要注意的是:这个函数并不像其他API函数一样保存esi,edi和ebx寄存器的值,在函数返回时候这些寄存器的值可能会改变,所以如果程序用到这些寄存器的话,必须手动去保存和恢复他们
- <span style="font-family:Microsoft YaHei;font-size:13px;"> .386
- .model flat,stdcall
- option casemap:none
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- ; Include 文件定义
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- include windows.inc
- include user32.inc
- includelib user32.lib
- include kernel32.inc
- includelib kernel32.lib
-
- L macro var:VARARG
- LOCAL @lbl
- .const
- @lbl db var,0
- .code
- exitm <offset @lbl>
- endm
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- ; 数据段
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- .data
- szMsg1 db '这是外层异常处理程序(将处理异常)',0dh,0ah
- db '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
- szMsg2 db '这是内层异常处理程序(对异常不进行处理)',0dh,0ah
- db '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
- szCaption db '提示信息',0
- szBeforeUnwind db '现在将开始 Unwind,当前的 FS:[0] = %08X',0
- szAfterUnwind db 'Unwind 返回,当前的 FS:[0] = %08X',0
- szSafe1 db '回到了外层子程序的安全位置!',0
- szSafe2 db '回到了内层子程序的安全位置!',0
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- ; 代码段
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- .code
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- ; 外层错误 Handler,将处理异常
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- _Handler1 proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
- local @szBuffer[256]:byte
-
- pushad
- mov esi,_lpExceptionRecord
- mov edi,_lpContext
- assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT,fs:nothing
- invoke wsprintf,addr @szBuffer,addr szMsg1,\
- [edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
- invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
- ;********************************************************************
- ; 将 EIP 指向安全的位置并恢复堆栈
- ;********************************************************************
- mov eax,_lpSEH
- push [eax + 8]
- pop [edi].regEip
- push _lpSEH
- pop [edi].regEsp
- ;********************************************************************
- ; 对前面的 Handler 进行 Unwind 操作
- ;********************************************************************
- invoke wsprintf,addr @szBuffer,addr szBeforeUnwind,dword ptr fs:[0]
- invoke MessageBox,NULL,addr @szBuffer,addr szCaption,MB_OK
-
- invoke RtlUnwind,_lpSEH,NULL,NULL,NULL
-
- invoke wsprintf,addr @szBuffer,addr szAfterUnwind,dword ptr fs:[0]
- invoke MessageBox,NULL,addr @szBuffer,addr szCaption,MB_OK
- ;********************************************************************
- assume esi:nothing,edi:nothing
- popad
- mov eax,ExceptionContinueExecution
- ret
-
- _Handler1 endp
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- ; 内层错误 Handler,不处理异常
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- _Handler2 proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
- local @szBuffer[256]:byte
-
- pushad
- mov esi,_lpExceptionRecord
- mov edi,_lpContext
- assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
- invoke wsprintf,addr @szBuffer,addr szMsg2,\
- [edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
- invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
- assume esi:nothing,edi:nothing
- popad
- mov eax,ExceptionContinueSearch
- ret
-
- _Handler2 endp
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- _Test2 proc
-
- assume fs:nothing
- push offset _SafePlace
- push offset _Handler2
- push fs:[0]
- mov fs:[0],esp
- ;********************************************************************
- ; 会引发异常的指令
- ;********************************************************************
- pushad
- xor eax,eax
- mov dword ptr [eax],0
- popad ;这一句将无法被执行
- _SafePlace:
- invoke MessageBox,NULL,L("回到了内层子程序的安全位置!"),L("提示信息"),MB_OK
- pop fs:[0]
- add esp,8
- ret
-
- _Test2 endp
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- _Test1 proc
-
- assume fs:nothing
- push offset _SafePlace
- push offset _Handler1
- push fs:[0]
- mov fs:[0],esp
- invoke _Test2
- _SafePlace:
- invoke MessageBox,NULL,L("回到了外层子程序的安全位置!"),L("提示信息"),MB_OK
- pop fs:[0]
- add esp,8
- ret
-
- _Test1 endp
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- start:
- invoke _Test1
- invoke ExitProcess,NULL
- ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
- end start
- </span>