> 技术文档 > 使用微软Detours库进行模块枚举

使用微软Detours库进行模块枚举

Detours 是微软开发的一个强大的 Windows API 钩子库,用于监视和拦截函数调用。它广泛应用于微软产品团队和众多独立软件开发中,旨在无需修改原始代码的情况下实现函数拦截和修改。Detours 在调试、监控、日志记录和性能分析等方面表现出色,已成为开发者的重要工具。本章将指导读者运用 Detours 库实现模块查询与枚举功能,帮助读者熟悉该库的使用技巧。

DetourFindFunction

该函数的主要功能是通过模块名称和函数名称来获取函数的地址,这对于在运行时动态加载模块并查找函数地址非常有用。

函数原型

其中参数一用于指定函数的模块名称、参数二则用于指定要查找的函数名称。

PVOIDDetourFindFunction( _In_ LPCSTR pszModule, _In_ LPCSTR pszFunction);
  • 1.
  • 2.
  • 3.
  • 4.

我们可以通过使用 DetourFindFunction 获取自身进程内的 GetProcAddress 函数地址,并将其存储在 MyGetProcAddress 函数指针中。然后使用 LoadLibraryA 加载指定的动态链接库,并通过 MyGetProcAddress 函数指针获取任意模块中的函数地址。

使用示例

#include #include #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;}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

DetourCodeFromPointer

该函数的主要功能是处理可能的代码跳转或包装指针,并返回实际的代码入口点。这在处理被包装或钩子的代码时特别有用,因为它可以跳过钩子或包装层,直接获取原始代码的地址。

函数原型

其中参数一用于指定指向代码的指针,参数二则用于接收指向全局数据的指针。

PVOIDDetourCodeFromPointer( _In_ PVOID pPointer, _Out_opt_ PVOID* ppGlobals);
  • 1.
  • 2.
  • 3.
  • 4.
使用示例

#include #include #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;}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

DetourCopyInstruction

该函数的主要功能是拷贝给定地址的机器指令到目标地址,并处理指令中的相对地址(如跳转、调用)。这在实现代码拦截、跳转或重定向时非常有用,特别是在需要精确控制指令级别的操作时。

函数原型

其中参数一用于指定目标地址(即将指令拷贝到的地址),参数二用于存储指令的额外数据池地址,参数三用于指定源地址(即要拷贝的指令的地址),参数四用于接收指令中目标地址(跳转或调用的目标地址)的指针,参数五用于接收指令中额外数据大小的指针。

PVOIDDetourCopyInstruction( PVOID pDst, PVOID *ppDstPool, PVOID pSrc, PVOID *ppTarget, LONG *plExtra);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
使用示例

#include #include #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;}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

DetourSetCodeModule

该函数的主要功能是设置指定代码模块的范围,以便 Detours 可以正确地处理代码拦截和重定向。这在多模块应用程序中非常有用,因为它允许你精确控制哪些代码可以被拦截或重定向。

函数原型

其中参数一用于指定要设置的代码模块的句柄,参数二则是一个布尔值,如果为 TRUE,表示仅限于该模块中的引用;如果为 FALSE,表示不限制引用。

BOOL WINAPIDetourSetCodeModule( HMODULE hModule, BOOL fLimitReferencesToModule);
  • 1.
  • 2.
  • 3.
  • 4.

通过这些步骤,你可以使用 DetourSetCodeModule 设置代码模块的范围,从而精确控制哪些代码可以被 Detours 拦截或重定向。

使用示例

#include #include #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;}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

DetourGetContainingModule

该函数的主要功能是查找包含指定地址的模块的句柄。这在进行代码拦截和重定向时非常有用,因为它允许你确定特定函数或代码段所在的模块。

函数原型

该函数仅需要传入一个参数,即一个指向内存地址的指针,表示要查找其所属模块的地址。

HMODULE WINAPIDetourGetContainingModule(PVOID pvAddr);
  • 1.
使用示例

#include #include #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;}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.

DetourEnumerateModules

该函数的主要功能是遍历当前进程中的所有模块,通过反复调用该函数并传入上一个模块的句柄,你可以枚举当前进程中的所有模块。

函数原型

该函数仅需要传入一个参数,即上一个模块的句柄。如果是第一次调用该函数,应传入 NULL

HMODULE WINAPIDetourEnumerateModules(HMODULE hModuleLast);
  • 1.

该函数通常可配合 DetourGetEntryPointDetourGetModuleSize 一起使用,通过三个函数的配合,则可获取到当前进程中模块名、模块入口点及模块大小信息。

使用示例

#include #include #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;}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

DetourEnumerateExports

该函数的主要功能是枚举指定模块中的所有导出函数,并对每个导出函数调用指定的回调函数。回调函数可以用于处理或操作每个导出函数。

函数原型

其中参数一用于指定要枚举的模块的句柄,参数二用于传递给回调函数的上下文指针,可以是任何类型的数据,通常用于传递状态信息。参数三则指向回调函数的指针,该回调函数在每个导出函数上调用。

BOOL WINAPIDetourEnumerateExports( HMODULE hModule, PVOID pContext, PF_DETOUR_ENUMERATE_EXPORT_CALLBACK pfExportCallback);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

在回调函数中,参数一用于传递给 DetourEnumerateExports 的上下文指针。参数二指定导出函数的序号。参数三指定导出函数的名称。参数四指定导出函数的地址。

typedef BOOL (CALLBACK *PF_DETOUR_ENUMERATE_EXPORT_CALLBACK)( PVOID pContext, ULONG nOrdinal, LPCSTR pszName, PVOID pCode);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
使用示例

#include #include #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;}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

DetourEnumerateImports

该函数的主要功能是枚举指定模块中的所有导入函数,并对每个导入模块和导入函数调用指定的回调函数。回调函数可以用于处理或操作每个导入模块和导入函数。

函数原型

参数一用于指定要枚举的模块句柄,参数二指定回调函数上下文指针,参数三指定回调函数指针(该回调函数在每个导入模块上调用),参数四指定回调函数指针(该回调函数在每个导入函数上调用)。

BOOL WINAPIDetourEnumerateImports( HMODULE hModule, PVOID pContext, PF_DETOUR_IMPORT_FILE_CALLBACK pfImportFileCallback, PF_DETOUR_IMPORT_FUNC_CALLBACK pfImportFuncCallback);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

在文件回调函数中,参数一用于传入上下文指针,参数二传递模块名称。

typedef BOOL (CALLBACK *PF_DETOUR_IMPORT_FILE_CALLBACK)( PVOID pContext, LPCSTR pszFile);
  • 1.
  • 2.
  • 3.
  • 4.

在函数回调函数中,参数一用于传入上下文指针,参数二为导入函数的序号,参数三为导入函数的名称,参数四为指向导入函数地址的指针。

typedef BOOL (CALLBACK *PF_DETOUR_IMPORT_FUNC_CALLBACK)( PVOID pContext, DWORD nOrdinal, LPCSTR pszFunc, PVOID *ppvFunc);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
使用示例

#include #include #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;}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.