[toc]
终止处理SEH
终结处理器由 __try
、__finally
、__leave
构成,能够保证无论 __try
中的指令以何种方式退出,都必然会执行 __finally
块,但不会处理异常,只是做清理操作
SEH 的使用范围是线程相关的,每个线程都有自己的函数
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 <iostream> #include <windows.h> int main() { __try{ printf("__try { ... }\n"); __leave; } __finally{ printf("__finally { ... }\n"); if (AbnormalTermination()) printf("异常退出\n"); else printf("正常退出\n"); } return 0; }
|
异常处理SEH
异常处理器由关键字 __try
和 __except
构成,能够保证 __try
中如果产生异常,会执行过滤表达式中的内容,应该在过滤表达式提供的过滤函数中处理想要处理的异常
__except
后的括号中会存在一个异常过滤表达式,表达式中的返回值必定是以下说明的几个之一
1 2 3
| EXCEPTION_EXECUTE_HANDLER(1); EXCEPTION_CONTINUE_SEARCH(0); EXCEPTION_CONTINUE_EXECUTION(-1);
|
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
| DWORD ExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo, DWORD ExceptionCode) { printf("ExceptionCode: %X\n", ExceptionCode); if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionCode){ ExceptionInfo->ContextRecord->Eax = 30; ExceptionInfo->ContextRecord->Edx = 0; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_EXECUTE_HANDLER; } int main() { int number = 0; __try{ number /= 0; } __except (ExceptionFilter(GetExceptionInformation(), GetExceptionCode())){ printf("__try 中 产生了异常,但是并没有处理异常 %x\n", GetExceptionCode()); } printf("numebr = %d\n", number); return 0; }
|
顶层异常UEH
UEH 全称为顶层异常处理器,这个函数只能有一个,被保存在全局变量中,由于只会被系统默认的最底层 SEH 调用,故又被称为是 SEH 的一种,是整个异常处理中的最后一环,所以通常不会再次执行异常处理操作,而是进行内存 dump,将消息发送给服务器,进行异常分析
注意: UEH 在 win7 之后,只有在非调试模式下才会被调用,故可被用于反调试
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
| LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) { printf("ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode); if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode){ ExceptionInfo->ContextRecord->Eax = 30; ExceptionInfo->ContextRecord->Edx = 0; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_EXECUTE_HANDLER; } int main() { int number = 0; SetUnhandledExceptionFilter(TopLevelExceptionFilter); __try{ number /= 0; } __except (EXCEPTION_EXECUTE_HANDLER){ printf("这个地方永远不会执行\n"); } printf("number = %d\n", number); system("pause"); return 0; }
|
向量异常VEH
VEH 是向量化异常处理的一种,被保存在一个全局的链表中,进程内的所有线程都可以使用这个函数,是第一个处理异常的函数
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
| LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) { printf("ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode); if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode){ ExceptionInfo->ContextRecord->Eax = 30; ExceptionInfo->ContextRecord->Edx = 0; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_EXECUTE_HANDLER; } int main() { int number = 0; AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler); __try{ number /= 0; } __except (EXCEPTION_EXECUTE_HANDLER){ printf("这个地方永远不会执行\n"); } printf("number = %d\n", number); system("pause"); return 0; }
|
向量异常VCH
VCH:和 VEH 类似,但是只会在异常被处理的情况下最后调用
VCH 不会对异常进行处理,调用的时机和异常处理的情况有关
异常传递顺序:VEH -> SEH -> UEH -> VCH
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
| LONG WINAPI VectoredContinueHandler(EXCEPTION_POINTERS* ExceptionInfo) { printf("VCH: ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode); return EXCEPTION_CONTINUE_SEARCH; } LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) { printf("VEH: ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode); if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode){ ExceptionInfo->ContextRecord->Eax = 30; ExceptionInfo->ContextRecord->Edx = 0; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_SEARCH; } return EXCEPTION_EXECUTE_HANDLER; } LONG WINAPI TopLevelExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) { printf("UEH: ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode); if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode){ ExceptionInfo->ContextRecord->Eax = 30; ExceptionInfo->ContextRecord->Edx = 0; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_SEARCH; } return EXCEPTION_EXECUTE_HANDLER; } DWORD StructedExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) { printf("SEH: ExceptionCode: %X\n", ExceptionInfo->ExceptionRecord->ExceptionCode); if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionInfo->ExceptionRecord->ExceptionCode){ ExceptionInfo->ContextRecord->Eax = 30; ExceptionInfo->ContextRecord->Edx = 0; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_EXECUTE_HANDLER; } int main() { int number = 0; AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler); AddVectoredContinueHandler(TRUE, VectoredContinueHandler); SetUnhandledExceptionFilter(TopLevelExceptionFilter); __try{ number /= 0; } __except (StructedExceptionFilter(GetExceptionInformation())){ printf("SEH: 异常处理器\n"); } printf("number = %d\n", number); system("pause"); return 0; }
|
SEH探究原理
1 2 3 4 5 6 7 8 9 10 11
| PEXCEPTION_REGISTRATION_RECORD ExceptionList = nullptr; __asm push fs:[0] __asm pop ExceptionList __asm push ExceptionRoutine __asm push fs : [0] __asm mov fs : [0], esp __asm mov eax, ExceptionList __asm mov fs:[0], eax __asm add esp, 0x08
|
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
| void test1() { __try { printf("__try { 1... }\n"); __try { printf("__try { 2... }\n"); } __except (1) { printf("__except (1) { ... }\n"); } } __except (1) { printf("__except (1) { ... }\n"); } }
void test2() { }
void ShowSEH() { EXCEPTION_REGISTRATION_RECORD* header = nullptr; __asm push fs : [0] __asm pop header while (header != (EXCEPTION_REGISTRATION_RECORD*)-1) { printf("function: %08X\n", header->Handler); header = header->Next; } printf("\n"); } EXCEPTION_DISPOSITION NTAPI ExceptionRoutine( _Inout_ struct _EXCEPTION_RECORD* ExceptionRecord, _In_ PVOID EstablisherFrame, _Inout_ struct _CONTEXT* ContextRecord, _In_ PVOID DispatcherContext ) { printf("自定义SEH: ExceptionCode: %X\n", ExceptionRecord->ExceptionCode); if (EXCEPTION_INT_DIVIDE_BY_ZERO == ExceptionRecord->ExceptionCode) { ContextRecord->Eax = 30; ContextRecord->Edx = 0; ContextRecord->Ecx = 1; return ExceptionContinueExecution; } return ExceptionContinueSearch; } int main() { test1(); test2(); ShowSEH(); __asm push ExceptionRoutine __asm push fs : [0] __asm mov fs : [0], esp
int number = 0; number /= 0;
ShowSEH();
__asm mov eax, fs:[0] __asm mov eax, [eax] __asm mov fs : [0], eax __asm add esp, 8 ShowSEH(); return 0; }
|
异常发生时的处理流程:
- CPU 检测到异常,查 IDT 表执行中断处理程序
CommonDispatchException
(若是模拟异常,则顺序为 CxxThrowException RaiseException RtlRaiseException() NtRaiseException KiRaiseException
)
KiDispatchException
对异常进行分发,查找哪个处理程序处理异常,通过 IRETD 返回3环(模拟异常则通过系统调用返回3环)
KiUserExceptionDispatcher
若是3环的异常,KiDispatchException
会修改返回3环的 eip,将 eip 指向这个函数,当线程回到3环时,将从这个函数开始执行
RtlDispatchException
查找异常处理程序在哪里,先查 VEH
- VEH,(SEH)
- 代码返回到
KiUserExceptionDispatcher
- 调用
ZwContinue
再次进入0环(ZwContinue
调用 NtContinue
,主要作用是恢复 _TRAP_FRAME
,然后通过 _KiServiceExit
返回到3环)
- 线程回到3环后从修正的位置开始执行
总结:
- 当异常交由用户处理时,按照以下顺序调用异常处理方式:VEH -> SEH -> UEH -> VCH
- 当 VEH 表示处理了异常,就不会传递给 SEH,但会传递异常给 VCH
- 当 VEH 没处理,就会传递给 SEH
- 当 SEH 的所有异常处理函数没有能够处理异常,会调用默认的 UEH 处理函数
- 当 SEH 处理了异常,从 except 开始执行,就不会再将异常传递给 VCH
- 当 SEH 返回异常产生处执行,在返回前会调用 VCH
课堂复习
若所有的 SEH 都不能处理异常,那么最后会由谁处理异常?
若 SEH 不能处理异常,异常会传递给 UEH,UEH 实际上是系统默认的 SEH 调用的
VCH 处理程序会在什么情况下被调用?
只有之前的 SEH,VEH 或 UEH 中的任何一个处理了异常,这个函数才会被调用
SEH 是全局有效吗?它们被保存在什么地方?
- SEH 是线程相关的,保存在
FS:[0]
内,对应的就是 TEB.NT_TIB.ExceptionList
字段
- UEH 是进程相关的,若任何一个异常处理程序无法处理,就被调用,实际保存在一个全局的函数指针中
- VEH 和 VCH 是进程相关的,保存在全局链表中,两个函数只是保存的标志位不同
什么是异常?什么是中断?
- 异常通常是在 CPU 满足特定的条件时内部产生的,是一个同步事件,必须立即进行处理
- 中断通常由外部设备产生,如鼠标键盘等,是一个异步事件,可以不进行处理
异常的种类有哪些?有什么特点?
- 错误类:通常可修复,异常产生时,eip 指向的是产生异常的指令(除零错误,硬件断点,内存断点)
- 陷阱类:通常可修复,异常产生时,eip 指向的是下一条指令 (int 3)
- 终止类:无法修复,寄存器的指向是无意义的
windbg 中分别使用哪些系列的指令查看数据、修改数据、设置断点?
- 查看数据:db/dq/da/du/dw/dd/dt(查看结构体)
- 修改数据:eb/eq/ea/eu/ew
- 断点相关:bp/bu/bl/be/bd/bc
- 为指定模块加载符号:.reload /f /i demo.exe -> ml
- 流程相关:t(F11)/p(F10)/g(F5)
陷阱处理器被保存在哪里?windbg 中使用什么指令可以查看它?
Windows 中中断和异常是统一管理的,所有的处理函数都被保存在 CPU 相关的 IDT 中,使用 !IDT 可以进行查看
异常的产生方式有几种?分别是什么?
- 指令满足特定的条件,CPU 自动触发
- 用户使用
RaiseException
函数主动抛出异常
异常分发
异常分发过程使用的函数及具体的功能(以 int 3 为例)
KiTrrap03
CommonDispatchException
- 谁调用的:
KiTrrap03
- 调用了谁:
KiDispatchException
- 功能:
- 构建了一个
EXCEPTION_RECORD
结构体并使用接收的参数填充
- 调用了
KiDispatchException
并传递相关的参数(先前模式/分发次数/陷阱帧/异常记录)
1 2 3 4 5 6 7 8 9
| typedef _EXCEPTION_RECORD{ DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD* ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation [EXCEPTION_MAXIMUN_PARAMETERS]; }
|
KiDispatchException
![2019.12.30-1](D:\git png\2019.12.30-1.png)
![2019.12.30-2](D:\git png\2019.12.30-2.png)
![2019.12.30-3](D:\git png\2019.12.30-3.png)