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

9月都快結束了,之前一直忙到寫自己的東西加上上班。基本沒有時間研究下彙編和C C++方面的感興趣的東西。再怎麼說嘛,9月還是得寫一篇撒,以後每月至少一篇吧。給自己定了,希望大家監督。嘿嘿!

這篇文章就來談談平常很常見的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[16];
    char  _newByteCode[16];
 
    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] // esp just is ecx, also is __InlineHOOK_Base's this pointer.
        mov ecx, dword ptr[eax] // 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[eax]; // add params size.
        push dword ptr[edx];       // push the dll file name pointer.
        loop __pushargs
 
 
__callfunc:
        call [eax+8]                      // call my function .
        mov ecx, dword ptr[esp]  // 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[16];    // 保存正常的LoadLibraryA前16個代碼字節,用於UnHook,不然怎麼還原呢。呵呵!
    char  _newByteCode[16];   // 我們替換給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 [ebp+8],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 [ebp+8]
7C801D90 FF 15 AC 13 80 7C           call        dword ptr ds:[7C8013ACh]
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 [ebp+8]
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 [eax+875FF7Ch],al
7C801D90 FF 15 AC 13 80 7C          call        dword ptr ds:[7C8013ACh]
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 [ebp+8]
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函數是否被修改的。技術這東西就是你有招我也有招!呵呵!

好了。這篇文章終於寫完了,累!有什麼不對的地方還望各位批評!在此感謝。。。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章