seh to fasm 研究

seh to fasm 研究

作者:小鱼

为啥有此文呢,可能你会说seh文章很多啊。那请你去各大搜索引擎搜索下相关fasm的seh文章以及官方搜索
下更是甚少,几乎没有。所以就有了此文,希望能帮到一些和我一样喜欢fasm的朋友们。


1.概念

 SEH的英文全称是"Structured Exception Handling", 即"结构化异常处理",是windows提供给我们异常
处理机制。
 在高级语言中的_try{} _finally{} 和 _try{} _except {} 也就是利用seh机制。只是它们是将本身的s
eh机制进行了包装,其实如果你想,我们汇编语言也可以完全通过宏来进行包装。但是我们既然是程序爱好者
,那么我们就应该对各种底层机制非常了解,所以今天就让我带领大家进入seh的天堂吧!!!!!

引用:

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

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

2.如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,

如果你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.

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

理例程,可交由链起来的其他例程处理.

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

5.如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了

最后异常处理例程的话,系统转向对它的调用.

6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个

对话框, 你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试

器也处理不了,系统就调用ExitProcess终结程序.

7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会.

如果你看了上面的步骤一头雾水的话,别着急,化点时间慢慢理解或者进入下一部分实例操作.
 
 


Windows下的异常处理有两种方式:

1.finally型

 finally类型是你的异常在未得到线程相关处理例程的处理,在操作系统即将结束程序前调用的处理例程,
所以它是与进程相关的而不是和线程相关的。因为无论哪个是哪个线程发生异常未能被处理都会调用这个例程。


实战演练:
 
 
format PE GUI 4.0
entry start

include 'win32ax.inc'

SIZE_OF_80387_REGISTERS1             equ  80
MAXIMUM_SUPPORTED_EXTENSION1         equ 512
ExceptionContinueExecution1          equ   0
EXCEPTION_MAXIMUM_PARAMETERS         equ  15

struct  EXCEPTION_RECORD
  ExceptionCode         dd      ?
  ExceptionFlags        dd      ?
  pExceptionRecord      dd      ?
  ExceptionAddress      dd      ?
  NumberParameters      dd      ?
  ExceptionInformation  dd EXCEPTION_MAXIMUM_PARAMETERS dup(?)
ends

  struct  FLOATING_SAVE_AREA1
  ControlWord   dd         ?
  StatusWord    dd         ?
  TagWord             dd           ?       ;  **
  ErrorOffset   dd         ?
  ErrorSelector   dd       ?       ;  **
  DataOffset    dd         ?
  DataSelector  dd         ?
  RegisterArea  db  SIZE_OF_80387_REGISTERS1 dup(?)
  Cr0NpxState   dd         ?
ends


  struct  CONTEXT1
  ContextFlags  dd         ?
  iDr0          dd         ?
  iDr1          dd         ?
  iDr2          dd         ?
  iDr3          dd         ?
  iDr6          dd         ?
  iDr7          dd         ?
  FloatSave     FLOATING_SAVE_AREA1 <>
  reg_Gs        dd         ?             ;  gs register 
  reg_Fs        dd         ?             ;  fs register 
  reg_Es        dd         ?             ;  es register 
  reg_Ds        dd         ?             ;  ds register
  reg_Edi       dd         ?
  reg_Esi       dd         ?
  reg_Ebx       dd         ?
  reg_Edx       dd         ?
  reg_Ecx       dd         ?
  reg_Eax       dd         ?
  reg_Ebp       dd         ?    ;  SEH
  reg_Eip       dd         ?    ;  SEH
  reg_Cs        dd         ?             ;  cs register 
  reg_Flag      dd         ?             ;  eflags register 
  reg_Esp       dd         ?             ;  esp register    ;  SEH
  reg_Ss        dd         ?             ;  ss register

  ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION1 dup(?)
ends


 .data
  
  szMsg  db '异常发生的位置: %08x, 异常代码: %08x, 标志: %08x', 0
  szText  db '嘿嘿俺安全了', 0
  szCap  db 'Finally 异常处理例子', 0
  lpOldExcep rd 1
  
 .code

 proc Finally_Handler lpExp:DWORD
  locals
   szBuffer db 200 dup (?)
  endl
  
  pushad
  mov eax,[lpExp]
 
  virtual at eax
   .lpExcept dd ?
   .lpContext dd ?
  end virtual
 
  mov esi, [.lpExcept]
  mov edi, [.lpContext]
  
  callw wsprintf, addr szBuffer, szMsg, [edi+CONTEXT1.reg_Eip],/
    [esi+EXCEPTION_RECORD.ExceptionCode], [esi+EXCEPTION_RECORD.ExceptionFlags]
  add esp, 14h
  mov [edi+CONTEXT1.reg_Eip], _safe
  callw MessageBox, NULL, addr szBuffer, szCap, MB_OK
  
  popad
  xor eax, eax  ;使eax = -1,因为windows 就是根据返回值来决定下一步如何处理的。
  dec eax    ; eax = -1 表示将恢复之前保存的环境快,然后继续执行
  ret     ; eax = 1  表示结束这个进程,不弹出对话框
 endp     ; eax = 0  表示按照默认的异常处理例程去处理,也就是弹出错误对话框,然后结束进程
  
start:

  callw SetUnhandledExceptionFilter, Finally_Handler
  mov [lpOldExcep], eax
  ; Exception code
  xor ecx, ecx
  mov eax, 200
  cdq
  div ecx
 _safe:
  callw MessageBox, NULL, szText, szCap, MB_OK
  callw SetUnhandledExceptionFilter, [lpOldExcep]
  callw ExitProcess, NULL
  
  
 .import
 
  library kernel32, 'kernel32.dll',/
    user32, 'user32.dll'
   
  include 'api/kernel32.inc'
  include 'api/user32.inc'
  
  

我们来分析下这段代码:

  这句程序首先通过SetUnhandledExceptionFilter函数来设置异常处理例程,这个函数只有一个参数
那就是异常处理例程的地址。 我们的异常处理函数也只有一个参数,这个参数是指向EXCEPTION_POINTERS结构的指针。

struct  EXCEPTION_POINTERS
 .lpExcept dd ?
 .lpContext dd ?
ends

 这个结构中的第一个成员是指向EXCEPTION_RECORD结构的指针。 第二个成员是指向CONTEXT结构的指针。
EXCEPTION_RECORD结构保存了异常产生的原因、产生的位置等情况。CONTEXT结构保存了异常产生时刻的运行环境。

 首先我们通过 mov eax,[lpExp] 获得我们这个回调函数的参数值。
 
 紧接着我们通过我们fasm的宏 virtual 来建立虚拟数据,这个虚拟数据仅仅包含在源代码中,并不包含在
编译的程序中。fasm也提供了assume,但是我觉得virtual更爽些,(*^__^*) 嘻嘻……

 virtual宏的格式:
 
 virtual at 偏移地址
  ;定义虚拟数据
 end virtual
 
 这样我们就可以直接引用这些虚拟数据,而这些虚拟数据的地址就是virtual at后面的地址 + 数据字节大小。因为
eax寄存器中保存的是我们的EXCEPTION_POINTERS结构的指针,所以此时我们就可以通过建立EXCEPTION_POINTERS结构虚拟
数据,然后通过这些数据标号名 我们就可以来引用了,这样给我们的源代码的可读性带来好处,否则我们必须通过raw asm。
很多搞病毒的朋友就喜欢raw asm,它们貌似就喜欢别人看不懂自己的代码,以此为乐,好了不扯了。

 然后我们通过
 
     mov esi, [.lpExcept]
     mov edi, [.lpContext]
   来获得指向 EXCEPTION_RECORD结构的指针,和CONTEXT结构的指针,接下来我们就可以通过+结构中偏移来进行变址寻址
 了。

 然后我们通过mov [edi+CONTEXT1.reg_Eip], _safe   ; [edi+结构偏移],这样通过变址寻址就可以访问结构中各个
成员。


    这里通过改写context结构的中的eip寄存器,这样我们等下返回-1,windows就会恢复我们之前的contexe结构,然后去
执行,由于这里我们改写了eip寄存器,所以我们就可以跳过发生异常错误的地址,从而跳到安全的地址中执行。


     返回 1 表示我已经处理了异常,可以优雅地结束了
     返回 equ 0 表示我不处理,其他人来吧,于是windows调用默认的处理
程序显示一个错误框,并结束
     返回 -1 表示错误已经被修复,请从异常发生处继续执行 。你可以试着让程序返回0和-1然后编译程序,就会理解我
所有苍白无力的语言...

;---------------------------------------------------------------------------------------------------------------

  SHE
 
2. Thread Exception Handler, 线程相关的异常处理,通常每个线程的fs段寄存器指向一个TIB结构。该结构如下:

struct NT_TIB
 ExceptionList dd ? ;seh 链入口
 StackBase  dd ?   ;堆栈基地址
 StackLimit  dd ? ;堆栈大小
 SubSystemTib dd ?
 FiberData  dd ?
 AribitraryUserPointer dd ?
 Self   dd ? ; 本nt_tib结构的线性地址
ends

由于fs:[0] 中就是[NT_TIB.ExceptionList], ExceptionList成员的值指向的是EXCEPTION_REGISTRATION。该结构如下:

struct EXCEPTION_REGISTRATION
 prev  dd ? ;前一个EXCEPTION_REGISTRATION结构的地址
 handler  dd ? ;异常处理回调函数的地址
ends

第一个成员是之前EXCEPTION_REGISTRATION 结构的地址,handler是我们异常处理例程的地址。。既然是这样的话,
我们来思考,我们只要自己建立一个EXCEPTION_REGISTRATION结构,然后修改TIB结构中的ExceptionList成员为我们
的结构,这样我们的就将关联了此线程的异常处理例程,只要有异常发生,windows就会首先调用线程相关的异常处理
例程,如果失败,或者是不存在线程相关的异常处理例程的话,再去调用进程相关的异常处理例程,也就是上面我们说
的finally型的。

 好,因为最常用的是用堆栈来建立,所以我就采用堆栈来建立吧,当然静态内存也是可以的。

例子:

format PE GUI 4.0
entry start

include 'win32ax.inc'


 .data
  
  szMsg db 'seh测试', 0
  szCap db '线程相关异常处理例程', 0
  
 
 .code

Thread_Handler:
  callw MessageBox, NULL, szMsg, szCap, MB_OK
  mov eax, 1 ; eax = 1表示由其他例程进行处理 , eax = 0表示已经修复异常,恢复context继续执行。
  ret
  
  
start:
  push Thread_Handler
  push dword [fs:0]
  mov dword [fs:0], esp
  ;Exception Code
  xor eax, eax
  xchg [eax], eax
  callw MessageBeep, MB_OK 
  callw ExitProcess, 0
 
 
 
 .import
 
  library kernel32, 'kernel32.dll',/
    user32, 'user32.dll'
    
  include 'api/kernel32.inc'
  include 'api/user32.inc'
 
 
 哈哈,这个够简单吧。由于我只是想让大家先了解下框架,等下我们再来深入的探究。由于我们没有
获得这个回调函数的参数的相信信息,所以我们不能修改相应的寄存器值,所以我们的这个程序运行仅仅是
弹出一个对话框,紧接由系统默认的异常处理例程处理,自然就有那个讨厌的对话框。

 注意:这个返回值和finally型可不一样。
 
 、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
 好像目前而来我们并没有得到很多的好处,除了在异常发生后,执行一点我们那微不足道的代码,事实上seh
 可以修复我们的代码并且可以让我们从我们想执行的地方开始执行。好了,我们继续来深入的探析seh机制。
 
 
 
 
 3.深入学习
 
 首先Thread Exception Handler回调函数的参数定义和finally型不同,其函数格式不是stdcall 的形式,
而是c的格式。也就是需要我们自己修正堆栈

 这个回调函数有四个参数,但是一般是前3个参数是必须的,最后一个参数没有什么用处。
 
 proc Thread_Handler lpExceptRecord:DWORD, lpSeh:DWORD, lpContext:DWORD, lpDispatcherContext:DWORD
 
 endp
 
 第一个参数lpExceptRecord的是EXCEPTION_RECORD结构, 其实我们在学习finally型的异常处理例程时候已经学习了。

EXCEPTION_RECORD这个结构中包含了我们异常的相关信息,例如异常产生的原因,异常的发生位置等信息。

 第二个参数lpSeh指向的注册回调函数时使用的EXCEPTION_REGISTRATION结构的地址。也就是在上面哪个例子中
我们在堆栈中构造的这个结构的地址。

 第三个参数lpContext指向的是我们的contexe结构,这里我就不多说了和我们上面的finally型介绍的是一样的。
 
 第四个参数没有什么用,所以这里我就不说了。
 
 
 由于这个调用例程是的c的约定形式,所以就不能用proc伪指令了。我们直接通过标号,然后通过esp做堆栈指针,
来访问参数。

 例子:
 
format PE GUI 4.0
entry start

include 'win32ax.inc'

SIZE_OF_80387_REGISTERS1            equ  80
MAXIMUM_SUPPORTED_EXTENSION1        equ  512
ExceptionContinueExecution1         equ   0

 

 struct  FLOATING_SAVE_AREA1
  ControlWord   dd         ?
  StatusWord    dd         ?
  TagWord             dd           ?       ;  **
  ErrorOffset   dd         ?
  ErrorSelector   dd       ?       ;  **
  DataOffset    dd         ?
  DataSelector  dd         ?
  RegisterArea  db  SIZE_OF_80387_REGISTERS1 dup(?)
  Cr0NpxState   dd         ?
ends


  struct  CONTEXT1
  ContextFlags  dd         ?
  iDr0          dd         ?
  iDr1          dd         ?
  iDr2          dd         ?
  iDr3          dd         ?
  iDr6          dd         ?
  iDr7          dd         ?
  FloatSave     FLOATING_SAVE_AREA1 <>
  reg_Gs        dd         ?             ;  gs register 
  reg_Fs        dd         ?             ;  fs register 
  reg_Es        dd         ?             ;  es register 
  reg_Ds        dd         ?             ;  ds register
  reg_Edi       dd         ?
  reg_Esi       dd         ?
  reg_Ebx       dd         ?
  reg_Edx       dd         ?
  reg_Ecx       dd         ?
  reg_Eax       dd         ?
  reg_Ebp       dd         ?    ;  SEH
  reg_Eip       dd         ?    ;  SEH
  reg_Cs        dd         ?             ;  cs register 
  reg_Flag      dd         ?             ;  eflags register 
  reg_Esp       dd         ?             ;  esp register    ;  SEH
  reg_Ss        dd         ?             ;  ss register

  ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION1 dup(?)
ends


struct SEH
        PrevLink        dd      ?       ;the address of the previous seh structure 
        CurrentHandler  dd      ?       ;the address of the exception handler 
        SafeOffset      dd      ?       ;The offset where it's safe to continue execution 
        PrevEsp         dd      ?       ;the old value in esp 
        PrevEbp         dd      ?       ;The old value in ebp 
ends

 

 .data
  szMsg db '除法的商是%d', 0
  szCap db '异常处理例程', 0
  szBuf db 200 dup (?)
  
 .code

Theard_Handler:
 
 virtual at esp+4
  .lpExceptRecord dd ?
  .lpSeh   dd ?
  .lpContext  dd ?
  .lpDisPatcherContext dd ?
 end virtual
  
  mov edi, [.lpContext]
  mov [edi+CONTEXT1.reg_Ecx], 20
  lea eax, [Execute]
  mov [edi+CONTEXT1.reg_Eip], Execute ;从偶们指定的地方开始执行
  sub eax, eax  ;返回0,表示context修复,恢复context执行。
  ret 16
  
  
 
start:
  push Theard_Handler
  push dword [fs:0]
  mov dword [fs:0], esp
  xor ecx, ecx
  mov eax, 200
  cdq
Execute:
  div ecx
  callw wsprintf, szBuf, szMsg, ecx
  add esp, 12
  callw MessageBox, NULL, szBuf, szCap, MB_OK
  callw ExitProcess, NULL
  
 
 
 .import
 
  library kernel32, 'kernel32.dll',/
    user32, 'user32.dll'
    
  include 'api/kernel32.inc'
  include 'api/user32.inc'
 
这个例子中我们通过在异常处理例程中恢复ecx寄存器的值和eip的值,使程序继续执行,并显示除法的结果。

好,到这里这篇文章已经基本介绍完了,当然本文不可能把seh的文章能全部的介绍完,只能带领大家进入seh的领域,把文章中的代码和分析多多读下,你会发现的更多。。


参考文献: 冷雨飘心的SEH in ASM研究
  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章