SEH in ASM 研究(一)

  SEH出现已绝非日,但很多人可能还不彻底了解Seh运行机制;有关seh知识资料不是很多,asm级详细资料就更少!seh不仅可以简化处理,使你更加健壮,还被广泛应用于反跟踪以及加解密中,因此,了解seh非常必要, 但遗憾是有关seh详细介绍中文资料非常少,在实战基础上,把自己学习点笔记奉献给大家,希望对喜欢ASM朋友有所帮助.如有,请高手不吝指正.

  、SEH背景知识

  SEH("Structured Exception Handling"),即结构化异常处理.是操作系统提供给设计者强有力处理或异常武器.在VISUAL C中你或许已经熟悉了_try{} _finally{} 和_try{} _except {} 结构,这些并不是编译本身所固有,本质上只不过是对windows内在提供结构化异常处理包装,不用这些高级语言编译器所提供包装 ,照样可以利用系统提供强大seh处理功能,在后面你将可以看到,用系统本身提供seh结构和规则以及ASM语言, 我们将对SEH机制以及实现有个彻底了解.

  发生异常时系统处理顺序(by Jeremy Gordon):

  1.系统首先判断异常是否应发送给目标异常处理例程,如果决定应该发送,并且目标正在被调试,则系统挂起并向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,这不是正好可以用来探测调试器存在吗?

  2.如果你没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关异常处理例程,如果你安装了线程相关异常处理例程,系统就把异常发送给你seh处理例程,交由其处理.

  3.每个线程相关异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关异常处理例程, 可交由链起来其他例程处理.

  4.如果这些例程均选择不处理异常,如果处于被调试状态,操作系统仍会再次挂起通知debugger.

  5.如果未处于被调试状态或者debugger没有能够处理,并且你SetUnhandledExceptionFilter安装了最后异常处理例程话,系统转向对它.

  6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会默认系统处理,通常显示个对话框,你可以选择关闭或者最后将其附加到调试器上调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统就ExitProcess终结.

  7.不过在终结的前,系统仍然对发生异常线程异常处理句柄来次展开,这是线程异常处理例程最后清理机会. 如果你看了上面步骤头雾水话,别着急,化点时间慢慢理解或者进入下部分例子操作.

   2.初步实战演习:

  安装异常处理句柄.

  有两种类型异常处理句柄,种是final型,这是在你异常未能得到线程相关处理例程处理操作系统在即将关闭的前会 回调例程,这个例程是进程相关而不是线程相关,因此无论是哪个线程发生异常未能被处理,都会这个例程.

  I. 见下面例子1:

;//例子1---final型异常处理=
;// ex. 1,by Hume,2001,just copy make your own hd.h and compile&link
;//
.386
.model flat, stdcall
option map :none ; sensitive
hd.h     ;//相关头文件你自己维护个吧
;//
.data
szCap  db "By Hume[AfO],2001...",0
szMsgOK db "OK,the exceptoin was handled by final handler!",0
szMsgERR1 db "It would never Get here!",0
buff  db 200 dup(0)
.code
_start:
;//prog begin
  lea  eax,Final_Handler
  invoke  SetUnhandledExceptionFilter,eax ;//SetUnhandledExceptionFilter来安装final SEH
                         ;//原型很简单SetUnhandledExceptionFilter proto
                         ;//pTopLevelExceptionFilter:DWORD
    xor  ecx,ecx
    mov  eax,200  
    cdq
  div  ecx
                         ;//以下永远不会被执行
    invoke  MessageBox,NULL,addr szMsgERR1,addr szCap,MB_OK+MB_ICONEXCLAMATION
    invoke  ExitProcess,NULL
   
;//
Final_Handler:
  invoke  MessageBox,NULL,addr szMsgOK,addr szCap,MB_OK+MB_ICONEXCLAMATION
  mov  eax,EXCEPTION_EXECUTE_HANDLER    ;//1 这时不出现非法操作讨厌对话框
  ;mov  eax,EXCEPTION_CONTINUE_SEARCH  ;//0 出现,这时是系统默认异常处理过程,被终结了
  ;mov  eax,EXCEPTION_CONTINUE_EXECUTION ;//-1 不断出现对话框,你将陷入死循环,可别怪我
  ret                   ;我们并没有修复ecx,所以不断产生异常,然后不断这个例程
;//=Prog Ends
end _start
COMMENT $
 简单来解释几句,windows根据你异常处理返回值来决定如何进步处理
    EXCEPTION_EXECUTE_HANDLER      equ 1  表示我已经处理了异常,可以优雅地结束了
    EXCEPTION_CONTINUE_SEARCH      equ 0  表示我不处理,其他人来吧,于是windows默认处理
                           显示框,并结束
    EXCEPTION_CONTINUE_EXECUTION    equ -1 表示已经被修复,请从异常发生处继续执行你可以试着让返回0和-1然后编译,就会理解我所有苍白无力语言...
$
;//
  II.另种是per_Thread Exception Handler->线程相关异常处理,通常每个线程化准备好运行时fs指向个TIB结构

  (THREAD INFORMATION BLOCK),这个结构个元素fs:[0]指向个_EXCEPTION_REGISTRATION结构

  后面_EXCEPTION_REGISTRATION为了简化,用ERR来代替这个结构...不要说没见过啊...

  fs:[0]->
  _EXCEPTION_REGISTRATION struc
  prev dd ?           ;前个_EXCEPTION_REGISTRATION结构
  handler dd ?         ;异常处理例程入口....呵呵,现在明白该如何作了吧
  _EXCEPTION_REGISTRATION ends
  我们可以建立个ERR结构然后将fs:[0]换成指向他指针,当然最常用是堆栈,如果你用静态内存区也可以,没有人阻止你在asm世界,放心地干吧,除了多S几次的外,通常不会有更大损失把handler域换成你入口,就可以在发生异常时代码了,好马上实战下,见例子2

;//
;// ex. 2,by Hume,2001 线程相关异常处理
;//
.386
.model flat, stdcall
option map :none ; sensitive
hd.h     ;//相关头文件你自己维护个吧
;//
.data
szCap  db "By Hume[AfO],2001...",0
szMsgOK db "It's now in the Per_Thread handler!",0
szMsgERR1 db "It would never Get here!",0
buff  db 200 dup(0)
.code
_start:
;//prog begin
 ASSUME FS:NOTHING
    push  off perThread_Handler
  push  fs:[0]  
    mov  fs:[0],esp           ;//建立SEH基本ERR结构,如果不明白,就仔细研究下吧
    xor  ecx,ecx            
    mov  eax,200  
    cdq
  div  ecx
                         ;//以下永远不会被执行
    invoke  MessageBox,NULL,addr szMsgERR1,addr szCap,MB_OK+MB_ICONINFORMATION
    pop  fs:[0]
    add  esp,4
    invoke  ExitProcess,NULL   
;//
perThread_Handler:
    invoke  MessageBox,NULL,addr szMsgOK,addr szCap,MB_OK+MB_ICONINFORMATION
    mov  eax,1     ;//ExceptionContinueSearch,不处理,由其他例程或系统处理
    ;mov  eax,0     ;//ExceptionContinueExecution,表示已经修复CONTEXT,可从异常发生处继续执行
  ret            ;//这里如果返回0,你会陷入死循环,不断跳出对话框....
;//=Prog Ends
end _start
COMMENT $
 嘿嘿,这个简单吧,我们由于没有足够资料,暂时还不能修复ecx值使的从异常发生处继续执行,只是简单显示个MSG,然后让系统处理,自然跳出讨厌对话框了....
 注意和final返回值含义区别...
$
;//
  好像到此为止,我们并没有从异常处理中得到任何好处,除了在异常发生后可以执行点我们微不足道代码,事实上SEH可以修复这些异常或者干我们想干事情然后从希望地方继续执行,嘿嘿,很爽吧,可惜我们没有足够信息,那里找到我们所需要信息? 欲知后事如何,且看下回分解...

   3、传递给异常处理句柄参数

  要想明白seh处理例程如何得到感兴趣信息,首先要明白SEH作用机制.事实上,当异常发生时,系统给了我们个处理异常机会,他首先会我们自定义seh处理例程,当然也包括了相关信息,在的前,系统把包含这些信息结构指针压入stack,供我们异常处理例程, 传递给例程参数通常是 4个,其中只有 3个有明确意义,另个到现在为止还没有发现有什么作用, 这些参数是:pExcept:DWORD,pErr:DWORD,pContext:DWORD,pDispatch意义如下:

  pExcept: --- EXCEPTION_RECORD结构指针

  pErr:  --- 前面ERR结构指针

  pContext: --- CONTEXT结构指针

  pDispatch:---没有发现有啥意义

  ERR结构是前面介绍_EXCEPTION_REGISTRATION结构,往前翻翻,Dispatch省略,下面介绍

  EXCEPTION_RECORD和CONTEXT结构定义:

;//以下是两个成员详细结构
    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里面有很多类型,你可以在windows.inc里查找STATUS_来找到更多异常类型,下面只给出hex值,具体标识定义请查阅windows.inc,你最可能遇到几种类型如下:

  C0000005h----读写内存冲突

  C0000094h----非法除0

  C00000FDh----堆栈溢出或者说越界

  80000001h----由Virtual Alloc建立起来属性页冲突

  C0000025h----不可持续异常,无法恢复执行,异常处理例程不应处理这个异常

  C0000026h----在异常处理过程中系统使用代码,如果系统从某个例程莫名奇妙返回,则出现此代码,

  如果RtlUnwind时没有Exception Record参数也同样会填入这个代码

  80000003h----调试时因代码中3中断

  80000004h----处于被单步调试状态

  注:也可以自己定义异常代码,遵循如下规则:

       _____________________________________________________________________+
       位:   31~30      29~28     27~16     15~0
       _____________________________________________________________________+
       含义:  严重程度     29位      功能代码    异常代码
            0成功    0Mcrosoft  MICROSOFT定义 用户定义
            1通知    1客户
            2警告     28位
            3    被保留必须为0
  ExceptionFlags 异常标志

  0----可修复异常

  1----不可修复异常

  2----正在展开,不要试图修复什么,需要话,释放必要资源

  pExceptionRecord 如果本身导致异常,指向那个异常结构

  ExceptionAddress 发生异常eip地址

  ExceptionInformation 附加消息,在RaiseException可指定或者在异常号为C0000005h即内存异常时含义如下

  第个dword 0读冲突 1写冲突

  第 2个dword 读写冲突地址

;//解释结束
                             off.
    CONTEXT STRUCT          ; _
     ContextFlags DWORD   ?   ; |      +0
     iDr0     DWORD   ?   ; |      +4
     iDr1     DWORD   ?   ; |      +8
     iDr2     DWORD   ?   ; >调试寄存器 +C
     iDr3     DWORD   ?   ; |      +10
     iDr6     DWORD   ?   ; |      +14
     iDr7     DWORD   ?   ; _|      +18
     FloatSave  FLOATING_SAVE_AREA <> ;浮点寄存器区 +1C~~~88h
     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
;//以上是两个成员详细结构
  I、传递给final句柄参数,只有两个可描述为EXCEPTION_POINTERS结构,定义如下:

      EXCEPTION_POINTERS STRUCT
       pExceptionRecord DWORD   ?      
       ContextRecord  DWORD   ?
      EXCEPTION_POINTERS ENDS
  在call xHandler的前,堆栈结构如下:

  esp  -> *EXCEPTION_RECORD

  esp+4 -> *CONTEXT record ;//具体结构见下面

  然后执行call _Final_Handler,这样在里要什么不轻而易举了吗?

  II、 传递给per_thread句柄参数,如下:

  在call xHandler的前,在堆栈中形成如下结构

      esp  -> *EXCEPTION_RECORD
      esp+4 -> *ERR          ;//注意这也就是fs:[0]指向
      esp  -> *CONTEXT record     ;//po to registers
      esp  -> *Param          ;//呵呵,没有啥意义
  然后执行 call _Per_Thread_Handler

  handler原型是这样

  invoke HANDLER,*EXCEPTION_RECORD,*_EXCEPTION_REGISTRATION,*CONTEXT record,*Param

  即编译代码如下:

    PUSH *Param          ;//通常不重要,没有什么意义
    push *CONTEXT record      ;//上面结构
    push *ERR           ;//the struc above
    push *EXCEPTION_RECORD     ;//see above
    CALL HANDLER
    ADD ESP,10h
  好现在你明白了应该如何访问具体有关系统信息细节了吧,下部分,让我们来看看如何应用...(待续)



Tags: 

延伸阅读

最新评论

发表评论