使用微軟Detours庫進行模組列舉

lyshark發表於2024-08-20

Detours 是微軟開發的一個強大的 Windows API 鉤子庫,用於監視和攔截函式呼叫。它廣泛應用於微軟產品團隊和眾多獨立軟體開發中,旨在無需修改原始程式碼的情況下實現函式攔截和修改。Detours 在除錯、監控、日誌記錄和效能分析等方面表現出色,已成為開發者的重要工具。本章將指導讀者運用 Detours 庫實現模組查詢與列舉功能,幫助讀者熟悉該庫的使用技巧。

DetourFindFunction

該函式的主要功能是透過模組名稱和函式名稱來獲取函式的地址,這對於在執行時動態載入模組並查詢函式地址非常有用。

函式原型

其中引數一用於指定函式的模組名稱、引數二則用於指定要查詢的函式名稱。

PVOID DetourFindFunction(
    _In_ LPCSTR pszModule,
    _In_ LPCSTR pszFunction
);

我們可以透過使用 DetourFindFunction 獲取自身程序內的 GetProcAddress 函式地址,並將其儲存在 MyGetProcAddress 函式指標中。然後使用 LoadLibraryA 載入指定的動態連結庫,並透過 MyGetProcAddress 函式指標獲取任意模組中的函式地址。

使用示例

#include <windows.h>
#include <iostream>
#include "detours.h"

#pragma comment(lib,"detours.lib")

typedef FARPROC(WINAPI *GetProcAddress_t)(HMODULE hModule, LPCSTR lpProcName);

int main(int argc, char *argv[])
{
    // 查詢 kernel32.dll 中的 GetProcAddress 函式地址
    PVOID pFuncAddr = DetourFindFunction("kernel32.dll", "GetProcAddress");
    if (pFuncAddr == NULL)
    {
        return 0;
    }

    std::cout << "GetProcAddress address: " << pFuncAddr << std::endl;

    // 將找到的地址轉換為函式指標
    GetProcAddress_t MyGetProcAddress = (GetProcAddress_t)pFuncAddr;

    // 使用找到的函式指標
    HMODULE hModule = LoadLibraryA("user32.dll");
    if (hModule != NULL)
    {
        // 查詢模組中彈窗函式地址
        FARPROC pMessageBoxA = MyGetProcAddress(hModule, "MessageBoxA");
        if (pMessageBoxA != NULL)
        {
            // 輸出彈窗地址
            std::cout << "MessageBoxA address: " << pMessageBoxA << std::endl;
        }

        FreeLibrary(hModule);
    }

    system("pause");
    return 0;
}

DetourCodeFromPointer

該函式的主要功能是處理可能的程式碼跳轉或包裝指標,並返回實際的程式碼入口點。這在處理被包裝或鉤子的程式碼時特別有用,因為它可以跳過鉤子或包裝層,直接獲取原始程式碼的地址。

函式原型

其中引數一用於指定指向程式碼的指標,引數二則用於接收指向全域性資料的指標。

PVOID DetourCodeFromPointer(
    _In_ PVOID pPointer,
    _Out_opt_ PVOID* ppGlobals
);

使用示例

#include <windows.h>
#include <iostream>
#include "detours.h"

#pragma comment(lib,"detours.lib")

// 一個示例函式
void SampleFunction()
{
    std::cout << "SampleFunction called." << std::endl;
}

int main(int argc, char *argv[])
{
    // 獲取 SampleFunction 的地址
    PVOID pFuncAddr = (PVOID)&SampleFunction;

    // 獲取實際的程式碼入口點
    PVOID pCodeAddr = DetourCodeFromPointer(pFuncAddr, NULL);

    // 輸出地址
    std::cout << "Original Function Address: " << pFuncAddr << std::endl;
    std::cout << "Code Entry Point Address: " << pCodeAddr << std::endl;

    system("pause");
    return 0;
}

DetourCopyInstruction

該函式的主要功能是複製給定地址的機器指令到目標地址,並處理指令中的相對地址(如跳轉、呼叫)。這在實現程式碼攔截、跳轉或重定向時非常有用,特別是在需要精確控制指令級別的操作時。

函式原型

其中引數一用於指定目標地址(即將指令複製到的地址),引數二用於儲存指令的額外資料池地址,引數三用於指定源地址(即要複製的指令的地址),引數四用於接收指令中目標地址(跳轉或呼叫的目標地址)的指標,引數五用於接收指令中額外資料大小的指標。

PVOID DetourCopyInstruction(
    PVOID pDst,
    PVOID *ppDstPool,
    PVOID pSrc,
    PVOID *ppTarget,
    LONG *plExtra
);

使用示例

#include <windows.h>
#include <iostream>
#include "detours.h"

#pragma comment(lib,"detours.lib")

// 一個示例函式
void SampleFunction()
{
    std::cout << "SampleFunction called." << std::endl;
}

int main(int argc, char *argv[])
{
    // 獲取 SampleFunction 的地址
    PVOID pSrc = (PVOID)&SampleFunction;

    // 建立一個緩衝區用於儲存複製的指令
    BYTE buffer[16] = { 0 };
    PVOID pDst = buffer;

    // 呼叫 DetourCopyInstruction 複製指令
    PVOID pNext = DetourCopyInstruction(pDst, NULL, pSrc, NULL, NULL);

    // 輸出結果
    std::cout << "Source Address: " << pSrc << std::endl;
    std::cout << "Next Instruction Address: " << pNext << std::endl;
    std::cout << "Copied Instructions: ";
    for (int i = 0; i < 16; i++)
    {
        std::printf("%02X ", buffer[i]);
    }
    std::cout << std::endl;

    system("pause");
    return 0;
}

DetourSetCodeModule

該函式的主要功能是設定指定程式碼模組的範圍,以便 Detours 可以正確地處理程式碼攔截和重定向。這在多模組應用程式中非常有用,因為它允許你精確控制哪些程式碼可以被攔截或重定向。

函式原型

其中引數一用於指定要設定的程式碼模組的控制代碼,引數二則是一個布林值,如果為 TRUE,表示僅限於該模組中的引用;如果為 FALSE,表示不限制引用。

BOOL WINAPI DetourSetCodeModule(
    HMODULE hModule,
    BOOL fLimitReferencesToModule
);

透過這些步驟,你可以使用 DetourSetCodeModule 設定程式碼模組的範圍,從而精確控制哪些程式碼可以被 Detours 攔截或重定向。

使用示例

#include <windows.h>
#include <iostream>
#include "detours.h"

#pragma comment(lib,"detours.lib")

int main(int argc, char *argv[])
{
    // 獲取當前程序的主模組控制代碼
    HMODULE hModule = GetModuleHandle(NULL);

    // 設定程式碼模組的範圍
    BOOL result = DetourSetCodeModule(hModule, TRUE);

    if (result)
    {
        std::cout << "Successfully set code module." << std::endl;
    }
    else
    {
        std::cerr << "Failed to set code module." << std::endl;
        return 1;
    }

    system("pause");
    return 0;
}

DetourGetContainingModule

該函式的主要功能是查詢包含指定地址的模組的控制代碼。這在進行程式碼攔截和重定向時非常有用,因為它允許你確定特定函式或程式碼段所在的模組。

函式原型

該函式僅需要傳入一個引數,即一個指向記憶體地址的指標,表示要查詢其所屬模組的地址。

HMODULE WINAPI DetourGetContainingModule(PVOID pvAddr);

使用示例

#include <windows.h>
#include <iostream>
#include "detours.h"

#pragma comment(lib,"detours.lib")

// 一個示例函式
void SampleFunction()
{
    std::cout << "SampleFunction called." << std::endl;
}

int main(int argc, char *argv[])
{
    // 獲取 SampleFunction 的地址
    PVOID pFuncAddr = (PVOID)&SampleFunction;

    // 呼叫 DetourGetContainingModule 獲取包含該地址的模組控制代碼
    HMODULE hModule = DetourGetContainingModule(pFuncAddr);

    if (hModule != NULL)
    {
        // 獲取模組檔名
        char moduleName[MAX_PATH] = { 0 };
        if (GetModuleFileNameA(hModule, moduleName, sizeof(moduleName)))
        {
            std::cout << "Module containing SampleFunction: " << moduleName << std::endl;
        }
        else
        {
            std::cerr << "Failed to get module file name." << std::endl;
        }
    }
    else
    {
        std::cerr << "Failed to find containing module." << std::endl;
        return 1;
    }

    system("pause");
    return 0;
}

DetourEnumerateModules

該函式的主要功能是遍歷當前程序中的所有模組,透過反覆呼叫該函式並傳入上一個模組的控制代碼,你可以列舉當前程序中的所有模組。

函式原型

該函式僅需要傳入一個引數,即上一個模組的控制代碼。如果是第一次呼叫該函式,應傳入 NULL

HMODULE WINAPI DetourEnumerateModules(HMODULE hModuleLast);

該函式通常可配合 DetourGetEntryPointDetourGetModuleSize 一起使用,透過三個函式的配合,則可獲取到當前程序中模組名、模組入口點及模組大小資訊。

使用示例

#include <windows.h>
#include <iostream>
#include "detours.h"

#pragma comment(lib,"detours.lib")

int main(int argc, char *argv[])
{
    HMODULE hModule = NULL;

    // 列舉當前程序中的所有模組
    while ((hModule = DetourEnumerateModules(hModule)) != NULL)
    {
        // 獲取模組檔名
        char moduleName[MAX_PATH] = { 0 };
        if (GetModuleFileNameA(hModule, moduleName, sizeof(moduleName)))
        {
            std::cout << "Module: " << moduleName << std::endl;
        }

        // 呼叫 DetourGetEntryPoint 獲取模組的入口點地址
        PVOID pEntryPoint = DetourGetEntryPoint(hModule);
        if (pEntryPoint != NULL)
        {
            std::cout << "Entry point address: " << pEntryPoint << std::endl;
        }

        // 呼叫 DetourGetModuleSize 獲取模組的大小
        DWORD moduleSize = DetourGetModuleSize(hModule);
        if (moduleSize != 0)
        {
            std::cout << "Module size: " << moduleSize << " bytes." << std::endl;
        }
    }
    system("pause");
    return 0;
}

DetourEnumerateExports

該函式的主要功能是列舉指定模組中的所有匯出函式,並對每個匯出函式呼叫指定的回撥函式。回撥函式可以用於處理或操作每個匯出函式。

函式原型

其中引數一用於指定要列舉的模組的控制代碼,引數二用於傳遞給回撥函式的上下文指標,可以是任何型別的資料,通常用於傳遞狀態資訊。引數三則指向回撥函式的指標,該回撥函式在每個匯出函式上呼叫。

BOOL WINAPI DetourEnumerateExports(
    HMODULE hModule,
    PVOID pContext,
    PF_DETOUR_ENUMERATE_EXPORT_CALLBACK pfExportCallback
);

在回撥函式中,引數一用於傳遞給 DetourEnumerateExports 的上下文指標。引數二指定匯出函式的序號。引數三指定匯出函式的名稱。引數四指定匯出函式的地址。

typedef BOOL (CALLBACK *PF_DETOUR_ENUMERATE_EXPORT_CALLBACK)(
    PVOID pContext,
    ULONG nOrdinal,
    LPCSTR pszName,
    PVOID pCode
);

使用示例

#include <windows.h>
#include <iostream>
#include "detours.h"

#pragma comment(lib,"detours.lib")

// 回撥函式,用於處理每個匯出函式
BOOL CALLBACK ExportCallback(PVOID pContext, ULONG nOrdinal, LPCSTR pszName, PVOID pCode)
{
    std::cout << "Ordinal: " << nOrdinal << ", Name: " << (pszName ? pszName : "(unnamed)") << ", Address: " << pCode << std::endl;
    return TRUE;
}

int main(int argc, char *argv[])
{
    // 獲取當前程序的主模組控制代碼
    HMODULE hModule = GetModuleHandle(NULL);
    if (hModule == NULL)
    {
        std::cerr << "Failed to get module handle." << std::endl;
        return 1;
    }

    // 呼叫 DetourEnumerateExports 列舉匯出函式
    if (!DetourEnumerateExports(hModule, NULL, ExportCallback))
    {
        std::cerr << "Failed to enumerate exports." << std::endl;
        return 1;
    }

    system("pause");
    return 0;
}

DetourEnumerateImports

該函式的主要功能是列舉指定模組中的所有匯入函式,並對每個匯入模組和匯入函式呼叫指定的回撥函式。回撥函式可以用於處理或操作每個匯入模組和匯入函式。

函式原型

引數一用於指定要列舉的模組控制代碼,引數二指定回撥函式上下文指標,引數三指定回撥函式指標(該回撥函式在每個匯入模組上呼叫),引數四指定回撥函式指標(該回撥函式在每個匯入函式上呼叫)。

BOOL WINAPI DetourEnumerateImports(
    HMODULE hModule,
    PVOID pContext,
    PF_DETOUR_IMPORT_FILE_CALLBACK pfImportFileCallback,
    PF_DETOUR_IMPORT_FUNC_CALLBACK pfImportFuncCallback
);

在檔案回撥函式中,引數一用於傳入上下文指標,引數二傳遞模組名稱。

typedef BOOL (CALLBACK *PF_DETOUR_IMPORT_FILE_CALLBACK)(
    PVOID pContext,
    LPCSTR pszFile
);

在函式回撥函式中,引數一用於傳入上下文指標,引數二為匯入函式的序號,引數三為匯入函式的名稱,引數四為指向匯入函式地址的指標。

typedef BOOL (CALLBACK *PF_DETOUR_IMPORT_FUNC_CALLBACK)(
    PVOID pContext,
    DWORD nOrdinal,
    LPCSTR pszFunc,
    PVOID *ppvFunc
);

使用示例

#include <windows.h>
#include <iostream>
#include "detours.h"

#pragma comment(lib, "detours.lib")

// 檔案回撥函式,用於處理每個匯入模組(DLL)
BOOL CALLBACK ImportFileCallback(PVOID pContext, LPCSTR pszFile)
{
    std::cout << "Import Module: " << pszFile << std::endl;
    return TRUE; // 繼續列舉
}

// 函式回撥函式,用於處理每個匯入函式
BOOL CALLBACK ImportFuncCallback(PVOID pContext, DWORD nOrdinal, LPCSTR pszFunc, PVOID *ppvFunc)
{
    std::cout << "  Ordinal: " << nOrdinal << ", Name: " << (pszFunc ? pszFunc : "(unnamed)") << ", Address: " << *ppvFunc << std::endl;
    return TRUE; // 繼續列舉
}

int main(int argc, char *argv[])
{
    // 獲取當前程序的主模組控制代碼
    HMODULE hModule = GetModuleHandle(NULL);
    if (hModule == NULL)
    {
        return 1;
    }

    // 呼叫 DetourEnumerateImports 列舉匯入函式
    if (!DetourEnumerateImports(hModule, NULL, (PF_DETOUR_IMPORT_FILE_CALLBACK)ImportFileCallback, (PF_DETOUR_IMPORT_FUNC_CALLBACK)ImportFuncCallback))
    {
        return 1;
    }

    system("pause");
    return 0;
}

相關文章