Hook CreateFile时SymInitialize()调用CreateFile()触发死循环的解决方法
Hook CreateFile记录调用栈触发死循环问题说明
问题背景
开发中计划对CreateFile()函数进行Hook并重写逻辑,同时在自定义的CreateFile()实现中记录调用栈信息。实际运行时调用SymInitialize()初始化符号处理句柄,程序直接陷入无限死循环。调试确认根因为SymInitialize()内部会调用CreateFile(),递归触发自定义Hook函数的栈采集逻辑,最终栈溢出死循环。
问题解答
1. SymInitialize()内部调用CreateFile()的具体原因
SymInitialize()是DbgHelp.dll提供的符号解析引擎初始化接口:
- 当接口第三个参数
fInvadeProcess传入TRUE时,会自动遍历当前进程已加载的所有PE模块(exe、dll、sys等),逐一读取磁盘上的模块文件、搜索匹配的PDB符号文件,这些文件访问操作都依赖CreateFile()完成。 - 即使
fInvadeProcess传入FALSE,后续调用SymFromAddr()、SymGetLineFromAddr64()等符号解析接口时,也会按需调用CreateFile()加载对应模块的符号文件,本质都是符号加载流程的正常文件访问需求。
2. 递归死循环规避方案
核心是阻断递归调用路径,按可靠性从高到低排序:
- 加线程级重入锁:使用线程局部存储(TLS)定义布尔标记,进入自定义Hook函数时先检查标记,若当前线程已处于Hook处理流程中,直接调用原始
CreateFile逻辑,跳过栈采集步骤;若未处于处理流程,先将标记置为有效,执行完所有逻辑(包括栈采集)后再将标记恢复为无效。该方案可以覆盖所有DbgHelp接口触发的CreateFile调用,是最通用的解决方式。
重入标记简单实现参考:
thread_local bool g_isInHookLogic = false; HANDLE WINAPI newCreateFile(/* 函数参数保持和原定义一致 */) { if (g_isInHookLogic) { return oldCreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } g_isInHookLogic = true; // 执行栈采集、自定义业务逻辑 ShowTraceStack((char*)"trace information.\n"); HANDLE res = oldCreateFile(L".\\newFiles.txt", dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, NULL); g_isInHookLogic = false; return res; }
- 提前初始化符号引擎:
SymInitialize()属于一次性初始化接口,一个进程只需要调用一次。可以在安装Hook之前就完成SymInitialize()调用,提前加载完所有需要的模块符号,避免Hook生效后初始化流程触发递归。注意不要在每次栈采集时重复调用该接口,既会引发递归,也违反DbgHelp的使用规范。 - 符号操作临时卸载Hook:执行栈采集和符号解析前临时卸载Hook,操作完成后重新挂载,该方案存在线程安全漏洞,多线程场景下不推荐使用。
3. 无递归风险的调用栈记录替代方案
可以从架构上拆分栈采集和符号解析流程,从根源避免DbgHelp触发文件调用:
- 分离原始栈采集和符号解析流程:Hook逻辑中只调用
CaptureStackBackTrace或RtlCaptureStackBackTrace采集原始栈帧地址、计算栈哈希,这两个接口完全在内存中完成栈回溯,不会触发任何文件操作。采集到的原始栈数据存入线程安全的队列,由独立的、不挂载Hook的工作线程异步完成后续符号解析、日志存储操作。 - 使用ETW内核追踪:通过系统提供的事件追踪机制,在内核层捕获文件创建事件和对应调用栈,完全不需要用户态Hook,不存在重入问题,性能远高于用户态Inline Hook,适合生产环境使用。
问题复现代码
#include <windows.h> #include <stdio.h> #include "detours.h" #include <fstream> #include <io.h> #pragma comment(lib, "detours.lib") #include <DbgHelp.h> //SymInitialize #pragma comment(lib,"dbghelp.lib") #define STACK_INFO_LEN 200 struct stackInfo { PDWORD hashValue; // hash value to identify same stack char* szBriefInfo; // callstack info }; stackInfo ShowTraceStack(char* szBriefInfo) { static const int MAX_STACK_FRAMES = 12; void* pStack[MAX_STACK_FRAMES]; static char szStackInfo[STACK_INFO_LEN * MAX_STACK_FRAMES]; static char szFrameInfo[STACK_INFO_LEN]; HANDLE process = GetCurrentProcess(); // The handle used must be unique to avoid sharing a session with another component, SymInitialize(process, NULL, TRUE); PDWORD hashValue = (PDWORD)malloc(sizeof(DWORD)); // allow memory for hashVavlue, it will be rewrited in function CaptureStackBackTrace WORD frames = CaptureStackBackTrace(0, MAX_STACK_FRAMES, pStack, hashValue); if (szBriefInfo == NULL) { strcpy_s(szStackInfo, "stack traceback:\n"); } else { strcpy_s(szStackInfo, szBriefInfo); } for (WORD i = 0; i < frames; ++i) { DWORD64 address = (DWORD64)(pStack[i]); DWORD64 displacementSym = 0; char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); pSymbol->MaxNameLen = MAX_SYM_NAME; DWORD displacementLine = 0; IMAGEHLP_LINE64 line; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); if (SymFromAddr(process, address, &displacementSym, pSymbol) && SymGetLineFromAddr64(process, address, &displacementLine, &line)) { _snprintf_s(szFrameInfo, sizeof(szFrameInfo), "\t%s() at %s:%d(0x%x)\n", pSymbol->Name, line.FileName, line.LineNumber, pSymbol->Address); } else { _snprintf_s(szFrameInfo, sizeof(szFrameInfo), "\terror: %d\n", GetLastError()); } strcat_s(szStackInfo, szFrameInfo); } stackInfo traceStackInfo; traceStackInfo.hashValue = hashValue; traceStackInfo.szBriefInfo = szStackInfo; return traceStackInfo; } HANDLE (*__stdcall oldCreateFile)(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE) = CreateFileW; HANDLE WINAPI newCreateFile( _In_ LPCWSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, _In_ DWORD dwCreationDisposition, _In_ DWORD dwFlagsAndAttributes, _In_opt_ HANDLE hTemplateFile ) { ShowTraceStack((char*)"trace information.\n"); return oldCreateFile( L".\\newFiles.txt", dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, NULL); } void hook() { DetourRestoreAfterWith(); DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&)oldCreateFile, newCreateFile); DetourTransactionCommit(); } void unhook() { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourDetach(&(PVOID&)oldCreateFile, newCreateFile); DetourTransactionCommit(); } void myProcess() { HANDLE hFile = CreateFile(TEXT(".\\CreateFileDemo.txt"), GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { OutputDebugString(TEXT("CreateFile fail!\r\n")); } const int BUFSIZE = 4096; char chBuffer[BUFSIZE]; memcpy(chBuffer, "Test", 4); DWORD dwWritenSize = 0; BOOL bRet = WriteFile(hFile, chBuffer, 4, &dwWritenSize, NULL); if (bRet) { OutputDebugString(TEXT("WriteFile success!\r\n")); } } int main(){ hook(); myProcess(); unhook(); }
内容的提问来源于stack exchange,提问作者GuangJun Liu
相关产品推荐
相关产品推荐

