首页
论坛
课程
招聘
[原创]SEH 在程序中的运用
2009-11-18 20:08 6569

[原创]SEH 在程序中的运用

2009-11-18 20:08
6569
结构化异常处理
    当某一线程发生异常时,程序的控制权会立即进入Ring0异常处理程序,这是属于操作系统的部分, 如果发生的异常是如页异常之类的异常,Ring0处理程序可以处理完它后重新回到程序中执行,而被中断 过的进程可能根本就不知道发生过异常。
    但事情并不总是如此,有时进程会发生一些始料不及的异常,例如访问不存在的内存,被0除等, 这些异常Ring0处理程序不知该如何处理它,而进程本身也可能想自己处理这些情况,这是就要用到结构化异常处理(SEH)。在C/C++中也有异常处理的语句如_try,_catch等,这些语句的实现也与SEH紧密联系。
    当系统遇到一个它不知道如何处理的异常时,它就查找异常处理链表,注意每个线程都有它自己的异 常处理链表。异常链表以FS:[0]所指向的位置为链表头。
    异常处理开始时,系统把一些与当前线程和与异常有关的内容传给链头所指向的处理程序;处理程序 由用户编写或编译器生成,它的返回值可以是告诉系统:异常处理以完成,可以继续执行程序,或未处理 异常,可由链表的下一个处理程序处理等,可以一次传递下去。

  下面我以以前写的一个WinMD5程序来说明:

下面是未运用SEH链的情况下的部分关键代码:



GetMd5Thread PROC PFile:DWORD

LOCAL @FileText[MAX_PATH]:BYTE
LOCAL @TEMP[MAX_PATH]:BYTE
LOCAL @hFileRead,@hMapFile,@pMemory,@FileSize

invoke CreateFile, PFile,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
.if eax==0
jmp reterr
.endif



mov @hFileRead,eax
invoke ExtractFileName,PFile,addr @FileText ; 取短文件名

invoke CreateFileMapping,@hFileRead,NULL,PAGE_READONLY,0,0,NULL
.if eax==0
invoke CloseHandle,@hFileRead
jmp reterr
ret
.endif

mov @hMapFile,eax

invoke MapViewOfFile,@hMapFile,FILE_MAP_READ,0,0,0
.if eax==0
invoke CloseMapFile,@hMapFile,@hFileRead
jmp reterr
.endif
mov @pMemory,eax
invoke SendDlgItemMessage,hWinMain,IDC_PGB,PBM_SETPOS,1,0 ;进度开始(注意:这里并不是实时进度)
invoke GetFileSize,@hFileRead,0
mov @FileSize,eax
invoke _MD5,@pMemory,eax
invoke wsprintf ,addr @TEMP,addr szReceive, addr @FileText, @FileSize,eax
invoke SendDlgItemMessage,hWinMain,IDC_EDT_OUT,EM_REPLACESEL,0,addr @TEMP
invoke SendDlgItemMessage,hWinMain,IDC_PGB,PBM_SETPOS,100,0 ;进度结束
invoke RtlZeroMemory,addr FileName,MAX_PATH ; 清空数据
INVOKE SendDlgItemMessage,hWinMain,IDC_EDT_FILE,WM_SETTEXT,0, addr FileName
invoke UnmapViewOfFile,@pMemory
invoke CloseMapFile,@hMapFile,@hFileRead
ret

reterr:

invoke MessageBox,0,CTEXT("文件打开失败,请检查是否为有效文件!"),CTEXT("WinMd5 for ASM v1.1"),MB_ICONERROR OR MB_OK

ret

GetMd5Thread endp




上面红色的部分,是我们自己预防错误的代码,这个程序不算复杂,如果复杂点的程序,其代码肯定会显得很臃肿,并且可能会忽略些错误,给用户带来不好的使用感受,那么有没有一种机制可以统一处理这些错误了?当然有,这就是我们的SHE机制了
下面是上面的程序采用了SEH机制的全部源码:


;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Programmed by nohacks, nohacks@163.com
; Website: http://hi.baidu.com/nohacks
; Win32 ASM is Masm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 版本信息
; WinMD5 for ASM V1.1 - 可以拖放取得文件的MD5值
;
; 2007年11月21日
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


.386
.model flat, stdcall
option casemap :none

include windows.inc
include user32.inc
include shell32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib
include comdlg32.inc
includelib comdlg32.lib
includelib shell32.lib
include debug.inc
include Stdlib.Inc
includelib Stdlib.lib

m2m Macro M1,M2
push M2
pop M1
endm



SEH struct
PrevLink dd ? ; 用来保存SHE原地址
CurrentHandler dd ? ; SEH处理函数地址
SafeOffset dd ? ; 执行恢复地址
PrevEsp dd ? ; 用来保存 SEH前的esp
PrevEbp dd ? ; 用来保存 SEH前的ebp
SEH ends



.const
ICO_ICO EQU 1
DLG_MAIN EQU 1000
IDC_EDT_FILE EQU 1001
IDC_EDT_OUT EQU 1002
IDC_PGB EQU 1003
IDC_BTN_EXIT EQU 1004
.data?
hWinMain dd ?
hInstance dd ?
hMenu HANDLE dword ?
TheThread DWORD ?
ValidPE dd ?
.data
FileName db MAX_PATH dup(0)
temp db '%s',0DH,0AH,0
FilterString db "全部文件(*.*)",0,"*.*",0,0
szReceive db '文件名 :%s',0dh,0ah
db '文件大小 :%d','字节',0dh,0ah
db '文件MD5值:%s ',0dh,0ah,0
TEMP db '.',0

include md5.asm



_SetWindowCenter proc _hWnd:DWORD
LOCAL swidth,sheight,dwidth,dheight
LOCAL rect:RECT
invoke GetSystemMetrics,SM_CXSCREEN
mov swidth,eax
invoke GetSystemMetrics,SM_CYSCREEN
mov sheight,eax
invoke GetWindowRect,_hWnd,addr rect
mov eax,rect.right
sub eax,rect.left
mov dwidth,eax
sub swidth,eax
mov eax,rect.bottom
sub eax,rect.top
mov dheight,eax
sub sheight,eax
shr sheight,1
shr swidth,1
invoke SetWindowPos,_hWnd,HWND_NOTOPMOST,swidth,sheight,dwidth,dheight,SWP_SHOWWINDOW
ret
_SetWindowCenter endp

;********************************************************************
CloseMapFile PROC hMapFile:DWORD,hFileRead:DWORD
invoke CloseHandle,hMapFile
;mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

GetMd5Thread PROC PFile:DWORD
LOCAL seh:SEH
LOCAL md5 ,ProcessId
LOCAL @FileText[MAX_PATH]:BYTE
LOCAL @TEMP[MAX_PATH]:BYTE
LOCAL @hFileRead,@hMapFile,@pMemory,@FileSize

;SEH异常处理

assume fs:nothing
push fs:[0]
pop seh.PrevLink
mov seh.CurrentHandler,offset SEHHandler
mov seh.SafeOffset,offset FinalExit
lea eax,seh
mov fs:[0], eax
mov seh.PrevEsp,esp
mov seh.PrevEbp,ebp


invoke CreateFile, PFile,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
0

mov @hFileRead,eax

invoke ExtractFileName,PFile,addr @FileText ; 取短文件名

invoke GetFileSize,@hFileRead,0 ;取文件尺寸
mov @FileSize,eax

invoke CreateFileMapping,@hFileRead,NULL,PAGE_READONLY,0,0,NULL
mov @hMapFile,eax
invoke MapViewOfFileEx,@hMapFile,FILE_MAP_READ,0,0,0,0
mov @pMemory,eax
invoke SendDlgItemMessage,hWinMain,IDC_PGB,PBM_SETPOS,1,0 ;进度开始(注意:这里并不是实时进度)
invoke _MD5,@pMemory, @FileSize

;MD5转为大写
mov md5,eax
invoke lstrlen, md5
invoke CharUpperBuff,md5,eax

invoke wsprintf ,addr @TEMP,addr szReceive, addr @FileText, @FileSize,md5
invoke SendDlgItemMessage,hWinMain,IDC_EDT_OUT,EM_REPLACESEL,0,addr @TEMP
invoke SendDlgItemMessage,hWinMain,IDC_PGB,PBM_SETPOS,100,0 ;进度结束
invoke RtlZeroMemory,addr FileName,MAX_PATH ; 清空数据
INVOKE SendDlgItemMessage,hWinMain,IDC_EDT_FILE,WM_SETTEXT,0, addr FileName
invoke UnmapViewOfFile,@pMemory
invoke CloseMapFile,@hMapFile,@hFileRead

;断开SHE链

push seh.PrevLink
pop fs:[0]


ret


SEH执行恢复地址:

FinalExit:

invoke MessageBox,0,CTEXT("文件打开失败,请检查是否为有效文件!"),CTEXT("WinMd5 for ASM v1.1"),MB_ICONERROR OR MB_OK

ret

GetMd5Thread endp

_ProcDlgMain proc uses ebx edi esi, \
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

LOCAL @hMem,@hFile,@Size,@Read
mov eax,wMsg
cmp eax ,WM_DROPFILES
je GetFile
cmp eax,WM_COMMAND
je Exit
cmp eax,WM_INITDIALOG
je boxStart
cmp eax,WM_CLOSE
je boxClose

retFalse:
mov eax,FALSE
ret

boxClose:

invoke EndDialog,hWnd,NULL
jmp retTrue

boxStart:

push hWnd
pop hWinMain
invoke LoadIcon,hInstance,ICO_ICO
invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,eax ;设置窗口图标
invoke SetWindowPos,hWnd,HWND_TOPMOST,0,0,0,0, SWP_NOMOVE or SWP_NOSIZE ;窗口置顶
INVOKE _SetWindowCenter,hWinMain ;使窗体出现在屏幕中心
invoke DragAcceptFiles,hWnd,TRUE ;允许拖放文件
jmp retTrue

GetFile:

invoke DragQueryFile,wParam,0,addr FileName,MAX_PATH ;取拖放文件名
invoke SendDlgItemMessage,hWnd,IDC_EDT_FILE,EM_REPLACESEL,0, addr FileName ;输出到编辑框
invoke DragFinish,wParam ;释放拖放资源
invoke CreateThread,NULL,0,addr GetMd5Thread,addr FileName,0,NULL ;创建线程


Exit:
mov eax,wParam
.if eax==IDC_BTN_EXIT
invoke ExitProcess,NULL
.endif


retTrue:
mov eax,TRUE
ret
_ProcDlgMain endp
;--------------------------------------------------------------------;
;函数功能:SEH回调函数
;-------------------------------------------------------------------;
SEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
mov edx,pFrame
assume edx:ptr SEH
mov eax,pContext
assume eax:ptr CONTEXT
push [edx].SafeOffset
pop [eax].regEip
push [edx].PrevEsp
pop [eax].regEsp
push [edx].PrevEbp
pop [eax].regEbp
mov eax,ExceptionContinueExecution
ret
SEHHandler endp



Start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,_ProcDlgMain,0
invoke ExitProcess,NULL
End Start




分析:


SEH struct
PrevLink dd ?    ; 用来保存SHE原地址
CurrentHandler dd ?    ; SEH处理函数地址
SafeOffset dd ?    ; 执行恢复地址
PrevEsp dd ?      ; 用来保存 SEH前的esp
PrevEbp dd ?     ;  用来保存 SEH前的ebp
SEH ends


程序开始先注册一个SEH结构

GetMd5Thread PROC PFile:DWORD
       LOCAL seh:SEH
        LOCAL md5 ,ProcessId
        LOCAL   @FileText[MAX_PATH]:BYTE
        LOCAL   @TEMP[MAX_PATH]:BYTE
        LOCAL        @hFileRead,@hMapFile,@pMemory,@FileSize       

         ;SEH异常处理
                
         assume fs:nothing
                 push fs:[0]
                 pop seh.PrevLink                                                 ;备份当前SEH
                 mov seh.CurrentHandler,offset SEHHandler      ;SEH处理函数地址
                 mov seh.SafeOffset,offset FinalExit                   ;执行恢复地址
                 lea eax,seh                                                         
                 mov fs:[0], eax                                                  ;装载我们的SHE
                 mov seh.PrevEsp,esp                                        ;备份ESP
                 mov seh.PrevEbp,ebp
                         ;备份EBP

程序打开文件前前建立SEH,一开始就假设寄存器 fs为空(assume fs:nothing)。 记住这一步不能省却,因为MASM假设fs寄存器为ERROR。接下来保存 Windows使用的旧SEH处理函数地址到我们自己定义的结构中,同时保存我们的SEH处理函数地址和异常处理时的执行恢复地址,这样一旦错误发生就能由异常处理函数安全地恢复执行了。同时还保存当前esp及ebp的值,以便我们的SEH处理函数将堆栈恢复到正常状态。
  
invoke CloseMapFile,@hMapFile,@hFileRead
                       
   ;断开SHE链
                       
        push seh.PrevLink
                pop fs:[0]        
       
                       

一旦SEH不再使用,必须从SEH链上断开。

FinalExit:
           
       invoke MessageBox,0,CTEXT("文件打开失败,请检查是否为有效文件!"),CTEXT("WinMd5 for ASM v1.1"),MB_ICONERROR OR MB_OK
      
  ret

这里就是我们在前面定义的SEH执行恢复地址,当程序从错误中恢复过来,就会执行这里。

在本例中,如果用户拖放的是个文件夹,程序就会发生异常,就会进入了我们指定的处理程序

SEHHandler proc uses edx esi pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
   
   LOCAL ExceptionCode  

    mov edx,pFrame
    assume edx:ptr SEH
    mov eax,pContext
    assume eax:ptr CONTEXT

    mov esi, pExcept

    m2m ExceptionCode ,[esi][EXCEPTION_RECORD.ExceptionCode]  

   ;you code                     错误代码记录在变量ExceptionCode里

    debug TEXTEQU <调试信息:SEH安装成功!>

    %echo debug
  
    我们处理的第一件事就是输出相应的调试信息,之后开始真正的异常处理。

   ;执行恢复地址
    push [edx].SafeOffset
    pop [eax].regEip
   ;恢复ESP
    push [edx].PrevEsp
    pop [eax].regEsp
   ;恢复EBP
    push [edx].PrevEbp
    pop [eax].regEbp

     在本例中异常处理只是简单地恢复esp、ebp寄存器的值并将eip寄存器的值置为一个能使线程安全地继续执行的地址。这些处理信息都是由系统从我们预先填充的seh结构体中抽出并保存在了CONTEXT结构体中,CONTEXT结构体的指针又传递给了系统。

   mov eax,ExceptionContinueExecution
    ret

      我们以ExceptionContinueExecution的值来返回(该值为零),就是告诉系统应该恢复线程的上下文并继续执行。即eip的值等于标记FinalExit的地址,而esp和ebp寄存器的值恢复为原值,线程从标记FinalExit处继续其执行。

上面程序的SEH相关代码,您完全可以直接COPY到您的代码中。

经过上面2个代码的比较,我们应该知道了SEH的妙用,熟练运用能使您的代码更简洁高效。

看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (15)
雪    币: 579
活跃值: 活跃值 (58)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 活跃值 17 2009-11-19 03:57
2
0
居然得了格精华,没人顶吗?呵呵
雪    币: 142
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiejienet 活跃值 2009-11-19 07:51
3
0
刚看到,顶顶,
很有用的技术,不顶就不厚道了
雪    币: 1225
活跃值: 活跃值 (35)
能力值: (RANK:680 )
在线值:
发帖
回帖
粉丝
jackozoo 活跃值 14 2009-11-19 09:34
4
0
虽然SEH已是老东西了, 不过有讲解有代码有bin的就应该支持

PS. 原来WinMD5是出自你之手, 很小很不错的一个工具, 以前还曾经用过一段时间.
雪    币: 6882
活跃值: 活跃值 (397)
能力值: (RANK:1290 )
在线值:
发帖
回帖
粉丝
玩命 活跃值 31 2009-11-19 10:06
5
0
昨天看这个帖子好像没发完。。。   今天过来顶~~~
雪    币: 42
活跃值: 活跃值 (30)
能力值: ( LV9,RANK:320 )
在线值:
发帖
回帖
粉丝
charme 活跃值 7 2009-11-19 10:44
6
0
这个根据加密解密书上的那个MD5.h做个inc  再结合微软的那个SEH的专门介绍的文章就可以搞出来了!呼呼,,我也刚研究过这个东西,呼呼,,跟你的差不多,,
雪    币: 348
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
inioo 活跃值 2009-11-19 11:15
7
0
   有技术含量,支持
雪    币: 30
活跃值: 活跃值 (1131)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
riusksk 活跃值 41 2009-11-19 12:09
8
0
软件全称是WinMd5 for ASM v1.1,作者已经发在论坛上了,开源的:
http://bbs.pediy.com/showthread.php?p=382854
雪    币: 30
活跃值: 活跃值 (1131)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
riusksk 活跃值 41 2009-11-19 12:13
9
0
请问楼主是非安全·后生吗?
雪    币: 579
活跃值: 活跃值 (58)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 活跃值 17 2009-11-19 12:17
10
0
非安全·后生?

不是吧,应该不是我,你在哪看到的?
雪    币: 2262
活跃值: 活跃值 (584)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
非虫 活跃值 7 2009-11-19 12:19
11
0
此非安全乃彼非安全。
喜欢的代码风格。
雪    币: 30
活跃值: 活跃值 (1131)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
riusksk 活跃值 41 2009-11-19 12:35
12
0
非安全·后生就是黑客手册的编辑啊!
雪    币: 30
活跃值: 活跃值 (1131)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
riusksk 活跃值 41 2009-11-19 12:36
13
0
那楼主跟黑客手册又是啥关系捏??
雪    币: 579
活跃值: 活跃值 (58)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 活跃值 17 2009-11-19 12:38
14
0
呵呵,没啥关系,先有他后有我
雪    币: 30
活跃值: 活跃值 (1131)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
riusksk 活跃值 41 2009-11-19 13:05
15
0
非安全这昵称很难不联想到非安全·黑客手册啊,到处漂浮着nohack的影子!
雪    币: 579
活跃值: 活跃值 (58)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
非安全 活跃值 17 2009-11-19 13:19
16
0
呵呵,理解,以前还有人问我手册是不是我办的了,我的QQ昵称是nohacks
游客
登录 | 注册 方可回帖
返回