前言

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异常处理以及顶层异常处理的更多相关文章

  1. SEH:结构化异常处理 学习

    SEH:结构化异常处理 结构化异常处理机制提供了一个操作系统,用于优化结构的方案,为客户提供更强大的程序执行环境.试想一下,你写程序不用考虑内存访问错误,那里是空指针错误,一直在按照程序的逻辑结构来写 ...

  2. 反调试——Windows异常-SEH

    反调试--Windows异常-SEH 概念: SEH:Structured Exception Handling SEH是Windows默认的异常处理机制 如何使用 在代码中使用 __try​​__e ...

  3. Windows的SEH机理简要介绍

    1.异常分类 一般来说,我们把Exception分为2类,一类是CPU产生的异常,我们称之为CPU异常(或者硬件异常).另一类为是通过调用RaiseException API产生的软件异常,我们称之为 ...

  4. JAVA中的异常(异常处理流程、异常处理的缺陷)

    异常处理流程 1)首先由try{...}catch(Exception e){ System.out.println(e); e.printStackTrace(); }finally{...}结构 ...

  5. Java中的异常处理(三) - 自定义异常处理

    1.异常处理类 package second; public class MyException extends Exception { MyException (){ } MyException ( ...

  6. 异常处理过程和异常处理的执行顺序(针对try{}catch{}finally{}而言)

    1.异常的处理方式有两种分别为:try{}catch{}finally{}和throws下面简单说一下这两者的区别和联系. 2.出现异常之后如果没有进行捕获处理系统就会直接将这个异常栈的跟踪信息直接打 ...

  7. php5和php7的异常处理机制 ----thinkphp5 异常处理的分析

    1.php异常和错误 在其他语言中,异常和错误是有区别的,但是PHP,遇见自身错误时,会触发一个错误,而不是跑出异常.并且,php大部分情况,都会触发错误,终止程序执行,在php5中,try catc ...

  8. Java基础-异常处理机制 及异常处理的五个关键字:try/catch/finally/throw /throws

    笔记: /** 异常处理机制: 抓抛模型 * 1."抛", 一旦抛出,程序终止! printStackTrace()显示异常路径! * 2."抓", 抓住异常 ...

  9. JAVA高级--异常处理概念和异常处理机制

    什么是异常 程序运行的过程中发生的一些不正常事件 异常分类 Throwable Error  错误 Exception IOException         RuntimeException    ...

随机推荐

  1. 如何在 ASP.Net Core 中实现 健康检查

    健康检查 常用于判断一个应用程序能否对 request 请求进行响应,ASP.Net Core 2.2 中引入了 健康检查 中间件用于报告应用程序的健康状态. ASP.Net Core 中的 健康检查 ...

  2. 浅析MyBatis(二):手写一个自己的MyBatis简单框架

    在上一篇文章中,我们由一个快速案例剖析了 MyBatis 的整体架构与整体运行流程,在本篇文章中笔者会根据 MyBatis 的运行流程手写一个自定义 MyBatis 简单框架,在实践中加深对 MyBa ...

  3. python-实现链式栈

    7 """ 8 用一个类来实现一个节点 9 """ 10 class Node(object): 11 def __init__(self, ...

  4. IaaS, PaaS和SaaS的区别

    从小型企业到全球企业,云都是一个非常热门的话题,它是一个非常广泛的概念,涵盖了很多在线领域. 无论是应用程序还是基础架构部署,当您开始考虑将业务转移到云时,了解各种云服务的差异和优势比以往任何时候都更 ...

  5. PTA 中序输出度为1的结点

    6-9 中序输出度为1的结点 (10 分)   本题要求实现一个函数,按照中序遍历的顺序输出给定二叉树中度为1的结点. 函数接口定义: void InorderPrintNodes( BiTree T ...

  6. lms框架即将发布第一个版本了

    lms微服务框架介绍 LMS框架旨在帮助开发者在.net平台下,通过简单的配置和代码即可快速的使用微服务进行开发. LMS通过.net框架的主机托管应用,内部通过dotnetty/SpanNetty实 ...

  7. sqli-labs系列——第二关

    less2 and 1=1有回显,and 1=2无回显,为数值型注入 order by 4–+报错,有3行 查询数据库名 ?id=0' union select 1,(select group_con ...

  8. Manjaro Linux平台用pyinstaller打包python可执行文件

    技术背景 当我们创建一个python项目,最终的成果如果希望用户能够不依赖于python源代码也能够正常的执行,就会比较的人性化.因为源代码数量众多,很难让每个用户都自行管理所有的源代码,因此我们需要 ...

  9. 敏捷史话(十二):你现在接触的敏捷也许是“黑暗敏捷”——Ron Jeffries

    他很少提起往事,也不再提及二十年前那场引起软件行业变革的会议,他专注于当下,一直活跃在敏捷领域.八十多岁的他依然运营维护着网站和博客,是极限编程网站 XProgramming.com 的作者,该网站是 ...

  10. KubeEdge边缘自治设计原理

    这一篇内容主要是KubeEdge中边缘节点组件EdgeCore的原理介绍. KubeEdge架构-EdgeCore 上图中深蓝色的都是kubeedg自己实现的组件,亮蓝色是k8s社区原生组件.这篇主要 ...