影の疾风 发表于 2011-8-5 09:56:51

浅谈SEH结构化异常处理

想象一下,如果程序某个线程错误了,这可能会导致整个程序崩溃。假如线程出现错误的时候,系统通知我,让我看下是什么原因导致错误的,然后修补它,避免了这个错误,也避免了程序的崩溃,那该多好啊!不用假如了,系统的确提供了这样的功能,不过我们一直没去留意而已。

先来介绍一下SEH:
    结构化异常处理(Structured Exception Handling,SEH)是微软一种未公开技术,也是当前被人用得最多的未公开技术。(此结构化异常处理,非平时说的__try、__finally和__except等,他们是有区别)

    因为此帖并不是SEH教程,所以只会浅谈一下SEH的作用。

    发生错误时,系统是这样处理的:


            1、因为有多种异常,系统首先判断异常是否应发送给目标程序,如果应该发送,并且目标程序正处于被调试状态,则系统挂起程序,填写 _EXCEPTION_DEBUG_INFO结构,将成员dwFirstchance置为1,并且向调试器发送 EXCEPTION_DEBUG_EVENT消息。剩下的事情由调试器全权负责了,调试器可能处理这个异常,也可能无法处理。
   
            2、如果调试器未能处理异常,或程序根本没有被调试,系统就会查找是否存在与线程相关的异常处理过程,如果目标程序中存在与线程相关的异常处理过程,系统就调用程序的线程相关的SEH异常处理例程,交由它处理。
   
            3、与线程相关的异常处理过程可能有一个或多个,每个线程可以选择处理或者不处理异常,如果它不处理并且存在多个线程相关的异常处理过程,可交由链起来的其他异常处理过程进行处理,以此类推。
   
            4、如果程序线程的异常处理均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序,通知调试器,这次EXCEPTION_DEBUG_INFO结构的dwFirstchance成员置为0.
   
            5、如果程序未处于被调试状态或者调试器仍然未能处理,并且程序调用了API函数SetUnhandledExceptionFilter设置了与进程相关的异常处理过程的话,系统转向对它的调用。
   
            6、如果程序没有设置进程相关的异常处理过程或者进程相关的异常处理过程也未能处理这个异常,系统会调用默认的系统异常处理程序,通常显示一个对话框(相信你们都见过“内存不能为written/read”这类对话框),可以选择“关闭”或者最后将其附加到调试器上的“调试”按钮。如果没有调试器能被附加到它上面你或者调试器还是处理不了异常,系统就调用ExitProcess终结程序。
   
            7、不过在终结之前,系统慧慈调用发生异常的线程中所有的异常处理过程,也是线程异常处理过程获得的最后清理未释放资源的机会,其后程序就终结了。



先说一下fs段异常处理的结构

EXCEPTION_REGISTRATION struc
prev    dd    ?    //指向前一个结构异常处理指针
handler    dd    ?    //异常时代码处理的地址
ExCEPTION_REGISTRATION ends


说完原理,再来看liudahainnn的源码。

===============
.if eax == VK_A                  //按下A键,触发事件(源程序是加载DLL到进程,并且运行的,这里我省略了)
;invoke Attack,eax
;invoke CreateThread,0,0,offset MyThread,VK_SPACE,0,addr hThread    //使用CreateThread创建线程
;invoke CloseHand

-----------------------
MyThread    proc    _lParam                //创建线程后执行这个函数

      
    assume    fs:nothing
      pushad                  //保存环境
      push ebp
      push offset _SafePlace                //异常处理开始地址
      push    fs:                  //FS段压栈,保存指向上一异常处理结构的指针
      mov    fs:,esp                  //把ESP栈顶指针给过FS段开始,说明出现异常从ESP处读取异常处理结构
    ;********************************************************************
    ; 会引发异常的指令
;********************************************************************

                      ;在这里写实验call的代码
      MOV EBX ,0h
      CALL EBX                  //因为0到FFFFh是禁止读写的,这必然会引发一个错误
                add esp,4                  //暂时还没明白,为什么会有这句话,不过这句话也执行不到
      
      
   
            
      _SafePlace:                  //出现错误之后,跳到这里执行
      ;invoke    MessageBox,NULL,addr szSafe,addr szCaption,MB_OK    //下面都注释了的,所以不执行。
      ;assume    fs:nothing
      ;pop    fs:
      ;pop    eax
      ;jmp _OK
      ;invoke TerminateThread,hThread,0
;********************************************************************
; 恢复原来的 SEH 链
;********************************************************************
      pop    fs:                  //恢复之前指向的结构化异常指针
      add    esp,08h                  //之前PUSH了3个,pop了1个,所以要堆栈平衡
      popad
      
      ret

MyThread endp
===================================


有兴趣研究的同学,可以下载它的源码来看下,其实汇编也不是那么可怕,只要有心学,大家都会的……再给出一列我写的例子,我的就是内联汇编写,毕竟没试过纯汇编写。

    lea eax,dword ptr     //变相申请两个DWORD空间
    xchg dword ptr fs:,eax    //eax与fs段开头地址交换,大家记得fs:永远是指向当前异常处理结构指针就OK了
    push _handler      //之前虽然申请了空间,但ESP未作改变,不知道大家还记得不记得,这里填充
    push eax            //压入前异常处理结构指针,即原来的fs:,填充,这样fs:的开头指针就指向这个PUSH的内容
    mov eax,0            //开始出现异常了
    mov eax,      //出现异常,马上调用fs:处理异常
    jmp _exit            //这句执行不了了
_handler:    pop fs:            //结构异常开始处理
    add esp,4            //恢复堆栈平衡
    jmp XXX            //跳到你处理异常后执行的地址
_exit:    ret            //结束

当然,源代码远非这么简单,因为还有一个结构没讲,_Handlerproc,有兴趣的朋友自己翻阅一下资料,写此帖已经花了我很多心血了。

_Handlerproc里面有一个_lpContext成员,保存了出现异常时寄存器的状态,可以从这里读出EIP的值,恢复原路线执行。

这样,就可以做到即使出现错误,也不会导致程序被强制关闭的目的,实现CALL任意位置,游戏不会关闭。
页: [1]
查看完整版本: 浅谈SEH结构化异常处理