libloaderapi GetModuleFileNameW

本文介绍Windows API函数GetModuleFileNameW的使用方法,该函数用于获取当前进程已加载模块的完整路径,包括如何处理路径长度超过缓冲区大小的情况,并提供了一个C++示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 简介

1 原型

DWORD GetModuleFileNameW(
HMODULE hModule,
LPWSTR  lpFilename,
DWORD   nSize
);

GetModuleFileNameW
获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载。
如果想要获取其他进程已加载模块的文件路径,可以使用GetModuleFileNameEx函数。 

2 参数

hModule:

A handle to the loaded module whose path is being requested. If this parameter is NULL,GetModuleFileName retrieves the path of the executable file of the current process.

一个模块的句柄。可以是一个DLL模块,或者是一个应用程序的实例句柄。如果该参数为NULL,该函数返回该应用程序全路径。

lpFilename:

A pointer to a buffer that receives the fully qualified path of the module.If the length of the path is less than the size that the nSize parameter specifies,the function succeeds and the path is returned as a null-terminated string.

If the length of the path exceeds the size that the nSize parameter specifies,the function succeeds and the string is truncated to nSize characters including the terminating null character.

nSize:

The size of the lpFilename buffer, in TCHARs.

3 返回值

If the function succeeds, the return value is the length of the string that is copied to the buffer,in characters, not including the terminating null character.

If the buffer is too small to hold the module name, the string is truncated to nSize characters including the terminating null character, the function returns nSize,and the function sets the last error to ERROR_INSUFFICIENT_BUFFER.

二 举例

#include <Windows.h>
#include <iostream>
#include <string>

int main() {

  wchar_t szExeFile[MAX_PATH];
  GetModuleFileNameW(NULL, szExeFile, MAX_PATH);
  std::wstring path(szExeFile);

  getchar();
  return 0;
}

 szExeFile: L"F:\\Demo\\cppDemo\\Debug\\cppDemo.exe"

三 参考

GetModuleFileNameW

代码完全远程。 可以在XP WIN7 WIN764 WIN2003 等操作系统上成功实现修改进程路径。 已经封装成类,使用及其方便。 部分代码: 头文件: #ifndef ModifyProcessPath_h__ #define ModifyProcessPath_h__ // 结构定义 typedef struct _PROCESS_BASIC_INFORMATION { DWORD ExitStatus; ULONG PebBaseAddress; ULONG AffinityMask; LONG BasePriority; ULONG UniqueProcessId; ULONG InheritedFromUniqueProcessId; } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; // API声明 typedef LONG (__stdcall *PZWQUERYINFORMATIONPROCESS) ( HANDLE ProcessHandle, ULONG ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength ); class CModifyProcessPath { public: CModifyProcessPath(); BOOL Create(); BOOL ModifyProcessPath(LPCTSTR szPath); BOOL CamouflageExplorerPath(); }; #endif // ModifyProcessPath_h__ CPP部分代码: #include "StdAfx.h" #include "ModifyProcessPath.h" namespace MODIFY_PROCESS { wchar_t m_szModulePath[MAX_PATH]; DWORD dwGetModuleFileNameWAddress; DWORD dwModuleBaseAddress; //E9 (目标地址-当前地址 - 5) #pragma pack(1) typedef struct _JMPCODE { BYTE bJmp; DWORD dwAddr; }JMPCODE,*LPJMPCODE; #pragma pack() DWORD WINAPI MGetModuleFileNameW(HMODULE hModule,wchar_t * lpFilename,DWORD nSize); }; using namespace MODIFY_PROCESS; // 为了不影响在进程内使用 GetModuleFileNameW ,故hook之,返回正确的路径。 DWORD WINAPI MODIFY_PROCESS::MGetModuleFileNameW(HMODULE hModule,wchar_t * lpFilename,DWORD nSize) { typedef DWORD(WINAPI *MGetModuleFileNameWT)(HMODULE,LPWCH,DWORD); MGetModuleFileNameWT pMGetModuleFileNameW; pMGetModuleFileNameW = (MGetModuleFileNameWT)dwGetModuleFileNameWAddress; if(hModule == NULL || hModule ==(HMODULE)MODIFY_PROCESS::dwModuleBaseAddress) { StringCbCopyW(lpFilename,nSize,m_szModulePath); return wcslen(m_szModulePath); } return pMGetModuleFileNameW(hModule,lpFilename,nSize); } CModifyProcessPath::CModifyProcessPath() { } BOOL CModifyProcessPath::Create() { ZeroMemory(MODIFY_PROCESS::m_szModulePath,sizeof(MODIFY_PROCESS::m_szModulePath)); MODIFY_PROCESS::dwGet
<think>我们面对的问题是:在钩子(Hook)DLL中拦截API调用时,通过Windows API(如GetModuleHandleEx等)获取调用者模块时,返回的是钩子DLL的模块句柄,而不是原始调用模块。我们需要一种方法来准确判断原始调用的模块。 原因分析: 当我们在钩子DLL中拦截API调用时,当前的调用栈可能是这样的: 1. 原始调用模块(比如A.exe)中的代码调用某个API(例如MessageBox)。 2. 这个调用被我们的钩子函数(在钩子DLL中)拦截。 3. 在钩子函数中,我们调用GetModuleHandleEx等函数来获取调用者模块时,由于此时执行流程在钩子DLL中,所以获取到的是钩子DLL的模块句柄。 解决方案: 为了获取原始调用模块,我们需要回溯调用栈,找到在钩子函数之前被调用的那个模块(即原始调用者)。这可以通过栈回溯(Stack Walk)来实现。 具体步骤: 1. 在钩子函数中,使用栈回溯函数(如RtlCaptureStackBackTrace)获取当前的调用栈。 2. 分析调用栈,跳过钩子DLL内部的函数调用,直到找到第一个不在钩子DLL中的调用模块,这个模块就是原始调用者。 注意:由于钩子DLL可能被注入到多个进程中,并且可能被多个线程同时调用,因此需要确保线程安全。 示例代码(C/C++): 以下是一个简单的示例,展示如何在钩子函数中获取原始调用模块: ```c #include <windows.h> #include <dbghelp.h> // 需要链接Dbghelp.lib // 我们的钩子函数 typedef int (WINAPI *OriginalMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT); OriginalMessageBoxW OriginalMessageBox = NULL; // 获取调用者模块的HMODULE HMODULE GetCallerModule(DWORD dwSkipFrames = 0) { // 使用栈回溯 PVOID frames[2]; USHORT frameCount = CaptureStackBackTrace(dwSkipFrames + 1, 2, frames, NULL); if (frameCount < 2) { return NULL; } // 获取第二个帧(即调用钩子函数的那个帧)的模块 HMODULE hModule; if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)frames[1], &hModule)) { return hModule; } return NULL; } // 钩子函数 int WINAPI HookedMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) { // 获取原始调用模块 // 注意:我们跳过一帧,因为当前帧(0)是HookedMessageBoxW,下一帧(1)是钩子DLL中的调用(如果有),再下一帧(2)才是原始调用者。 // 但是,由于我们是通过函数指针调用的钩子,所以实际的调用栈可能有所不同。可能需要调整跳过的帧数。 // 这里我们跳过2帧:0(当前函数)和1(调用当前函数的函数,可能是钩子框架中的分发函数),然后取第2帧。 HMODULE hCallerModule = GetCallerModule(2); if (hCallerModule) { WCHAR szModulePath[MAX_PATH]; GetModuleFileNameW(hCallerModule, szModulePath, MAX_PATH); // 此时szModulePath就是原始调用模块的路径 // 可以记录或处理 } // 调用原始函数 return OriginalMessageBox(hWnd, lpText, lpCaption, uType); } ``` 注意事项: 1. 需要链接Dbghelp.lib,并且确保在程序初始化时调用SymInitialize(如果需要更详细的符号信息)。 2. CaptureStackBackTrace函数在Windows XP及以后版本可用。 3. 跳过的帧数(dwSkipFrames)可能需要根据实际情况调整。因为调用栈的深度和钩子的实现方式有关(比如是inline hook还是通过detours等)。 4. 如果钩子函数是通过跳转指令(如inline hook)直接跳转过来的,那么调用栈中可能没有中间函数,此时可能需要跳过0帧或1帧。这需要根据实际测试调整。 替代方案: 如果使用Detours等钩子库,它们可能提供了获取原始调用者地址的方法。例如,Detours库中可以使用DetourGetContainingModule函数来获取某个地址所在的模块,然后我们可以通过调用栈地址来获取调用者模块。 总结: 通过栈回溯,我们可以跳过钩子DLL自身的帧,从而找到原始调用模块。但需要注意跳过的帧数以及不同Windows版本之间的兼容性。 引用: [^1]: Microsoft Docs: CaptureStackBackTrace function (https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-capturestackbacktrace) [^2]: Microsoft Docs: GetModuleHandleEx function (https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw) §§相关问题§§ 1. 如何在不同版本的Windows上安全地进行栈回溯? 2. 除了栈回溯,还有哪些方法可以获取原始调用模块? 3. 如何避免栈回溯对性能的影响? 4. 在64位和32位进程中,栈回溯有什么不同?</think>### 解决方案:准确获取原始调用模块的方法 在钩子 DLL 中检测 API 调用来源时,由于执行环境被劫持,直接使用 `GetModuleHandle(NULL)` 或 `GetReturnAddress()` 会返回钩子 DLL 的模块信息。以下是几种可靠获取原始调用模块的方法: --- ### 方法 1:栈回溯(Stack Walking) 通过分析调用栈找到钩子函数之前的返回地址,定位原始调用模块。 ```c #include <dbghelp.h> #pragma comment(lib, "dbghelp.lib") HMODULE GetRealCallerModule() { void* stack[2]; USHORT frames = CaptureStackBackTrace(1, 2, stack, NULL); // 跳过当前帧 if (frames >= 2) { HMODULE hModule = NULL; // 获取栈中第一个非钩子DLL的地址对应的模块 if (GetModuleHandleExA( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)stack[1], &hModule)) { return hModule; } } return NULL; } ``` **原理**: - `CaptureStackBackTrace` 获取调用栈 - 跳过当前帧(索引 0),索引 1 指向原始调用位置 - `GetModuleHandleExA` 通过地址解析模块句柄 --- ### 方法 2:内联钩子(Inline Hook)保存上下文 在 API 入口点插入跳转指令前保存原始返回地址: ```asm ; 汇编伪代码(x86) OriginalFunction: push ebp mov ebp, esp pushad ; 保存寄存器 mov eax, [ebp+4] ; 获取返回地址 mov [g_originalReturn], eax ; 保存到全局变量 popad jmp HookedFunction ; 跳转到钩子 ``` **关键步骤**: 1. 修改 API 入口点为跳转指令 2. 在跳转前从栈中读取返回地址(`[ebp+4]`) 3. 在钩子函数中通过 `GetModuleHandleEx` 解析地址对应的模块 --- ### 方法 3:TEB/PEB 结构分析 通过线程环境块直接访问栈内存: ```c HMODULE GetCallerFromTeb() { NT_TIB* tib = (NT_TIB*)NtCurrentTeb(); void** stackBase = (void**)tib->StackBase; // 从栈顶向下搜索返回地址(通常位于[ebp+4]) for (void** ptr = stackBase; ptr > tib->StackLimit; --ptr) { MEMORY_BASIC_INFORMATION mbi; if (VirtualQuery(*ptr, &mbi, sizeof(mbi)) { if (mbi.State == MEM_COMMIT && mbi.AllocationBase) { return (HMODULE)mbi.AllocationBase; } } } return NULL; } ``` --- ### 方法 4:异常处理链(Vectored Exception Handling) 注册异常处理程序捕获调用上下文: ```c PVOID g_originalReturn = NULL; LONG NTAPI VectoredHandler(PEXCEPTION_POINTERS pExp) { if (pExp->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) { g_originalReturn = (PVOID)pExp->ContextRecord->Eip; // x86 return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } // 注册异常处理 AddVectoredExceptionHandler(1, VectoredHandler); // 在钩子中触发断点 __asm int 3 ; 触发STATUS_BREAKPOINT ``` --- ### 关键注意事项 1. **跨平台兼容性**: - x86 使用 `[ebp+4]` 获取返回地址 - x64 使用 `__builtin_frame_address(0)` 和 `_ReturnAddress()` ```c #if defined(_M_X64) void* retAddr = _ReturnAddress(); #endif ``` 2. **线程安全**: - 使用线程局部存储(TLS)保存返回地址 ```c __declspec(thread) void* tls_retAddr; ``` 3. **现代系统支持**: - Windows 10+ 可用 `RtlPcToFileHeader` 直接解析地址: ```c PVOID baseAddress; if (NT_SUCCESS(RtlPcToFileHeader(retAddr, &baseAddress))) { return (HMODULE)baseAddress; } ``` --- ### 性能优化建议 - **缓存结果**:对相同返回地址缓存模块句柄 - **避免深度回溯**:限定栈搜索深度(通常 3-5 层足够) - **钩子选择性启用**:仅对目标模块启用钩子 > 引用:Microsoft Docs 关于栈回溯的说明[^1],Intel 64 架构调用规范[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值