首页
社区
课程
招聘
未解决 [原创] SEH原理探究
发表于: 2021-1-23 21:47 6382

未解决 [原创] SEH原理探究

2021-1-23 21:47
6382


SEH(结构体异常处理)是一个老生常谈的问题,今天就让小弟来谈谈自己的感想。可能个人的风格偏向于喜欢研究它的设计理念,所以就直接忽略了逆向的过程(我已经逆了N遍...)。

接下来,我会从这个两个角度去说明。(本文主要以x86为主)

先放一个总结的图,以便后续叙述。

操作系统的SEH设计理念

 1.    介绍异常链表


        站在操作系统的角度,寻找异常处理的设计是以 fs:[0]指向链表头结点,链表是由 EXCEPTION_REGISTRATION结构体串联的一个链表。


        a.    EXCEPTION_REGISTRATION结构体(以函数为单位)  

        struct EXCEPTION_REGISTRATION
       {
           struct _EXCEPTION_REGISTRATION *prev;
          void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
        }

        b.    链表的效果图   

               

                    注:-1表示是链表的结尾了,其中的Handler是操作系统放置上去的。(这个函数不是SEH研究的范畴了暂时不讨论...


2.    寻找处理函数过程

        

         a.    寻找处理异常的结点

                当异常发生时,操作系统(严格上讲应该是CPU..)会顺着 FS:[0]指向的链表去依次遍历每一个结点,寻找可以处理这个异常的结点。


         b.    展开操作

                从fs:[0]指向的结点开始,依次调用handler中的扫尾操作,直到步骤1寻找到的这个结点处。

         

        可能会有人疑问,为什么要进行展开操作呢?


         其实很简单,如果我们直接调用找到的结点handler,那么就会破坏掉这个结点之前的handler堆栈。如下图:

                

        此时fun_two发生异常,堆栈是这样的形式,但是fun_two无法处理这个异常,只有fun_one可以处理,假设可以直接调用fun_one的handle,那么这个handler就有一定的几率覆盖fun_two的栈注册异常的空间,如果再有一个异常,那么直接GG!所以展开的操作,可以说成是清理空间-扫尾操作,让handler有一个安全的执行环境!      

       但是这么注册异常就有一个效率问题,比如我们写了N多个函数,而且还是各种嵌套。那么光找到这个结点就很耗费很多时间,而且再加上展开操作..这谁顶得住呀!!!

        此时编译器就想办法了,为了提供效率等等问题,编译器就对注册的异常结点进行了扩展。接下来才是重头戏!!!


编译器扩展的设计理念


        编译器看了看操作系统的设计德行,思考着自己如何进行扩展呢?然后就扩展了注册的异常结点。(其实结构体的设计是一个东西的基础也是最重要的部分,因为结构体代表的是数据之间的关系。代码只是处理规定了这些数据的处理过程。就像操作系统,把它比如为数据库不为过吧.....还有就是在逆向的时候如果什么数据关系都不知道....那就是瞎逆。  这里扯扯蛋....


        1. 扩展的结构体

   typedef struct _EXCEPTION_REGISTRATION PEXCEPTION_REGISTRATION;//扩展的异常结点结构
   struct _EXCEPTION_REGISTRATION{
     DOWRD old_esp;
     PEXCEPTION_POINTERS xpointers;
     struct _EXCEPTION_REGISTRATION *prev;//指向上一个结点位置
     void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
     struct scopetable_entry *scopetable;
     int trylevel;
     int _ebp;
   };
       
     scopetable_entry    这个结构内部又形成一个异常链表    
      +0x000 previousTryLevel : Uint4B    //上一个结点的位置    
      +0x004 lpfnFilter    : Ptr32 int    //过滤函数    
      +0x008 lpfnHandler    : Ptr32 int    //处理函数  
                 
 结构体中的前两个成员是__except_handler3函数中动态生成的。
 old_esp:代表是执行执行lpfnHandler时的esp环境。
 xpointers:是为了满足编译器的设计的两个函数 Getxxx--获取 EXCEPTION_RECORD  Getxxx --获取 CONTEXT
 prev:指向前一个EXCEPTION_REGISTRATION
 handler:处理函数
 scopetable:编译器自己定义的异常链表(基于数组)
     previousTryLevel:指向上一个结点
     lpfnFilter:相当于原来操作系统的handler
     lpfnHandler:真正处理的函数
 trylevel:编译器用来寻找当前异常是由哪个scopetable_entry处理
 ebp:用于获取异常发生时堆栈的ebp位置,进而获取堆栈中的参数等等。

    此时 fs:[0]链表变成了如下图所示:

            

      2.    异常处理过程

     

            当程序出现异常的时候,根据第一个图,最终会来到Registertion_Exception.handler处,但是此时这个是一个统一的函数_except_handler3,这个函数就像是进入编译器领地的大门。

            1.    _except_handler3流程讲解

                    

             a.    这个函数首先会根据 trylevel指定的 scopetable_entry开始寻找哪个entry处理这个异常。(是不是有点似曾相识....)

             再解释一下 trylevel(附一个逆向分析的图):

            

            b.    找到可以异常的scopetable_entry,开始调用 __global_unwind2,将fs:[0]指向这个上图这个胖胖的结点!(这是操作系统设计的)

            c.     调用编译器自己设计的__local_unwind2的进行展开,调用_finally中释放资源了、析构了等等!

            d.    最终调用找到了lpfnHandler来对异常进行处理。


注:在研究一个东西的时候层次很重要,就像是处理一个事情的时候角度问题很重要。做产品更应该如此!!!


本文只是讲解了大致思想,构建了这个全局观后,再去看什么内嵌异常了,展开过程中又异常了等等,很容易理解。


                                                                                                                                                                

                                                                                                    



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-6-27 08:03 被烟花易冷丶编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (7)
雪    币: 1230
活跃值: (1705)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
有问题,或者有错误希望大佬能指出来一起探究... 我也是业余研究一下,反复斟酌了几遍才发了出来!
2021-1-23 21:49
0
雪    币: 137
活跃值: (1290)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
如果可以说说try catch在c里是怎么实现就好了
2021-1-24 11:58
0
雪    币: 1230
活跃值: (1705)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
huojier 如果可以说说try catch在c里是怎么实现就好了
好的,后期整理一下发出来。
2021-1-24 12:26
0
雪    币: 137
活跃值: (1290)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
烟花易冷丶 好的,后期整理一下发出来。
 期待
2021-1-24 14:47
0
雪    币: 239
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
感谢大佬分享,能附加代码实例讲解下对我这种菜鸟就更加友好了,哈哈。。。
2021-11-9 12:21
0
雪    币: 5159
活跃值: (3804)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
huojier 如果可以说说try catch在c里是怎么实现就好了

这是我若干年前参考vc的crt写的异常处理实现,配合我自己的crt库在vc下工作良好。C++异常处理性能比msvcrt要高很多(与gcc的sjlj异常相当)。但配合clang不能工作。

不过具体细节我自己也记不清楚了,未经严格测试,仅供你参考。

此代码仅针对x86目标,未实现x64版本,因为当时还没有x64这玩意。

上传的附件:
2021-11-9 13:40
0
雪    币: 137
活跃值: (1290)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
无心红叶 这是我若干年前参考vc的crt写的异常处理实现,配合我自己的crt库在vc下工作良好。C++异常处理性能比msvcrt要高很多(与gcc的sjlj异常相当)。但配合clang不能工作。不过具体细节我自 ...

感谢奉献, x32的以前一开始接触的时候挺懵逼的,这对新人挺有帮助的

最后于 2021-11-10 20:18 被huojier编辑 ,原因:
2021-11-10 20:17
0
游客
登录 | 注册 方可回帖
返回
//