SEH("Structured Exception Handling"),即结构化异常处理.是操作系统提供给程序设计者的强有力的处理程序错
误或异常的武器.在VISUAL C++中你或许已经熟悉了_try{} _finally{} 和_try{} _except {} 结构,这些并不是
编译程序本身所固有的,本质上只不过是对windows内在提供的结构化异常处理的包装.
发生异常时系统的处理顺序(by Jeremy Gordon):
1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,这不是正好可以用来探测调试器的存在吗?
2.如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.
3.每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程, 可交由链起来的其他例程处理.
4.如果这些例程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger.
5.如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了最后异常处理例程的话,系统转向对它的调用.
6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框, 你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统就调用ExitProcess终结程序.
7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会.
从上面来看,值得提到的一点是,如果一个程序应用SetUnhandledExceptionFilter来安装异常处理函数,并且在此异常处理函数里面完成该程序的功能代码,并且通过int 3(单步执行)来引起异常,那么如果此程序被调试的话并且处理了int 3异常的话,SetUnhandledExceptionFilter所安装的异常处理函数就不会被调用,程序功能也因此会不正常,这也是反调试的一个有效手段.要对付这种手段需要修改windows的异常分发函数,强行让windows总把异常分发给最后异常处理函数.
那么安装异常处理函数就有两种方法,一种是SetUnhandledExceptionFilter,这样的话异常处理函数总是被安装到异常链的最后,并且具有一定的局限性但是此种安装方法能保证异常处理函数在进程有效的,此无论是哪个线程发生异常未能被处理,都会调用这个例程.另外一种方法是找到异常链直接把异常处理函数加入到异常链里面去,这样安装的异常处理函数是线程有效的,只有本线程才有用.
异常链的链表头放在fs:[0]里面在,每个异常结构如下:
_EXCEPTION_REGISTRATION struc
prev dd ? ;前一个_EXCEPTION_REGISTRATION结构
handler dd ? ;异常处理例程入口....呵呵,现在明白该怎么作了吧
_EXCEPTION_REGISTRATION ends
prev为0xFFFFFFFF的时候表示是异常链的链尾了.可以使用以下的代码完成安装:
push offset perThread_Handler ;//异常处理函数
push fs:[0]
mov fs:[0],esp ;//建立SEH的基本ERR结构,如果不明白,就仔细研究一下吧
perThread_Handler有四个参数:pExcept:DWORD,pErr:DWORD,pContext:DWORD,pDispatch意义如下:
pExcept: --- EXCEPTION_RECORD结构的指针
pErr: --- 前面ERR结构的指针
pContext: --- CONTEXT结构的指针
pDispatch:---没有发现有啥意义
EXCEPTION_RECORD以及CONTEXT结构如下:
==================EXCEPTION_RECORD STRUCT======================
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
===================CONTEXT STRUCT================================
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
而硬件断点都需要利用iDr0-iDr3来实现,那么perThread_Handler函数如下书写就可以清除硬件断点:
push ebp
mov ebp,esp
push ebx
mov eax,dword ptr ss:[ebp+10] //得到Context结构
push ebp
xor ebx,ebx
mov dword ptr ds:[eax+4],ebx //分别清除iDr0-iDr3
mov dword ptr ds:[eax+8],ebx
mov dword ptr ds:[eax+C],ebx
mov dword ptr ds:[eax+10],ebx
mov eax,0
pop ebx
leave
retn 10