You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 06:55:18