结构化异常处理
当某一线程发生异常时,程序的控制权会立即进入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的妙用,熟练运用能使您的代码更简洁高效。
[课程]Android-CTF解题方法汇总!
上传的附件: