Window SEH异常 – 异常基础
SEH数据结构
TIB 结构
TIB(ThreadInformatio它位于TEB(ThreadEnvironmentBlock,线程环境块)的头部,而TEB是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TEB。
1 | typedef struct _NT_TIB |
然Windows系统经历了多次更新换代,但是从Windows 2000到Windows10,TIB结构都没有变化。其中,与异常处理相关的项是指向EXCEPTION_REGISTRATION_RECORD结构的指针ExceptionList,它位于TIB的偏移0处,同时在TEB的偏移0处。在x86平台的用户模式下,Windows将FS段选择器指向当前线程的TEB据,即TEB总是由fs:[O]指向的(在x64平台上,这个指向关系变成了gs:[O]。关于x64平台上的异常处理,会在8.5节详细讲述)。而当线程运行在内核模式下时,Windows将FS段选择器指向内核中的KPCRB结构(Processor Control Region Block ,处理器控制块),该结构的头部同样是上述的NT_TIB构。
EXCEPTION_REGISTRATION_RECORD结构
TEB移量为0的_EXCEPTION_REGISTRATION_RECORD主要用T描述线程异常处理程的地址,多个该结构的链表描述了多个线程异常处理过程的嵌套层次关系,其定义如下。
1 | typedef struct _EXCEPTION_REGISTRATION_RECORD |
其中,“Next”是指向下一个_EXCEPTIONREGISTRATION_RECORD(简称“ERR”)的指针,形成一链状结构,而链表头就存放在也:[O]指向的TEE中;"Handler”指向异常处理回调函数,如下图。
当程序运行过程中产生异常时,系统的异常分发器就会从fs:[0]处取得异常处理的链表头,然后查找异常处理链表并依次调用各个链表节点中的异常处理回调函数。由于TEE是线程的私有数据结构,相应地,钱程也都有自己的异常处理链表,即SEH机制的作用范围仅限于当前线程。从数据结构的角度来讲,SEH链就是一个只允许在链表头部进行增加和删除节点操作的单向链表,且链表头部永远保存在fs:[0]处的TEB结构中。
EXCEPTION_POINTERS结构
一个异常发生时,在没有调试器干预的情况下,操作系统会将异常信息转交给用户态的异常处理过程。实际上,由于同一个线程在用户态和内核态使用的是两个不同的拢,为了让用户态的常处理程序能够访问与异常相关的数据,操作系统必须把与本次异常相关联的EXCEPTION_RECORD结构和CONTEXT结构放到用户态战中,同时在党中放置一个_EXCEPTION_POINTERS结构,它包含两个指针,一指向EXCEPTION_RECORD锚构,另一个指向CONTEXT结构,示例如下。
1 |
这样,用户态的异常处理程序就能够取得异常的具体信息和发生异常时线程的状态信息,并根据具体情况进行处理了。
SEH处理程序的安装与卸载
由于fs:[0]总是指向当前异常处理程序的链表头,当程序中需要安装一个新的SEH异常处理程序时,只要填写一个新的EXCEPTION_REGISTRATION_RECORD结构,并将其插入该链表的头部即可。根据SEH的设计要求,它的作用范围与安装它的函数相同,所以通常在函数头部安装SEH异常处理程序,在函数返回前卸载可以说,SEH是基于战帧的异常处理机制。
在安装SEH理程序之前,需要准备一个符合SEH标准的回调函数,然后使用如下代码进行SEH异常处理程序的安装。
1 | assume fs:nothing |
“assume fs:nothing”是MASM编译器的特殊要求,若不满足该要求将出现编译错误,后面3行则是注册回调函数。“push offset SEHandler”和“push fs:[0]”相继向棋中压入了Handler和当前的SEH链表头,这两个元素构成了一个新的_EXCEPTION_REGISTRATION_RECORD结构,此时它的位置就在桔顶,即esp指向的位置。然后,esp(也就是最新的链表头)保存到fs:[0]中也就是修改TIB结中的ExceptionList,相当于向链表中插入了一个新节点。该操作前后SEH链表的变化如下图。
理解了SEH的安装过,再看SEH的卸载就比较简单了,只要把刚才保存的fs:[O]的原始值填回并恢复栓的平衡即可,相当于从链表头部删掉了一个节点,示例如下。
1 | mov esp,dword ptr fs:[0] |