Windows下如何在类中实现fopen()函数Hook?
问题与解决方案
编译错误原因
非静态类成员函数Hook::hook_fopen带有隐含的this指针,其函数类型为FILE *(__cdecl Hook::*)(const char *,const char *),和普通非成员函数指针类型FILE*(__cdecl*)(const char*, const char*)不兼容,因此无法直接转换成void*赋值给IAT表项,导致编译报错C2440。
方案一:用类实现多模块Hook
要让类成员函数能作为Hook函数,有两种可行方式:
方式1:将hook_fopen改为静态成员函数
静态成员函数没有this指针,类型和普通非成员函数一致,修改后可直接用于Hook:
void **IATfind(const char *function, HMODULE module); // 定义原始函数指针类型 typedef FILE*(__cdecl* popen)(const char* filename, const char* mode); struct Hook { // 静态成员变量存储原始函数指针 static popen orig_fopen; // 改为静态成员函数 static FILE* __cdecl hook_fopen(const char* filename, const char* mode) { printf("Rewrite fopen: %s\n", filename); return orig_fopen("hook_test.txt", mode); } void DetourIATptr(const char *function, HMODULE module) { void **funcptr = IATfind(function, module); if (funcptr == 0) { printf("cannot find function address\n"); return; } DWORD oldrights, newrights = PAGE_READWRITE; // 修改内存保护属性为可读写 VirtualProtect(funcptr, sizeof(LPVOID), newrights, &oldrights); orig_fopen = (popen)*funcptr; *funcptr = (void*)&Hook::hook_fopen; // 恢复原内存保护属性 VirtualProtect(funcptr, sizeof(LPVOID), oldrights, &newrights); } void Do() { DetourIATptr("fopen", 0); } }; // 静态成员变量需在类外初始化 popen Hook::orig_fopen = nullptr; int main(int argc, CHAR *argv[]) { Hook hk; hk.Do(); printf("main\n"); fopen("myfile.txt", "w"); return 0; } void **IATfind(const char *function, HMODULE module) { // 查找指定函数的IAT表项 if (module == 0) module = GetModuleHandle(0); PIMAGE_DOS_HEADER pImgDosHeaders = (PIMAGE_DOS_HEADER)module; PIMAGE_NT_HEADERS pImgNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pImgDosHeaders + pImgDosHeaders->e_lfanew); PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImgDosHeaders + pImgNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); if (pImgDosHeaders->e_magic != IMAGE_DOS_SIGNATURE) { printf("libPE Error : e_magic is no valid DOS signature\n"); return 0; } for (IMAGE_IMPORT_DESCRIPTOR *iid = pImgImportDesc; iid->Name != NULL; iid++) { for (int funcIdx = 0; *(funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module)) != NULL; funcIdx++) { char *modFuncName = (char*)(*(funcIdx + (SIZE_T*)(iid->OriginalFirstThunk + (SIZE_T)module)) + (SIZE_T)module + 2); const uintptr_t nModFuncName = (uintptr_t)modFuncName; bool isString = !(nModFuncName & (sizeof(nModFuncName) == 4 ? 0x80000000 : 0x8000000000000000)); if (isString) { if (!_stricmp(function, modFuncName)) { printf("address finded\n"); return funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module); } } } } return 0; }
若需要每个Hook实例维护独立的原始函数指针,可结合全局Map或线程局部存储实现,静态成员方式是最简便的基础方案。
方式2:使用Thunk技术(复杂但支持非静态成员)
如果必须使用非静态成员函数(比如每个实例需要独立状态),可手动构造Thunk代码段,将this指针注入其中,让Thunk函数间接调用成员函数。这种方式需要处理内存权限和代码生成,实现较繁琐,适合需要实例级状态的场景。
方案二:不用类,在Hook函数中识别调用模块
若不需要类结构,仅需在hook_fopen中获取调用函数的模块,可通过栈回溯实现:
修改后的代码示例
void **IATfind(const char *function, HMODULE module); typedef FILE*(__cdecl* popen)(const char* filename, const char* mode); popen orig_fopen; // 获取调用者所在模块 HMODULE GetCallerModule() { void* callstack[10]; DWORD frames = CaptureStackBackTrace(1, 10, callstack, NULL); if (frames == 0) return NULL; HMODULE module = NULL; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)callstack[0], &module); return module; } FILE* __cdecl hook_fopen(const char* filename, const char* mode) { HMODULE callerModule = GetCallerModule(); char modulePath[MAX_PATH] = {0}; if (callerModule != NULL) { GetModuleFileName(callerModule, modulePath, MAX_PATH); printf("Rewrite fopen: %s, called from module: %s\n", filename, modulePath); } else { printf("Rewrite fopen: %s, caller module unknown\n", filename); } return orig_fopen("hook_test.txt", mode); } void DetourIATptr(const char *function, void *newfunction, HMODULE module) { void **funcptr = IATfind(function, module); if (funcptr == 0) { printf("cannot find function address\n"); return; } DWORD oldrights, newrights = PAGE_READWRITE; VirtualProtect(funcptr, sizeof(LPVOID), newrights, &oldrights); orig_fopen = (popen)*funcptr; *funcptr = newfunction; VirtualProtect(funcptr, sizeof(LPVOID), oldrights, &newrights); } int main(int argc, CHAR *argv[]) { DetourIATptr("fopen", (void*)hook_fopen, 0); printf("main\n"); fopen("myfile.txt", "w"); return 0; } void **IATfind(const char *function, HMODULE module) { if (module == 0) module = GetModuleHandle(0); PIMAGE_DOS_HEADER pImgDosHeaders = (PIMAGE_DOS_HEADER)module; PIMAGE_NT_HEADERS pImgNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pImgDosHeaders + pImgDosHeaders->e_lfanew); PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImgDosHeaders + pImgNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); if (pImgDosHeaders->e_magic != IMAGE_DOS_SIGNATURE) { printf("libPE Error : e_magic is no valid DOS signature\n"); return 0; } for (IMAGE_IMPORT_DESCRIPTOR *iid = pImgImportDesc; iid->Name != NULL; iid++) { for (int funcIdx = 0; *(funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module)) != NULL; funcIdx++) { char *modFuncName = (char*)(*(funcIdx + (SIZE_T*)(iid->OriginalFirstThunk + (SIZE_T)module)) + (SIZE_T)module + 2); const uintptr_t nModFuncName = (uintptr_t)modFuncName; bool isString = !(nModFuncName & (sizeof(nModFuncName) == 4 ? 0x80000000 : 0x8000000000000000)); if (isString) { if (!_stricmp(function, modFuncName)) { printf("address finded\n"); return funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module); } } } } return 0; }
注意:使用CaptureStackBackTrace需要链接Dbghelp.lib,编译时需添加该依赖。通过此方法可获取调用者的模块路径,进而判断是否为恶意模块。
内容的提问来源于stack exchange,提问作者Caty
相关产品推荐
相关产品推荐

