VEH和VCH科普

本文转自梦织未来(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后面。大家可能有点儿不理解,我给说个例子,代码如下所示:

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);这样的话,打印出来的就是:
FirstVEHandler
LastVEHandler

这里又不会执行seHandler了,原因是EXCEPTION_CONTINUE_EXECUTION是忽略了此异常所以就进不了seHandler,但是记住我们必须在LastVEHandler里让eip加1,这里就等于LastVEHandler把异常给处理掉了。


从上面两幅图,虽然不能直观的看出几个函数的执行顺序,但是总结一下,他们的顺序也就是我说的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,我们可以简单看看这个函数。

.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);
它的汇编代码是这样的:

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)
{
        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,防止那些新手调试还是很有效的。

比如以下小程序:

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("存在调试器!");
}

阅读更多
上一篇反硬件断点
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭