本文转自梦织未来(www.mengwuji.net)
地址:http://www.mengwuji.net/forum.php?mod=viewthread&tid=1371
作者:mengwuji
很久没发帖子了,痛苦的失眠
,就顺便来讲讲VEH这玩意儿。
一般,再好的程序员,也不能说自己写出的代码是没bug的、执行后不会产生异常。所以呢,好的程序员总会在可能认为会出现异常问题的那段代码加一个异常处理。如下:
try
{
//可能会出问题的代码...
}except(1){
//出了问题就来执行这里的内容
}
这是c语言的异常处理,内部原理呢,是使用的SEH(结构化异常处理)对异常进行了监控,SEH我下篇帖子会详细说明。
上面的处理看起来是把“显然有可能发生异常”的代码给处理了。但是一个大型的项目,比如游戏,有一般十几万行到几十万行,甚至上百上千万行的代码,程序员没有注意到有段代码可能会产生异常,程序员就不会显式的调用try语句来捕获此异常。这时候,就需要别的办法来找问题了。
windows从xp以后,提供了一种简称为VEH(向量化异常处理)的机制,这个VEH呢,是对整个进程来说是全局的(SEH是针对线程的)异常捕获技术,只要通过简单的设置,只要当前进程发送了异常,都能被我们设置好的VEH捕获到。同时也提供了VCH,我们后面慢慢说。
下面我来说说具体实现。
windows提供了两个API用来添加VEH和VCH的,分别是:
PVOID WINAPI AddVectoredExceptionHandler(
_In_ ULONG FirstHandler,
_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler
);
PVOID WINAPI AddVectoredContinueHandler(
_In_ ULONG FirstHandler,
_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler
);
由于他们参数是一样的,为了下面讲解的清楚点儿,我先来介绍它们的参数。
FirstHandler-----这个参数,是当我们进程中添加了多个VEH或者VCH时才有效,比如调用了两次AddVectoredExceptionHandler,那么进程中存在两个向量化处理异常回调A和B,这时候此参数就决定了A先执行还是A后执行,如果FirstHandler等于零,那么A就代表最后执行,非零值就代表第一个执行。当然这种说法也不全对,要看B的FirstHandler参数具体是什么值
。
VectoredHandler------是一个回调函数,这个回调函数是我们提供的,当有异常发生时,windows就会把异常信息交给我们的这个回调函数去执行。
AddVectoredExceptionHandler是添加VEH,AddVectoredContinueHandler是添加VCH,区别在于AddVectoredExceptionHandler是执行在所有SEH的前面,AddVectoredContinueHandler是执行在所有SEH后面。大家可能有点儿不理解,我给说个例子,代码如下所示:


一般,再好的程序员,也不能说自己写出的代码是没bug的、执行后不会产生异常。所以呢,好的程序员总会在可能认为会出现异常问题的那段代码加一个异常处理。如下:
try
{
//可能会出问题的代码...
}except(1){
//出了问题就来执行这里的内容
}
这是c语言的异常处理,内部原理呢,是使用的SEH(结构化异常处理)对异常进行了监控,SEH我下篇帖子会详细说明。
上面的处理看起来是把“显然有可能发生异常”的代码给处理了。但是一个大型的项目,比如游戏,有一般十几万行到几十万行,甚至上百上千万行的代码,程序员没有注意到有段代码可能会产生异常,程序员就不会显式的调用try语句来捕获此异常。这时候,就需要别的办法来找问题了。
windows从xp以后,提供了一种简称为VEH(向量化异常处理)的机制,这个VEH呢,是对整个进程来说是全局的(SEH是针对线程的)异常捕获技术,只要通过简单的设置,只要当前进程发送了异常,都能被我们设置好的VEH捕获到。同时也提供了VCH,我们后面慢慢说。
下面我来说说具体实现。
windows提供了两个API用来添加VEH和VCH的,分别是:
PVOID WINAPI AddVectoredExceptionHandler(
_In_ ULONG FirstHandler,
_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler
);
PVOID WINAPI AddVectoredContinueHandler(
_In_ ULONG FirstHandler,
_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler
);
由于他们参数是一样的,为了下面讲解的清楚点儿,我先来介绍它们的参数。
FirstHandler-----这个参数,是当我们进程中添加了多个VEH或者VCH时才有效,比如调用了两次AddVectoredExceptionHandler,那么进程中存在两个向量化处理异常回调A和B,这时候此参数就决定了A先执行还是A后执行,如果FirstHandler等于零,那么A就代表最后执行,非零值就代表第一个执行。当然这种说法也不全对,要看B的FirstHandler参数具体是什么值

VectoredHandler------是一个回调函数,这个回调函数是我们提供的,当有异常发生时,windows就会把异常信息交给我们的这个回调函数去执行。
AddVectoredExceptionHandler是添加VEH,AddVectoredContinueHandler是添加VCH,区别在于AddVectoredExceptionHandler是执行在所有SEH的前面,AddVectoredContinueHandler是执行在所有SEH后面。大家可能有点儿不理解,我给说个例子,代码如下所示:
LONG __stdcall FirstVEHandler(
EXCEPTION_POINTERS *ExceptionInfo
)
{
printf("FirstVEHandler\n");
return EXCEPTION_CONTINUE_SEARCH;
}
LONG __stdcall LastVEHandler(
EXCEPTION_POINTERS *ExceptionInfo
)
{
printf("LastVEHandler\n");
ExceptionInfo->ContextRecord->Eip++;
return EXCEPTION_CONTINUE_EXECUTION;
}
void seHandler()
{
printf("seHandler\n");
}
void main()
{
AddVectoredExceptionHandler(0,FirstVEHandler);
AddVectoredContinueHandler(0,LastVEHandler);
__try{
__asm{ int 3 }
}__except(EXCEPTION_EXECUTE_HANDLER){
seHandler();
}
getchar();
}
这个代码肯定会引起异常,因为我在代码里直接用了一个int 3断点
,并且我用了一个结构化异常程序,当产生断点消息时,就会去执行seHandler函数。并且我还提供了一个VEH和一个VCH,那么他们被调用的情况是FirstVEHandler-----seHandler-----LastVEHandler.
但是打印出来后却是这样的:

但是打印出来后却是这样的:
FirstVEHandler
seHandler
LastVEHandler函数是没有被执行到,原因是__except(EXCEPTION_EXECUTE_HANDLER)这样的写法会让异常在seHandler处理了,处理完成后就没有异常了,所以LastVEHandler没有被执行。
为了xiang办法让LastVEHandler也执行,我们把代码改成这样的:__except(EXCEPTION_CONTINUE_EXECUTION);这样的话,打印出来的就是:
为了xiang办法让LastVEHandler也执行,我们把代码改成这样的:__except(EXCEPTION_CONTINUE_EXECUTION);这样的话,打印出来的就是:
FirstVEHandler
LastVEHandler
这里又不会执行seHandler了,原因是EXCEPTION_CONTINUE_EXECUTION是忽略了此异常所以就进不了seHandler,但是记住我们必须在LastVEHandler里让eip加1,这里就等于LastVEHandler把异常给处理掉了。
从上面两幅图,虽然不能直观的看出几个函数的执行顺序,但是总结一下,他们的顺序也就是我说的FirstVEHandler-----seHandler-----LastVEHandler.
为了弄明白AddVectoredExceptionHandler和AddVectoredContinueHandler到底做了什么,我们看看它们的反汇编代码:
AddVectoredExceptionHandler实际就是RtlAddVectoredExceptionHandler函数,我们看看此函数:
从上面两幅图,虽然不能直观的看出几个函数的执行顺序,但是总结一下,他们的顺序也就是我说的FirstVEHandler-----seHandler-----LastVEHandler.
为了弄明白AddVectoredExceptionHandler和AddVectoredContinueHandler到底做了什么,我们看看它们的反汇编代码:
AddVectoredExceptionHandler实际就是RtlAddVectoredExceptionHandler函数,我们看看此函数:
mov edi, edi
push ebp
mov ebp, esp
push 0
push [ebp+arg_4] ; VectoredHandler
push [ebp+arg_0] ; FirstHandler
call sub_77EE51DC
pop ebp
retn 8
这个函数没做什么,立马去调用了sub_77EE51DC这个函数,我们跟进去看看此函数内容:
.text:77EE51DC mov edi, edi
.text:77EE51DE push ebp
.text:77EE51DF mov ebp, esp
.text:77EE51E1 mov eax, large fs:18h
.text:77EE51E7 mov eax, [eax+30h]
.text:77EE51EA push esi
.text:77EE51EB push 10h
.text:77EE51ED push 0
.text:77EE51EF push dword ptr [eax+18h]
.text:77EE51F2 call RtlAllocateHeap ; 分配0x10大小的一个空间
.text:77EE51F7 mov esi, eax
.text:77EE51F9 test esi, esi
.text:77EE51FB jz short loc_77EE525F ; 分配失败跳转
.text:77EE51FD push ebx
.text:77EE51FE push edi
.text:77EE51FF push [ebp+arg_4]
.text:77EE5202 mov dword ptr [esi+8], 1
.text:77EE5209 call RtlEncodePointer ; 此函数既是加密也是解密,加密和解密的对象是我们提供的VectoredHandler,这里是加密。
.text:77EE520E mov ebx, [ebp+arg_8]
.text:77EE5211 imul ebx, 0Ch
.text:77EE5214 add ebx, offset unk_77F9723C ; 通过上面可以看出这里是定位一个全局数组,数组每个元素有0xC个大小
.text:77EE521A push ebx
.text:77EE521B mov [esi+0Ch], eax
.text:77EE521E lea edi, [ebx+4]
.text:77EE5221 call RtlAcquireSRWLockExclusive
.text:77EE5226 cmp [edi], edi ; 这里可以看出来是个链表
.text:77EE5228 jnz short loc_77EE5241
.text:77EE522A mov ecx, large fs:18h
.text:77EE5231 mov eax, [ebp+arg_8]
.text:77EE5234 mov ecx, [ecx+30h] ; Peb
.text:77EE5237 add eax, 2 ; 这里的2我在其他函数中分析大概是PROCESS_INITIALIZING标记
.text:77EE523A add ecx, 28h ; Peb->CrossProcessFlags
.text:77EE523D lock bts [ecx], eax
.text:77EE5241
.text:77EE5241 loc_77EE5241: ; CODE XREF: sub_77EE51DC+4Cj
.text:77EE5241 cmp [ebp+arg_0], 0
.text:77EE5245 jz loc_77ECF76D
.text:77EE524B mov eax, [edi] ; 头结点插入
.text:77EE524D mov [esi], eax
.text:77EE524F mov [esi+4], edi
.text:77EE5252 mov [eax+4], esi
.text:77EE5255 mov [edi], esi
.text:77EE5257
.text:77EE5257 loc_77EE5257: ; CODE XREF: sub_77EE51DC-15A62j
.text:77EE5257 push ebx
.text:77EE5258 call RtlReleaseSRWLockExclusive
.text:77EE525D pop edi
.text:77EE525E pop ebx
.text:77EE525F
.text:77EE525F loc_77EE525F: ; CODE XREF: sub_77EE51DC+1Fj
.text:77EE525F mov eax, esi
.text:77EE5261 pop esi
.text:77EE5262 pop ebp
.text:77EE5263 retn 0Ch
.text:77ECF76D loc_77ECF76D: ; CODE XREF: sub_77EE51DC+69j
.text:77ECF76D mov eax, [edi+4] ; 尾结点插入
.text:77ECF770 mov [esi], edi
.text:77ECF772 mov [esi+4], eax
.text:77ECF775 mov [eax], esi
.text:77ECF777 mov [edi+4], esi
.text:77ECF77A jmp loc_77EE5257
.text:77EE5263 sub_77EE51DC endp
汇编看着总是痛苦的,我弄成c的吧....
PVOID __declspec(naked) RtlGetProcessHeap()
{
__asm{
mov eax,fs:[0x18]
mov eax,[eax+0x30]
mov eax,[eax+0x18]
retn
}
}
_declspec(naked) PTEB NtGetTeb()
{
__asm{
mov eax,FS:[0x18]
}
}
typedef struct _RTL_VECTORE_HANDLER
{
LIST_ENTRY list;
ULONG Refs;
PVECTORED_EXCEPTION_HANDLER VectoredHandler;
}RTL_VECTORE_HANDLER,*PRTL_VECTORE_HANDLER;
typedef struct _VH_HEADER
{
RTL_SRWLOCK SRWLock;
LIST_ENTRY list;
}VH_HEADER,*PVH_HEADER;
extern VH_HEADER RtlpVectoredLock[2];
PVOID __stdcall RtlEncodePointer(IN PVOID Pointer)
{
ULONG Cookie;
NTSTATUS Status;
Status = ZwQueryInformationProcess(
(HANDLE)-1,
ProcessCookie,
&Cookie,
sizeof(Cookie),
NULL);
if(!NT_SUCCESS(Status))
{
return Pointer;
}
return (PVOID)((ULONG_PTR)Pointer ^ Cookie); //Cookie是一个进程相关的随机值,这个值和系统时间等数字有关
}
PVOID WINAPI AddVectoredExceptionHandler(
_In_ ULONG FirstHandler,
_In_ PVECTORED_EXCEPTION_HANDLER VectoredHandler,
_In_ ULONG Index
)
{
PRTL_VECTORE_HANDLER veh;
//分配一个veh
veh = RtlAllocateHeap(RtlGetProcessHeap(),0,sizeof(RTL_VECTORED_EXCEPTION_HANDLER));
if(veh != NULL)
{
//初始化一下,并且简单的给加一下密
veh->Refs = 1;
veh->VectoredHandler = RtlEncodePointer(VectoredHandler);
//RtlpVectoredLock这个是系统的一个全局数组
RtlAcquireSRWLockExclusive(&RtlpVectoredLock[Index].SRWLock);
if (IsListEmpty(&RtlpVectoredLock[Index].list))
{
NtGetTeb()->ProcessEnvironmentBlock->CrossProcessFlags = PROCESS_INITIALIZING + Index; //说明是第一次加,我们给一个标记吧
}
if(FirstHandler != 0) //加链表头部还是尾部
{
InsertHeadList(&RtlpVectoredLock[Index].list,
&veh->list);
}
else
{
InsertTailList(&RtlpVectoredLock[Index].list,
&veh->list);
}
RtlReleaseSRWLockExclusive(&RtlpVectoredLock[Index].SRWLock);
}
return veh;
}
执行AddVectoredExceptionHandler和AddVectoredContinueHandler函数都会最终是执行上面这个函数。
上面函数的结构是我通过分析别的地方得到的,参考了一下其他的开源项目。
注意,上面有一个系统全局数组RtlpVectoredLock,RtlpVectoredLock只有两个元素,每个元素都是一个VH_HEADER结构。当我们执行AddVectoredExceptionHandler的时候,系统会构造一个RTL_VECTORE_HANDLER结构,这个结构会加入到RtlpVectoredLock[0].list链表中;当我们执行AddVectoredContinueHandler的时候,系统构造的一个RTL_VECTORE_HANDLER结构会加入到RtlpVectoredLock[1].list链表中。
那当有异常的时候,我们的VEH和VCH是怎么得到执行的呢。
当发生异常的时候,首先我们异常所在的线程会进入内核中,然后内核进行异常信息的收集。收集好了后,会判断当前是用户模式异常还是内核模式异常。
用户模式异常的话,就判断当前进程有没有被调试,有的话,把此异常信息提交给调试器,然后调试器进行处理。没有调试器的话,那么内核会调用用户模式的KiUserExceptionDispatcher函数。KiUserExceptionDispatcher函数内部会去搜索我们为此进程添加的VEH和VCH和为此线程添加的SEH,这个时候,我们的VEH和VCH就会得到执行了。
内核模式异常的话,就判断当前有没有系统调试器,有的话,把此异常提交给系统调试器,然后调试器进程处理。没有系统调试器的话,那么内核就会调用内核模式的KiUserExceptionDispatcher函数,然后执行方式大致和同上。
通过研究发现是用户KiUserExceptionDispatcher函数执行了我们的VEH和VCH,我们可以简单看看这个函数。
上面函数的结构是我通过分析别的地方得到的,参考了一下其他的开源项目。
注意,上面有一个系统全局数组RtlpVectoredLock,RtlpVectoredLock只有两个元素,每个元素都是一个VH_HEADER结构。当我们执行AddVectoredExceptionHandler的时候,系统会构造一个RTL_VECTORE_HANDLER结构,这个结构会加入到RtlpVectoredLock[0].list链表中;当我们执行AddVectoredContinueHandler的时候,系统构造的一个RTL_VECTORE_HANDLER结构会加入到RtlpVectoredLock[1].list链表中。
那当有异常的时候,我们的VEH和VCH是怎么得到执行的呢。
当发生异常的时候,首先我们异常所在的线程会进入内核中,然后内核进行异常信息的收集。收集好了后,会判断当前是用户模式异常还是内核模式异常。
用户模式异常的话,就判断当前进程有没有被调试,有的话,把此异常信息提交给调试器,然后调试器进行处理。没有调试器的话,那么内核会调用用户模式的KiUserExceptionDispatcher函数。KiUserExceptionDispatcher函数内部会去搜索我们为此进程添加的VEH和VCH和为此线程添加的SEH,这个时候,我们的VEH和VCH就会得到执行了。
内核模式异常的话,就判断当前有没有系统调试器,有的话,把此异常提交给系统调试器,然后调试器进程处理。没有系统调试器的话,那么内核就会调用内核模式的KiUserExceptionDispatcher函数,然后执行方式大致和同上。
通过研究发现是用户KiUserExceptionDispatcher函数执行了我们的VEH和VCH,我们可以简单看看这个函数。
.text:77F07048 public KiUserExceptionDispatcher
.text:77F07048 KiUserExceptionDispatcher proc near ; DATA XREF: .text:off_77EF61D8o
.text:77F07048
.text:77F07048 var_14 = dword ptr -14h
.text:77F07048 var_10 = dword ptr -10h
.text:77F07048 var_C = dword ptr -0Ch
.text:77F07048 var_4 = dword ptr -4
.text:77F07048 arg_0 = dword ptr 4
.text:77F07048
.text:77F07048 cld
.text:77F07049 mov ecx, [esp+arg_0] //这里参数是内核模式提供的,这个函数不是call过来的,是jmp过来的,第二个参数是PCONTEXT类型,表示发生异常时线程的信息
.text:77F0704D mov ebx, [esp+0] //第一个参数是PEXCEPTION_RECORD类型,表示异常的信息
.text:77F07050 push ecx
.text:77F07051 push ebx
.text:77F07052 call RtlDispatchException //这个函数是我给起的名字.....下面我们不用看了,就看这个函数里面最重要
.text:77F07057 or al, al
.text:77F07059 jz short loc_77F07067
.text:77F0705B pop ebx
.text:77F0705C pop ecx
.text:77F0705D push 0
.text:77F0705F push ecx
.text:77F07060 call ZwContinue
.text:77F07065 jmp short loc_77F07072
.text:77F07067 ; ---------------------------------------------------------------------------
.text:77F07067
.text:77F07067 loc_77F07067: ; CODE XREF: KiUserExceptionDispatcher+11j
.text:77F07067 pop ebx //异常没有被处理....好吧
.text:77F07068 pop ecx
.text:77F07069 push 0
.text:77F0706B push ecx
.text:77F0706C push ebx
.text:77F0706D call ZwRaiseException
.text:77F07072
.text:77F07072 loc_77F07072: ; CODE XREF: KiUserExceptionDispatcher+1Dj
.text:77F07072 add esp, 0FFFFFFECh
.text:77F07075 mov [esp+14h+var_14], eax
.text:77F07078 mov [esp+14h+var_10], 1
.text:77F07080 mov [esp+14h+var_C], ebx
.text:77F07084 mov [esp+14h+var_4], 0
.text:77F0708C push esp
.text:77F0708D call RtlRaiseException
.text:77F07092 retn 8
.text:77F07092 KiUserExceptionDispatcher endp
KiUserExceptionDispatcher主要是通过调用RtlDispatchException 实现用户层异常的派发的。我们可以简单看看RtlDispatchException函数的汇编代码:
.text:77EDF8B4 RtlDispatchException proc near ; CODE XREF: KiUserExceptionDispatcher+Ap
.text:77EDF8B4
.text:77EDF8B4 var_64 = dword ptr -64h
.text:77EDF8B4 var_60 = dword ptr -60h
.text:77EDF8B4 var_5C = dword ptr -5Ch
.text:77EDF8B4 var_54 = dword ptr -54h
.text:77EDF8B4 var_14 = dword ptr -14h
.text:77EDF8B4 var_10 = dword ptr -10h
.text:77EDF8B4 StackBase = dword ptr -0Ch
.text:77EDF8B4 StackLimit = dword ptr -8
.text:77EDF8B4 var_1 = byte ptr -1
.text:77EDF8B4 arg_0 = dword ptr 8
.text:77EDF8B4 arg_4 = dword ptr 0Ch
.text:77EDF8B4
.text:77EDF8B4 ; FUNCTION CHUNK AT .text:77EC6019 SIZE 00000021 BYTES
.text:77EDF8B4 ; FUNCTION CHUNK AT .text:77EDE86D SIZE 00000058 BYTES
.text:77EDF8B4 ; FUNCTION CHUNK AT .text:77F3A8DA SIZE 00000014 BYTES
.text:77EDF8B4 ; FUNCTION CHUNK AT .text:77F3AB2D SIZE 0000001C BYTES
.text:77EDF8B4 ; FUNCTION CHUNK AT .text:77F50538 SIZE 0000005D BYTES
.text:77EDF8B4
.text:77EDF8B4 mov edi, edi
.text:77EDF8B6 push ebp
.text:77EDF8B7 mov ebp, esp
.text:77EDF8B9 sub esp, 64h
.text:77EDF8BC push esi
.text:77EDF8BD push [ebp+arg_4] ;PCONTEXT
.text:77EDF8C0 mov esi, [ebp+arg_0]
.text:77EDF8C3 push esi ;PEXCEPTION_RECORD
.text:77EDF8C4 mov [ebp+var_1], 0
.text:77EDF8C8 call RtlCallVectoredExceptionHandlers ;这里就是处理AddVectoredExceptionHandler函数所加入的VEH和VCH
.text:77EDF8CD test al, al
.text:77EDF8CF jnz loc_77F50538
.text:77EDF8D5 push ebx
.text:77EDF8D6 push edi
.text:77EDF8D7 lea eax, [ebp+StackBase]
.text:77EDF8DA push eax
.text:77EDF8DB lea eax, [ebp+StackLimit]
.text:77EDF8DE push eax
.text:77EDF8DF call sub_77EDF742 ;获取栈顶部和栈底部
.text:77EDF8E4 call sub_77F0732F ;获取线程的SEH链
.text:77EDF8E9 and [ebp+var_10], 0
.text:77EDF8ED push 0
.text:77EDF8EF push 4
.text:77EDF8F1 mov ebx, eax
.text:77EDF8F3 lea eax, [ebp+var_10]
.text:77EDF8F6 push eax
.text:77EDF8F7 push 22h
.text:77EDF8F9 or edi, 0FFFFFFFFh
.text:77EDF8FC push edi
.text:77EDF8FD mov byte ptr [ebp+arg_0+3], 1
.text:77EDF901 call NtQueryInformationProcess
.text:77EDF906 test eax, eax
.text:77EDF908 jl loc_77EDE8BB
.text:77EDF90E test byte ptr [ebp+var_10], 40h
.text:77EDF912 jz loc_77EDE8BB
.text:77EDF918 mov byte ptr [ebp+arg_0+3], 0
.text:77EDF91C
.text:77EDF91C loc_77EDF91C: ; CODE XREF: RtlDispatchException-FF7j
.text:77EDF91C call sub_77F0732F
.text:77EDF921 mov ebx, eax
.text:77EDF923 xor edi, edi
.text:77EDF925
.text:77EDF925 loc_77EDF925: ; CODE XREF: RtlDispatchException+F8j
.text:77EDF925 cmp ebx, 0FFFFFFFFh ;比较是否是SEH尾部
.text:77EDF928 jz loc_77EC6027
.text:77EDF92E cmp byte ptr [ebp+arg_0+3], 0
.text:77EDF932 jnz short loc_77EDF963
.text:77EDF934 cmp ebx, [ebp+StackLimit] ;下面的几句是比较SEH是否超出线程所允许的栈空间
.text:77EDF937 jb loc_77F3A8E5
.text:77EDF93D lea eax, [ebx+8]
.text:77EDF940 cmp eax, [ebp+StackBase]
.text:77EDF943 ja loc_77F3A8E5
.text:77EDF949 test bl, 3 ;比较是否是4字节对齐
.text:77EDF94C jnz loc_77F3A8E5
.text:77EDF952 mov eax, [ebx+4] ;下面的几句比较是判断我们提供的SEH回调函数不能位于线程栈空间内
.text:77EDF955 cmp eax, [ebp+StackLimit]
.text:77EDF958 jb short loc_77EDF963
.text:77EDF95A cmp eax, [ebp+StackBase]
.text:77EDF95D jb loc_77F3A8E5
.text:77EDF963
.text:77EDF963 loc_77EDF963: ; CODE XREF: RtlDispatchException+7Ej
.text:77EDF963 ; RtlDispatchException+A4j
.text:77EDF963 push [ebp+var_10]
.text:77EDF966 push dword ptr [ebx+4]
.text:77EDF969 call sub_77EDF783
.text:77EDF96E test al, al
.text:77EDF970 jz loc_77F3A8E5
.text:77EDF976 push dword ptr [ebx+4]
.text:77EDF979 lea eax, [ebp+var_14]
.text:77EDF97C push eax
.text:77EDF97D push [ebp+arg_4]
.text:77EDF980 push ebx
.text:77EDF981 push esi
.text:77EDF982 call sub_77F07198 ;这里就是调用当前线程的SEH回调函数了
.text:77EDF987 cmp edi, ebx
.text:77EDF989 jz loc_77F3A8DA
.text:77EDF98F
.text:77EDF98F loc_77EDF98F: ; CODE XREF: RtlDispatchException+5B02Cj
.text:77EDF98F xor ecx, ecx
.text:77EDF991 sub eax, ecx
.text:77EDF993 jz loc_77EC6019
.text:77EDF999 dec eax
.text:77EDF99A jnz loc_77F3AB2D
.text:77EDF9A0
.text:77EDF9A0 loc_77EDF9A0: ; CODE XREF: RtlDispatchException+70CDCj
.text:77EDF9A0 test byte ptr [esi+4], 8
.text:77EDF9A4 jnz loc_77EC6027
.text:77EDF9AA
.text:77EDF9AA loc_77EDF9AA: ; CODE XREF: RtlDispatchException+5B287j
.text:77EDF9AA ; RtlDispatchException+5B290j ...
.text:77EDF9AA mov ebx, [ebx]
.text:77EDF9AC jmp loc_77EDF925
.text:77EDF9AC RtlDispatchException endp
.text:77EDF9AC
这个函数的代码比这里罗列的要多,只是这个函数代码很多小段分布在其他地方,不好整理。
主要我们看看RtlCallVectoredExceptionHandlers函数的内容,这个函数的声明是:
BOOLEAN __stdcall RtlCallVectoredExceptionHandlers(
PEXCEPTION_RECORD ExceptionRecord,
PCONTEXT Context);
它的汇编代码是这样的:
主要我们看看RtlCallVectoredExceptionHandlers函数的内容,这个函数的声明是:
BOOLEAN __stdcall RtlCallVectoredExceptionHandlers(
PEXCEPTION_RECORD ExceptionRecord,
PCONTEXT Context);
它的汇编代码是这样的:
sub_77EDF9B6 proc near
arg_0= dword ptr 8
arg_4= dword ptr 0Ch
mov edi, edi
push ebp
mov ebp, esp
push 0
push [ebp+arg_4]
push [ebp+arg_0]
call RtlCallVectoreHandlers ;又是我自己起的名字.....
pop ebp
retn 8
sub_77EDF9B6 endp
RtlCallVectoredExceptionHandlers函数直接调用了RtlCallVectoreHandlers,我们看看RtlCallVectoreHandlers的声明:
BOOLEAN __stdcall RtlCallVectoreHandlers(
PEXCEPTION_RECORD ExceptionRecord,
PCONTEXT Context,
ULONG Index);
RtlCallVectoreHandlers函数的第三个参数是表示执行的是AddVectoredExceptionHandler函数所添加的VEH,为1就是执行AddVectoredContinueHandler函数所添加的VCH。
RtlCallVectoreHandlers的汇编代码也很不好复制在这里,可能IDA有相关操作可以复制零碎的汇编代码,知道的人请说一下...我这里把逆向的c代码贴出来...
BOOLEAN __stdcall RtlCallVectoreHandlers(
PEXCEPTION_RECORD ExceptionRecord,
PCONTEXT Context,
ULONG Index);
RtlCallVectoreHandlers函数的第三个参数是表示执行的是AddVectoredExceptionHandler函数所添加的VEH,为1就是执行AddVectoredContinueHandler函数所添加的VCH。
RtlCallVectoreHandlers的汇编代码也很不好复制在这里,可能IDA有相关操作可以复制零碎的汇编代码,知道的人请说一下...我这里把逆向的c代码贴出来...
BOOLEAN __stdcall RtlCallVectoreHandlers(PEXCEPTION_RECORD ExceptionRecord,PCONTEXT Context,ULONG Index)
{
PTEB Teb;
PLIST_ENTRY pList,pRemovList;
BOOLEAN bRet;
LONG control;
EXCEPTION_POINTERS Exceptins;
PRTL_VECTORE_HANDLER veh;
PVECTORED_EXCEPTION_HANDLER vHandler;
bRet = FALSE;
Teb = (PTEB)NtGetTeb();
if (!(Teb->ProcessEnvironmentBlock->CrossProcessFlags & (1<<(Index+2))))
{
//判断存在ProcessUsingVEH或者ProcessUsingVCH
return FALSE;
}
Exceptins.ExceptionRecord = ExceptionRecord;
Exceptins.ContextRecord = Context;
RtlAcquireSRWLockExclusive(&RtlpVectoredLock[Index].SRWLock);
pRemovList = NULL;
pList = &RtlpVectoredLock[Index].list.Flink;
while(pList != &RtlpVectoredLock[Index].list)
{
veh = CONTAINING_RECORD(pList,RTL_VECTORED_EXCEPTION_HANDLER,list);
veh->Refs ++;
RtlReleaseSRWLockExclusive(&RtlpVectoredLock[Index].SRWLock);
vHandler = (PVECTORED_EXCEPTION_HANDLER)RtlEncodePointer(veh->VectoredHandler);
//这里来执行我们的VEH或者VCH
control = vHandler(&Exceptins);
RtlAcquireSRWLockExclusive(&RtlpVectoredLock[Index].SRWLock);
pList = pList->Flink;
if(--veh->Refs == 0)
{
if(RemoveEntryList(&veh->list))
{
Teb->ProcessEnvironmentBlock->CrossProcessFlags = Index + 0x2;
}
//这里是把移除的vHandler加入到一个临时链表
veh->list.Flink = pRemovList;
pRemovList = &veh->list;
}
if (control == EXCEPTION_CONTINUE_EXECUTION)
{
//这里是不要往下执行veh的意思,我们就在此处终结执行
bRet = TRUE;
break;
}
}
RtlReleaseSRWLockExclusive(&RtlpVectoredLock[Index].SRWLock);
while (pRemovList)
{
veh = (PRTL_VECTORED_EXCEPTION_HANDLER)pRemovList;
pRemovList = pRemovList->Flink;
RtlFreeHeap(Teb->ProcessEnvironmentBlock->ProcessHeap,0,veh);
}
return bRet;
}
执行VEH或者VCH的原理倒是明白了,不知道大家有没有清楚。
实际这玩意儿做反调试很不错,使用好VEH、VCH和SEH,防止那些新手调试还是很有效的。
比如以下小程序:
实际这玩意儿做反调试很不错,使用好VEH、VCH和SEH,防止那些新手调试还是很有效的。
比如以下小程序:
int g_count = 0;
LONG __stdcall veHandler(
EXCEPTION_POINTERS *ExceptionInfo
)
{
g_count++;
ExceptionInfo->ContextRecord->Eip++;
return EXCEPTION_CONTINUE_EXECUTION;
}
DWORD WINAPI ThreadProc(
LPVOID lpThreadParameter
)
{
HANDLE hThread = (HANDLE)lpThreadParameter;
while (true)
{
if (WaitForSingleObject(hThread,1000) != WAIT_TIMEOUT)
{
printf("进程被调试!");
Sleep(1000);
ExitProcess(0);
}
__asm{
int 3
}
}
return 0;
}
DWORD WINAPI ThreadProc2(
LPVOID lpThreadParameter
)
{
static int i = 0;
while (true)
{
Sleep(1100);
if (i == g_count)
{
printf("进程被调试!");
Sleep(1000);
ExitProcess(0);
}
i = g_count;
}
return 0;
}
void main()
{
AddVectoredExceptionHandler(1,veHandler);
HANDLE hThread2 = CreateThread(NULL,NULL,ThreadProc2,NULL,NULL,NULL);
HANDLE hThread = CreateThread(NULL,NULL,ThreadProc,hThread2,NULL,NULL);
WaitForSingleObject(hThread,INFINITE);
printf("存在调试器!");
}