Inline Hook 之监视任意函数

前面已经写过两次inline hook的博文了,第一篇为:《C/C++ HOOK API(原理深入剖析之-LoadLibraryA)》,这篇博文的方法是通过修改任意函数的前面N个字节,实现跳转并进入到我们自定义的hook函数里,执行完毕我们的hook函数之后,再直接调用被hook的函数。第一篇的方法没有考虑多线程的情况,所以在多线程环境下会有问题。第二篇为:《 Inline HOOK API 改进版(hot-patching)》,这篇的初衷是为了解决多线程的问题,因为这种方式是一直hook的,直到程序结束。因此在多线程情况下,并不会在hook与unhook期间执行被hook的函数。不过,第二种方式有局限性,它主要针对windows的很多API而设计,在函数头部必须有空闲的两个字节和函数首地址之前的5个空闲字节(一般为5个nop指令),这样能够实现一个short jmp和一个long jmp。从而实现了hook,在此就不再累述了。

好了,本文已经是第三次写hook相关的东西了。前两种hook有个共同点就是他们都是直接hook掉某个函数,也就是说,当执行完我们自定义的hook函数之后,又从被hook函数的首部开始执行,被hook函数一进入就被跳转了。而本文,则要实现在某个函数体内部任意地方进行hook并跳转,执行完我们的函数之后,再回到原来的位置继续向下执行完未执行的逻辑。那么,初看这种方式似乎与前面写的两篇hook没有什么差别,都是hook,都是跳转然后回到被hook的函数。但仔细一想,你会发现本文要实现的方式要比前面两种hook复杂,因为hook的地方是函数体内任意地方,那么回来的时候就不是直接调用被hook的函数了,而是要回到之前hook的地方去。这期间就涉及到hook函数的返回地址问题和被hook函数的返回地址问题。

说了这么多,可能还是有点晕,先不管为什么要这么做,也不管这种hook方式能有什么用途(在本文最后会说明用途),下面我们先写一些代码,在实践中来想一想这种方式有什么用途,并且与之前的两篇hook进行比较。

首先,我们需要一个自定义的hook函数,这个函数也就是被hook函数被hook后跳转到的地方,这个hook函数负责hook与unhook,还可以监视寄存器,监视内存,也可以管理hook的次数,以供我们灵活的hook需求。直接贴代码吧:

view plaincopy to clipboardprint?

#include <iostream>  

#include <windows.h>  

 

#pragma warning( disable : 4311 )  

#pragma warning( disable : 4312 )  

 

#define HOOK_BYTES 5  

typedef unsigned int uint;  

 

uint  hookAddr = 0;  

char  old_code[ HOOK_BYTES ];  

char  new_code[ HOOK_BYTES ];  

 

void printRegisters( void );  

 

bool hook( void )  

{  

    DWORD dwFlag;  

    if ( VirtualProtect( ( void* )hookAddr, HOOK_BYTES, PAGE_EXECUTE_READWRITE, &dwFlag ) )  

    {  

        memcpy( old_code, ( void* )hookAddr, HOOK_BYTES );  

        memcpy( ( void* )hookAddr, new_code, HOOK_BYTES );  

        VirtualProtect( ( void* )hookAddr, HOOK_BYTES, dwFlag, &dwFlag );  

        return true;  

    }  

    return false;  

}  

 

void unhook( void )  

{  

    DWORD dwFlag;  

    if ( VirtualProtect( ( void* )hookAddr, HOOK_BYTES, PAGE_EXECUTE_READWRITE, &dwFlag ) )  

    {  

        memcpy( ( void* )hookAddr, old_code, HOOK_BYTES );  

        VirtualProtect( ( void* )hookAddr, HOOK_BYTES, dwFlag, &dwFlag );  

    }  

}  

 

namespace global  

{  

    uint gEAX = 0;  

    uint gEBX = 0;  

    uint gECX = 0;  

    uint gEDX = 0;  

    uint gESP = 0;  

    uint gEBP = 0;  

    uint gESI = 0;  

    uint gEDI = 0;  

 

    uint gRet = 0;  // 临时的返回地址  

    uint gTmp = 0;  // 一些临时的值保存  

    uint gPar = 0;  // 被hook函数的正常返回地址  

    uint gCnt = 1;  // 当前hook的次数  

    uint gMax = 0;  // 最大hook次数,为0表示一直hook  

    bool bEnt = 0;  // 是否为第一次进入hook函数  

}  

 

void __declspec( naked ) hook_jmp( void )  

{  

    __asm  

    {  

__entry:  

        pushad  

        {  

            cmp global::bEnt, 0   // 如果没有进入则表示需要unhook  

            je  __first  

 

            cmp global::gMax, 0   // 如果为0,则一直启用hook逻辑  

            je  __second  

 

            mov eax, global::gCnt  

            cmp eax, global::gMax // 如果当前hook次数没有达到最大次数,则继续  

            jl  __second  

 

            mov global::gCnt, 1   // reset state  

            mov global::bEnt, 0   // reset state  

            mov global::gMax, 0   // reset state  

 

            mov eax, global::gPar // 被hook函数的正常返回地址  

            mov global::gRet, eax // 准备跳转到被hook函数的上层调用,结束hook  

            popad  

            jmp __ret  

        }  

 

__first:  

        // 保存相关重要寄存器值  

        {  

            popad  

            mov global::gEAX, eax    

            mov global::gEBX, ebx  

            mov global::gECX, ecx  

            mov global::gEDX, edx  

            mov global::gESP, esp   

            mov global::gEBP, ebp  

            mov global::gESI, esi  

            mov global::gEDI, edi  

        }  

 

        // 第一次进入,unhook并监视相关状态  

        pushad  

        {  

            mov global::bEnt, 1    // 记录状态  

 

            mov edi, global::gEBP  // 被hook函数的ebp  

            mov eax, [ edi + 4 ]   // 被hook函数的返回地址(其上层调用地址)  

            mov global::gPar, eax  // 保存返回地址  

            mov esi, __entry       // 将被hook函数的返回地址修改为  

            mov [ edi + 4 ], esi   // 本函数的首地址,以便执行完被hook函数的  

                                   // 剩余逻辑之后能够返回到本函数,决定是否  

                                   // 还需要hook。  

 

            call printRegisters    // 打印寄存器值[测试],或者其他  

            call unhook            // unhook  

 

            mov  eax, hookAddr     // 获得被hook的内存地址  

            mov  global::gRet, eax  

        }  

        popad  

 

        pop  global::gTmp          // 移除本函数的返回地址,并将hook的地址设置  

        jmp __ret                  // 为本函数的返回地址,从而实现跳转  

 

__second:  

        // 第二次进入, 继续hook, 这次进入是被hook函数ret返回的,没有新的ret地址被压栈  

        {  

            mov global::bEnt, 0    // 设置状态  

            add global::gCnt, 1    // 增加hook计数  

 

            call hook              // hook  

 

            mov  eax, global::gPar // 将被hook函数的返回地址设置为本函数的  

            mov  global::gRet, eax // 返回地址,从而实现正常的函数流程  

        }  

        popad  

 

__ret:  

        push global::gRet                   // 修改本函数的返回地址  

        ret  

    }  

}  

 

void setHookBytes( uint addr )  

{  

    hookAddr = addr;  

    new_code[ 0 ] = ( char )0xe8; // call 指令机器码  

    ( uint& )new_code[ 1 ] = ( uint )hook_jmp - addr - 5; // 计算跳转偏移  

}  

 

void printRegisters( void )  

{  

    printf( "EAX = 0x%08x\n", global::gEAX );  

    printf( "EBX = 0x%08x\n", global::gEBX );  

    printf( "ECX = 0x%08x\n", global::gECX );  

    printf( "EDX = 0x%08x\n", global::gEDX );  

    printf( "ESP = 0x%08x\n", global::gESP );  

    printf( "EBP = 0x%08x\n", global::gEBP );  

    printf( "ESI = 0x%08x\n", global::gESI );  

    printf( "EDI = 0x%08x\n", global::gEDI );  

#include <iostream>

#include <windows.h>

#pragma warning( disable : 4311 )

#pragma warning( disable : 4312 )

#define HOOK_BYTES 5

typedef unsigned int uint;

uint  hookAddr = 0;

char  old_code[ HOOK_BYTES ];

char  new_code[ HOOK_BYTES ];

void printRegisters( void );

bool hook( void )

{

    DWORD dwFlag;

    if ( VirtualProtect( ( void* )hookAddr, HOOK_BYTES, PAGE_EXECUTE_READWRITE, &dwFlag ) )

    {

        memcpy( old_code, ( void* )hookAddr, HOOK_BYTES );

        memcpy( ( void* )hookAddr, new_code, HOOK_BYTES );

        VirtualProtect( ( void* )hookAddr, HOOK_BYTES, dwFlag, &dwFlag );

        return true;

    }

    return false;

}

void unhook( void )

{

    DWORD dwFlag;

    if ( VirtualProtect( ( void* )hookAddr, HOOK_BYTES, PAGE_EXECUTE_READWRITE, &dwFlag ) )

    {

        memcpy( ( void* )hookAddr, old_code, HOOK_BYTES );

        VirtualProtect( ( void* )hookAddr, HOOK_BYTES, dwFlag, &dwFlag );

    }

}

namespace global

{

    uint gEAX = 0;

    uint gEBX = 0;

    uint gECX = 0;

    uint gEDX = 0;

    uint gESP = 0;

    uint gEBP = 0;

    uint gESI = 0;

    uint gEDI = 0;

    uint gRet = 0;  // 临时的返回地址

    uint gTmp = 0;  // 一些临时的值保存

    uint gPar = 0;  // 被hook函数的正常返回地址

    uint gCnt = 1;  // 当前hook的次数

    uint gMax = 0;  // 最大hook次数,为0表示一直hook

    bool bEnt = 0;  // 是否为第一次进入hook函数

}

void __declspec( naked ) hook_jmp( void )

{

    __asm

    {

__entry:

        pushad

        {

            cmp global::bEnt, 0   // 如果没有进入则表示需要unhook

            je  __first

            cmp global::gMax, 0   // 如果为0,则一直启用hook逻辑

            je  __second

            mov eax, global::gCnt

            cmp eax, global::gMax // 如果当前hook次数没有达到最大次数,则继续

            jl  __second

            mov global::gCnt, 1   // reset state

            mov global::bEnt, 0   // reset state

            mov global::gMax, 0   // reset state

            mov eax, global::gPar // 被hook函数的正常返回地址

            mov global::gRet, eax // 准备跳转到被hook函数的上层调用,结束hook

            popad

            jmp __ret

        }

__first:

        // 保存相关重要寄存器值

        {

            popad

            mov global::gEAX, eax 

            mov global::gEBX, ebx

            mov global::gECX, ecx

            mov global::gEDX, edx

            mov global::gESP, esp

            mov global::gEBP, ebp

            mov global::gESI, esi

            mov global::gEDI, edi

        }

        // 第一次进入,unhook并监视相关状态

        pushad

        {

            mov global::bEnt, 1    // 记录状态

            mov edi, global::gEBP  // 被hook函数的ebp

            mov eax, [ edi + 4 ]   // 被hook函数的返回地址(其上层调用地址)

            mov global::gPar, eax  // 保存返回地址

            mov esi, __entry       // 将被hook函数的返回地址修改为

            mov [ edi + 4 ], esi   // 本函数的首地址,以便执行完被hook函数的

                                   // 剩余逻辑之后能够返回到本函数,决定是否

                                   // 还需要hook。

            call printRegisters    // 打印寄存器值[测试],或者其他

            call unhook            // unhook

            mov  eax, hookAddr     // 获得被hook的内存地址

            mov  global::gRet, eax

        }

        popad

        pop  global::gTmp          // 移除本函数的返回地址,并将hook的地址设置

        jmp __ret                  // 为本函数的返回地址,从而实现跳转

__second:

        // 第二次进入, 继续hook, 这次进入是被hook函数ret返回的,没有新的ret地址被压栈

        {

            mov global::bEnt, 0    // 设置状态

            add global::gCnt, 1    // 增加hook计数

            call hook              // hook

            mov  eax, global::gPar // 将被hook函数的返回地址设置为本函数的

            mov  global::gRet, eax // 返回地址,从而实现正常的函数流程

        }

        popad

__ret:

        push global::gRet                   // 修改本函数的返回地址

        ret

    }

}

void setHookBytes( uint addr )

{

    hookAddr = addr;

    new_code[ 0 ] = ( char )0xe8; // call 指令机器码

    ( uint& )new_code[ 1 ] = ( uint )hook_jmp - addr - 5; // 计算跳转偏移

}

void printRegisters( void )

{

    printf( "EAX = 0x%08x\n", global::gEAX );

    printf( "EBX = 0x%08x\n", global::gEBX );

    printf( "ECX = 0x%08x\n", global::gECX );

    printf( "EDX = 0x%08x\n", global::gEDX );

    printf( "ESP = 0x%08x\n", global::gESP );

    printf( "EBP = 0x%08x\n", global::gEBP );

    printf( "ESI = 0x%08x\n", global::gESI );

    printf( "EDI = 0x%08x\n", global::gEDI );

}

如上,hook_jmp函数即为我们自定义的hook函数,当被hook函数被hook之后,就会跳转到这个函数里,执行相关逻辑,上面我加了很详细的注释。应该很容易看懂。还是先看怎么使用这套方法,再来细说,代码如下:

view plaincopy to clipboardprint?

void testHook( void )  

{  

    printf( "This is a hook test 1.\n" );  

    printf( "This is a hook test 2.\n" );  

    printf( "This is a hook test 3.\n" );  

    printf( "This is a hook test 4.\n" );  

    printf( "______________________\n" );  

}  

 

int main( void )  

{  

    uint hook_addr = 0x0042ec7b;  

    setHookBytes( hook_addr );  

      

    global::gMax = 2;  

    if ( hook() )  

    {  

        testHook();  

        testHook();  

        testHook();  

    }  

    system( "pause" );  

    return 0;  

void testHook( void )

{

    printf( "This is a hook test 1.\n" );

    printf( "This is a hook test 2.\n" );

    printf( "This is a hook test 3.\n" );

    printf( "This is a hook test 4.\n" );

    printf( "______________________\n" );

}

int main( void )

{

    uint hook_addr = 0x0042ec7b;

    setHookBytes( hook_addr );

   

    global::gMax = 2;

    if ( hook() )

    {

        testHook();

        testHook();

        testHook();

    }

    system( "pause" );

    return 0;

}

如上,testHook函数即为被hook的函数,在main函数中,0x0042ec7b则为testHook函数里的第二个printf调用的地址,在你的机器上可能不一样。这里只是测试之用。testHook函数具体反汇编代码如下:view plaincopy to clipboardprint?

void testHook( void )  

{  

0042EC50  push        ebp    

0042EC51  mov         ebp,esp   

0042EC53  sub         esp,0C0h   

0042EC59  push        ebx    

0042EC5A  push        esi    

0042EC5B  push        edi    

0042EC5C  lea         edi,[ebp-0C0h]   

0042EC62  mov         ecx,30h   

0042EC67  mov         eax,0CCCCCCCCh   

0042EC6C  rep stos    dword ptr es:[edi]   

    printf( "This is a hook test 1.\n" );  

0042EC6E  push        offset string "This is a hook test 1.\n" (487E24h)   

0042EC73  call        @ILT+4550(_printf) (42D1CBh)   

0042EC78  add         esp,4   

    printf( "This is a hook test 2.\n" );  

0042EC7B  push        offset string "This is a hook test 2.\n" (487E08h)   

0042EC80  call        @ILT+4550(_printf) (42D1CBh)   

0042EC85  add         esp,4   

    printf( "This is a hook test 3.\n" );  

0042EC88  push        offset string "This is a hook test 3.\n" (487DECh)   

0042EC8D  call        @ILT+4550(_printf) (42D1CBh)   

0042EC92  add         esp,4   

    printf( "This is a hook test 4.\n" );  

0042EC95  push        offset string "This is a hook test 4.\n" (487DD0h)   

0042EC9A  call        @ILT+4550(_printf) (42D1CBh)   

0042EC9F  add         esp,4   

    printf( "______________________\n" );  

0042ECA2  push        offset string "_____________________.\n" (487DB4h)   

0042ECA7  call        @ILT+4550(_printf) (42D1CBh)   

0042ECAC  add         esp,4   

}  

0042ECAF  pop         edi    

0042ECB0  pop         esi    

0042ECB1  pop         ebx    

0042ECB2  add         esp,0C0h   

0042ECB8  cmp         ebp,esp   

0042ECBA  call        @ILT+3570(__RTC_CheckEsp) (42CDF7h)   

0042ECBF  mov         esp,ebp   

0042ECC1  pop         ebp    

0042ECC2  ret  

void testHook( void )

{

0042EC50  push        ebp 

0042EC51  mov         ebp,esp

0042EC53  sub         esp,0C0h

0042EC59  push        ebx 

0042EC5A  push        esi 

0042EC5B  push        edi 

0042EC5C  lea         edi,[ebp-0C0h]

0042EC62  mov         ecx,30h

0042EC67  mov         eax,0CCCCCCCCh

0042EC6C  rep stos    dword ptr es:[edi]

    printf( "This is a hook test 1.\n" );

0042EC6E  push        offset string "This is a hook test 1.\n" (487E24h)

0042EC73  call        @ILT+4550(_printf) (42D1CBh)

0042EC78  add         esp,4

    printf( "This is a hook test 2.\n" );

0042EC7B  push        offset string "This is a hook test 2.\n" (487E08h)

0042EC80  call        @ILT+4550(_printf) (42D1CBh)

0042EC85  add         esp,4

    printf( "This is a hook test 3.\n" );

0042EC88  push        offset string "This is a hook test 3.\n" (487DECh)

0042EC8D  call        @ILT+4550(_printf) (42D1CBh)

0042EC92  add         esp,4

    printf( "This is a hook test 4.\n" );

0042EC95  push        offset string "This is a hook test 4.\n" (487DD0h)

0042EC9A  call        @ILT+4550(_printf) (42D1CBh)

0042EC9F  add         esp,4

    printf( "______________________\n" );

0042ECA2  push        offset string "_____________________.\n" (487DB4h)

0042ECA7  call        @ILT+4550(_printf) (42D1CBh)

0042ECAC  add         esp,4

}

0042ECAF  pop         edi 

0042ECB0  pop         esi 

0042ECB1  pop         ebx 

0042ECB2  add         esp,0C0h

0042ECB8  cmp         ebp,esp

0042ECBA  call        @ILT+3570(__RTC_CheckEsp) (42CDF7h)

0042ECBF  mov         esp,ebp

0042ECC1  pop         ebp 

0042ECC2  ret 

我们hook的就是第18行(0042EC7B)那句代码,setHookBytes构建了一个5字节的call语句,0xe8为CALL指令的机器码,后面4个字节是CALL的偏移量(目标地址 - 当前地址 - CALL指令占用的5个字节)。

在main函数中,构建了hook的5个字节之后,设置了hook次数,如main函数那段代码的第15行:global::gMax = 2,则会hook两次。然后是main函数那段代码的第16行,调用hook函数,将5个字节的call指令写入0042EC7B中,并保存了0042EC7B中原来的代码到old_code中。之后,我们便可以调用testHook函数进行测试hook的流程了。最终输出结果为:

This is a hook test 1.

EAX = 0x00000017

EBX = 0x7ffdc000

ECX = 0x8df97741

EDX = 0x00499148

ESP = 0x0012fd84

EBP = 0x0012fe54

ESI = 0x00000000

EDI = 0x0012fe54

This is a hook test 2.

This is a hook test 3.

This is a hook test 4.

______________________

This is a hook test 1.

EAX = 0x00000017

EBX = 0x7ffdc000

ECX = 0x8df97741

EDX = 0x00499148

ESP = 0x0012fd84

EBP = 0x0012fe54

ESI = 0x00000000

EDI = 0x0012fe54

This is a hook test 2.

This is a hook test 3.

This is a hook test 4.

______________________

This is a hook test 1.

This is a hook test 2.

This is a hook test 3.

This is a hook test 4.

______________________

可以看出,前面两次调用testHook函数时,都执行了hook_jmp函数,并调用了printRegisters函数将寄存器打印了出来,之后又回到testHook中,继续输出后面的3句字符串。当两次hook之后,第三次调用testHook时,就不会再输出寄存器了,也没有被hook了。

我们来看几个比较hook_jmp中比较关键的几个地方:

第104到110行:这段汇编代码,主要用于保存testHook函数(被hook函数)的正常的返回地址(main函数里调用testHook的下一句指令的地址)到global::gPar变量中,并将hook_jmp的首地址(也就是__entry标签指示的地址)写入testHook函数的返回地址所在的内存里。这样当unhook并执行完testHook之后又能回到hook_jmp中,进一步判断是否需要下一次hook。如果不需要再hook(已经达到最大hook次数)时,则会执行第79到80行的两句汇编代码,这两句汇编代码的作用是将hook_jmp函数的返回地址设置为testHook函数正常的返回地址,也就是main函数里调用testHook函数的下一句汇编代码的地址(ret指令的原理如果不清楚,请看前两篇hook文章或查阅相关资料)。这样一来,当不再需要hook时,就能顺利的从hook_jmp函数返回后直接跳转到main函数的作用域里。这样整个调用流程就符合原本的调用流程了。

第113行:这句代码是在调用了printRegister函数之后进行unhook操作,将原本的5个字节的代码重新拷贝到testHook函数的相应代码地址的内存里,本例中为testHook函数中第二句printf函数调用的地址。unhook之后,第115到116行的两句代码与第79到80行的两句汇编代码类似,只不过这时是将被hook的内存地址设置为hook_jmp的返回地址,这样就能在第一次进入testHook函数并执行完毕返回时,能够跳转到被hook的地址(hookTest函数里第2句printf调用的地址)继续向下执行剩余的逻辑。

第131到132行:这两句汇编代码与第79到80行的两句汇编代码一致,都是将main函数里相应的代码地址设置为hook_jmp函数的ret返回地址,这样就能直接从hook_jmp跳转到main函数里继续向下执行,这样也就代表testHook被顺利的调用完成。

所以,总结下来,hook_jmp函数会进入两次,第一次用于监视一些数据,本例只监视了相关寄存器,还可以增加监视指定内存地址等等。第一次进入时,会保存被hook函数(testHook函数)的返回地址,并将其修改为hook_jmp函数的首地址,这样做是为了执行完testHook函数之后能够第二次进入hook_jmp函数。那么,第二次进入后,首先是判断是否还需要hook,不需要则直接返回到main函数里,如果还要继续hook,则再次调用hook函数,然后跳转到main函数里。这样就构成了一个严密的调用流程,一切都看起来很和谐的调用,有点类似缓冲区溢出攻击的原理。

hook_jmp函数中需要注意寄存器的保存,否则输出的寄存器值并不是testHook函数执行到hook位置时的寄存器状态,这样就丧失了监视的意义。

原理上其实比较简单,构建稍微细致了一些,与前两篇hook最大的不同就是需要手动修改ret的返回地址,从而达到hook的目的,不像之前的两篇hook,在进入hook函数之后,要回到被hook的函数时,只需要直接call就可以了,并不需要维护ret指令的返回地址。另外,由于本文的hook方式与第一篇的hook方式类似,所以本文的方式并没有支持多线程环境。

再者,本文的方式还有另外一些局限性:

1. 不能hook函数大括号以外的代码,也就是反汇编代码中大括号之前和之后的代码,因为本文的hook方式是需要通过被hook函数的ebp获取ret地址的,如果设置ebp的相关代码被hook,就得不到正确的ret返回地址等。而且hook大括号之外的代码意义并不大。

2. 被hook的函数的栈帧不能被编译器优化掉,否则得不到ret地址和ebp。

3. hook的代码地址必须为某条汇编指令机器码序列的首地址,本文并没有兼容任意代码地址hook,当然任意代码地址是可以实现的,不过在实际中要监视一些状态,这样做并没有太大的意义。所以你需要明确的知道你hook的代码地址是合法的,并且不会破坏原有的代码逻辑。

好了,整个hook的逻辑和原理就介绍得差不多了,再附上一张流程图:

如图所示,testHook函数如果被hook了,它是不会直接返回到main函数的,而是要先进入hook_jmp函数,再由hook_jmp函数返回到main函数,知道不再hook的时候,testHook才有“权利”直接返回到main函数里。

好了差不多就介绍完了,最后说说这套hook的用途,其实最初我是想用于反外挂检测上,于是写了这么一套hook逻辑,方便在不用调试客户端程序的情况下,监视在运行有外挂的客户端的一些关键的反外挂检测点,例如某个反外挂检测逻辑是否被执行,某些检测结果值的监视等。外挂一般加了比较强悍的保护壳,这些壳具有反调试等功能。典型的就是VMP壳和UPX壳。当然在这之前尝试了脱壳等方式调试跟踪有外挂的客户端,但是脱壳和绕过反调试机制的工作量都相对较大一些,我们的一贯思想又是以最少的工作量去做到好的反外挂效果。于是我便写了这套hook规则,用于检测当前反外挂系统是否被破解,是否被绕开。通过这套规则,就可以很方便的设立观测点,监视相关逻辑和数据,当然还可以有更多用途,此处不再一一说明,另外为此还写了一个可视化工具,方便工作中使用,并成功检测了多个外挂的破解机制,如图:

左边为hook逻辑的界面,右边tool页面为调用任意函数的功能,同时支持查看模块和查看并修改内存。基本上已经够用了。- -

你可能感兴趣的:(多线程,汇编,String,api,测试,hook)