异常(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接管,我们在分析中存在一个局部展开,一个全局展开,其全局展开。

  

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

posted @ 2020-04-03 09:53  OneTrainee  阅读(340)  评论(0编辑  收藏  举报