SEH in ASM 研究

SEH in ASM 研究(一)

By Hume/冷雨飘心

为什么老调重弹: 
SEH出现已绝非一日,但很多人可能还不彻底了解Seh的运行机制;有关seh的知识资料不是很多,asm级的详细

资料就更少!seh不仅可以简化程序错误处理,使你的程序更加健壮,还被广泛应用于反跟踪以及加解密中,因

此,了解seh非常必要但遗憾的是关于seh详细介绍的中文资料非常少,在实践的基础上,把自己学习的一点笔

记奉献给大家,希望对喜欢ASM的朋友有所帮助.如有错误,请高手不吝指正.

PART I 简单接触

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

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

二.初步实战演习:

安装异常处理句柄. 有两种类型的异常处理句柄,一种是final型的,这是在你的异常未能得到线程相关处理例程处理操作系统在即将关闭程序之前会回调的例程,这个例程是进程相关的而不是线程相关的,因此无论是哪个线程发生异常未能被处理,都会调用这个例程. I. 见下面的例子1: ;//================================================= ;// ex. 1,by Hume,2001,例子1---final型的异常处理 ;//================================================== .386 .model flat, stdcall option casemap :none ; case sensitive include 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: 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,所以不断产生异常,然 ;后不断调用这个例程 end _start 简单来解释几句,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 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 szMsgERR1 db "It would never Get here!",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 ;以下永远不会被执行 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,你会陷入死循环,不断跳出对话框.... end _start 

嘿嘿,这个简单吧,我们由于没有足够的资料,暂时还不能修复ecx的值使之从异常发生处继续执行,只是简单显示一个MSG,然后系统处理,自然跳出讨厌的对话框了.... 
注意和final返回值的含义不同... 
;========================================================================================

好像到此为止,我们并没有从异常处理中得到任何好处,除了在异常发生后可以执行一点我们微不足道的代码,

事实上SEH可以修复这些异常或者干我们想干的事情然后从希望的地方继续执行,嘿嘿,很爽吧,可惜我们没有

足够的信息,那里找到我们所需要的信息? 
欲知后事如何,且看下回分解...

PART II 继续深入

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

要想明白seh处理例程如何得到感兴趣的信息,首先要明白SEH的作用机制.事实上,当异常 
发生时,系统给了我们一个处理异常的机会,他首先会调用我们自定义的seh处理例程,当然也包括 
了相关信息,在调用之前,系统把包含这些信息结构的指针压入stack,供我们的异常处理例程调用, 
传递给例程的参数通常是四个,其中只有三个有明确意义,另一个到现在为止还没有发现有什么作用, 
这些参数是: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----调试时因代码中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即内存异常

时含义如下 
第一个dword 0==读冲突 1==写冲突 
第二个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 ;//point 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


好现在你明白了应该如何访问具体有关系统信息的细节了吧,下一部分,让我们来看看如何应用...

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内容去执行程序,通过这种手段,我们可以修复程序,使其继续执行.

看下面的例子. 
读之前,先再罗嗦几句,由于前面介绍了seh例程被调用的时候,系统把相关信息已经压入堆栈, 
所以我们只要在程序里寻址调用就行了,怎么寻址呢???唉....回顾一下call指令执行的基本 
知识,通过[esp+4]即刻找到*EXCEPTION_RECORD,其余的不用说了吧,如果执行了 
push ebp 
mov ebp,esp 
的话,就是[ebp+8]指向 
*EXCEPTION_RECORD,这也是大多数程序用的和我们最常见到的,明白了吗?不明白?我--去--跳--楼. 
;________________________________________________________________________ 
;|EX.3 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,MB_OK+MB_ICONINFORMATION 
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提供的优势来简化程序,你可以试一下下面的代码代替 
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 
还只是刚刚开始,还有以下几个问题没有涉及到,也是我准备继续写的,但由于时间关系,争取元旦 
不玩乐写出来吧: 
提高篇

1) 关于SEH嵌套的问题,程序如何执行和有关堆栈的展开(stack unwind) 
2) 如何利用seh进入ring0级 
3) seh实现单步自跟踪 
4) seh测指令执行时间? 
5) seh ANti Tricks 
6) Matt Petrek的有关seh的C++实现 
7) Some Rewrite Masm Example,为啥高手都用Tasm啊 
7) More I Haven't thought out 
Last) 欢迎大家也贡献自己有关seh的思

11

SEH IN ASM 研究(二)

---提高篇 
By Hume[AfO]/冷雨飘心 

part 4 关于异常处理的嵌套和堆栈展开 

在实际程序设计过程中,不可能只有一个异常处理例程,这就产生了异常处理程序嵌套的问题,可能很多 
处理例程分别监视若干子程序并处理其中某种异常,另外一个监视所有子程序可能产生的共性异常,这作 
起来实际很容易,也方便调试.你只要依次建立异常处理框架就可以了. 
关于VC++异常处理可以嵌套很多人可能比较熟悉,用起来更容易不过实现比这里也就复杂得多,在VC++ 
中一个程序所有异常只指向一个相同的处理句例程,然后在这个处理例程里再实现对各个子异常处理例程的 
调用,他的大致方法是建立一个子异常处理例程入口的数组表,然后根据指针来调用子处理例程,过程比较烦 
琐,原来打算大致写一点,现在发现自己对C/C++了解实在太少,各位有兴趣还是自己 
参考MSDN Matt Pietrek 1996年写的一篇文章<<Structured Exception Handling>>,里面有非常详细的说明,对于系统的实现细节也有所讨论,不过相信很多 
人都没有兴趣.hmmm...:)实际上Kernel的异常处理过程和VC++的很相似.

    有异常嵌套就涉及到异常展开的问题,也许你注意到了如果按照我前面的例子包括final都不处理异常的话, 
最后系统在终结程序之前会来一次展开,在试验之后发现,展开不会调用final只是对per_thread例程展开 
(right?).什么是堆栈展开?为什么要进行堆栈展开?如何进行堆栈展开? 
    
    我曾经为堆栈展开迷惑过,原因是各种资料的描述很不一致,Matt Pietrek说展开后前面的ERR结构被释放, 
并且好像链后面如果决定处理必须展开,很多C/C++讲述异常处理的书也如斯说这使人很迷惑,我们再来看看Jeremy 
Gordon的描述,堆栈展开是处理异常的例程自愿进行的.呵呵,究竟事实如何? 
    
    在迷惑好久之后我终于找到了答案:Matt Pietrek讲的没有错,那是VC++以及系统kernel的处理方法,Jeremy 
Gordon说的也是正确的,那是我门asm Fans的自由!

    好了现在来说堆栈展开,堆栈展开是异常处理例程在决定处理某个异常的时候给前面不处理这个异常的处理 
例程的一个清洗的机会,前面拒绝处理这个异常的例程可以释放必要的句柄对象或者释放堆栈或者干点别的工作... 
那完全是你的自由,叫stack unwind似乎有点牵强.堆栈展开有一个重要的标志就是 
EXCEPTION_RECORD.ExceptionFlag为2,表示正在展开,你可以进行相应的处理工作,但实际上经常用的是6这是 
因为还有一个UNWIND_EXIT什么的,具体含义我也没有搞明白,不过对我们的工作好像没有什么影响.

注意在自己的异常处理例程中,unwind不是自动的,必须你自己自觉地引发,如果所有例程都不处理系统最后的 
展开是注定的,当然如果没有必要你也可以不展开. 
win32提供了一个api RtlUnwind来引发展开,如果你想展开一下,就调用这个api吧,少候讲述自己代码如何展开

RtlUnwind调用描述如下: 
        PUSH Return value        ;返回值,一般不用 
        PUSH pExceptionRecord    ;指向EXCEPTION_RECORD的指针 
        PUSH OFFSET CodeLabel    ;展开后从哪里执行 
        PUSH LastStackFrame      ;展开到哪个处理例程终止返回,通常是处理异常的Err结构 
        CALL RtlUnwind 
调用这个api之前要注意保护ebx,esi和edi,否则...嘿嘿

MASM格式如下:        
        invoke RtlUnwind,pFrame,OFFSET return_code_Address,pExceptionRecord,Return_value

这样在展开的时候,就以pExceptionRecord.flag=2 依次调用前面的异常处理例程,到决定异常的处理例程停止, 
Jeremy Gordon手动展开代码和我下面的例子有所不同.他描述最后决定处理异常的ERR结构的prev成员为-1,好像 
我的结果和他的有所差异,因此采用了另外的方法,具体看下面的例子. 
    
最后一点要注意在嵌套异常处理程序的时候要注意保存寄存器,否则你经常会得到系统异常代码为C00000027h 
的异常调用,然后就是被终结.

一下给出一点垃圾代码演示可能有助于理解,注意link的时候要加入 /section:.text,RWE 否则例子里面的 
代码段不能写,SMC功能会产生异常以致整个程序不能进行.

注意:2K/XP下非法指令异常的代码不一致,另外用下面的方法SMC代码段也不可以!不知如何解决? 
只用于9X,为了在2k/Xp下也能运行我加了点代码,有兴趣看看,另外帮我解决一下2K/Xp下SMC的问题?thx!

    下面例子很烂,不过MASM格式写起来容易一点,也便于理解. 
;----------------------------------------- 
;Ex4,演示堆栈展开和异常嵌套处理 by Hume,2002 
;humewen@21cn.com 
;hume.longcity.net 
;----------------------------------------- 
.586 
.model flat, stdcall 
option casemap :none ; case sensitive 
include hd.h 
include mac.h

;;-------------- 
per_xHandler1        proto C :DWORD,:DWORD,:DWORD,:DWORD 
per_xHandler2        proto C :DWORD,:DWORD,:DWORD,:DWORD 
per_xHandler3        proto C :DWORD,:DWORD,:DWORD,:DWORD 
;-----------------------------------------

.data 
sztit db "except Mess,by hume[AfO]",0 
count dd 0,0 
Expt1_frm dd 0                    ;ERR结构指针,用于堆栈展开手动代码 
Expt2_frm dd 0 
Expt3_frm dd 0 
        
;;----------------------------------------- 
    .CODE 
_Start: 
        assume fs:nothing 
        push    offset per_xHandler3 
        push    fs:[0] 
        mov    fs:[0],esp 
        mov    Expt3_frm,esp

        push    offset per_xHandler2 
        push    fs:[0] 
        mov    fs:[0],esp 
        mov    Expt2_frm,esp

        push    offset per_xHandler1 
        push    fs:[0] 
        mov    fs:[0],esp 
        mov    Expt1_frm,esp 
        ;-------------------------- 
        ;install xhnadler 
        ;----------------------------------------- 
        
        xor    ebx,ebx 
        mov    eax,200 
        cdq 
        div    ebx                  ;除法错误

        invoke    MessageBox,0,ddd("Good,divide overflow was solved!"),addr sztit,40h

        sub    eax,eax 
        mov    [eax],ebx            ;内存写错误

succ: 
        invoke    MessageBox,0,ddd("Good,memory write violation solved!"),addr sztit,40h 
        
        db      0F0h,0Fh,0C7h,0C8h ;什么cmpchg8b指令的非法形式?我从来没有成功过!! 
                                    ;演示程序中使用seh实现SMC技术,加密??... 
        invoke    MessageBox,0,ddd("illeagal instruction was solved!"),addr sztit,20h 
        ;-------------------------- 
        ;uninstall xhnadler 
        ;-----------------------------------------

        pop    fs:[0]            
        add    esp,4 
        pop    fs:[0]            
        add    esp,4 
      ;或者add esp,10h 
      
        pop    fs:[0]            
        add    esp,4

    invoke    ExitProcess,0 
;-----------------------------------------        
;异常处理句柄1,处理除法异常错误 
per_xHandler1 PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD 
        pushad 
        MOV    ESI,pExcept 
ASSUME ESI:PTR EXCEPTION_RECORD 
        TEST    [ESI].ExceptionFlags,1 
        JNZ    @cantdo1 
        TEST    [ESI].ExceptionFlags,6 
        JNZ    @unwind1 
        CMP    [ESI].ExceptionCode,0C0000094h 
        JNZ    @cantdo1 
        MOV    EDI,pContext

ASSUME EDI:PTR CONTEXT 
        m2m    [edi].regEbx,20            ;将ebx置20,修复除法错误,继续执行 
        popad 
    MOV EAX, ExceptionContinueExecution 
        RET

@unwind1: 
        invoke    MessageBox,0,CTEXT("state: unwinding in xhandler1..."),addr sztit,0 
@cantdo1: 
        popad 
        MOV    EAX,ExceptionContinueSearch 
    RET 
per_xHandler1 ENDP 
;----------------------------------------- 
;异常处理句柄2,处理内存写错误,扩展可以有其他的例子如自动扩充堆栈 
per_xHandler2 PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD 
        
        pushad 
    MOV    ESI,pExcept 
ASSUME ESI:PTR EXCEPTION_RECORD 
        MOV    EDI,pContext 
ASSUME EDI:PTR CONTEXT

        call    Dispcont                            ;显示一点lame的消息,自己调试用

        TEST    [ESI].ExceptionFlags,1 
        JNZ    @cantdo2 
        TEST    [ESI].ExceptionFlags,6 
        JNZ    @unwind2 
        CMP    [ESI].ExceptionCode,0C0000005h 
        JNZ    @cantdo2 
        .data                                        ;ASM的数据定义灵活性,如果需要这是可以的 
        validAddress dd 0 
        .code 
        
        m2m    [EDI].regEax,<offset validAddress>    ;置eax为有效地址      
        popad 
    MOV EAX, ExceptionContinueExecution 
        RET

@unwind2: 
        invoke    MessageBox,0,CTEXT("hmmm... unwinding in xhandler2..."),addr sztit,40h 
@cantdo2: 
        popad 
        MOV    EAX,ExceptionContinueSearch 
    RET 
per_xHandler2 ENDP 
;-----------------------------------------

per_xHandler3 PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD 
    pushad 
        MOV    ESI,pExcept 
ASSUME ESI:PTR EXCEPTION_RECORD 
        MOV    EDI,pContext 
ASSUME EDI:PTR CONTEXT     

        TEST    [ESI].ExceptionFlags,1 
        JNZ    @cantdo3 
        TEST    [ESI].ExceptionFlags,6 
        JNZ    @unwind3 
        ;----------------------------------------- 
                                                    
        push    ecx 
        mov    ecx,cs 
        xor    cl,cl 
        jecxz win2k_Xp        
win9X: 
        pop    ecx 
        CMP    [ESI].ExceptionCode,0C000001DH      ;非法指令异常,与2K/XP下的不一致 
        JNZ    @cantdo3 
        jmp    ok_here 
win2k_Xp: 
        pop    ecx                                  ;注意,只有在9X下才可以 
        CMP    [ESI].ExceptionCode,0C000001EH      ;非法指令异常->2K/XP 
        JNZ    @cantdo3                            ;sMc不成

        mov    [edi].regEip,offset safereturn 
        popad 
        mov    eax,0 
        ret 
        
        
        
        push    ebx 
        push    esi 
        push    edi      
comment $ 调用RtlUnwind展开堆栈        
        lea    ebx,unwindback 
        invoke    RtlUnwind,Expt3_frm,ebx,esi,0 
        $ 
        mov    dword ptr [esi+4],2                    ;置展开标志,准备展开,这里是 
                                                      ;手动代码 
        mov    ebx,fs:[0] 
       

selfun: 
        ;mov    eax,Expt2_frm                    ;这里显示了ASM手动展开的灵活性 
        mov    eax,Expt3_frm                      
        cmp    ebx,eax                            ;按照Jeremy Gordon的好像不大对头 
        ;cmp    dword ptr [ebx],-1                ;这样好像有问题,只好如上,请教答案          
        jz      unwindback 
        push    ebx 
        push    esi                                ; 压入Err和Exeption_registration结构 
        call    dword ptr[ebx+4] 
        add    esp,8 
        mov    ebx,[ebx] 
        jmp    selfun

unwindback: 
        invoke    MessageBox,0,CTEXT("I am Back!"),addr sztit,40h 
        pop    edi 
        pop    esi 
        pop    ebx                                  ;一定要保存这三个寄存器!

        MOV    EAX,[EDI].regEip 
        MOV    DWORD PTR[EAX],90909090H            ;改为nop指令...SMC呵呵这次不神秘了吧 
                                                    ;SMC注意连接选项 
        popad 
    MOV EAX, ExceptionContinueExecution 
        RET

@unwind3: 
        invoke    MessageBox,0,CTEXT("Note... unwinding in xhandler3..."),addr sztit,40h 
@cantdo3: 
        popad 
        MOV    EAX,ExceptionContinueSearch 
    RET 
per_xHandler3 ENDP 
;----------------------------------------- 
;lame routine for debug 
Dispcont    proc 
                inc    count 
                call    dispMsg 
                ret 
Dispcont    endp

dispMsg    proc 
        local szbuf[200]:byte 
        pushad 
        mov    eax,dword ptr[esi] 
        mov    ebx,dword ptr[esi+4] 
        mov    ecx,dword ptr[edi+0b8h] 
        mov    edx,dword ptr[edi+0a4h] 
        .data 
        fmt    db "Context eip--> %8X ebx--> %8X ",0dh,0ah 
                db "Flags Ex.c-> %8x ***--> %8X",0dh,0ah 
                db "it's the %d times xhandler was called!",0 
        .code 
        invoke    wsprintf,addr szbuf,addr fmt,ecx,edx,eax,ebx,count 
        invoke    MessageBox,0,addr szbuf,CTEXT("related Mess of context"),0 
        popad 
    ret 
dispMsg    endp

;;------------------------------------------------ 
END    _Start 
;---------------------------------下面是上面用到的宏,我的mac.h比较长,就不贴了----- 
    ddd    MACRO Text                        ;define data in .data section 
        local name                  ;This and other can be used as: ddd("My god!") 
        .data                  ;isn't cool? 
            name    db Text,0 
        .code 
        EXITM <addr name> 
    ENDM

CTEXT MACRO y:VARARG                    ;This is a good macro 
        LOCAL sym 
    CONST segment 
        IFIDNI <y>,<> 
            sym db 0 
        ELSE 
            sym db y,0 
        ENDIF 
    CONST ends 
        EXITM <OFFSET sym> 
    ENDM

    m2m MACRO M1, M2                          ;mov is too boring sometimes! 
      push M2 
      pop M1 
    ENDM    
;----------------------------------------- 
最后更正一点前面介绍的传送给final型的参数是指向EXCEPTION_POINTERS 的指针,压栈前的堆栈 
是如下的,不好意思,原来写的时候我也没深入研究,可能模糊了一点,如有错误,请大家指正 
push    ptEXCEPTION_POINTERS 
call    xHandler 
下面补充一个final参数获得的一个例子 
;-------------------------------------------- 
; Ex5,演示final处理句柄的参数获取,更正前面 
; 模糊的介绍 
;-------------------------------------------- 
.586 
.model flat, stdcall 
option casemap :none ; case sensitive 
include hd.h 
include mac.h

;;-------------- 
.data 
sztit db "exceptION MeSs,by hume[AfO]",0 
fmt    db "Context eip--> %8X ebx--> %8X ",0dh,0ah 
        db "Flags Ex.c-> %8x ***--> %8X",0 
szbuf db 200 dup(0) 
;;----------------------------------------- 
    .CODE 
_Start: 
        assume fs:nothing 
        push    offset _final_xHandler0 
        call    SetUnhandledExceptionFilter 
        xor    ebx,ebx 
        mov    eax,200 
        cdq 
        div    ebx 
        invoke    MessageBox,0,ddd("Good,divide overflow was solved!"),addr sztit,40h 
        xor    eax,eax 
        mov    [eax],ebx 
        
    invoke    ExitProcess,0   

;----------------------------------------- 
_final_xHandler0: 
        push    ebp 
        mov    ebp,esp 
        
        mov    eax,[ebp+8]      ;the pointer to EXCEPTION_POINTERS 
        mov    esi,[eax]        ;pointer to _EXCEPTION_RECORD 
        mov    edi,[eax+4]      ;pointer to _CONTEXT 
        test    dword ptr[esi+4],1 
        jnz    @_final_cnotdo 
        test    dword ptr[esi+4],6 
        jnz    @_final_unwind

        ;call    dispMsg 
       

        cmp    dword ptr[esi],0c0000094h 
        jnz    @_final_cnotdo

        mov    dword ptr [edi+0a4h],10 
        call    dispMsg 
      
        mov    eax,EXCEPTION_CONTINUE_EXECUTION      ;GO ON 
        jmp    @f

@_final_unwind: 
        invoke    MessageBox,0,CTEXT("state:In final unwind..."),addr sztit,0 
                                          ;好像不论处理不处理异常,系统展开的时候 
                                          ;都不会被调用,right? 
@_final_cnotdo:                            ;请教是真的吗?还是我写的有问题 
        mov    eax,EXCEPTION_CONTINUE_SEARCH 
        jmp    @f        
@@:      
        mov    esp,ebp 
        pop    ebp 
        ret 
;----------------------------------------- 
dispMsg    proc 
        pushad 
        mov    eax,[esi] 
        mov    ebx,[esi+4] 
        mov    ecx,[edi+0b8h] 
        mov    edx,[edi+0a4h] 
        invoke    wsprintf,addr szbuf,addr fmt,ecx,edx,eax,ebx 
        invoke    MessageBox,0,addr szbuf,CTEXT("related Mess of context"),0 
        popad 
    ret 
dispMsg    endp 
;;------------------------------------------------ 
        
END    _Start 
;====================================================================================

BTW:够长了吧,基本内容介绍完毕,更多内容下一部分介绍一点利用Seh的tricks,哪位大侠有什么好的想法 
或者有什么错误,请不吝指正,毕竟我是菜鸟吗...

===================================================================================== 
一点闲话:请CUT-------------------------- 
    最近很郁闷,生活上的工作上的,就这样懒懒懒散散地目无光彩地苟活于世,最近看了一点C++,有的地方 
头大,于是拿起老家伙asm写了点以前许诺过的东西,感觉还是ASM最有助于理解基本原理~~~不过C/C++给了我 
们另外的工具,虽然目标代码不够紧凑(理想状态),毕竟不需要我们每个人写内核,C/C++可以提高生产能力. 
    crack也一样,如果一些基本概念都不懂明白还谈什么crack?昨天看精华III一些高手的文章,真是惭愧啊!

    我的专业不是计算机或者以后永远也不会搞计算机,这些当也许只是业余爱好,永远不会放弃的业余爱好. 
    以后也许很长一段时间里面我会离开大家,毕竟,面临的还有生活. 
    感谢CCG,BCG的各位高手,AfO的成员们,以及那些曾无私指导过以及给我生活动力的朋友们! 
    我要奋斗! 
    我以此激励自己也同样希望能够激励大家! 
                                                        2002.1.28深夜~~沉思中




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值