结构化异常处理(SEH) 操作系统或程序在运行,难免会遇到各种各样的错误,如除零,非法内存访问,文件打开错误,内存不足,磁盘读写错误,外设操作失败。为了保证系统在遇到错误时不至于崩溃,仍能够健壮稳定地继续运行下去,windows会对运行在其中的程序提供一次补救的机会来处理错误这种机制就是异常处理机制。S.E.H即异常处理结构体(Structure Exception Handler),它是windows异常处理机制所采用的重要数据结构,每个S.E.H包含两个DWORD指针:S.E.H链表指针和异常处理函数句柄,共8个字节,如下图
下面分别对用户模式下的原始型SEH和封装型SEH 进行讨论。
原始型SEH
SEH 的进程相关类型是整个进程作用范围的异常处理函数,通过WIN32 API 函数SetUnhandledExceptionFilter 进行注册,而操作系统内部使用一个全局变量来记录这个顶层的处理函数,因此只能有一个全局性的异常处理函数。而线程相关类型的作用范围是本线程内,并且可注册多个,甚至可以嵌套注册。两者相比线程相关类型在实际应用中使用较为广泛,因此此处重点对此类型进行研究。
当线程初始化时,会自动向栈中安装一个异常处理结构,作为线程默认的异常处理。SEH 最基本的数据结构是保存在堆栈中的称为EXCEPTION_REGISTRATION 的结构体,结构体包括2个元素:第1个元素是指向下一个EXCEPTION_REGISTRATION 结构的指针(prev),第2个元素是指向异常处理程序的指针(handler)。这样一来,基于堆栈的异常处理程序就相互连接成一个链表。异常处理结构在堆栈中的典型分布如图1 所示。最顶端的异常处理结构通过线程控制块(TEB)0 Byte
偏移处指针标识,即FS:[0]处地址。
用于进行实际异常处理的函数原型可表示如下:
该函数的最重要的2个参数是指向_EXCEPTION_RECORD 结构的ExceptionRecord 参数和指向_CONTEXT 结构的ContextRecord 参数,前者主要包括异常类别编码、异常发生地址等重要信息;后者主要包括异常发生时的通用寄存器、调试寄存器和指令寄存器的值等重要的线程执行环境。而用于注册异常处理函数的典型汇编代码可表示如下:
当异常发生时,操作系统的异常分发函数在进行初始处理后,如果异常没有被处理就会开始在上图所示的线程堆栈上遍历异常处理链,直到异常被处理,如果仍没有注册函数处理异常,则将异常交给缺省处理函数或直接结束产生异常的进程。
封装型SEH
通过使用_try{}/_except(){}/_finally{}等关键字,使开发人员更方便地在软件中使用SEH 是封装型SEH 的主要特点。该机制的异常处理数据结构定义如下:
显而易见,结构体中后3 个成员是新增的。而scopetable_entry 的结构如下所示:
封装型SEH 的基本思想是为每个函数内的_try{}块建立一scopetable表,每个_try{}块对应于scopetable中的一项,该项指向_try{}块对应的scopetable_entry 结构,该结构含有与_except(){}/_finally{}对应的过滤函数和处理函数。若有_try{}块嵌套,则在scopetable_entry 结构的prev_entryindex成员中指明,多层嵌套形成单向链表。而每个函数只注册一个VC_EXCEPTION_REGISTRATION
结构, 该结构中的handler 成员是一个重要的运行时库函数_except_handler3。该异常处理回调函数负责对结构中的成员进行设置,查找处理函数并根据处理结果决定是继续执行还是让系统继续遍历外层SEH 链。为了弄清看似复杂的封装型SEH 原理,此处通过分析一个简单的使用封装型SEH 的函数的反汇编实现,从而深入地了解封装型SEH 的实现过程。该函数的C 语言实现如下:
对应该函数的序言部分反汇编代码如下:
显而易见, 压入堆栈的结构与VC_EXCEPTION_REGISTRATION 结构是一致的。查找scopetable 的地址为0x00422048,在调试器中查找该地址起始的内容如下:
FFFFFFFF ;scopetable_entry0 的prev_entryindex 值
00000000 ;lpfnFilter 地址值,为0,对应_finally{}
004010EE ;lpfnHandler 地址值
00000000 ;scopetable_entry1 的prev_entryindex 值
004010C6 ;lpfnFilter 地址值
004010C9 ;lpfnHandler 地址值
…
第1 组值对应0 号try 块,而该块对应_finally{}块,所以过滤函数地址为0;第2 组值对应1 号try 块,而该块对应_except(){}块,所以有过滤函数和处理函数的地址。进一步查看0x004010EE 地址处的反汇编代码如下:
显然上述语句与_finally{}块中的C 语言语句是对应的,而其他的地址经过查找也是分别对应的。在进入0 号try 块时的反汇编语句如下:
而在退出_try 块时对应的反汇编语句如下:
根据异常处理的堆栈结构可知, [EBP-4] 处值就是VC_EXCEPTION_REGISTRATION 结构中_index 的值。异常处理机制在进入和退出每个_try 块前设置相应的_index 值,这样就可正确处理封装型SEH 内发生的各种异常。以上通过实例进一步验证和明确了封装型SHE 的内部机理,它只是扩展了原始型SEH 的功能,简化了软件开发人员的工作。
利用
未完。。。
分享到:
相关推荐
《0day安全:软件漏洞分析技术》第6章在栈溢出中利用SEH,自己写的例子,有兴趣的同学可以调试下。
利用SEH和INT3实现API HOOK.rar
从零开始学习软件漏洞挖掘系列教程第三篇:利用SEH机制Exploit it.。从零开始学习软件漏洞挖掘系列教程第三篇:利用SEH机制Exploit it.
SEH的堆溢出利用代码,适合入门的初学者,前置知识:SEH
ExpHandel_B回调:代码://可注释 存在的意义 是更好理解SEH执行流程EXCEPTION_DISPOSITION ExpHandel_B(
西门子 SEH62.1时控开关设置说明
SEH反调试.rar
seh异常处理练习的程序,里面的程序里面用了SEH,并且SEH函数中有简单的反调试代码.通过调试此程序可更好的理解SEH和动态反调试
SEH异常处理.rar
C++异常和SEH的区别,入门级的设计模式,大家可以参考下
易语言SEH异常处理源码.rar
支持易语言程序代码自设SEH捕获异常
程序发生错误 找不到 libcc_s_seh-1.dll,安装该 dll 后,重新运行即可
[易语言源码]-高效线程SEH示例源码
seh 调试 反调试 破解 代码 SEH反调试的实现与调试 在没有异常时永远都是错误的注册码,只有当触发异常时,程序才走到注册成功的地方…
C++ SEH HOOK拦截任意API 1G视频教程
SEH异常处理结合花指令
用SEH技术实现API Hook.zip
这两天琢磨了下SEH,这里记录一下自己琢磨的一些心得。主要记录一些自己的理解。可能有一些概念理解的不够清晰,有一些说法比较狭隘,欢迎看到本文的朋友一起讨论、修正,非常感谢。