winston 发表于 2011-12-6 11:59:37

C/C++ HOOK API(原理深入剖析之-LoadLibraryA)

这篇文章就来谈谈平常很常见的HOOK技术,这里呢。写得比较简单,方法很多。只讲原理!希望大鸟们别吐我口水哈 - -。好!切入正题。首先是概念吧。什么是钩子(HOOK)?
钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
这上面只是一个概念,对它有所了解而已。上面主要应用在Windows消息处理机制里面的一个解释。这里我只是单纯的谈谈拦截我们常用的LoadLibraryA加载这个函数。让我们的程序或者目标程序在调用这个函数加载链接库的时候,先执行我们自己写的函数,然后在进行正常加载。通俗的说就是a----->b.我们在中间加上一个c。 a-------->c----->b让他先执行c然后再执行b。这里的c就是我们自己的函数了。
呵呵,概念说得差不多了,开始行动写代码撒:


#include <iostream>
#include <Windows.h>
using namespace std;

#pragma warning( disable: 4309 )
#pragma warning( disable: 4311 )

typedef HMODULE ( WINAPI *HOOKAPI )( IN LPCSTR );
#define MYHOOKMETHOD ( __fun ) HMODULE WINAPI __fun
#define DECLARE_REGISTER ( __0bj, __lawfunc, __newfunc ) Inline_Hook< HOOKAPI, 1 > __Obj( __lawfunc, __newfunc )

struct __InlineHOOK_Base
{
    DWORD _argsBytes;
    void* _lawFunc;
    void* _newFunc;
    char_lawByteCode;
    char_newByteCode;

    bool unhook ( void )
    {
      // It's hooked.
      if ( memcmp( _newByteCode, _lawFunc, 16 ) == 0 )
      {
            DWORD dwOldFlag;
            VirtualProtect( _lawFunc, 8, PAGE_EXECUTE_READWRITE, &dwOldFlag );
            memcpy( _lawFunc, _lawByteCode, 16 );
            VirtualProtect( _lawFunc, 8, dwOldFlag, &dwOldFlag );
            return true;
      }

      return false;
    }

    bool hook ( void )
    {
      // It's saved.
      if ( memcmp( _lawByteCode, _lawFunc, 16 ) == 0 )
      {
            DWORD dwOldFlag;
            VirtualProtect( _lawFunc, 8, PAGE_EXECUTE_READWRITE, &dwOldFlag );
            memcpy( _lawFunc, _newByteCode, 16 );
            VirtualProtect( _lawFunc, 8, dwOldFlag, &dwOldFlag );
            return true;
      }

      return false;
    }

    __InlineHOOK_Base( void* lawfun, void* newfun, DWORD args );
};


void __declspec( naked ) __Inline_Hook_Func ( void )
{
    __asm
    {
      push ebp// save maybe usefull register.
      push ebx
      push esi
      push ecx
      call __InlineHOOK_Base::unhook       // first, remove the hook in order to call the normal function.
      test eax, eax                                    // check the remove was successful
      jz __return


__getargnum:
      mov eax, dword ptr // esp just is ecx, also is __InlineHOOK_Base's this pointer.
      mov ecx, dword ptr // get first 4 bytes, that is params total size.
      shr ecx, 2                         // get params num, equal with __InlineHOOK_Base::_argsBytes / sizeof( DWORD )
      test ecx, ecx                  // check whether there are params.
      jz __callfunc                  // no param


__pushargs:
      mov edx, esp                     // __InlineHOOK_Base's this pointer.
      add edx, 14h                     // navigate to first call ret addr.
      add edx, dword ptr; // add params size.
      push dword ptr;       // push the dll file name pointer.
      loop __pushargs


__callfunc:
      call                       // call my function .
      mov ecx, dword ptr// get __InlineHOOK_Base's this pointer.

      push edx                         // save my function return value.
      push eax

      call __InlineHOOK_Base::hook   // rehook.

      pop eax                  // get saved return value, provided to my superiors to use
      pop edx


__return:
      pop ecx
      pop esi
      pop ebx
      pop ebp
      ret
    }
}

__InlineHOOK_Base ::__ InlineHOOK_Base ( void* lawfun, void* newfun, DWORD args )
: _lawFunc( lawfun ), _newFunc( newfun ), _argsBytes( args * 4 )
{
    _newByteCode[ 0 ] = 0xB9;                               // mov ecx, ...
    ( DWORD& )_newByteCode[ 1 ] = ( DWORD )this;
    _newByteCode[ 5 ] = 0xB8;                              // mov eax, ...
    ( DWORD& )_newByteCode[ 6 ] = ( DWORD )__Inline_Hook_Func;
    ( WORD& )_newByteCode[ 10 ] = 0xD0FF;       // call eax
    _newByteCode[ 12 ] = 0x000000C3;               // ret

    if ( args > 0 )
    {
      _newByteCode[ 12 ] = 0xC2;      // ret ...
      ( WORD& )_newByteCode[ 13 ] = ( WORD )_argsBytes;
      _newByteCode[ 15 ] = 0;
    }

    memcpy( _lawByteCode, _lawFunc, 16 ); // save
}


template< typename _function, DWORD args >
struct Inline_Hook : __InlineHOOK_Base
{
    Inline_Hook( _function lawfun, _function newfun )
      :__InlineHOOK_Base( lawfun, newfun, args ) { hook(); }
    ~Inline_Hook( void ){ unhook(); }
};


MYHOOKMETHOD ( myLoadLibrary )( LPCSTR lpcStrFileName )
{
    ::MessageBox( NULL, lpcStrFileName, "LoadLibrary Name", MB_OK | MB_ICONINFORMATION );
    return LoadLibraryA( lpcStrFileName );
}


DECLARE_REGISTER ( __inline_hook , LoadLibraryA , myLoadLibrary );


int main ( void )
{
    HMODULE hIntstance = LoadLibraryA ( "d3d9.dll" );
    return 0;
}上面这个程序是我写的一个测试。原理很简单,也就是在调用LoadLibraryA
加载动态链接库之前,先把LoadLibraryA的前16个代码字节给替换成我们自己的HOOK拦截代码,原理跟我之前的一篇Shell Code原理类似!改变了前16个字节后,这时就是已经HOOK了的LoadLibraryA了。然后在程序调用这个函数,进入后。将先调用我们自己写的函数。这里我们自己的函数是myLoadLibrary。这里面我就随便写了个测试。弹一个MessageBox显示DLL的名称!然后再执行正常的LoadLibraryA。看到这里,或许大家会产生两个疑问。
1.为什么替换的是16个字节?
2.在调用了我们的函数后,再调用正常的LoadLibraryA。这里的LoadLibraryA不是已经被我们给替换了吗?怎么正常呢?
首先,第一个问题。这里就得看上方蓝色的函数__InlineHOOK_Base
了。先是这个结构体:
    DWORD _argsBytes;         // 参数所占的字节数
    void* _lawFunc;                  // 指向老的Hook前的LoadLibraryA函数的一个指针
    void* _newFunc;               // 指向我们自己的中间函数的指针
    char_lawByteCode;    // 保存正常的LoadLibraryA前16个代码字节,用于UnHook,不然怎么还原呢。呵呵!
    char_newByteCode;   // 我们替换给LoadLibraryA的16个代码字节,用于Hook,不然怎么执行我们自己的函数呢,呵呵!
所以在我们调用LoadLibraryA之前会调用__InlineHOOK_Base构造函数。因为是全局对象。如:
DECLARE_REGISTER ( __inline_hook , LoadLibraryA , myLoadLibrary );

    _newByteCode[ 0 ] = 0xB9;                               // mov ecx, ...
    ( DWORD& )_newByteCode[ 1 ] = ( DWORD )this;
    _newByteCode[ 5 ] = 0xB8;                              // mov eax, ...
    ( DWORD& )_newByteCode[ 6 ] = ( DWORD )__Inline_Hook_Func;
    ( WORD& )_newByteCode[ 10 ] = 0xD0FF;       // call eax
    _newByteCode[ 12 ] = 0x000000C3;               // ret

    if ( args > 0 )
    {
      _newByteCode[ 12 ] = 0xC2;      // ret ...
      ( WORD& )_newByteCode[ 13 ] = ( WORD )_argsBytes;
      _newByteCode[ 15 ] = 0;
    }
上面这段代码的功能就是将语句转化成字节码,存到_newByteCode数组中,然后在HOOK的时候会拷贝到正常的LoadLibraryA中。将其前16字节替换成这里的。为什么是16字节,原因含简单,那就是这里的字节码就只用得到15个。哈哈!将上面的字节码翻译成C++就是:
__Inline_Hook_Func();//一句!
其他的就是为了将this保存到ECX中,返回如果有参数,且这个LoadLibraryA只有一个参数,在我们替换的字节码中手工给保持堆栈平衡,就会ret _argBytes这么字节数!调用外面就不用ADD ESP了保持堆栈平衡了!这也是LoadLibraryA原先的返回方式!这里这些指令的用法和为什么把this保存到ECX中,就不多说了!我的SHELL CODE那篇文章里有提到!

    memcpy( _lawByteCode, _lawFunc, 16 ); // 保存正常的字节码,用于还原!

这样把我们要替换进去的字节码给准备好了,下一步就是拷贝进去的过程了。Inline_Hook 构造函数是调用了HOOK函数的。再看HOOK函数的实现是:

// It's saved.
      if ( memcmp( _lawByteCode, _lawFunc, 16 ) == 0 )// 看看是否保存了,否则还原不了没办法unhook。
      {
            DWORD dwOldFlag;
            VirtualProtect( _lawFunc, 8, PAGE_EXECUTE_READWRITE, &dwOldFlag );//这个函数就不用说了,呵呵,查资料吧!
            memcpy( _lawFunc, _newByteCode, 16 );    // 拷贝我们的HOOK代码进LoadLibraryA中!
            VirtualProtect( _lawFunc, 8, dwOldFlag, &dwOldFlag );
            return true;    // 拷贝成功!
      }unhook原理类似,也就是将正常的拷贝进去!
好了!到了现在,功能函数都差不多了,现在就差__Inline_Hook_Func
这个函数的实现了!

这个函数全是汇编。嘿嘿!我用的英文注释了的哈。大致能看明白,英语太差了!
这里只说它的功能:
1.这个函数的调用代码字节我们已经拷贝到了LoadLibraryA中。如果我们调用LoadLibraryA( "XXXX.DLL" );将会调用__Inline_Hook_Func
这个函数。这个函数的前缀是不是很奇怪?大鸟别笑。呵呵!我前面的文章也提到了这些前缀的意思。这里不用多说。现在看看我们拷贝进LoadLibraryA后,大家可以看看LoadLibraryA的字节码的前后对比:
替换前:
7C801D7B
8B FF
               


   mov         edi,edi

7C801D7D
55
                     
            
push      ebp

7C801D7E
8B EC
               
            
mov         ebp,esp

7C801D80
83 7D 08 00      


         
cmp         dword ptr ,0

7C801D84
53
                  


push      ebx

7C801D85
56   
                  


push      esi

7C801D86
74 14
               


je          7C801D9C

7C801D88
68 60 E1
80 7C   


   push      7C80E160h

7C801D8D FF 75 08                        push      dword ptr

7C801D90 FF 15 AC 13 80 7C         call      dword ptr ds:

7C801D96 85 C0                               test      eax,eax

7C801D98 59                                    pop         ecx

7C801D99 59                                    pop         ecx

7C801D9A 74 12                               je          7C801DAE

7C801D9C 6A 00                               push      0   

7C801D9E 6A 00                               push      0   

7C801DA0 FF 75 08                           push      dword ptr

7C801DA3 E8 AB FF FF FF               call      7C801D53

7C801DA8 5E                                    pop         esi

7C801DA9 5B                                    pop         ebx

7C801DAA 5D                                 pop         ebp

7C801DAB C2 04 00                        
ret         4
上面红色的字节码就是将要被替换的。 蓝色的ret 4可以看出开始的疑问,为什么是ret 4.
替换后:
7C801D7B
B9 E0 A5 42 00
             mov         ecx,offset
__Obj
(42A5E0h)

7C801D80
B8 BC 12 41 00
            mov         eax,offset
__Inline_Hook_Func
(4112BCh)

7C801D85
FF D0
                              call      eax
7C801D87
C2 04 00
                     
ret         4

7C801D8A
00
80 7C FF 75 08          add         byte ptr ,al

7C801D90 FF 15 AC 13 80 7C          call      dword ptr ds:

7C801D96 85 C0                              test      eax,eax

7C801D98 59                                 pop         ecx
7C801D99 59                                 pop         ecx

7C801D9A 74 12                               je          7C801DAE

7C801D9C 6A 00                               push      0   

7C801D9E 6A 00                               push      0   

7C801DA0 FF 75 08                           push      dword ptr

7C801DA3 E8 AB FF FF FF               call      7C801D53

7C801DA8 5E                                    pop         esi

7C801DA9 5B                                    pop         ebx

7C801DAA 5D                                 pop         ebp

7C801DAB C2 04 00                        
ret         4
上面的字节码,相信一看就明白了!呵呵!替换后就会在0x7C801D87这里返回了,下面的代码就作废了! - -


2.
__Inline_Hook_Func
这个函数在进入之后,先是unhook,因为下面要调用myLoadLibrary函数。肯定要正常的LoadLibraryA函数咯。之后就是一系列的参数准备。知道调用了myLoadLibrary函数,弹出了对话框。之后回到
__Inline_Hook_Func
函数。保存返回值(句柄)。之后再hook掉LoadLibraryA函数。让第二次还能进来先调用我们的函数。之后就是返回值给上层主调函数了!
上面的2问题也就解开了。那就是因为
__Inline_Hook_Func一开始就给我们unhook了。
myLoadLibrary函数使用的一直都是正常的
LoadLibraryA
函数!

其他:
这里只是讲了原理,练下手可以。呵呵!一般稍微有水平一点的程序是检测了这个
LoadLibraryA函数是否被修改的。技术这东西就是你有招我也有招!呵呵!

好了。这篇文章终于写完了,累!有什么不对的地方还望各位批评!在此感谢。。。
来自:http://blog.csdn.net/masefee/article/details/4566121


页: [1]
查看完整版本: C/C++ HOOK API(原理深入剖析之-LoadLibraryA)