zoukankan      html  css  js  c++  java
  • 异常(2) --- 编译器对于SEH异常的拓展

    Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

    异常(2) --- 编译器对于SEH异常的拓展

    异常(1) 中,我们介绍了用户模拟异常与CPU异常的收集,以及内核层与用户层异常的处理,其中介绍过SEH异常。

    我们之前只提到过编译器拓展SEH异常的,但是由于篇幅有限,并没有详细介绍过其是如何拓展的,现在,我们就来介绍一下,其编译器如何拓展SEH异常的。

     

    1._try{} _excpet(){} 异常处理结构
    2. _try{} _excpet(){} 反汇编分析 - 其如何将自己挂在链表上的
    3._EXCEPTION_REGISTRATION 结构中的实现细节
    4.局部展开 -  _try{}_finally{}反汇编分析
    5.全局展开 -  try{}嵌套时找到最近一层excpt_handler

     

    1._try{} _excpet(){} 异常处理结构

      有过C++编程经验的人来说,这个肯定不陌生,我们看如下代码:

      

      1)异常过滤表达式

        异常过滤表达式存在三种:常量,等式,过滤函数。

        1> 常量

          如下,Windows提供三种定义,当然这只是最简单的,无法处理比较复杂的异常。

          // Defined values for the exception filter expression
          EXCEPTION_EXECUTE_HANDLER      1    // 走当前的异常处理程序
          EXCEPTION_CONTINUE_SEARCH      0 // 搜索下一个异常处理程序
          EXCEPTION_CONTINUE_EXECUTION (-1) // 到代码出错的位置继续执行

        2> 等式

          调用GetExceptionCode()函数来获取异常码,之后来判断异常码。

            GetExceptionCode()==0xc0000095

          其实其本质也是常量运算,当其表达式匹配时结果为1(EXCEPTION_EXECUTE_HANDLER ),当不匹配时0(EXCEPTION_CONTINUE_SEARCH ),其会寻找下一个SEH异常。

        3> 调用异常处理函数

          这里面也可以写函数,_except_handler,来自行对异常内容进行操作。

          对于该函数,只要返回上面的三种值即可。

          

      2)异常的各种宏介绍

        在excpt.h中有很多宏,上面已经用到过两个 GetExceptionInformation() GetExceptionCode(),其定义如下:

          #define GetExceptionCode        _exception_code
          #define exception_code          _exception_code
          #define GetExceptionInformation (struct _EXCEPTION_POINTERS*)_exception_info
          #define exception_info          (struct _EXCEPTION_POINTERS*)_exception_info

        我们在VEH那一节中,添加的VEH异常例程就是  _EXCEPTION_POINTERS*,很好理解。

     

    2. _try{} _excpet(){} 反汇编分析 - 其如何将自己挂在链表上的

      我们看其反汇编,无论其几层_try{}嵌套,其最终只挂入一次链表,其是如何实现的呢?

      答案是:在挂入链表的结构上进行了拓展,之前的结构如下:

      struct _EXCEPTION_REGISTRATION_RECORD {

        struct _EXCEPTION_REGISTRATION_RECORD* Next; //0x0

        enum _EXCEPTION_DISPOSITION (*Handler)(struct _EXCEPTION_RECORD* arg1, VOID* arg2, struct _CONTEXT* arg3, VOID* arg4);//0x4

      };

       而现在的结构如下

      struct _EXCEPTION_REGISTRATION{

        struct _EXCEPTION_REGISTRATION_RECORD* Next; //0x0

        enum _EXCEPTION_DISPOSITION (*Handler)(struct _EXCEPTION_RECORD* arg1, VOID* arg2, struct _CONTEXT* arg3, VOID* arg4);   //0x4

        struct scopetable_entry * scopetable

        int trylevel;

        ine _ebp;

      };

      我们根据这个结构来查看反汇编代码:

      可以发现,其编译器先处理SEH异常结构,再来提升堆栈。

      另外,值得注意的是:Release版本会进行大量优化,但当出现_try{}_except(){},其不会对其进行优化,因为要保证堆栈结构。

      

       要明白,其是拓展结构,并没有影响原来的结构,原来的结构在这里依然可以使用的,故其SEH拓展后的结构如下所示:

       

    3._EXCEPTION_REGISTRATION 结构中的实现细节

      我们之前介绍过,无论一个函数中有多少个Try,其只要一个_EXCEPTION_REGISTRATION结构体就好。

      但是,我们肯定很好奇,其是如何实现的。下面,我们就来分析一下其是如何来实现的。

      1)ScopeTable表结构

        其是一串结构体数组,理解它的含义是理解SEH拓展的关键,结构体如下:

          previousTryLevel - 上一个try的索引

          lpfnFilter - except过滤表达式位置

          lpfnHandler - except_handler执行函数

      2)我们现在分析一层复杂的ScopeTable结构:

        如下图,很明显,第一个previousTryLevel表示的是其存在上一层的嵌套,现在我们有一个问题,try0先执行还是try1先执行?

        当然是try1先执行,然后沿着previousTryLevel找到try0的except,明白了这个逻辑再来看这张图就很好理解。

        lpfnFilter指向其过滤表达式,lpfnHanler指向_except_handler,异常处理代码。

        

      3)trylevel的含义

        我们看其反汇编代码,当做的第一件事就是往trylevel中填写一个数字,我们在ScopeTable中看到其存在一个编号。

        因此,很容易推断出 trylevel记录当前try所在的编号。

        通过trylevel这个编号,进入表通过 (Scopetable+0x0c*trylevel) 计算,就很容易找到各个元素。

        

       4)_except_handler3函数分析

        对于拓展的SEH异常,其固定添加一个handler函数,而不是像我们之前可以自定义的,因为其要负责处理大量的数据结构。

        其在xp操作系统下固定填写一个ntdll!_except_handler3函数,该函数就是负责完成上面那些逻辑的。

        因为时间关系,暂时就不逆向该函数,但之后一定回头认认真真逆向一遍。

        

    4.局部展开 -  _try{}_finally{}反汇编分析

      我们还存在一种语句,_try{}_finally{}语句,这种语句存在一种特殊机制,即使你在try{}中执行return,finally代码也一定会执行。

      1)__try{}_finally反汇编分析:

        

      2)__local_unwind2函数分析

        

      3)_try{}_except函数的scopetable表

        我们查看该语句的地址表,其中lpexceptHandler的地址就是_try{}_finally{}的地址。

        因此我们就可以推断函数的执行过程,如果lpfilter值为0,则lpexcepthandler为finally函数。

        

      4)总结

        其编译器对其进行各种操作,将_finally语句打包成一个lpexcept_handler函数。

        为确保执行,当在_try{}中出现return语句,其会先调用一个局部展开函数,该函数会查表寻边遍历的_fianlly函数运行。

          既然把finally{}作为一个函数,故编译器在编译它时末尾写上return语句。

        在_except函数后面肯定为retn,其作为一个函数调用,肯定不会正常执行,其如下图:

        

    5.全局展开 -  try{}嵌套时找到最近一层excpt_handler

      前面我们提到过局部展开,当在try{}中遇到return时,其会调用局部展开,找到_finally函数执行,然后再返回。

      现在我们再考虑一种情况,当很多_try{}_finaly{}嵌套时,出现异常,其如何调用?

      如下图的实现机制-其本质就是调用全局展开。

       

      当出现异常错误时,有_except_handler3接管,我们在分析中存在一个局部展开,一个全局展开,其全局展开。

      

      全局展开机制有点复杂,现在先不分析,明确其作用就好。

  • 相关阅读:
    Java 基础
    Java 数据类型
    Spring 拦截器实现事物
    SSH 配置日记
    Hibernate 知识提高
    Jsp、Servlet
    leetcode 97. Interleaving String
    leetcode 750. Number Of Corner Rectangles
    leetcode 748. Shortest Completing Word
    leetcode 746. Min Cost Climbing Stairs
  • 原文地址:https://www.cnblogs.com/onetrainee/p/12622785.html
Copyright © 2011-2022 走看看