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);
該函式通常可配合 DetourGetEntryPoint
及 DetourGetModuleSize
一起使用,透過三個函式的配合,則可獲取到當前程序中模組名、模組入口點及模組大小資訊。
使用示例
#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;
}