概念
全称 Structured Exception Handling
是windows操作系统默认的异常处理机制;
使用
使用 _try 包裹可能出现异常的语句
_except()处理异常,当括号内为真的时候,执行处理语句;
例如:
1 | int main() |
原理
当程序触发异常后,程序会进行ip寄存器的跳转:
非调试状态下运行程序,触发后判断是否存在异常处理器(一个函数,在上述例子中,写上了try和except()编译器给程序添加了异常处理器,并会处理except中的内容),否则退出程序;
调试状态下,操作系统会优先将异常抛给调试进程(断点原理),之后调试器的选择有:
- 修改触发异常的代码继续执行
- 忽略异常交给SEH执行
则windows发生异常后的处理顺序为:调试器,SEH,结束程序;
结构
1 | typedef struct _EXCEPTION_REGISTRATION_RECORD |
这是一个链表结构中的节点,所以SEH是以链式存在的;
第一个节点位置位于 fs:[0] 段寄存器处(同时是TEB结构,即TEB第一个字段就是异常处理链);
当异常发生时,从第一个节点开始处理,之后向后传递依次处理,直到处理成功,可以返回原本位置继续执行,否则退出程序;
最后一个节点next指针指向 0xFFFFFFFF;
当在程序中写入了_try和_except之后,操作系统会动态的生成一个节点结构,从头部插入;
总结
要提一嘴的是,windows异常抛出的种类特别多,而调试器的设置有很重要的因素,在逆向的时候,遇到一些异常(比如c0005,0地址执行),直接运行过去会导致断点在SEH已经处理完的时候,并不会断在异常发生的时候,这和调试器的异常捕获设置有关;
调试器一般就只会在CC断点异常处断下;
也可以找到fs:[0]的地方,将断点直接打在SEH链表头部,这样发生异常就能断下来;
对于32位程序来说,用高级语言写的_try_except生成的新节点插入会在汇编中以如下的形式体现:
1 | push ExceptionHandler ;编译器生成的异常处理器 |
此时在栈中,原先SEH头部地址在上,相当于新节点的next,编译器给的函数在下,相当于新节点的Handler,此时esp指向新节点的第一个字段next,也就是新节点头部,所以将esp又给予fs:[0],为原链表在头部添加了一个新节点;
但要注意这只在这个函数体(栈帧)里有效,函数结束时会做出相应的栈平衡,并释放栈;