Loading... * 中断和异常 * 中断由外部因素产生,是异步的。如键盘,鼠标消息。 * 异常是在CPU执行指令时,满足某种条件的时候主动产生,如除零错误,页错误。 * #### 异常的种类 * **陷阱: 可恢复,恢复后继续在异常发生地址的下一条指令执行(软件断点 int3,硬件读写断点)** * **错误: 可恢复,恢复后继续在异常发生地址处执行(内存断点,硬件执行断点,内存访问)** * 终止: 不可恢复,退出进程 * #### Windows中的异常处理机制 * #### 异常的执行优先级 ![1546881530608.png](http://www.irohane.top/usr/uploads/2021/03/2401362206.png) - #### 异常的分发流程 - IDT:中断和异常是统一管理的,系统为每一种中断或异常都提供了处理函数,这些函数的地址就保存在IDT中,使用`!idt /a` 查看IDT中的所有内容 - 异常分发的源头 : CPU -> IDT -> KiTrapN(填充陷阱帧)-> CommonDispatchException(填充异常结构) -> KiDispatchException(分发异常) - 用户态异常分发的源头: KiUserExceptionDispatcher - 用户 RtlDispatchException -> VEH -> SEH(UEH) -> VCH - 内核 RtlDispatchException -> SEH - DbgkFowardException 将异常传递给三环的调试器 --- 故事理解: 一个富商在马路上被车撞了【发生异常】->首先拍照取证【拍照取证,在哪里被撞得,人的病情如何保存环境】->送到最近的医院紧急处理,并将取证信息存到医院【R0,内核调试器】->如果医院处理不了就将人送到远处ICU【R3,自己设置的异常机制】活下来就去之前的医院消除病例【R0,因为在紧急处理的时候进入医院保存了当时病例需要消除】,消除之后找到被撞的位置继续正常走。=。=【虽然粗暴但是有用,对照下图配合康康】 [【这里有大佬的详解】](https://bbs.pediy.com/thread-266554.htm) ![异常1.0.png](http://www.irohane.top/usr/uploads/2021/03/547551504.png) * #### VEH: 向量化异常处理 * 进程相关,可以有多个,最先执行 * 添加: AddVectoredExceptionHandler * 移除: RemoveVectoredExceptionHandler * #### VCH: 向量化异常处理 * 进程相关,可以有多个,异常被处理的情况下最后执行 * 添加: AddVectoredContinueHandler * 移除: RemoveVectoredContinueHandler * #### UEH: 顶层异常处理函数 * 进程相关,只存在一个,谁都处理不了就执行,进程被调试时不会被执行 * 注册:SetUnhandledExceptionFilter * #### SEH: 结构化异常处理程序 * 线程相关,保存在 FS:[0]的位置,关键字 __ try,__ except,__ finally, __ leave * __ try的后面必须有一个__ except或__ finally块,中间不能有其他代码 * 应该使用 __ leave 离开当前的 __ try 语句块,而不是使用 return go 等 * ##### 组合方式 1. __ try{} __ except(){} 捕获并处理异常 2. __ try{} __ finallyP{} 无论是否产生异常都执行 3. 异常可以嵌套,但是不能同时使用 __ except 和 __finally * ##### 过滤表达式 1. EXCEPTION_EXCUTE_HANDLER(1) 执行异常处理快 except{ ... } 2. EXCEPTION_CONTINUE_SHEARCH(0) 交由上层处理 3. EXCEPTION_CONTINUE_EXCUTION(-1) 重新执行一次,通常修复之后才会返回 * 过滤表达式可以是一个函数调用,但是必须返回以上三个值 * 可以使用 GetExceptionCode 和 GetExceptionInformation 获取异常信息 * GetExceptionCode 能在过滤表达式和异常处理快中使用, * GetExceptionInformation 只能在过滤表达式中使用 ##### SEH链 - SEH链,是个链表,保存这SEH的函数地址,链表的首地址保存在TEB的第一个字段中,通过FS寄存器来获取(FS:[0])。链表中的每个元素都是这样一个结构体 ```cpp // SEH 结构体 typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; PEXCEPTION_ROUTINE Handler; } EXCEPTION_REGISTRATION_RECORD; // SEH函数原型 EXCEPTION_DISPOSITION NTAPI EXCEPTION_ROUTINE ( _Inout_ struct _EXCEPTION_RECORD *ExceptionRecord, _In_ PVOID EstablisherFrame, _Inout_ struct _CONTEXT *ContextRecord, _In_ PVOID DispatcherContext ); push fs:[0] push new_handler mov fs:[0], esp ``` ## 异常处理器 由 __try、 __finally 和 __leave构成。能够保证无论 __try 块中的指令以何种方式退出,都必然会执行 __finally 块。[不会进行异常处理,只进行清理操作]SEH 的使用范围是线程相关的,每个线程都有自己的函数(SEH链表是局部链表,在**堆栈**中) ```cpp __try { // 被检查的代码块,通常是程序的逻辑部分 printf("__try { ... }n"); // 使用 __leave 跳出当前的 __try __leave; } __finally { // 终结处理块,通常用于清理当前程序 // 无论 __try 以何种方式退出,都会执行这里的指令 printf("__finally { ... }n"); // 使用 AbnormalTermination 判断 __try 的退出方式 // 正常退出,返回值是 false if (AbnormalTermination()) printf("异常退出n"); else printf("正常退出n"); } ``` ## 异常处理 由关键字 __try 、 __except 构成,能够保证 __try 中如果产生了异常,会执行过滤表达式中的内容,应该在过滤表达式提供的过滤函数中处理想要处理的异常 EXCEPTION_EXECUTE_HANDLER(1):表示该异常被处理,从异常处下一条指令继续执行 EXCEPTION_CONTINUE_SEARCH(0):表示异常不能被处理,交给下一个SEH EXCEPTION_CONTINUE_EXECUTION(-1):表示异常被忽略,从异常处继续执行 ```cpp // 异常处理器: 由关键字 __try 和 __except 构成 // 如果 __try 中产生了异常,会执行过滤表达式中的内容 // 应该在过滤表达式提供的过滤函数中处理想要处理的异常 // 异常过滤表达式中最常见的情况就是编写一个异常过滤函数,对异常进行处理 DWORD ExceptionFilter(DWORD ExceptionCode, PEXCEPTION_POINTERS ExceptionInfo) { printf("ExceptionCode: %Xn", ExceptionCode); // 如果当前产生的异常是除零异常,那么就通过修改寄存器处理异常 if (ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { // 在这里对寄存器执行的所有修改都会直接被应用到程序中 ExceptionInfo->ContextRecord->Eax = 1; ExceptionInfo->ContextRecord->Ecx = 1; // 如果异常被处理了,那么就返回重新执行当前的代码 return EXCEPTION_CONTINUE_EXECUTION; } // 如果不是自己能够处理的异常,就不处理只报告 return EXCEPTION_EXECUTE_HANDLER; } int main() { int number = 0; __try { // __try 中的是可能产生异常的代码 // idiv eax, ecx number /= 0; } // 通常会为异常过滤表达式提供一个异常处理函数用于处理异常,并返回处理结果 // GetExceptionCode: 用于获取异常的类型,能在过滤表达式和异常处理器中使用 // GetExceptionInformation: 用于获取异常的信息,只能写在过滤表达式中 // 异常过滤表达式 __except (ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) { // 异常处理器,只有 __except 返回 EXCEPTION_EXECUTE_HANDLER 才会执行 printf("__try 中产生了异常,但是并没有处理异常 %Xn", GetExceptionCode()); } printf("numebr = %dn", number); return 0; } ``` ## UEF异常处理器 opLevelEH 全称顶层异常处理器(UEF),这个函数只能有一个,被保存在全局变量中。由于只会被系统默认的最底层 SEH 调用,所以又会被称作是 SEH 的一种,是整个异常处理的最后一环。所以通常都不会再此执行异常处理操作,而是进行内存 dump ,将消息发送给服务器,进行异常分析。在win7 之后,只有在非调试模式下才会被调用,可以用来反调试。 ```cpp LONG WINAPI TopLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { printf("ExceptionCode: %Xn", ExceptionInfo->ExceptionRecord->ExceptionCode); // 如果当前的异常是除零异常,那么就通过修改寄存器处理异常 if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { ExceptionInfo->ContextRecord->Eax = 1; ExceptionInfo->ContextRecord->Ecx = 1; // 异常如果被处理了,那么就返回重新执行当前的代码 return EXCEPTION_CONTINUE_EXECUTION; } // 如果不是自己能够处理的异常,就不处理只报告 return EXCEPTION_EXECUTE_HANDLER; } int main() { int number = 0; // 通过一个函数可以直接的安装 UEF SetUnhandledExceptionFilter(TopLevelExceptionFilter); __try { number /= 0; } // 异常一旦被 SEH 处理,就不会再传递给 UEF __except (EXCEPTION_CONTINUE_SEARCH) { printf("不会被执行n"); } printf("number = %dn", number); system("pause"); return 0; } ``` ## VEH向量异常处理 Vectored Exception Handler 向量化异常处理的一种,被保存在一个全局的链表中,进程内的所有线程都可以使用这个函数,是第一个处理异常的函数。 ```cpp LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) { printf("ExceptionCode: %Xn", ExceptionInfo->ExceptionRecord->ExceptionCode); if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { ExceptionInfo->ContextRecord->Eax = 1; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_EXECUTE_HANDLER; } int main() { int number = 0; // 通过一个API可以直接安装VEH // 参数一是布尔值,如果为 TRUE,就将当前的函数添加到全局 VEH 函数的链表头部 // 否则则为尾部 AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler); __try { number /= 0; } // 异常首先被 VEH 接收到,如果无法处理才会传递给 SEH __except (EXCEPTION_EXECUTE_HANDLER) { printf("永远不会被执行n"); } printf("number = %dn", number); system("pause"); return 0; } ``` ## VCH VCH:和 VEH 类似,但是只会在异常被处理的情况下最后调用。 ```cpp LONG WINAPI VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) { printf("VEH: ExceptionCode: %Xn", ExceptionInfo->ExceptionRecord->ExceptionCode); if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { ExceptionInfo->ContextRecord->Eax = 1; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_SEARCH; } return EXCEPTION_EXECUTE_HANDLER; } DWORD StructedExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { printf("SEH: ExceptionCode: %Xn", ExceptionInfo->ExceptionRecord->ExceptionCode); if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { ExceptionInfo->ContextRecord->Eax++; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_SEARCH; } return EXCEPTION_EXECUTE_HANDLER; } LONG WINAPI TopLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { printf("UEF: ExceptionCode: %Xn", ExceptionInfo->ExceptionRecord->ExceptionCode); if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) { ExceptionInfo->ContextRecord->Eax++; ExceptionInfo->ContextRecord->Ecx = 1; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_EXECUTE_HANDLER; } LONG WINAPI VectoredContinueHandler(PEXCEPTION_POINTERS ExceptionInfo) { // VCH 不会对异常进行处理,调用的时机和异常处理的情况有关 printf("VCH: ExceptionCode: %Xn", ExceptionInfo->ExceptionRecord->ExceptionCode); return EXCEPTION_CONTINUE_SEARCH; } int main() { int number = 0; AddVectoredExceptionHandler(TRUE, VectoredExceptionHandler); AddVectoredContinueHandler(TRUE, VectoredContinueHandler); SetUnhandledExceptionFilter(TopLevelExceptionFilter); __try { number /= 0; } __except (StructedExceptionFilter(GetExceptionInformation())) { printf("SEH: 异常处理器n"); } printf("number = %dn", number); system("pause"); return 0; } ``` 最后修改:2022 年 06 月 16 日 © 允许规范转载 赞 0 如果觉得我的文章对你有用,请随意赞赏