PART II 继续深入
三、传递给异常处理例程的参数
I、传递给final型的参数,只有一个即指向EXCEPTION_POINTERS结构的指针, EXCEPTION_POINTERS定义如下:
EXCEPTION_POINTERS STRUCT
pExceptionRecord DWORD ?
ContextRecord DWORD ?
EXCEPTION_POINTERS ENDS
执行时堆栈结构如下:
esp -> ptEXCEPTION_POINTERS
然后执行call _Final_Handler
注意堆栈中的参数是指向EXCEPTION_POINTERS 的指针,而不是指向pExceptionRecord的指针
以下是EXCEPTION_POINTERS两个成员的详细结构
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里面有很多类型,但你最可能遇到的几种类型如下:
C0000005h----读写内存冲突
C0000094h----非法除0
C00000FDh----堆栈溢出或者说越界
80000001h----由Virtual Alloc建立起来的属性页冲突
C0000025h----不可持续异常,程序无法恢复执行,异常处理例程不应处理这个异常
C0000026h----在异常处理过程中系统使用的代码,如果系统从某个例程莫名奇妙的返回,则出现此代码,例如调用RtlUnwind时没有Exception Record参数时产生的异常填入的就是这个代码
80000003h----调试时因代码中int3中断
80000004h----处于被单步调试状态
注:也可以自己定义异常代码,遵循如下规则:
_____________________________________________________________________+
位: 31~30 29~28 27~16 15~0
_____________________________________________________________________+
含义: 严重程度 29位 功能代码 异常代码
0==成功 0==Mcrosoft MICROSOFT定义 用户定义
1==通知 1==客户
2==警告 28位
3==错误 被保留必须为0
ExceptionFlags 异常标志
0----可修复异常
1----不可修复异常
2----正在展开,不要试图修复什么,需要的话,释放必要的资源
pExceptionRecord 如果程序本身导致异常,指向那个异常结构
ExceptionAddress 发生异常的eip地址
ExceptionInformation 附加消息,在调用RaiseException可指定或者在异常号为C0000005h即内存异常时(ExceptionCode=C0000005h) 的含义如下,其他情况下一般没有意义
第一个dword 0==读冲突 1==写冲突
第二个dword 读写冲突地址
;==========CONTEXT具体结构含义================================
CONTEXT STRUCT ; _
ContextFlags DWORD ? ; |--------------- +00
iDr0 DWORD ? ; | +04
iDr1 DWORD ? ; | +08
iDr2 DWORD ? ; >调试寄存器 +0C
iDr3 DWORD ? ; | +10
iDr6 DWORD ? ; | +14
iDr7 DWORD ? ; _| +18
FloatSave FLOATING_SAVE_AREA <> ;浮点寄存器区 +1C~~+88
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
以上是两个成员的详细结构,下面给出一个final型的例子,这也是本文所讨论的最后一个final型的例子,以后的例子集中在thread类型上.
;--------------------------------------------
; Ex3,演示final处理句柄的参数获取,加深前面
; 参数传递的介绍理解如果难于理解请先看partIII
; 再回来看这个例子
;--------------------------------------------
;;代码部分删除,腾讯不给发
;;------------------------------------------------
II、 传递给per_thread型异常处理程序的参数,如下:
在堆栈中形成如下结构
esp -> *EXCEPTION_RECORD
esp+4 -> *ERR ;注意这也就是fs:[0]的指向
esp -> *CONTEXT record ;point to registers
esp -> *Param ;呵呵,没有啥意义
-
然后执行 call _Per_Thread_xHandler
操作系统调用handler的MASM原型是这样
invoke xHANDLER,*EXCEPTION_RECORD,*_EXCEPTION_REGISTRATION,*CONTEXT,*Param
即编译后代码如下:
PUSH *Param ;通常不重要,没有什么意义
push *CONTEXT record ;上面的结构
push *ERR ;the struc above
push *EXCEPTION_RECORD ;see above
CALL HANDLER
ADD ESP,10h
-
下一部分给出thread类型的具体实例.
PART III 不是终结
我们的目标是分三步走,学会SEH,现在让我们接触最有趣的部分:SEH的应用.seh设计的最初目的就是为了使应用程序运行得更健壮,因此SEH用于除错,避免应用程序和系统的崩溃是最常见的用途.例如:
1.比如你的程序里出现了除0错,那你就可以在你的seh处理程序中将除数改为非零值,per_Thread seh返回0(ExceptionContinueExecution)、final返回-1 (EXCEPTION_CONTINUE_EXECUTION),系统就会根据你的意图用改变过的context加载程序在异常处继续执行,由于被除数已经改变为非零值,你的程序就可以正常仿佛什么也没有发生的继续执行了.
2.seh还可以处理内存读写异常,如果你分配的堆栈空间不够,产生溢出,这时你就可以处理这个异常,再多分配一些空间,然后结果是你的程序照常运行了,就好像什么也没有发生过,这在提高内存运用效率方面很值得借鉴,虽然会降低一些程序的执行效率.另外,在很多加壳或反跟踪软件中,利用vitualAlloc和VitualProtect制造异常来进入异常程序,或仅仅是用,mov [0],XXX来进入异常程序,要比用int3或者int1或
pushf
and [esp],100h
popf
进入要隐蔽得多,如果可以随机引起这些异常的话,效果会更好...当然应用很多了,感兴趣自己去找.话题似乎有点远了,让我们回到最基础的地方.
前面的例子中你可能已经注意到,假如我们改变了Context的内容,(注意啊,context包含了系统运行时各个重要的寄存器),并且返回0(ExceptionContinueExecution-->perThread SEH),或者-1(EXCEPTION_CONTINUE_EXECUTION,final SEH),就表示要系统已现有的context继续执行程序,当然我们的改变被重载了,就像周星驰的月光宝盒改变了历史一样奇妙,程序就会以改变的context内容去执行程序,通过这种手段,我们可以修复程序,使其继续执行.
看下面的例子4.
读之前,先再罗嗦几句,由于前面介绍了seh例程被调用的时候,系统把相关信息已经压入堆栈,所以我们只要在程序里寻址调用就行了,怎么寻址呢???唉....回顾一下call指令执行的基本知识,一般对于近调用,通过[esp+4]即刻找到
*EXCEPTION_RECORD,其余的不用说了吧,如果执行了push ebp;mov ebp,esp的话,就是[ebp+8]指向*EXCEPTION_RECORD,这也是大多数程序用的和我们最常见到的,明白了吗?不明白?我--去--跳--楼.
;________________________________________________________________________
;|EX.4 By hume,2001,to show the basic simple seh function
;|________________________________________________________________________
.386
.model flat, stdcall
option casemap :none ; case sensitive
include hd.h ;//相关的头文件,你自己维护一个吧
.data
szCap db "By Hume[AfO],2001...",0
szMsgOK db "It's now in the Per_Thread handler!",0
szMsg1 db "In normal,It would never Get here!",0
fmt db "%s ",0dh,0ah," 除法的商是:%d",0
buff db 200 dup(0)
.code
_start:
Assume FS:NOTHING
push offset perThread_Handler
push fs:[0]
mov fs:[0],esp ;//建立SEH的基本ERR结构,如果不
;//明白,就仔细研究一下吧
xor ecx,ecx
mov eax,200
cdq
div ecx
WouldBeOmit: ;//正常情况以下永远不会被执行
add eax,100 ;//这里不会执行,因为我们改变了eip的值
ExecuteHere:
div ecx ;//从这里开始执行,从结果可以看到
invoke wsprintf,addr buff,addr fmt,addr szMsg1,eax
invoke MessageBox,NULL,addr buff,addr szCap,40h+1000h
pop fs:[0] ;//修复后显示20,因为我们让ecx=10
add esp,4
invoke ExitProcess,NULL
perThread_Handler proc /
uses ebx pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
mov eax,pContext
Assume eax:ptr CONTEXT
mov [eax].regEcx,20 ;//Ecx改变
lea ebx, ExecuteHere
mov [eax].regEip,ebx ;//从我们想要的地方开始执行,嘿嘿,这就是很多
;//反跟踪软件把你引向的黑暗之域
mov eax,0 ;//ExceptionContinueExecution,表示已经修复
;//CONTEXT,可从异常发生处
;//reload并继续执行
ret
perThread_Handler endp
end _start
;//====================================================
哈哈,从这个例子里我门可以真正看到seh结构化处理的威力,他不仅恢复了ecx的内容而且使程序按照你想要的顺序执行了,哈哈,如果你对反跟踪很感兴趣的话,你还可以在例程中加入
xor ebx,ebx
mov [eax].iDr0,ebx
mov [eax].iDr2,ebx
mov [eax].iDr3,ebx
mov [eax].iDr4,ebx
清除断点,跟踪者....嘿嘿,不说你也体验过,当然也可以通过检验drx的值来判断是否被跟踪,更复杂地,你可以设置dr6,和dr7产生一些有趣的结果,我就不罗嗦了.
上面的例子理解了吧,因为我用的是MASM提供的优势来简化程序,老Tasm Fans可能会不以为然,你可以试一下下面的代码代替,是TASM,MASM compatibale的
perThread_Handler:
push ebp
mov ebp,esp
mov eax,[ebp+10h] ;取context的指针
mov [eax+0ach],20 ;将ecx=0,可以对照前面的例程和context结构
lea ebx, ExecuteHere
mov [eax+0b8h],ebx ;eip== offset ExecuteHere,呵呵
xor eax,eax
mov esp,ebp
pop ebp
ret
这是raw asm的,不过masm既然给我们设计了这么多好东西,我们为什么不好好利用呢?
好,到现在为止,基本知识已经结束了,我们应该可理解seh的相关文章和写简单的seh处理程序了,但关于seh还只是刚刚开始,很多内容和应用还没有涉及到,请继续看提高篇.
三、传递给异常处理例程的参数
I、传递给final型的参数,只有一个即指向EXCEPTION_POINTERS结构的指针, EXCEPTION_POINTERS定义如下:
EXCEPTION_POINTERS STRUCT
pExceptionRecord DWORD ?
ContextRecord DWORD ?
EXCEPTION_POINTERS ENDS
执行时堆栈结构如下:
esp -> ptEXCEPTION_POINTERS
然后执行call _Final_Handler
注意堆栈中的参数是指向EXCEPTION_POINTERS 的指针,而不是指向pExceptionRecord的指针
以下是EXCEPTION_POINTERS两个成员的详细结构
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里面有很多类型,但你最可能遇到的几种类型如下:
C0000005h----读写内存冲突
C0000094h----非法除0
C00000FDh----堆栈溢出或者说越界
80000001h----由Virtual Alloc建立起来的属性页冲突
C0000025h----不可持续异常,程序无法恢复执行,异常处理例程不应处理这个异常
C0000026h----在异常处理过程中系统使用的代码,如果系统从某个例程莫名奇妙的返回,则出现此代码,例如调用RtlUnwind时没有Exception Record参数时产生的异常填入的就是这个代码
80000003h----调试时因代码中int3中断
80000004h----处于被单步调试状态
注:也可以自己定义异常代码,遵循如下规则:
_____________________________________________________________________+
位: 31~30 29~28 27~16 15~0
_____________________________________________________________________+
含义: 严重程度 29位 功能代码 异常代码
0==成功 0==Mcrosoft MICROSOFT定义 用户定义
1==通知 1==客户
2==警告 28位
3==错误 被保留必须为0
ExceptionFlags 异常标志
0----可修复异常
1----不可修复异常
2----正在展开,不要试图修复什么,需要的话,释放必要的资源
pExceptionRecord 如果程序本身导致异常,指向那个异常结构
ExceptionAddress 发生异常的eip地址
ExceptionInformation 附加消息,在调用RaiseException可指定或者在异常号为C0000005h即内存异常时(ExceptionCode=C0000005h) 的含义如下,其他情况下一般没有意义
第一个dword 0==读冲突 1==写冲突
第二个dword 读写冲突地址
;==========CONTEXT具体结构含义================================
CONTEXT STRUCT ; _
ContextFlags DWORD ? ; |--------------- +00
iDr0 DWORD ? ; | +04
iDr1 DWORD ? ; | +08
iDr2 DWORD ? ; >调试寄存器 +0C
iDr3 DWORD ? ; | +10
iDr6 DWORD ? ; | +14
iDr7 DWORD ? ; _| +18
FloatSave FLOATING_SAVE_AREA <> ;浮点寄存器区 +1C~~+88
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
以上是两个成员的详细结构,下面给出一个final型的例子,这也是本文所讨论的最后一个final型的例子,以后的例子集中在thread类型上.
;--------------------------------------------
; Ex3,演示final处理句柄的参数获取,加深前面
; 参数传递的介绍理解如果难于理解请先看partIII
; 再回来看这个例子
;--------------------------------------------
;;代码部分删除,腾讯不给发
;;------------------------------------------------
II、 传递给per_thread型异常处理程序的参数,如下:
在堆栈中形成如下结构
esp -> *EXCEPTION_RECORD
esp+4 -> *ERR ;注意这也就是fs:[0]的指向
esp -> *CONTEXT record ;point to registers
esp -> *Param ;呵呵,没有啥意义
-
然后执行 call _Per_Thread_xHandler
操作系统调用handler的MASM原型是这样
invoke xHANDLER,*EXCEPTION_RECORD,*_EXCEPTION_REGISTRATION,*CONTEXT,*Param
即编译后代码如下:
PUSH *Param ;通常不重要,没有什么意义
push *CONTEXT record ;上面的结构
push *ERR ;the struc above
push *EXCEPTION_RECORD ;see above
CALL HANDLER
ADD ESP,10h
-
下一部分给出thread类型的具体实例.
PART III 不是终结
我们的目标是分三步走,学会SEH,现在让我们接触最有趣的部分:SEH的应用.seh设计的最初目的就是为了使应用程序运行得更健壮,因此SEH用于除错,避免应用程序和系统的崩溃是最常见的用途.例如:
1.比如你的程序里出现了除0错,那你就可以在你的seh处理程序中将除数改为非零值,per_Thread seh返回0(ExceptionContinueExecution)、final返回-1 (EXCEPTION_CONTINUE_EXECUTION),系统就会根据你的意图用改变过的context加载程序在异常处继续执行,由于被除数已经改变为非零值,你的程序就可以正常仿佛什么也没有发生的继续执行了.
2.seh还可以处理内存读写异常,如果你分配的堆栈空间不够,产生溢出,这时你就可以处理这个异常,再多分配一些空间,然后结果是你的程序照常运行了,就好像什么也没有发生过,这在提高内存运用效率方面很值得借鉴,虽然会降低一些程序的执行效率.另外,在很多加壳或反跟踪软件中,利用vitualAlloc和VitualProtect制造异常来进入异常程序,或仅仅是用,mov [0],XXX来进入异常程序,要比用int3或者int1或
pushf
and [esp],100h
popf
进入要隐蔽得多,如果可以随机引起这些异常的话,效果会更好...当然应用很多了,感兴趣自己去找.话题似乎有点远了,让我们回到最基础的地方.
前面的例子中你可能已经注意到,假如我们改变了Context的内容,(注意啊,context包含了系统运行时各个重要的寄存器),并且返回0(ExceptionContinueExecution-->perThread SEH),或者-1(EXCEPTION_CONTINUE_EXECUTION,final SEH),就表示要系统已现有的context继续执行程序,当然我们的改变被重载了,就像周星驰的月光宝盒改变了历史一样奇妙,程序就会以改变的context内容去执行程序,通过这种手段,我们可以修复程序,使其继续执行.
看下面的例子4.
读之前,先再罗嗦几句,由于前面介绍了seh例程被调用的时候,系统把相关信息已经压入堆栈,所以我们只要在程序里寻址调用就行了,怎么寻址呢???唉....回顾一下call指令执行的基本知识,一般对于近调用,通过[esp+4]即刻找到
*EXCEPTION_RECORD,其余的不用说了吧,如果执行了push ebp;mov ebp,esp的话,就是[ebp+8]指向*EXCEPTION_RECORD,这也是大多数程序用的和我们最常见到的,明白了吗?不明白?我--去--跳--楼.
;________________________________________________________________________
;|EX.4 By hume,2001,to show the basic simple seh function
;|________________________________________________________________________
.386
.model flat, stdcall
option casemap :none ; case sensitive
include hd.h ;//相关的头文件,你自己维护一个吧
.data
szCap db "By Hume[AfO],2001...",0
szMsgOK db "It's now in the Per_Thread handler!",0
szMsg1 db "In normal,It would never Get here!",0
fmt db "%s ",0dh,0ah," 除法的商是:%d",0
buff db 200 dup(0)
.code
_start:
Assume FS:NOTHING
push offset perThread_Handler
push fs:[0]
mov fs:[0],esp ;//建立SEH的基本ERR结构,如果不
;//明白,就仔细研究一下吧
xor ecx,ecx
mov eax,200
cdq
div ecx
WouldBeOmit: ;//正常情况以下永远不会被执行
add eax,100 ;//这里不会执行,因为我们改变了eip的值
ExecuteHere:
div ecx ;//从这里开始执行,从结果可以看到
invoke wsprintf,addr buff,addr fmt,addr szMsg1,eax
invoke MessageBox,NULL,addr buff,addr szCap,40h+1000h
pop fs:[0] ;//修复后显示20,因为我们让ecx=10
add esp,4
invoke ExitProcess,NULL
perThread_Handler proc /
uses ebx pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
mov eax,pContext
Assume eax:ptr CONTEXT
mov [eax].regEcx,20 ;//Ecx改变
lea ebx, ExecuteHere
mov [eax].regEip,ebx ;//从我们想要的地方开始执行,嘿嘿,这就是很多
;//反跟踪软件把你引向的黑暗之域
mov eax,0 ;//ExceptionContinueExecution,表示已经修复
;//CONTEXT,可从异常发生处
;//reload并继续执行
ret
perThread_Handler endp
end _start
;//====================================================
哈哈,从这个例子里我门可以真正看到seh结构化处理的威力,他不仅恢复了ecx的内容而且使程序按照你想要的顺序执行了,哈哈,如果你对反跟踪很感兴趣的话,你还可以在例程中加入
xor ebx,ebx
mov [eax].iDr0,ebx
mov [eax].iDr2,ebx
mov [eax].iDr3,ebx
mov [eax].iDr4,ebx
清除断点,跟踪者....嘿嘿,不说你也体验过,当然也可以通过检验drx的值来判断是否被跟踪,更复杂地,你可以设置dr6,和dr7产生一些有趣的结果,我就不罗嗦了.
上面的例子理解了吧,因为我用的是MASM提供的优势来简化程序,老Tasm Fans可能会不以为然,你可以试一下下面的代码代替,是TASM,MASM compatibale的
perThread_Handler:
push ebp
mov ebp,esp
mov eax,[ebp+10h] ;取context的指针
mov [eax+0ach],20 ;将ecx=0,可以对照前面的例程和context结构
lea ebx, ExecuteHere
mov [eax+0b8h],ebx ;eip== offset ExecuteHere,呵呵
xor eax,eax
mov esp,ebp
pop ebp
ret
这是raw asm的,不过masm既然给我们设计了这么多好东西,我们为什么不好好利用呢?
好,到现在为止,基本知识已经结束了,我们应该可理解seh的相关文章和写简单的seh处理程序了,但关于seh还只是刚刚开始,很多内容和应用还没有涉及到,请继续看提高篇.