SEH IN ASM 研究(2)

来源:互联网 发布:二叉树深度遍历java 编辑:程序博客网 时间:2024/06/01 10:42
                             PART II   继续深入
三、传递给异常处理例程的参数
    I、传递给final型的参数,只有一个即指向EXCEPTION_POINTERS结构的指针, EXCEPTION_POINTERS定义如下:
       EXCEPTION_POINTERS STRUCT
        pExceptionRecord  DWORD      ?              
       ContextRecord     DWORD      ?
      EXCEPTION_POINTERS ENDS
    执行时堆栈结构如下:
                 esp    -> ptEXCEPTION_POINTERS
                然后执行call _Final_Handler
注意堆栈中的参数是指向EXCEPTION_POINTERS 的指针,而不是指向pExceptionRecord的指针
以下是EXCEPTION_POINTERS两个成员的详细结构
EXCEPTION_RECORD STRUCT
          ExceptionCode         DWORD      ?       ;异常码
          ExceptionFlags        DWORD      ?    ;异常标志
          PExceptionRecord     DWORD      ?      ;指向另外一个EXCEPTION_RECORD的指针
          ExceptionAddress      DWORD      ?       ;异常发生的地址
          NumberParameters      DWORD      ?       ;下面ExceptionInformation所含有的dword数目
          ExceptionInformation  DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)
        EXCEPTION_RECORD ENDS                      ;EXCEPTION_MAXIMUM_PARAMETERS ==15
-
;================具体参数解释========================================
ExceptionCode 异常类型,SDK里面有很多类型,但你最可能遇到的几种类型如下:
              C0000005h----读写内存冲突
              C0000094h----非法除0
              C00000FDh----堆栈溢出或者说越界
              80000001h----由Virtual Alloc建立起来的属性页冲突
              C0000025h----不可持续异常,程序无法恢复执行,异常处理例程不应处理这个异常
              C0000026h----在异常处理过程中系统使用的代码,如果系统从某个例程莫名奇妙的返回,则出现此代码,例如调用RtlUnwind时没有Exception Record参数时产生的异常填入的就是这个代码
              80000003h----调试时因代码中int3中断
              80000004h----处于被单步调试状态
              注:也可以自己定义异常代码,遵循如下规则:
              _____________________________________________________________________+
      位:       31~30            29~28           27~16          15~0
              _____________________________________________________________________+
    含义:     严重程度          29位            功能代码        异常代码
              0==成功         0==Mcrosoft     MICROSOFT定义   用户定义
              1==通知         1==客户
              2==警告          28位
              3==错误         被保留必须为0
ExceptionFlags 异常标志
              0----可修复异常
              1----不可修复异常
              2----正在展开,不要试图修复什么,需要的话,释放必要的资源
pExceptionRecord 如果程序本身导致异常,指向那个异常结构
ExceptionAddress 发生异常的eip地址
ExceptionInformation 附加消息,在调用RaiseException可指定或者在异常号为C0000005h即内存异常时(ExceptionCode=C0000005h) 的含义如下,其他情况下一般没有意义
              第一个dword 0==读冲突 1==写冲突
第二个dword 读写冲突地址
;==========CONTEXT具体结构含义================================
        CONTEXT STRUCT                     ; _                  
          ContextFlags  DWORD      ?       ;  |---------------  +00
          iDr0          DWORD      ?       ;  |                 +04
          iDr1          DWORD      ?       ;  |                 +08
          iDr2          DWORD      ?       ;   >调试寄存器       +0C
          iDr3          DWORD      ?       ;  |                 +10
          iDr6          DWORD      ?       ;  |                 +14
          iDr7          DWORD      ?       ; _|                 +18
          FloatSave     FLOATING_SAVE_AREA <>  ;浮点寄存器区      +1C~~+88
          regGs         DWORD      ?       ;--|                 +8C
          regFs         DWORD      ?       ;  |/段寄存器         +90        
          regEs         DWORD      ?       ;  |/                +94
          regDs         DWORD      ?       ;--|                 +98
          regEdi        DWORD      ?       ;____________        +9C
          regEsi        DWORD      ?       ;       |  通用      +A0  
          regEbx        DWORD      ?       ;       |   寄       +A4
          regEdx        DWORD      ?       ;       |   存       +A8
          regEcx        DWORD      ?       ;       |   器       +AC
          regEax        DWORD      ?       ;_______|___组_      +B0    
          regEbp        DWORD      ?       ;++++++++++++++++    +B4
          regEip        DWORD      ?       ;    |控制           +B8
          regCs         DWORD      ?       ;    |寄存           +BC
          regFlag       DWORD      ?       ;    |器组            +C0
          regEsp        DWORD      ?       ;    |               +C4  
          regSs         DWORD      ?       ;+++++++++++++++++   +C8
          ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
        CONTEXT ENDS
以上是两个成员的详细结构,下面给出一个final型的例子,这也是本文所讨论的最后一个final型的例子,以后的例子集中在thread类型上.
;--------------------------------------------
; Ex3,演示final处理句柄的参数获取,加深前面
; 参数传递的介绍理解如果难于理解请先看partIII
; 再回来看这个例子
;--------------------------------------------
;;代码部分删除,腾讯不给发
;;------------------------------------------------
II、 传递给per_thread型异常处理程序的参数,如下:
    在堆栈中形成如下结构
             esp    -> *EXCEPTION_RECORD
             esp+4  -> *ERR                     ;注意这也就是fs:[0]的指向
             esp    -> *CONTEXT record           ;point to registers
             esp    -> *Param                    ;呵呵,没有啥意义
-
           然后执行 call _Per_Thread_xHandler
操作系统调用handler的MASM原型是这样
        invoke xHANDLER,*EXCEPTION_RECORD,*_EXCEPTION_REGISTRATION,*CONTEXT,*Param
        即编译后代码如下:
        PUSH *Param                     ;通常不重要,没有什么意义
        push *CONTEXT record            ;上面的结构
        push *ERR                       ;the struc above
        push *EXCEPTION_RECORD          ;see above
        CALL HANDLER
        ADD ESP,10h
-
    下一部分给出thread类型的具体实例.
                                 PART III 不是终结
我们的目标是分三步走,学会SEH,现在让我们接触最有趣的部分:SEH的应用.seh设计的最初目的就是为了使应用程序运行得更健壮,因此SEH用于除错,避免应用程序和系统的崩溃是最常见的用途.例如:
1.比如你的程序里出现了除0错,那你就可以在你的seh处理程序中将除数改为非零值,per_Thread seh返回0(ExceptionContinueExecution)、final返回-1 (EXCEPTION_CONTINUE_EXECUTION),系统就会根据你的意图用改变过的context加载程序在异常处继续执行,由于被除数已经改变为非零值,你的程序就可以正常仿佛什么也没有发生的继续执行了.
2.seh还可以处理内存读写异常,如果你分配的堆栈空间不够,产生溢出,这时你就可以处理这个异常,再多分配一些空间,然后结果是你的程序照常运行了,就好像什么也没有发生过,这在提高内存运用效率方面很值得借鉴,虽然会降低一些程序的执行效率.另外,在很多加壳或反跟踪软件中,利用vitualAlloc和VitualProtect制造异常来进入异常程序,或仅仅是用,mov [0],XXX来进入异常程序,要比用int3或者int1或
pushf
and [esp],100h
popf
进入要隐蔽得多,如果可以随机引起这些异常的话,效果会更好...当然应用很多了,感兴趣自己去找.话题似乎有点远了,让我们回到最基础的地方.  
前面的例子中你可能已经注意到,假如我们改变了Context的内容,(注意啊,context包含了系统运行时各个重要的寄存器),并且返回0(ExceptionContinueExecution-->perThread SEH),或者-1(EXCEPTION_CONTINUE_EXECUTION,final SEH),就表示要系统已现有的context继续执行程序,当然我们的改变被重载了,就像周星驰的月光宝盒改变了历史一样奇妙,程序就会以改变的context内容去执行程序,通过这种手段,我们可以修复程序,使其继续执行.
看下面的例子4.
读之前,先再罗嗦几句,由于前面介绍了seh例程被调用的时候,系统把相关信息已经压入堆栈,所以我们只要在程序里寻址调用就行了,怎么寻址呢???唉....回顾一下call指令执行的基本知识,一般对于近调用,通过[esp+4]即刻找到
*EXCEPTION_RECORD,其余的不用说了吧,如果执行了push ebp;mov ebp,esp的话,就是[ebp+8]指向*EXCEPTION_RECORD,这也是大多数程序用的和我们最常见到的,明白了吗?不明白?我--去--跳--楼.
;________________________________________________________________________
;|EX.4 By hume,2001,to show the basic simple  seh function
;|________________________________________________________________________
.386
.model flat, stdcall
option casemap :none   ; case sensitive
include hd.h           ;//相关的头文件,你自己维护一个吧
.data
szCap   db "By Hume[AfO],2001...",0
szMsgOK db "It's now in the Per_Thread handler!",0
szMsg1 db "In normal,It would never Get here!",0
fmt     db "%s ",0dh,0ah,"  除法的商是:%d",0
buff    db 200 dup(0)
.code
_start:
  Assume FS:NOTHING
        push    offset perThread_Handler
        push    fs:[0]      
        mov     fs:[0],esp                       ;//建立SEH的基本ERR结构,如果不
;//明白,就仔细研究一下吧
        xor     ecx,ecx                          
        mov     eax,200  
        cdq
        div ecx
WouldBeOmit:                                   ;//正常情况以下永远不会被执行
        add     eax,100                         ;//这里不会执行,因为我们改变了eip的值    
ExecuteHere:
        div     ecx                             ;//从这里开始执行,从结果可以看到
        invoke  wsprintf,addr buff,addr fmt,addr szMsg1,eax        
        invoke  MessageBox,NULL,addr buff,addr szCap,40h+1000h
        pop     fs:[0]                          ;//修复后显示20,因为我们让ecx=10
        add     esp,4
        invoke  ExitProcess,NULL        
perThread_Handler proc /
uses ebx pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD        
        mov eax,pContext
  Assume eax:ptr CONTEXT
        mov     [eax].regEcx,20  ;//Ecx改变
        lea     ebx, ExecuteHere
        mov     [eax].regEip,ebx ;//从我们想要的地方开始执行,嘿嘿,这就是很多
;//反跟踪软件把你引向的黑暗之域
        mov     eax,0         ;//ExceptionContinueExecution,表示已经修复
;//CONTEXT,可从异常发生处
                              ;//reload并继续执行
    ret                    
perThread_Handler endp
end _start
;//====================================================
哈哈,从这个例子里我门可以真正看到seh结构化处理的威力,他不仅恢复了ecx的内容而且使程序按照你想要的顺序执行了,哈哈,如果你对反跟踪很感兴趣的话,你还可以在例程中加入
        xor     ebx,ebx
        mov     [eax].iDr0,ebx
        mov     [eax].iDr2,ebx
        mov     [eax].iDr3,ebx
        mov     [eax].iDr4,ebx
清除断点,跟踪者....嘿嘿,不说你也体验过,当然也可以通过检验drx的值来判断是否被跟踪,更复杂地,你可以设置dr6,和dr7产生一些有趣的结果,我就不罗嗦了.
上面的例子理解了吧,因为我用的是MASM提供的优势来简化程序,老Tasm Fans可能会不以为然,你可以试一下下面的代码代替,是TASM,MASM compatibale的
perThread_Handler:
        push    ebp
        mov     ebp,esp
        mov     eax,[ebp+10h]     ;取context的指针
        mov     [eax+0ach],20     ;将ecx=0,可以对照前面的例程和context结构
        lea     ebx, ExecuteHere
        mov     [eax+0b8h],ebx    ;eip== offset ExecuteHere,呵呵
        xor     eax,eax
        mov     esp,ebp
        pop     ebp
        ret
这是raw asm的,不过masm既然给我们设计了这么多好东西,我们为什么不好好利用呢?
好,到现在为止,基本知识已经结束了,我们应该可理解seh的相关文章和写简单的seh处理程序了,但关于seh还只是刚刚开始,很多内容和应用还没有涉及到,请继续看提高篇.
原创粉丝点击