当前位置: 首页 > news >正文

SEH异常之编译器原理探究

_try_except原理

调用_except_handle3这个异常处理函数,这里并不是每个编译器的异常处理函数都是相同的,然后存入结构体,将esp的值赋给fs:[0],再就是提升堆栈的操作

每个使用 _try _except的函数,不管其内部嵌套或反复使用多少_try _except,都只注册一遍,即只将一个 _EXCEPTION_REGISTRATION_RECORD 挂入当前线程的异常链表中(对于递归函数,每一次调用都会创建一个 _EXCEPTION_REGISTRATION_RECORD,并挂入线程的异常链表中)。

typedef struct _EXCEPTION_REGISTRATION_RECORD {

    struct _EXCEPTION_REGISTRATION_RECORD *Next;

    PEXCEPTION_ROUTINE Handler;

  } EXCEPTION_REGISTRATION_RECORD;

可以看到只有一个异常处理函数

那么这里编译器是如何做到只用一个异常处理函数的呢?编译器把原来_EXCEPTION_REGISTRATION_RECORD结构进行了拓展,添加了三个成员

struct _EXCEPTION_REGISTRATION{
        struct _EXCEPTION_REGISTRATION *prev;
        void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
        struct scopetable_entry *scopetable;
        int trylevel;
        int _ebp;
    };       

新堆栈结构如下

scopetable

struct scopetable_entry
{
       DWORD previousTryLevel  //上一个try{}结构编号 
       PDWRD        lpfnFilter         //过滤函数的起始地址
       PDWRD        lpfnHandler    //异常处理程序的地址     
}

查看地址可以发现有三个结构体

存储着的正式异常函数的开始地址和结束地址

第一个值previousTryLevel是上一个try结构的编号,这里如果在最外层就是-1,如果在第二层就是0,如果在第三层就是1,以此类推

trylevel

该成员表示代码运行到了哪个try结构里面,进入一个try则加1,try结构执行完成之后则减1

_except_handler3

1.CPU检测到异常 -> 查中断表执行处理函数 -> CommonDispatchException -> KiDispatchException -> KiUserExceptionDispatcher                 -> RtlDispatchException ->VEH -> SEH

2.执行_except_handler3函数

<1> 根据trylevel 选择scopetable数组

<2> 调用scopetable数组中对应的lpfnFilter函数

1.EXCEPTION_EXECUTE_HANDLER(1) 执行except代码

2.EXCEPTION_CONTINUE_SEARCH(0) 寻找下一个

3.EXCEPTION_CONTINUE_EXECUTION(-1) 重新执行

<3> 如果lpfnFilter函数返回0 向上遍历 直到previousTryLevel=-1

假设有两个异常点

首先找到trylevel为0

然后找到异常过滤表达式为1

然后遍历数组的lpfnFilter

如果返回值为1则调用异常处理函数,如果为0则该异常函数不处理,如果为-1则继续从原异常点向下执行

假设在B这个地方出异常,得到trylevel为2

那么这里就回去遍历lpfnFilter为2的地方

假设这里返回值为0,则继续查找,注意这个地方是向上查找,首先判断当前previousTryLevel的值是否为-1,如果为-1就停止查找(-1代表已经是最外层)try结构,然后再向上找,假设这里返回值仍然为0,判断previousTryLevel的值为-1,就停止查找,没有找到响应的异常处理函数

_try_finally原理

无论try结构体中是什么代码,都会执行finally里面的代码

// SEH6.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

VOID ExceptionTest()
{
 __try
 {
  return;

  printf("Other code");
 }
 __finally
 {
  printf("Must run this code");
 }
}

int main(int argc, char* argv[])
{
 ExceptionTest();
 getchar();
 return 0;
}

局部展开

try里面没有异常,而是returncontinuebreak等语句时,就不会走_except_handle3这个函数,而是调用_local_unwind2进行展开

然后调用[ebx + esi*4 + 8]

跟进去就到了finally语句块的地方

我们探究一下实现的原理,这里本来应该是lpfnFilter参数,指向异常处理过滤的代码的地址,但是这里是0。只要这个地方的地址为0就是finally语句块

__global_unwind2函数最终会调用一个RtlUnwind函数,该函数内容比较杂乱,其大体流程如下

全局展开

// SEH6.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

VOID ExceptionTest()
{
 __try
 {
  __try
  {
   __try
   {
    *(int*)0 = 1;
   }
   __finally
   {
    printf("Must run this code : A");
   }
  }
  __finally
  {
   printf("Must run this code : B");
  }
 }
 __except(1)
 {
  printf("Here is Exception_functions");
 }
}

int main(int argc, char* argv[])
{
 ExceptionTest();
 getchar();
 return 0;
}

全局展开就是一层一层的向上找异常处理函数,finally模块还是照常执行

未处理异常

入口程序的最后一道防线

这里调用mainCRTStartup(),然后调用入口程序

相当于这里才是一个进程开始执行的地方

这里有一个call调用,跟进去看看

发现有修改fs:[0]的操作,这里就相当于编译器为我们注册了一个异常处理函数

这里到kernel32.dll里面的BaseProcessStart里面看一下,这里有一个注册SEH异常处理函数的操作

线程启动的最后一道防线

// SEH7.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
 int i = 1;
 return 0;
}

int main(int argc, char* argv[])
{
 CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

 getchar();

 return 0;

可以发现线程也是从kernel32.dll开始的

然后跟进调用

可以发现还是注册了一个异常处理函数

还是去IDA里面看BaseThreadStart函数,发现也注册了一个SEH异常的函数

UnhandledExceptionFilter

相当于编译器为我们生成了一段伪代码

__try
{

}
__except(UnhandledExceptionFilter(GetExceptionInformation())
{
 //终止线程
 //终止进程
}

只有程序被调试时,才会存在未处理异常

UnhandledExceptionFilter的执行流程:

1) 通过NtQueryInformationProcess查询当前进程是否正在被调试,如果是,返回EXCEPTION_CONTINUE_SEARCH,此时会进入第二轮分发 

2) 如果没有被调试: 

查询是否通过SetUnhandledExceptionFilter注册处理函数 如果有就调用 

如果没有通过SetUnhandledExceptionFilter注册处理函数 弹出窗口 让用户选择终止程序还是启动即时调试器 

如果用户没有启用即时调试器,那么该函数返回EXCEPTION_EXECUTE_HANDLER

SetUnhandledExceptionFilter

如果没有通过SetUnhandledExceptionFilter注册异常处理函数,则程序崩溃

测试代码如下,我自己构造一个异常处理函数callback并用SetUnhandledExceptionFilter注册,构造一个除0异常,当没有被调试的时候就会调用callback处理异常,然后继续正常运行,如果被调试则不会修复异常,因为这是最后一道防线,就会直接退出,起到反调试的效果

// SEH7.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

long _stdcall callback(_EXCEPTION_POINTERS* excp)
{
 excp->ContextRecord->Ecx = 1;
 return EXCEPTION_CONTINUE_EXECUTION;
}

int main(int argc, char* argv[])
{
 SetUnhandledExceptionFilter(callback);

 _asm
 {
  xor edx,edx
  xor ecx,ecx
  mov eax,0x10
  idiv ecx
 }

 printf("Run again!");
 getchar();
 return 0;
}

直接启动可以正常运行

使用od打开则直接退出

KiUserExceptionDispatcher

只有当前程序处于调试的时候才可能产生未处理异常

1) 调用RtlDispatchException  查找并执行异常处理函数

2) 如果RtlDispatchException返回真,调用ZwContinue再次进入0环,但线程再次返回3环时,会从修正后的位置开始执行。

3) 如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发
(参见KiUserExceptionDispatcher代码)

相关文章:

  • UniApp调用SDK原生接口
  • 【数字信号调制】基于PCM编码和QAM调制系统附matlab代码
  • 数字科技对零售业的改造,链动2+1模式系统如何颠覆传统?
  • 集成随机惯性权重和差分变异操作的樽海鞘群算法-附代码
  • A Lightweight and Accurate Recognition Framework for Signs of X-ray Weld Images
  • 【小程序】IDEA实现qq邮件的发送
  • Selenium入门之java爬虫入门
  • 基于C#的五子棋游戏设计
  • 记一次线上环境排查错误过程
  • 快速文本分类(FastText)
  • 【填坑】ESP32 bootloader初探(下)
  • Addflow for WPF 2019 Crack
  • 压缩网络相关
  • C++中的经验记录
  • 【题解】同济线代习题一.8.4
  • cookie和session
  • Docker容器管理
  • interface和setter,getter
  • Lsb图片隐写
  • Spark in action on Kubernetes - Playground搭建与架构浅析
  • 机器学习学习笔记一
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 前端性能优化——回流与重绘
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 学习JavaScript数据结构与算法 — 树
  • 终端用户监控:真实用户监控还是模拟监控?
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • 整理一些计算机基础知识!
  • ​flutter 代码混淆
  • ​queue --- 一个同步的队列类​
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • "无招胜有招"nbsp;史上最全的互…
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (二十四)Flask之flask-session组件
  • (附源码)spring boot基于小程序酒店疫情系统 毕业设计 091931
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (算法)Travel Information Center
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (原創) 未来三学期想要修的课 (日記)
  • (转)Groupon前传:从10个月的失败作品修改,1个月找到成功
  • .NET Compact Framework 3.5 支持 WCF 的子集
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .net企业级架构实战之7——Spring.net整合Asp.net mvc
  • .NET值类型变量“活”在哪?
  • .so文件(linux系统)
  • // an array of int
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce
  • [ANT] 项目中应用ANT