windows的SEH异常处理以及顶层异常处理
前言
windows的SEH结构化异常处理是基于线程的,传统的SEH结构化异常会基于堆栈形成一条包含异常回调函数地址的链(SEH链)。而fs:[0](TEB的第一个字段)指向这条链的链头,当有异常发生时并产送到SEH处时其会从fs:[0]开始遍历这条链。如果那个链结点的回调函数能正确处理异常则程序会返回到异常发生处,否则继续遍历一直到链尾。
顶层异常处理
一般程序开始创建任意线程运行前默认设置一个顶层异常处理程序。
_try
{
//程序入口
}
_except(UnhandledExceptionFilter())
{
}
所谓的顶层异常处理意思是,当程序的异常通过SEH链一直传递到最后一个我们自己设置的SEH链结点都没有处理此异常时,会调用这个顶层异常处理函数。
下面对顶层异常处理进行详细分析。
- SEH是基于线程的,因为每一个线程都有自己的TEB,又因为TEB的第一个字段指向SEH链的头部,所以每一个线程都有自己的SEH链,不同线程之间的SEH链也不同。每个线程创建之前都会安装顶层异常处理过程,而每一个顶层异常处理程序的过滤函数都是UnhandledExceptionFilter(),此函数会在内部调用全局变量kernel32!BasepCurrentTopLeveFilter保存的函数地址,此函数可以干预UnhandleExceptionFilter()的返回值。而我们可以调用SetUnhandledExceptionFilter()函数改变此全局变量的值为自定义函数的地址。所以顶层异常处理是基于进程的(顶层异常过滤函数返回值干预函数的值是全局变量保存的)。
- 如果程序是用MSC编译器编译的程序默认的异常处理函数一般是Kernel32!_except_handlerX(),Kernel32!_except_handlerX()会先调用UnhandleExceptionFilter()过滤函数,而如果我们在UnhandleExceptionFilter()过滤函数中已经把此异常处理了则就不会执行默认的异常处理,否则默认的异常处理就是结束应用程序。
顶层异常处理在反调试中的应用
为当程序产生一个异常时,首先会被系统内核捕捉然后系统内核会判断当前是否有调试器调试,有的话把异常交给调试器。如果没有调试器或者调试器处理不了此异常的话,异常会被分发给SEH链上的各个异常处理回调函数依次处理,这些回调函数的地址是通过一个链表存储。通过遍历这个链表从而调用各个异常处理回调函数。如果异常被某个回调函数正常处理了就继续从产生异常的代码处继续往下执行。如果一直遍历到链表的最后一个异常回调函数之前都没能够处理此异常的话,此异常就会交给最后一个默认的异常处理程序处理。
而最后一个默认异常处理函数在调用时会先调用UnhandledExceptionFilter( )过滤函数,此函数又会调用ZwQueryInformationProcess( )函数先判断是否有调试器存在,有的话会直接返回进行异常的二次分发(一般就是会结束进程)。
如果ZwQueryInformationProcess()函数没有检测到调试器的存在的话其将会执行默认异常处理。而一般默认异常处理是默认的终止程序的函数。(如果没有调试器的话异常是不会进行二次分发的,这点很重要)
但是windows提供一个函数来对UnhandledExceptionFilter( )过滤函数进行干预从而修改其返回值让其正常返回,而不执行默认的异常处理终止程序。此函数就是SetUnhandledExceptionFilter( ),此函数具有唯一的参数就是设置用来干预UnhandledExceptionFilter( )过滤函数的回调函数的地址,UnhandledExceptionFilter( )会在内部调用这个函数。(一般称这个函数为顶级异常处理函数,我认为这么称是不准确的。因为真正的顶级异常处理函数是默认的异常处理函数,此函数应该称为顶级过滤干预函数)
所以一般会利用此顶层异常处理函数的特性,主动产生异常,然后调用SetUnhandledExceptionFilter()设置顶层过滤函数的干扰函数来处理异常,如果用户调式程序的话,因为顶层异常处理会检测到有调试器,因此不会调用顶层过滤干扰函数来处理异常,导致异常无法处理从而终止运行。(需要我们改变ZwQueryInformationProcess( )函数的返回值来骗过UnhandledExceptionFilter( )函数让他以为无调试器)
传统的SEH
所谓传统的SEH结构化异常处理就是没有被编译器处理过,就是最纯正的SEH结构化异常处理。通过汇编语言来编写最原始的SEH结构化异常处理.
安装SEH,其中_Handler2是此异常处理的回调函数(调用约定为_cdecl)
push offset _Handler2
push fs:[0]
mov fs:[0],esp
卸载SEH
pop fs:[0] ;恢复SEH链
pop eax ;平衡堆栈
以下面汇编程序为例
start:
assume fs:nothing ;开始安装SEH异常处理程序
push offset _Handler3 ;设置异常处理回调函数的地址
push fs:[0] ;把下一个EXCETION_POINTERS结构
mov fs:[0],esp ;设置SEH链的头指针指向当前设置的EXCEPTION_POINTERS结构
push offset _Handler2
push fs:[0]
mov fs:[0],esp
push offset _Handler1
push fs:[0]
mov fs:[0],esp
pop fs:[0]
pop eax
pop fs:[0]
pop eax
pop fs:[0] ;恢复SEH链
pop eax ;平衡堆栈
invoke MessageBox,NULL,addr szDelete,NULL,MB_OK
invoke ExitProcess,NULL
end start
用OD查看程序,可以看到在没有执行程序前还没安装我们的SEH时,SEH链已经有数据。因为我们这个编译程序用的也是MSC编译器的ml.exe编译程序编译的,所以查看发现是程序也会安装的默认的异常处理回调函数是_except_handler4_command()。
然后我们执行程序,安装三个SEH后再查看SEH链,发现新增了3个结点。而且各个SEH链结点的异常回调函数地址为我们push的函数的地址
这就是传统的SEH链
编译器处理过的SEH
已MSC编译器为例,对于C而言编译器会在所有函数的入口点将SEH的异常处理回调函数都设置为_except_handlerX()。此函数的执行流程为:
- 编译器会在栈上为每一个_try块设置其索引,在函数一开始会设置_try的索引为-1。_except_handlerX()函数会获取当前_try的索引,如果_try索引为-1就表示异常没有发生在_try块中,表示此函数不能处理该异常,_except_handlerX()函数返回值为ExceptionContinueSearch表示不能处理此异常。然后异常就会沿着SEH链向上传递。
- 如果_try索引不为-1则说明异常发生在在_try块中,其再通过调用此_try对应的_except()里的过滤函数,然后根据过滤函数返回值决定是否调用_except()中的代码进行异常处理。
- 如果过滤函数的返回值能处理异常就去调用对应_except()内部的异常处理函数,如果过滤函数的返回值不能处理次异常就继续寻找上一层_try块(父_try块)的索引。直到最后寻找到最上层_try块的上面,即默认的_try索引为-1的地方,此时表示此函数已经不能处理该异常了,_except_handlerX()函数返回值为ExceptionContinueSearch表示不能处理此异常。然后异常就会沿着SEH链向上传递。
已下面c程序为例
#include <iostream>
#include <Windows.h>
using namespace std;
int main(int argc, char* argv[])
{
_try
{
cout<<"第三层try块"<<endl;
_try
{
cout<<"第二层try块"<<endl;
_try
{
cout<<"第一层try块"<<endl;
}
_except(EXCEPTION_CONTINUE_SEARCH)
{
}
}
_except(EXCEPTION_CONTINUE_SEARCH)
{
}
}
_except(EXCEPTION_CONTINUE_SEARCH)
{
}
return 0;
}
然后在OD中查看SEH链。发现SEH链上的各个异常处理回调函数都为_except_handler4(),此异常处理回调函数起到了一个代理的作用来完成_try和_except()处理。因为程序入口点为C运行库函数,C运行库函数在调用main函数,所以SEH链上有两个异常处理函数都是_except_handler4()。
ntdll.77C9B130是默认的异常处理回调函数,和0x00CE105A(_except_handler4())一样其会在内部调用_except_handler4_command(),只不过传递的参数不同。
有一点需要注意,当异常发生时或在SEH链中传递时,先调用异常处理回调函数_except_handler4(),然后在调用各个异常过滤函数,即_except()括号中的表达式判断能不能处理异常,如果返回值能处理就调用_except()内的异常处理代码。
参考资料:《看雪加密与解密》
windows的SEH异常处理以及顶层异常处理的更多相关文章
- SEH:结构化异常处理 学习
SEH:结构化异常处理 结构化异常处理机制提供了一个操作系统,用于优化结构的方案,为客户提供更强大的程序执行环境.试想一下,你写程序不用考虑内存访问错误,那里是空指针错误,一直在按照程序的逻辑结构来写 ...
- 反调试——Windows异常-SEH
反调试--Windows异常-SEH 概念: SEH:Structured Exception Handling SEH是Windows默认的异常处理机制 如何使用 在代码中使用 __try__e ...
- Windows的SEH机理简要介绍
1.异常分类 一般来说,我们把Exception分为2类,一类是CPU产生的异常,我们称之为CPU异常(或者硬件异常).另一类为是通过调用RaiseException API产生的软件异常,我们称之为 ...
- JAVA中的异常(异常处理流程、异常处理的缺陷)
异常处理流程 1)首先由try{...}catch(Exception e){ System.out.println(e); e.printStackTrace(); }finally{...}结构 ...
- Java中的异常处理(三) - 自定义异常处理
1.异常处理类 package second; public class MyException extends Exception { MyException (){ } MyException ( ...
- 异常处理过程和异常处理的执行顺序(针对try{}catch{}finally{}而言)
1.异常的处理方式有两种分别为:try{}catch{}finally{}和throws下面简单说一下这两者的区别和联系. 2.出现异常之后如果没有进行捕获处理系统就会直接将这个异常栈的跟踪信息直接打 ...
- php5和php7的异常处理机制 ----thinkphp5 异常处理的分析
1.php异常和错误 在其他语言中,异常和错误是有区别的,但是PHP,遇见自身错误时,会触发一个错误,而不是跑出异常.并且,php大部分情况,都会触发错误,终止程序执行,在php5中,try catc ...
- Java基础-异常处理机制 及异常处理的五个关键字:try/catch/finally/throw /throws
笔记: /** 异常处理机制: 抓抛模型 * 1."抛", 一旦抛出,程序终止! printStackTrace()显示异常路径! * 2."抓", 抓住异常 ...
- JAVA高级--异常处理概念和异常处理机制
什么是异常 程序运行的过程中发生的一些不正常事件 异常分类 Throwable Error 错误 Exception IOException RuntimeException ...
随机推荐
- 设置beeline连接hive的数据展示格式
问题描述:beeline -u 方式导出数据,结果文件中含有"|"(竖杠). 执行的sql为:beeline -u jdbc:hive2://hadoop1:10000/defau ...
- gtk---实现一个登录界面
输入框 如果在GTK+中需要输入一个字符串,可以使用输入框,这是一个单行的输入构件,可以用于输入和显示正文内容. 输入框的基本操作函数 1.gtk_entry_new(void); 这是新建一个输入框 ...
- Python的多进程和多线程
进程和线程 进程是系统进行资源分配的最小单位,线程是系统进行调度执行的最小单位: 一个应用程序至少包含一个进程,一个进程至少包含一个线程: 每个进程在执行过程中拥有独立的内存空间,而一个进程中的线程之 ...
- 第23 章 : Kubernetes API 编程范式
Kubernetes API 编程范式 需求来源 首先我们先来看一下 API 编程范式的需求来源. 在 Kubernetes 里面, API 编程范式也就是 Custom Resources Defi ...
- 「HTML+CSS」--自定义加载动画【009】
前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...
- servlet学习(一)
Tomcat 注:以下资料摘自孙鑫的<sevlet/JSP深入详解>,仅用于个人学习使用. 一.web技术的发展 早期web是静态页面的浏览,使用HTML编写,放入服务器. 1.1浏览器请 ...
- Nginx常用部分命令
Nginx一些命令 Windows 查看帮助信息 nginx -h 查看 nginx 版本 (小写字母 v) nginx -v 除版本信息外还显示配置参数信息 (大写字母 V) nginx -V 启动 ...
- [Fundamental of Power Electronics]-PART II-9. 控制器设计-9.3 关键项1/(1+T)和T/(1+T)以及闭环传递函数的构建
9.3 关键项\(1/(1+T)\)和\(T/(1+T)\)以及闭环传递函数的构建 从式(9.4)到(9.9)的传递函数可以很容易的由图形代数方法进行构建.假设我们已经分析了反馈系统模块,并且已经画出 ...
- 201871010113-贾荣娟 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告
项目 内容 课程班级博客链接 18级卓越班 这个作业要求链接 实验三-软件工程结对项目 这个课程学习目标 掌握软件开发流程,提高自身能力 这个作业在哪些方面帮助我实现了学习目标 本次实验让我对软件工程 ...
- (十六)Struts2的标签库
一.简介 Struts2的标签库使用OGNL为基础,大大简化了数据的输出,也提供了大量标签来生成页面效果,功能非常强大. 在早期的web应用开发中,jsp页面主要使用jsp脚本来控制输出.jsp页面嵌 ...