1997年的一篇文章感觉依旧封神,用户态掺杂内核分析,当时未公开的一些资料,似乎现在微软已经公开
https://blog.csdn.net/chenlycly/article/details/52575260
2011年boxcounter佬的一篇文章从内核模式分析
https://github.com/boxcounter/boxcounter.github.io/raw/master/attachments/SEH%E5%88%86%E6%9E%90%E7%AC%94%E8%AE%B0%EF%BC%88x86%E7%AF%87%EF%BC%89_v1.0.2.zip
TODO: 两篇文章都没看完,有空再来二周目
操作系统原生
当一个线程出现错误时,操作系统传递参数并调用用户定义的一个回调函数
回调函数样子
EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext);
|
参数1
typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;
|
参数2
指向establisher帧结构的指针 TODO
参数3
CONTEXT结构体指针保存特定线程的寄存器上下文,用于SEH时,CONTEXT结构表示异常发生时寄存器的值
kd> dt _context nt!_CONTEXT +0x000 ContextFlags : Uint4B +0x004 Dr0 : Uint4B +0x008 Dr1 : Uint4B +0x00c Dr2 : Uint4B +0x010 Dr3 : Uint4B +0x014 Dr6 : Uint4B +0x018 Dr7 : Uint4B +0x01c FloatSave : _FLOATING_SAVE_AREA +0x08c SegGs : Uint4B +0x090 SegFs : Uint4B +0x094 SegEs : Uint4B +0x098 SegDs : Uint4B +0x09c Edi : Uint4B +0x0a0 Esi : Uint4B +0x0a4 Ebx : Uint4B +0x0a8 Edx : Uint4B +0x0ac Ecx : Uint4B +0x0b0 Eax : Uint4B +0x0b4 Ebp : Uint4B +0x0b8 Eip : Uint4B +0x0bc SegCs : Uint4B +0x0c0 EFlags : Uint4B +0x0c4 Esp : Uint4B +0x0c8 SegSs : Uint4B +0x0cc ExtendedRegisters : [512] UChar
|
参数4
TODO
返回值
最后都要返回一个值来告诉系统下一步做什么
typedef enum _EXCEPTION_DISPOSITION { ExceptionContinueExecution, ExceptionContinueSearch, ExceptionNestedException,TODO ExceptionCollidedUnwind } EXCEPTION_DISPOSITION;
|
如何找到回调函数
结构化异常处理是基于线程的,三环FS:0指向TEB(Thread Environment Block)
kd> dt _TEB nt!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : Ptr32 Void ......
|
_TEB第一个成员NtTib(_NT_TIB)的第一个成员指向_EXCEPTION_REGISTRATION_RECORD结构的指针
typedef struct _NT_TIB { struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; PVOID StackBase; PVOID StackLimit; PVOID SubSystemTib; #if defined(_MSC_EXTENSIONS) union { PVOID FiberData; DWORD Version; }; #else PVOID FiberData; #endif PVOID ArbitraryUserPointer; struct _NT_TIB *Self; } NT_TIB;
typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; PEXCEPTION_ROUTINE Handler; } EXCEPTION_REGISTRATION_RECORD;
|
#include <windows.h> #include <stdio.h> DWORD scratch; EXCEPTION_DISPOSITION __cdecl _except_handler(struct _EXCEPTION_RECORD* ExceptionRecord, void* EstablisherFrame, struct _CONTEXT* ContextRecord, void* DispatcherContext) { unsigned i; printf("Hello from an exception handler\n"); ContextRecord->Eax = (DWORD)&scratch; return ExceptionContinueExecution; } int main() { DWORD handler = (DWORD)_except_handler; __asm { push handler push FS : [0] mov FS : [0] , ESP } __asm { xor eax,eax mov[eax], 1 } printf("After writing! scrath %d\n",scratch); __asm { mov eax, [ESP] mov FS : [0] , EAX add esp, 8 } return 0; }
|
mov[eax], 1时系统会先把异常抛给调试器,直接f11步过即可,先在_except_handler处打上断点,不然断不下来
展开(EH_UNWINDING)
#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <stdio.h> EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD* ExceptionRecord, void* EstablisherFrame, struct _CONTEXT* ContextRecord, void* DispatcherContext) { printf("Home Grown handler: Exception Code: %08X Exception Flags %X", ExceptionRecord->ExceptionCode, ExceptionRecord->ExceptionFlags); if (ExceptionRecord->ExceptionFlags & 1) printf(" EH_NONCONTINUABLE"); if (ExceptionRecord->ExceptionFlags & 2) printf(" EH_UNWINDING"); if (ExceptionRecord->ExceptionFlags & 4) printf(" EH_EXIT_UNWIND"); if (ExceptionRecord->ExceptionFlags & 8) printf(" EH_STACK_INVALID"); if (ExceptionRecord->ExceptionFlags & 0x10) printf(" EH_NESTED_CALL"); printf("\n");
return ExceptionContinueSearch; }
void HomeGrownFrame(void) { DWORD handler = (DWORD)_except_handler; __asm { push handler push FS : [0] mov FS : [0] , ESP }
*(PDWORD)0 = 0; printf("I should never get here!\n");
__asm { mov eax, [ESP] mov FS : [0] , EAX add esp, 8 } }
int main() { __try { HomeGrownFrame(); } __except (EXCEPTION_EXECUTE_HANDLER) { printf("Caught the exception in main()\n"); }
return 0; }
|
HomeGrownFrame过滤函数返回ExceptionContinueSearch不处理异常,沿着_EXCEPTION_REGISTRATION_RECORD.next找到main的处理函数,接管异常并处理,因此出错指令后面的 printf就永远不会执行
调用了两次异常处理函数_except_handler,当异常发生时,系统遍历EXCEPTION_REGISTRATION结构链表,直到它找到一个处理这个异常的处理程序。一旦找到,系统就再次遍历这个链表,直到处理这个异常的结点为止。在这第二次遍历中,系统将再次调用每个异常处理函数。关键的区别是,在第二次调用中,异常标志被设置为2。这个值被定义为EH_UNWINDING。
当一个函数的异常处理程序拒绝处理某个异常时,通常触发异常的函数执行流程并不会正常地从那个函数退出。第二次调用EH_UNWINDING操作系统给这个函数一个最后清理的机会,比如调用析构函数和__finally块之类的清理工作
展开操作会把堆栈上创建的_EXCEPTION_REGISTRATION_RECORD结构体清除
vs编译器层面的SEH
__try { } __except (过滤器表达式) { }
|
过滤表达式返回值
#define EXCEPTION_EXECUTE_HANDLER 1 #define EXCEPTION_CONTINUE_SEARCH 0 #define EXCEPTION_CONTINUE_EXECUTION (-1)
|
typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;
struct _EXCEPTION_REGISTRATION{ PEXCEPTION_POINTERS xpointers; struct _EXCEPTION_REGISTRATION *prev; void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD); struct scopetable_entry *scopetable; int trylevel; int _ebp; };
|
struct scopetable_entry { DWORD previousTryLevel; PDWRD lpfnFilter; PDWRD lpfnHandler; }
|
不管有多少try/except块,多少层嵌套,在使用SEH的一个函数中只创建一个EXCEPTION_REGISTRATION结构
每个try会对EXCEPTION_REGISTRATION.trylevel赋予不同的值,来scopetable里找到属于自己的异常处理流程,嵌套try通过scopetable_entry.previousTryLevel找到异常处理流程
测试程序
链接:https://pan.baidu.com/s/1_VfVoWSEE1EqOZ1KIaEm3A
提取码:tjxw
#include <Windows.h> #include <stdio.h> int flag = 0; int filter(PEXCEPTION_POINTERS exceptionInfo) { puts("second inner filter"); exceptionInfo->ContextRecord->Eax = &flag; return EXCEPTION_CONTINUE_EXECUTION; } int main() { __try { *(PDWORD)0 = 0; } __except (puts("first filter"), EXCEPTION_EXECUTE_HANDLER) { puts("first hadle"); }
__try { __try { __asm { xor eax, eax mov[eax], 1 } printf("%d\n", flag); } __except (filter(GetExceptionInformation())) { puts("second inner handle"); } } __except (puts("secode filter"), EXCEPTION_EXECUTE_HANDLER) { puts("second handle"); } puts("main end"); }
|
.text:00401070 ; int __cdecl main(int argc, const char **argv, const char **envp) .text:00401070 _main proc near ; CODE XREF: __scrt_common_main_seh+F5↓p .text:00401070 .text:00401070 ms_exc= CPPEH_RECORD ptr -18h .text:00401070 argc= dword ptr 8 .text:00401070 argv= dword ptr 0Ch .text:00401070 envp= dword ptr 10h .text:00401070 .text:00401070 ; __unwind { .text:00401070 push ebp .text:00401071 mov ebp, esp .text:00401073 push 0FFFFFFFFh ; _EXCEPTION_REGISTRATION.trylevel .text:00401075 push offset stru_402568 ; _EXCEPTION_REGISTRATION.scopetable .text:0040107A push offset __except_handler3 ; _EXCEPTION_REGISTRATION.handler .text:0040107F mov eax, large fs:0 ; prev .text:00401085 push eax ; _EXCEPTION_REGISTRATION.prev .text:00401086 mov large fs:0, esp ; 安装seh .text:0040108D sub esp, 8 .text:00401090 push ebx .text:00401091 push esi .text:00401092 push edi .text:00401093 mov [ebp+ms_exc.old_esp], esp ; 保存当前函数栈帧ESP .text:00401093 .text:00401096 ; __try { .text:00401096 mov [ebp+ms_exc.registration.TryLevel], 0 ; 当前try块的scopetable数组下标trylevel为0 .text:0040109D mov large dword ptr ds:0, 0 .text:0040109D ; } .text:004010A7 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh ; 当前try块结束 .text:004010AE jmp short loc_4010DC ; .text:004010AE ; 当前try块的scopetable数组下标trylevel为1 .text:004010AE .text:004010B0 ; --------------------------------------------------------------------------- .text:004010B0 .text:004010B0 $LN7: ; DATA XREF: .rdata:stru_402568↓o .text:004010B0 ; __except filter .text:004010B0 push offset aFirstFilter ; "first filter" .text:004010B5 call ds:__imp__puts .text:004010B5 .text:004010BB add esp, 4 .text:004010BE mov eax, 1 .text:004010C3 .text:004010C3 $LN9: .text:004010C3 retn .text:004010C3 .text:004010C4 ; --------------------------------------------------------------------------- .text:004010C4 .text:004010C4 $LN8: ; DATA XREF: .rdata:stru_402568↓o .text:004010C4 ; __except($LN7) .text:004010C4 mov esp, [ebp+ms_exc.old_esp] ; 执行expect块里的异常处理程序时,先恢复异常发送时的栈帧 .text:004010C7 push offset aFirstHadle ; "first hadle" .text:004010CC call ds:__imp__puts .text:004010CC .text:004010D2 add esp, 4 .text:004010D5 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh .text:004010D5 .text:004010DC .text:004010DC loc_4010DC: ; CODE XREF: _main+3E↑j .text:004010DC ; __try { .text:004010DC mov [ebp+ms_exc.registration.TryLevel], 1 ; 当前try块的scopetable数组下标trylevel为1 .text:004010DC ; } .text:004010DC .text:004010E3 ; __try { .text:004010E3 ; __try { .text:004010E3 mov [ebp+ms_exc.registration.TryLevel], 2 ; 当前try块的scopetable数组下标trylevel为2 .text:004010EA xor eax, eax .text:004010EC mov byte ptr [eax], 1 .text:004010EF mov eax, _flag .text:004010F4 push eax .text:004010F5 push offset _Format ; "%d\n" .text:004010FA call _printf .text:004010FA .text:004010FF add esp, 8 .text:004010FF ; } .text:004010FF ; } .text:004010FF .text:00401102 ; __try { .text:00401102 mov [ebp+ms_exc.registration.TryLevel], 1 ; 内部嵌套的try块结束后,立即将scopetable数组下标trylevel设置为外部try块的 .text:00401109 jmp short loc_401130 .text:00401109 .text:0040110B ; --------------------------------------------------------------------------- .text:0040110B .text:0040110B $LN15: ; DATA XREF: .rdata:stru_402568↓o .text:0040110B ; __except filter .text:0040110B mov ecx, [ebp+ms_exc.exc_ptr] .text:0040110E push ecx ; exceptionInfo .text:0040110F call _filter .text:0040110F .text:00401114 add esp, 4 .text:00401117 .text:00401117 $LN17: .text:00401117 retn .text:00401117 .text:00401118 ; --------------------------------------------------------------------------- .text:00401118 .text:00401118 $LN16: ; DATA XREF: .rdata:stru_402568↓o .text:00401118 ; __except($LN15) .text:00401118 mov esp, [ebp+ms_exc.old_esp] .text:0040111B push offset aSecondInnerHan ; "second inner handle" .text:00401120 call ds:__imp__puts .text:00401120 .text:00401126 add esp, 4 .text:00401126 ; } .text:00401126 .text:00401129 ; __try { .text:00401129 mov [ebp+ms_exc.registration.TryLevel], 1 .text:00401129 ; } .text:00401129 .text:00401130 .text:00401130 loc_401130: ; CODE XREF: _main+99↑j .text:00401130 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh .text:00401137 jmp short loc_401165 .text:00401137 .text:00401139 ; --------------------------------------------------------------------------- .text:00401139 .text:00401139 $LN11: ; DATA XREF: .rdata:stru_402568↓o .text:00401139 ; __except filter .text:00401139 ; __except filter .text:00401139 ; __except filter .text:00401139 ; __except filter .text:00401139 push offset aSecodeFilter ; "secode filter" .text:0040113E call ds:__imp__puts .text:0040113E .text:00401144 add esp, 4 .text:00401147 mov eax, 1 .text:0040114C .text:0040114C $LN13: .text:0040114C retn .text:0040114C .text:0040114D ; --------------------------------------------------------------------------- .text:0040114D .text:0040114D $LN12: ; DATA XREF: .rdata:stru_402568↓o .text:0040114D ; __except($LN11) .text:0040114D ; __except($LN11) .text:0040114D ; __except($LN11) .text:0040114D ; __except($LN11) .text:0040114D mov esp, [ebp+ms_exc.old_esp] .text:00401150 push offset aSecondHandle ; "second handle" .text:00401155 call ds:__imp__puts .text:00401155 .text:0040115B add esp, 4 .text:0040115E mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFFh .text:0040115E .text:00401165 .text:00401165 loc_401165: ; CODE XREF: _main+C7↑j .text:00401165 push offset aMainEnd ; "main end" .text:0040116A call ds:__imp__puts .text:0040116A .text:00401170 add esp, 4 .text:00401173 jmp short loc_401177 .text:00401173 .text:00401175 ; --------------------------------------------------------------------------- .text:00401175 jmp short loc_401179 .text:00401175 .text:00401177 ; --------------------------------------------------------------------------- .text:00401177 .text:00401177 loc_401177: ; CODE XREF: _main+103↑j .text:00401177 xor eax, eax .text:00401177 .text:00401179 .text:00401179 loc_401179: ; CODE XREF: _main+105↑j .text:00401179 mov ecx, [ebp+ms_exc.registration.Next] .text:0040117C mov large fs:0, ecx ;卸载seh .text:00401183 pop edi .text:00401184 pop esi .text:00401185 pop ebx .text:00401186 mov esp, ebp .text:00401188 pop ebp .text:00401189 retn .text:00401189 ; } .text:00401189 .text:00401189 _main endp
|
main函数里栈帧如下
$-24 saved_edi <--esp $-20 saved_esi ^ $-1C saved_ebx | $-18 当前函数栈帧ESP---> $-14 PEXCEPTION_POINTERS 是GetExceptionInformation等函数返回位置,异常发送时才会赋值 $-10 _EXCEPTION_REGISTRATION.prev <--fs:0 指向前一个EXCEPTION_REGISTRATION结构 $-C __except_handler3 $-8 _EXCEPTION_REGISTRATION.scopetable $-4 _EXCEPTION_REGISTRATION.trylevel 初始0xfffffffff ebp==> prev_ebp main_ret
|
scopetable_entry结构体数组情况
__except_handler3的细节暂时不分析了
从程序加载的dll找到