什么是SEH?
SEH( Structured Exception Handling , 结构化异常处理 )
结构化异常处理(SEH)是Windows操作系统提供的强大异常处理功能。而Visual C++中的__try{}/__finally{}和__try{}/__except{}结构本质上是对Windows提供的SEH的封装
我们知道SEH是基于线程的异常处理,我们利用 __try{}/__except{}来模拟一下SEH的异常处理:
在这里可以看到我们把EAX的值置为空指针,然后向空指针里写入值,引发 STATUS_ACCESS_VIOLATION(内存访问异常) ,然后在异常处理里面把EAX的值设置为 变量dwTest的地址,然后返回 EXCEPTION_CONTINUE_EXECUTION 表示异常被处理,从异常处继续执行,这里是MSDN对于异常处理( Exception Handling )返回值的定义:
123#define EXCEPTION_EXECUTE_HANDLER 1 //表示异常被处理,从下一条指令开始执行#define EXCEPTION_CONTINUE_SEARCH 0 //表示异常未被处理,交由下一个SEH#define EXCEPTION_CONTINUE_EXECUTION -1 //表示异常被处理,从异常处开始执行对于上面这段定义,很多人给出的注释不同,以上注释是我对他们的实验结果和理解。
Windows下各种异常处理的优先级
平时我们听说过很多异常处理术语:VEH SEH VCH UEF等等,下面我们用实验整理它们先后的处理顺序;
- VEH(向量化异常处理,最顶端的异常处理)
1234PVOID WINAPI AddVectoredExceptionHandler(_In_ ULONG FirstHandler,_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler);向进程里注册一个异常捕获函数,参数FirstHandler 决定插入到链表的位置(非0为头部,0为底部),异常处理中最先执行
- VCH(同上,最低端 的异常处理 )
1234PVOID WINAPI AddVectoredContinueHandler(_In_ ULONG FirstHandler,_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler);向进程里注册一个异常捕获函数,参数FirstHandler 决定插入到链表的位置(非0为头部, 0为底部 ) ,异常处理中最后执行
- SEH(结构化异常处理,基于线程栈的异常处理)
SEH是基于线程的异常处理,因为SEH链指针是在TEB(线程信息块)的第一个结构体成员(NT_TIB)的头部:fs:[0]
- UEF(TopLevelEH,顶级异常处理)
123LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(_In_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);TopLevelEH 为线程顶级异常处理器,通常可以处理到所有线程消息发生的异常。
这里我们可以发现以上异常处理回调函数参数大都为 EXCEPTION_POINTERS 结构体,我们查询下它的结构:
1234typedef struct _EXCEPTION_POINTERS {PEXCEPTION_RECORD ExceptionRecord;PCONTEXT ContextRecord;} EXCEPTION_POINTERS,*PEXCEPTION_POINTERS;EXCEPTION_RECORD:
12345678typedef struct _EXCEPTION_RECORD {DWORD ExceptionCode;//异常码,以STATUS_或EXCEPTION_开头,可自定义。(sehdef.inc)DWORD ExceptionFlags;//异常标志。0可修复;1不可修复;2正在展开,不要试图修复struct _EXCEPTION_RECORD*ExceptionRecord;//指向嵌套的异常结构,通常是异常中又引发异常PVOID ExceptionAddress;//异常发生的地址DWORD NumberParameters;//下面ExceptionInformation所含有的dword数目ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];//附加消息,如读或写冲突} EXCEPTION_RECORD;CONTEXT:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485typedef struct _CONTEXT {////The flags values within this flag control the contents of//a CONTEXT record.////If the context recordisused as aninputparameter, then//foreach portion of the context record controlled by a flag//whose valueisset, itisassumed that that portion of the//context record contains valid context. If the context record//isbeing used to modify a threads context, then only that//portion of the threads context will be modified.////If the context recordisused as an IN OUT parameter to capture//the context of a thread, then only those portions of the thread's//context corresponding tosetflags will be returned.////The context recordisnever used as an OUT only parameter.//DWORD ContextFlags;////This sectionisspecified/returnedifCONTEXT_DEBUG_REGISTERSis//setinContextFlags. Note that CONTEXT_DEBUG_REGISTERSisNOT//includedinCONTEXT_FULL.//DWORD Dr0;DWORD Dr1;DWORD Dr2;DWORD Dr3;DWORD Dr6;DWORD Dr7;////This sectionisspecified/returnedifthe//ContextFlags word contians the flag CONTEXT_FLOATING_POINT.//FLOATING_SAVE_AREA FloatSave;////This sectionisspecified/returnedifthe//ContextFlags word contians the flag CONTEXT_SEGMENTS.//DWORD SegGs;DWORD SegFs;DWORD SegEs;DWORD SegDs;////This sectionisspecified/returnedifthe//ContextFlags word contians the flag CONTEXT_INTEGER.//DWORD Edi;DWORD Esi;DWORD Ebx;DWORD Edx;DWORD Ecx;DWORD Eax;////This sectionisspecified/returnedifthe//ContextFlags word contians the flag CONTEXT_CONTROL.//DWORD Ebp;DWORD Eip;DWORD SegCs;//MUST BE SANITIZEDDWORD EFlags;//MUST BE SANITIZEDDWORD Esp;DWORD SegSs;////This sectionisspecified/returnedifthe ContextFlags word//contains the flag CONTEXT_EXTENDED_REGISTERS.//Theformatandcontexts are processor specific//BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];} CONTEXT;CONTEXT结构体大家应该都懂!下面我们简单的写一个Demo,试验一下他们的处理顺序:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455//SEHTest.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"//#include <windows.h>#include <stdio.h>#include <stdlib.h>//LONG__stdcall VEHandler(EXCEPTION_POINTERS*ExceptionInfo){printf("VEHandler\n");returnEXCEPTION_CONTINUE_SEARCH;}LONG__stdcall VCHandler(EXCEPTION_POINTERS*ExceptionInfo){printf("VCHandler\n");ExceptionInfo->ContextRecord->Eip++;returnEXCEPTION_CONTINUE_EXECUTION;}//LONGNTAPI TopLevelExcepFilter(EXCEPTION_POINTERS*pExcepInfo){printf("TopLevelEHandler\n");returnEXCEPTION_CONTINUE_EXECUTION;}//LONGNTAPI SEHander(EXCEPTION_POINTERS*ExceptionInfo){//异常处理printf("SEHandler\n");returnEXCEPTION_CONTINUE_SEARCH;}int_tmain(intargc, _TCHAR*argv[]){AddVectoredExceptionHandler(0,VEHandler);AddVectoredContinueHandler(0,VCHandler);SetUnhandledExceptionFilter(&TopLevelExcepFilter);__try{__asmint3}__except (SEHander(GetExceptionInformation())){}system("Pause");return0;}我们分别注册了VEH、VCH、 TopLevalEH 、SEH,我们看下结果:他们处理异常的优先级为:
- 调试器
- VEH
- SEH
- UEF
- VCH
为什么调试器在第一个呢?因为我们发现在VS里面调试直接接管了异常。(/手动滑稽)
认识SEH链及处理机机制
我们上一章发现SEH链表位于结构体 NT_TIB 的第一个结构体成员,而结构体 NT_TIB 也位于TEB的第一个结构体成员,一句话而言SEH链表
指针位于寄存器 FS : [ 0 ] 的位置:
我们直接访问 FS:[0] 即为TIB的结构体地址:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667ntdll!_TEB+0x000NtTib : _NT_TIB//SEH链表头指针+0x01cEnvironmentPointer : Ptr32 Void+0x020ClientId : _CLIENT_ID+0x028ActiveRpcHandle : Ptr32 Void+0x02cThreadLocalStoragePointer : Ptr32 Void+0x030ProcessEnvironmentBlock : Ptr32 _PEB+0x034LastErrorValue : Uint4B+0x038CountOfOwnedCriticalSections : Uint4B+0x03cCsrClientThread : Ptr32 Void+0x040Win32ThreadInfo : Ptr32 Void+0x044User32Reserved : [26] Uint4B+0x0acUserReserved : [5] Uint4B+0x0c0WOW32Reserved : Ptr32 Void+0x0c4CurrentLocale : Uint4B+0x0c8FpSoftwareStatusRegister : Uint4B+0x0ccSystemReserved1 : [54] Ptr32 Void+0x1a4ExceptionCode : Int4B+0x1a8ActivationContextStack : _ACTIVATION_CONTEXT_STACK+0x1bcSpareBytes1 : [24] UChar+0x1d4GdiTebBatch : _GDI_TEB_BATCH+0x6b4RealClientId : _CLIENT_ID+0x6bcGdiCachedProcessHandle : Ptr32 Void+0x6c0GdiClientPID : Uint4B+0x6c4GdiClientTID : Uint4B+0x6c8GdiThreadLocalInfo : Ptr32 Void+0x6ccWin32ClientInfo : [62] Uint4B+0x7c4glDispatchTable : [233] Ptr32 Void+0xb68glReserved1 : [29] Uint4B+0xbdcglReserved2 : Ptr32 Void+0xbe0glSectionInfo : Ptr32 Void+0xbe4glSection : Ptr32 Void+0xbe8glTable : Ptr32 Void+0xbecglCurrentRC : Ptr32 Void+0xbf0glContext : Ptr32 Void+0xbf4LastStatusValue : Uint4B+0xbf8StaticUnicodeString : _UNICODE_STRING+0xc00StaticUnicodeBuffer : [261] Uint2B+0xe0cDeallocationStack : Ptr32 Void+0xe10TlsSlots : [64] Ptr32 Void+0xf10TlsLinks : _LIST_ENTRY+0xf18Vdm : Ptr32 Void+0xf1cReservedForNtRpc : Ptr32 Void+0xf20DbgSsReserved : [2] Ptr32 Void+0xf28HardErrorsAreDisabled : Uint4B+0xf2cInstrumentation : [16] Ptr32 Void+0xf6cWinSockData : Ptr32 Void+0xf70GdiBatchCount : Uint4B+0xf74InDbgPrint : UChar+0xf75FreeStackOnTermination : UChar+0xf76HasFiberData : UChar+0xf77IdealProcessor : UChar+0xf78Spare3 : Uint4B+0xf7cReservedForPerf : Ptr32 Void+0xf80ReservedForOle : Ptr32 Void+0xf84WaitingOnLoaderLock : Uint4B+0xf88Wx86Thread : _Wx86ThreadState+0xf94TlsExpansionSlots : Ptr32 Ptr32 Void+0xf98ImpersonationLocale : Uint4B+0xf9cIsImpersonating : Uint4B+0xfa0NlsCache : Ptr32 Void+0xfa4pShimData : Ptr32 Void+0xfa8HeapVirtualAffinity : Uint4B+0xfacCurrentTransactionHandle : Ptr32 Void+0xfb0ActiveFrame : Ptr32 _TEB_ACTIVE_FRAME+0xfb4SafeThunkCall : UChar+0xfb5BooleanSpare : [3] UChar
1234567891011121314151617typedef struct _NT_TIB {struct _EXCEPTION_REGISTRATION_RECORD*ExceptionList;PVOID StackBase;PVOID StackLimit;PVOID SubSystemTib;#if defined(_MSC_EXTENSIONS)union {PVOID FiberData;DWORD Version;};#elsePVOID FiberData;#endifPVOID ArbitraryUserPointer;struct _NT_TIB*Self;} NT_TIB;typedef NT_TIB*PNT_TIB;如上述代码所示 结构体成员 ExceptionList 即为SEH链的头部指针So、 fs:[0]即为SEH链的指针,我们接着看对于SEH链的定义:
1234typedef struct _EXCEPTION_REGISTRATION_RECORD {struct _EXCEPTION_REGISTRATION_RECORD*Next;PEXCEPTION_ROUTINE Handler;} EXCEPTION_REGISTRATION_RECORD;第一个成员 Next 为指向下一个链表的指针,直到遇到 0xFFFFFFFF 结束,而结构体成员 Handler 为SEH的异常处理函数指针,我们接着看它对于SEH异常处理函数 EXCEPTION_ROUTINE 的定义:
1234567891011typedef_IRQL_requires_same__Function_class_(EXCEPTION_ROUTINE)EXCEPTION_DISPOSITIONNTAPIEXCEPTION_ROUTINE (_Inout_ struct _EXCEPTION_RECORD*ExceptionRecord,_In_ PVOID EstablisherFrame,_Inout_ struct _CONTEXT*ContextRecord,_In_ PVOID DispatcherContext);可以看到它对于SEH定义了以 EXCEPTION_DISPOSITION 为返回值的回调函数,我们接着查看它们的定义:
123456typedef enum _EXCEPTION_DISPOSITION {ExceptionContinueExecution,//继续执行异常代码ExceptionContinueSearch,//运行下一个异常处理器ExceptionNestedException,//在OS内部使用ExceptionCollidedUnwind//在OS内部使用} EXCEPTION_DISPOSITION;接着我们整理下它的异常处理过程:从上图可以明白 SEH接收到异常然后处理,处理失败返回 ExceptionContinueSearch(1) 继续运行下一个Handler处理,直到返回
ExceptionContinueSearch(0),若是一直处理不了直到遇到0xFFFFFFFF 把异常交给UEF处理。
SEH的注册及SEH的删除
通过上述的整理就可以知道,SEH的异常处理的定义为:
12345EXCEPTION_DISPOSITION NTAPI _except_handler(_Inout_ struct _EXCEPTION_RECORD*ExceptionRecord,//指向包含异常信息的EXCEPTION_RECORD结构_In_ PVOID EstablisherFrame,//指向该异常相关的EXCEPTION_REGISTRATION结构_Inout_ struct _CONTEXT*ContextRecord,//指向线程环境CONTEXT结构的指针_In_ PVOID DispatcherContext)现在要来谈SEH的注册了,我们的操作为:
123push @_except_handler ;异常处理器push dwod ptr fs:[0] ;取出 SEH链表头mov dwod ptr fs:[0],esp ;添加链表卸载SEH:
12pop dword ptr fs:[0] ;还原链表头add esp,4;删除 异常处理器这些操作很简单,很多前辈们帖子里都有,代码可能不一样,反正是一个意思就行。
异常的种类和常见的异常代码
这里是MSDN中定义的异常代码:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061/*lint-save-e767*/#define STATUS_WAIT_0 ((DWORD )0x00000000L)#define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L)#define STATUS_USER_APC ((DWORD )0x000000C0L)#define STATUS_TIMEOUT ((DWORD )0x00000102L)#define STATUS_PENDING ((DWORD )0x00000103L)#define DBG_EXCEPTION_HANDLED ((DWORD )0x00010001L)#define DBG_CONTINUE ((DWORD )0x00010002L)#define STATUS_SEGMENT_NOTIFICATION ((DWORD )0x40000005L)#define STATUS_FATAL_APP_EXIT ((DWORD )0x40000015L)#define DBG_TERMINATE_THREAD ((DWORD )0x40010003L)#define DBG_TERMINATE_PROCESS ((DWORD )0x40010004L)#define DBG_CONTROL_C ((DWORD )0x40010005L)#define DBG_PRINTEXCEPTION_C ((DWORD )0x40010006L)#define DBG_RIPEXCEPTION ((DWORD )0x40010007L)#define DBG_CONTROL_BREAK ((DWORD )0x40010008L)#define DBG_COMMAND_EXCEPTION ((DWORD )0x40010009L)#define STATUS_GUARD_PAGE_VIOLATION ((DWORD )0x80000001L)#define STATUS_DATATYPE_MISALIGNMENT ((DWORD )0x80000002L)#define STATUS_BREAKPOINT ((DWORD )0x80000003L)#define STATUS_SINGLE_STEP ((DWORD )0x80000004L)#define STATUS_LONGJUMP ((DWORD )0x80000026L)#define STATUS_UNWIND_CONSOLIDATE ((DWORD )0x80000029L)#define DBG_EXCEPTION_NOT_HANDLED ((DWORD )0x80010001L)#define STATUS_ACCESS_VIOLATION ((DWORD )0xC0000005L)#define STATUS_IN_PAGE_ERROR ((DWORD )0xC0000006L)#define STATUS_INVALID_HANDLE ((DWORD )0xC0000008L)#define STATUS_INVALID_PARAMETER ((DWORD )0xC000000DL)#define STATUS_NO_MEMORY ((DWORD )0xC0000017L)#define STATUS_ILLEGAL_INSTRUCTION ((DWORD )0xC000001DL)#define STATUS_NONCONTINUABLE_EXCEPTION ((DWORD )0xC0000025L)#define STATUS_INVALID_DISPOSITION ((DWORD )0xC0000026L)#define STATUS_ARRAY_BOUNDS_EXCEEDED ((DWORD )0xC000008CL)#define STATUS_FLOAT_DENORMAL_OPERAND ((DWORD )0xC000008DL)#define STATUS_FLOAT_DIVIDE_BY_ZERO ((DWORD )0xC000008EL)#define STATUS_FLOAT_INEXACT_RESULT ((DWORD )0xC000008FL)#define STATUS_FLOAT_INVALID_OPERATION ((DWORD )0xC0000090L)#define STATUS_FLOAT_OVERFLOW ((DWORD )0xC0000091L)#define STATUS_FLOAT_STACK_CHECK ((DWORD )0xC0000092L)#define STATUS_FLOAT_UNDERFLOW ((DWORD )0xC0000093L)#define STATUS_INTEGER_DIVIDE_BY_ZERO ((DWORD )0xC0000094L)#define STATUS_INTEGER_OVERFLOW ((DWORD )0xC0000095L)#define STATUS_PRIVILEGED_INSTRUCTION ((DWORD )0xC0000096L)#define STATUS_STACK_OVERFLOW ((DWORD )0xC00000FDL)#define STATUS_DLL_NOT_FOUND ((DWORD )0xC0000135L)#define STATUS_ORDINAL_NOT_FOUND ((DWORD )0xC0000138L)#define STATUS_ENTRYPOINT_NOT_FOUND ((DWORD )0xC0000139L)#define STATUS_CONTROL_C_EXIT ((DWORD )0xC000013AL)#define STATUS_DLL_INIT_FAILED ((DWORD )0xC0000142L)#define STATUS_FLOAT_MULTIPLE_FAULTS ((DWORD )0xC00002B4L)#define STATUS_FLOAT_MULTIPLE_TRAPS ((DWORD )0xC00002B5L)#define STATUS_REG_NAT_CONSUMPTION ((DWORD )0xC00002C9L)#define STATUS_HEAP_CORRUPTION ((DWORD )0xC0000374L)#define STATUS_STACK_BUFFER_OVERRUN ((DWORD )0xC0000409L)#define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD )0xC0000417L)#define STATUS_ASSERTION_FAILURE ((DWORD )0xC0000420L)#if defined(STATUS_SUCCESS) || (_WIN32_WINNT > 0x0500) || (_WIN32_FUSION >= 0x0100)#define STATUS_SXS_EARLY_DEACTIVATION ((DWORD )0xC015000FL)#define STATUS_SXS_INVALID_DEACTIVATION ((DWORD )0xC0150010L)#endif/*lint-restore*/下面我们举例几个常用到的异常代码:STATUS_ACCESS_VIOLATION(0xC0000005)
非法访问异常,试图访问不存在、没有访问权限,或是试图向没有写入权限的地址或是向内核区域写入发生的异常。
STATUS_BREAKPOINT(0x80000003)
断点异常,这个不用提了吧,就是我们常说的INT 3(0xCC)断点
STATUS_ILLEGAL_INSTRUCTION(0xC000001D)
CPU遇到无法解析的指令时发生该异常
STATUS_INTEGER_DIVIDE_BY_ZERO(0xC0000094)
除法中,分母为0时发生的异常
STATUS_SINGLE_STEP
单步调试异常,在EFlag寄存器把TF标志位置1发生的单步调试异常。
除此之外,也有很多平时可以遇到的,我只是举例了几个简单的。
SEH的异常处理
我这里就随便写个Demo了:
12345678910111213141516171819202122232425262728293031323334353637383940414243//SEHList.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"//#include <windows.h>#include <stdio.h>#include <stdlib.h>DWORD dwTest;EXCEPTION_DISPOSITION NTAPI ExceptHandler(_Inout_ struct _EXCEPTION_RECORD*ExceptionRecord,_In_ PVOID EstablisherFrame,_Inout_ struct _CONTEXT*ContextRecord,_In_ PVOID DispatcherContext){printf("进入异常处理\n");printf("异常地址:%X<异常代码:%X>\n", ExceptionRecord->ExceptionAddress,ExceptionRecord->ExceptionCode);ContextRecord->Eax=(DWORD)(&dwTest);returnExceptionContinueExecution;}int_tmain(intargc, _TCHAR*argv[]){printf("注册SEH\n");__asm{lea eax, ExceptHandlerpush eaxpush fs : [0]mov dword ptr fs : [0], ESP}__asm{xor eax,eaxmov dword ptr[eax],1234h}printf("删除SEH\n");__asm{pop dword ptr fs : [0]add esp,4}printf("dwTest=%X\n", dwTest);getchar();return0;}运行结果:
转自https://bbs.pediy.com/thread-223939.htm



