Microsoft Detours 2.1簡介

NetRoc

http://www.DbgTech.net/

本文從主站點轉貼過來的,附件和pdf請訪問http://www.DbgTech.net/下載

一、簡介

《Windows高級調試》第一章中提到了一個基於Microsoft Detours庫的內存泄露檢查工具LeakDiag。本文對這個庫進行一些介紹。

一句話來說,Detours是一個用來在二進制級別上對程序中的函數(Function)或者過程(Procedure)進行修改的工具庫。一般我們將這種技術稱爲"Hook"。Detours的實現原理是將目標函數的前幾個字節改爲jmp指令跳轉到自己的函數地址,以此接管對目標函數的調用,並插入自己的處理代碼。在現實中,這種技術可以應用在很多場景下。比如Hook某些Windows API,在實際調用到系統函數前進行一些過濾工作;軟件中使用到了一些沒有源代碼的第三方庫,但是又想增強其中某些函數的功能,等等。

圖1 Hook前後的程序執行流程對比。

 

圖2 Hook前後目標函數和跳板代碼的改變

 

Detours相對其他一些Hook庫和自己實現的代碼來說,通常有以下這些優點:

  • 考慮全面,代碼非常穩定,並且經過了微軟自己衆多產品的驗證。
  • 可以簡單的用純C/C++代碼實現對類的成員函數的Hook。
  • 購買版權之後的Detours Professional還可以支持x64和IA64處理器。以此爲基礎編寫的代碼擁有更強的可移植性。
  • 使用簡單,不需要了解彙編指令以及技術細節。

二、使用方法

一般來說,使用Detours的代碼都具有固定的模式。Detours 1.5和Detours 2.1的接口函數變了很多,這裏按照2.1版本對基本的使用方法進行說明。

常用的函數有下面幾個:

  • DetourTransactionBegin() :開始一次Hook或者Unhook過程。
  • DetourUpdateThread() :列入一個在DetourTransaction過程中要進行update的線程。這個函數的作用稍微有一些複雜,會在後面專門說明。
  • DetourAttach() :添加一個要Hook的函數。
  • DetourDetach () :添加一個要Unhook的函數。
  • DetourTransactionCommit() :執行當前的Transaction過程。在這個函數中才會真正進行Hook或者Unhook操作。前面三個函數都只是做一些記錄工作。

在使用的時候,這幾個函數的調用步驟基本上也是按照上面列出來的順序。舉例來說,現在想Hook掉API函數MessageBoxA,將消息框彈出的消息修改掉,可以按下面的方法做。

進行Hook的步驟:

  1. 首先需要定義目標函數的原型。如果目標函數是Windows API,可以到MSDN中查閱,但是需要注意ANSI版本和Unicode版本的區別。如果沒有確切的原型聲明,或者目標函數是通過逆向工程找出來的,那麼需要定義一個和目標函數原型兼容的聲明,即參數個數和調用約定要相同。如MessageBoxA的原型是:

    int MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

    使用typedef定義如下:

    typedef int (WINAPI *pfnMessageBoxA)( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

  2. 聲明一個指向目標函數的函數指針:

    pfnMessageBoxA g_pMessageBoxA = ::MessageBoxA;

  3. 編寫Hook函數的代碼,用於替換目標函數。
  4. 調用DetourTransactionBegin開始一次Detours事務。
  5. 對進程中每個可能調用到目標函數的線程,都需要使用DetourUpdateThread加入到update隊列中。這是因爲Hook時修改目標函數的前幾個字節,如果某個線程剛好執行到這幾個字節的位置時,粗暴的修改掉會造成該線程出現異常。Detours事務處理時,會先枚舉並暫停update隊列中所有線程,獲取它們的指令指針,如果發現這種情況,則將指令指針修改到跳板代碼的對應字節上。這樣就避免出現崩潰的問題。
  6. 對每個需要Hook的函數,調用DetourAttach加入到事務列表中。
  7. 調用DetourTransactionCommit進行實際的Hook操作。

Unhook的過程和上面的流程基本一樣,只是第6步改爲調用DetourDetach函數。

Hook MessageBoxA的完整示例代碼如下:

//Hook函數的向前聲明

int WINAPI Hook_MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

 

//目標函數原型聲明

typedef int (WINAPI *pfnMessageBoxA)( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

 

//指向目標函數的指針

pfnMessageBoxA g_pMessageBoxA = ::MessageBoxA;

 

BOOL StartHook()

{//開始Hook

    DetourTransactionBegin();

 

    //只有一個線程,所以GetCurrentThread

    DetourUpdateThread( GetCurrentThread());

 

    //添加MessageBoxA的Hook

    if( DetourAttach( &(PVOID&)g_pMessageBoxA, Hook_MessageBoxA) != NO_ERROR)

    {

        printf( "Hook MessageBoxA fail./n");

    }

 

    //完成事務

    if( DetourTransactionCommit() != NO_ERROR)

    {

        printf( "DetourTransactionCommit fail/n");

        return FALSE;

    }

    else

    {

        printf( "DetourTransactionCommit ok/n");

        return TRUE;

    }

}

 

BOOL StopHook()

{//停止Hook

    DetourTransactionBegin();

    DetourUpdateThread( GetCurrentThread());

 

    if( DetourDetach( &(PVOID&)g_pMessageBoxA, Hook_MessageBoxA) != NO_ERROR)

    {

        printf( "Hook MessageBoxA fail./n");

    }

 

    if( DetourTransactionCommit() != NO_ERROR)

    {

        printf( "DetourTransactionCommit fail/n");

        return FALSE;

    }

    else

    {

        printf( "DetourTransactionCommit ok/n");

        return TRUE;

    }

}

 

int WINAPI Hook_MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)

{

    //需要調用原函數時,可以直接使用前面定義的指針變量

    return g_pMessageBoxA( hWnd, "MessageBox after hook.", "TestDetours", MB_OK);

}

在附件的示例代碼中還包含了Hook類成員函數的代碼。流程和上面基本一致,只是需要用一些強制轉換來對付編譯器的類型檢查。

另外,Detours還包含一系列其他函數,如果需要使用的話,可以參考Detours安裝目錄下的示例。

三、使用Detours的注意事項

總體來說,Detours庫的代碼是非常穩定的,但是如果使用方法不對,會造成一些問題。有下面一些地方需要特別注意:

  1. 一定要枚舉線程並調用DetourUpdateThread函數。否則可能出現很低機率的崩潰問題,這種問題很難被檢查出來。
  2. 如果Hook函數在DLL中,那麼絕大多數情況下不能在Unhook之後卸載這個DLL,或者卸載存在造成崩潰的危險。因爲某些線程的調用堆棧中可能還包含Hook函數,這時卸載掉DLL,調用堆棧返回到Hook函數時內存位置已經不是合法的代碼了。
  3. Detours庫設計時並沒有考慮到卸載的問題,這是因爲鉤子的卸載本身是不安全的。當Detours庫代碼存在於DLL中的時候,即使Unhook了所有函數,清理了所有自己使用到的函數,還是會佔用一些內存。卸載這個DLL會造成內存泄露,特別是反覆的進行加載DLL->Hook->Unhook->卸載DLL的過程,會讓這個問題變得非常嚴重。後面會用一篇專題文章來討論Detours內存泄露問題的調試和解決。
  4. 有一些非常短的目標函數是無法Hook的。因爲jmp指令需要佔用一定空間,有些函數太過短小,甚至不夠jmp指令的長度,自然是沒有辦法Hook掉的。

Detours不支持9x內核的Windows系統。因爲9x內核下的內存模型和NT內核下有非常大的差別。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章