根据我们上节的异常讲解中,我们说过了SEH(Structured Exception Handler)是在无调试器接手的情况下,系统会遍历SE链,然后寻找相应的SEH函数来处理异常。
首先稍微复习下我们的TEB
kd> dt _teb ntdll!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : Ptr32 Void +0x020 ClientId : _CLIENT_ID +0x028 ActiveRpcHandle : Ptr32 Void +0x02c ThreadLocalStoragePointer : Ptr32 Void ..........
teb的第一项是一项_NT_TIB是纪录着线程信息块的结构,里面就有着我们今天的主角。
查看下tib的结构
ntdll!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void +0x00c SubSystemTib : Ptr32 Void +0x010 FiberData : Ptr32 Void +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 Void +0x018 Self : Ptr32 _NT_TIB -------------seperrator------------- typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD *Next; //指向下一个ERR PEXCEPTION_ROUTINE Handler; //异常处理函数 }EXCEPTION_REGISTRATION_RECORD -------------seperrator------------- typedef enum _EXCEPTION_DISPOSITION {
ExceptionContinueExecution = 0, ExceptionContinueSearch = 1, ExceptionNestedException = 2, ExceptionCollidedUnwind = 3 } EXCEPTION_DISPOSITION;
看这个第一项,就是一个SE的链,查看一下它的结构,正是一条异常处理链。x86平台下,fs:[0]就是指向TEB的指针,同时里面的值也就是指向第一个SEH的指针。x64下是gs:[0]。每一个结点的结构都是一个ERR(异常处理记录)。并且这条链是一条单向链,所以决定了它插入和删除只能在头部,也就说新注册的异常处理函数只会在头部新增。并且TEB是每个线程所私有的,所以每个线程的SEH是不同的,也就是SEH只对本线程有效,并且系统会维护链表最后的next指向0xFFFFFFFF。
异常处理回调函数原型如下:
__cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext);
同时,我们在上次讲了,KiDispatchException这个函数是在内核态的,而内核的栈与ring3的栈用的不是同一个,所以会进行相应的变换,相应的代码如下:
// // If the SS segment is not 32 bit flat, there is no point // to dispatch exception to frame based exception handler. // if (TrapFrame->HardwareSegSs != (KGDT_R3_DATA | RPL_MASK) || TrapFrame->EFlags & EFLAGS_V86_MASK ) {
ExceptionRecord2.ExceptionCode = STATUS_ACCESS_VIOLATION; ExceptionRecord2.ExceptionFlags = 0; ExceptionRecord2.NumberParameters = 0; ExRaiseException(&ExceptionRecord2); } // // Compute length of context record and new aligned user stack // pointer. // UserStack1 = (ContextFrame.Esp & ~CONTEXT_ROUND) - CONTEXT_ALIGNED_SIZE; // // Probe user stack area for writability and then transfer the // context record to the user stack. // ProbeForWrite((PCHAR)UserStack1, CONTEXT_ALIGNED_SIZE, CONTEXT_ALIGN); RtlCopyMemory((PULONG)UserStack1, &ContextFrame, sizeof(CONTEXT)); // // Compute length of exception record and new aligned stack // address. // Length = (sizeof(EXCEPTION_RECORD) - (EXCEPTION_MAXIMUM_PARAMETERS - ExceptionRecord->NumberParameters) * sizeof(ULONG) +3) & (~3); UserStack2 = UserStack1 - Length; // // Probe user stack area for writeability and then transfer the // context record to the user stack area. // N.B. The probing length is Length+8 because there are two // arguments need to be pushed to user stack later. // ProbeForWrite((PCHAR)(UserStack2 - 8), Length + 8, sizeof(ULONG)); RtlCopyMemory((PULONG)UserStack2, ExceptionRecord, Length); // // Push address of exception record, context record to the // user stack. They are the two parameters required by // _KiUserExceptionDispatch. // *(PULONG)(UserStack2 - sizeof(ULONG)) = UserStack1; *(PULONG)(UserStack2 - 2*sizeof(ULONG)) = UserStack2; // // Set new stack pointer to the trap frame. // KiSegSsToTrapFrame(TrapFrame, KGDT_R3_DATA); KiEspToTrapFrame(TrapFrame, (UserStack2 - sizeof(ULONG)*2)); // // Force correct R3 selectors into TrapFrame. // TrapFrame->SegCs = SANITIZE_SEG(KGDT_R3_CODE, PreviousMode); TrapFrame->SegDs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode); TrapFrame->SegEs = SANITIZE_SEG(KGDT_R3_DATA, PreviousMode); TrapFrame->SegFs = SANITIZE_SEG(KGDT_R3_TEB, PreviousMode); TrapFrame->SegGs = 0; // // Set the address of the exception routine that will call the // exception dispatcher and then return to the trap handler. // The trap handler will restore the exception and trap frame // context and continue execution in the routine that will // call the exception dispatcher. // TrapFrame->Eip = (ULONG)KeUserExceptionDispatcher; return;
nt!_EXCEPTION_POINTERS +0x000 ExceptionRecord : Ptr32 _EXCEPTION_RECORD +0x004 ContextRecord : Ptr32 _CONTEXT
我们再来看下源码的情况
;异常处理回调函数 myHandler proc C uses ebx esi edi pExcept,pFrame,pContext,pDispatch invoke MessageBox,0,addr messuc,addr szTit,MB_APPLMODAL or MB_OK invoke ExitProcess,0 myHandler endp ;程序入口点 _Start: assume fs:nothing push ebp mov ebp,esp push offset myHandler //压入我们的handler push fs:[0] //压入之前的ERR mov fs:[0],esp //fs:[0]更新成我们的处理地址 xor esi,esi mov eax, dword ptr [esi] invoke MessageBox,0,addr mesfail,addr szTit,MB_APPLMODAL or MB_OK mov esp,dword ptr fs:[0] //使esp指向我们的ERR,此时[esp+0]就是下一个ERR的地址 pop dword ptr fs:[0] //让fs:[0]恢复到未安装我们的handler的情况 mov esp,ebp pop ebp retn END _Start

handler:0x00401060是MessageBox的调用,上面四个push是MessageBox的参数
此处下断,观察堆栈和TIB的情况如何
堆栈,此时压入的依次是后一个ERR的地址,我们的handler:

TIB,可以看到此时的ExceptionList是我们的ERR的地址,正是0x0019ff78
至此,我们的异常处理就结束了。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/213229.html原文链接:https://javaforall.net
