前言

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. Vue3手册译稿 - 深入组件 - 自定义事件

    本章节需要掌握组件基础 emit我译成发射,觉得发射这个词比较形象的形容将子组件事件发射出来的一个动作. 事件名 像组件和props,事件名也会进行自动转换,如果你在子组件里发射一个驼峰命名的事件,你 ...

  2. C# 自定义时间进度条

    这篇文章对我帮助极大,我模仿着写了两遍大概摸清楚了自定义控件的流程.https://www.cnblogs.com/lesliexin/p/13265707.html 感谢大佬 leslie_xin ...

  3. 使用jQuery实现ajax请求

    <%-- Created by IntelliJ IDEA. User: Administrator Date: 2021/3/13 Time: 14:54 To change this tem ...

  4. P1781_宇宙总统(JAVA语言)

    //水题 题目背景 宇宙总统竞选 题目描述 地球历公元6036年,全宇宙准备竞选一个最贤能的人当总统,共有n个非凡拔尖的人竞选总统,现在票数已经统计完毕,请你算出谁能够当上总统. 输入输出格式 输入格 ...

  5. 【Azure Developer】Github Action部署资源(ARM模板)到Azure中国区时,遇见登录问题的解决办法

    问题描述 在参考文档"使用 GitHub Actions 部署 ARM 模板"一文中,由于是在中国区Azure上操作,所以生产的部署凭证为中国区凭证.当创建工作流时,在登录到Azu ...

  6. java例题_10小球 自由落体

    1 /*10 [程序 10 自由落体] 2 题目:一球从 100 米高度自由落下,每次落地后反跳回原高度的一半: 3 求它在 第 10 次落地时,共经过多少米? 4 第 10 次反弹多高? 5 */ ...

  7. java例题_09 1000以内的完全数

    1 /*9 [程序 9 求完数] 2 题目:一个数如果恰好等于它的所有因子之和,这个数就称为"完数". 3 例如 6=1+2+3.编程找出 1000 以内的所有完数. 4 */ 5 ...

  8. (数据科学学习手札115)Python+Dash快速web应用开发——交互表格篇(上)

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...

  9. 仅仅使用Google就完成了人生第一次破解

    2021年2月6日21:17:09 begin 起因 在异乡的打工人,不善言谈,幸有一老同学,周末常邀吃饭,感恩之心铭记于心.她结婚时,为表心意欲做视频,视频需要制作字幕,搜索之,偶遇一字幕软件,但是 ...

  10. [2020年10月28日普级组]1408.MSWORLD

    1408. M S W O R L D 1408.MSWORLD 1408.MSWORLD 题目描述 Bessie , Farmer John 的优选牛,刚刚获得了一个牛科动物选美比赛的冠军!并得到了 ...