Windows程序启动前就动手:用TLS回调在main函数之前挂钩LdrLoadDll(附完整C++代码)
Windows程序启动前就动手用TLS回调在main函数之前挂钩LdrLoadDll附完整C代码在Windows系统底层开发中有一种鲜为人知但极其强大的技术——TLS回调。这种技术允许开发者在程序入口点main或DllMain之前就执行自定义代码为安全监控、反调试和模块拦截等高级应用提供了前所未有的先发制人优势。1. TLS回调机制深度解析TLSThread Local Storage回调是Windows PE文件格式中一个鲜为人知但极其强大的特性。它最初设计用于线程局部存储的初始化但其执行时机使其成为系统级开发的利器。1.1 TLS回调的执行时机TLS回调的执行发生在程序生命周期的三个关键阶段进程启动时在所有全局/静态对象构造之前线程创建时在新线程开始执行用户代码之前进程/线程终止时在资源释放之后这种执行顺序意味着TLS回调可以监控所有模块加载行为拦截系统API调用实施安全策略检查初始化关键数据结构1.2 PE文件中的TLS结构在PE文件格式中TLS相关信息存储在特定的数据目录中typedef struct _IMAGE_TLS_DIRECTORY64 { ULONGLONG StartAddressOfRawData; ULONGLONG EndAddressOfRawData; ULONGLONG AddressOfIndex; ULONGLONG AddressOfCallBacks; DWORD SizeOfZeroFill; DWORD Characteristics; } IMAGE_TLS_DIRECTORY64;关键字段说明字段描述AddressOfCallBacks指向TLS回调函数数组的指针StartAddressOfRawDataTLS初始化数据的起始VAEndAddressOfRawDataTLS初始化数据的结束VA2. 实战配置TLS回调2.1 MSVC中的TLS配置在Visual Studio中配置TLS回调需要特殊的链接器指令// 告知链接器使用TLS #ifdef _WIN64 #pragma comment(linker, /INCLUDE:_tls_used) #pragma comment(linker, /INCLUDE:tls_callback_func) #else #pragma comment(linker, /INCLUDE:__tls_used) #pragma comment(linker, /INCLUDE:_tls_callback_func) #endif2.2 定义TLS回调数组TLS回调函数需要放置在特定的PE节中#ifdef _WIN64 #pragma const_seg(.CRT$XLF) EXTERN_C const #else #pragma data_seg(.CRT$XLF) EXTERN_C #endif PIMAGE_TLS_CALLBACK tls_callback_func[] { TLS_CALLBACK1, // 第一个回调函数 TLS_CALLBACK2, // 第二个回调函数 0 // 数组终止符 }; #ifdef _WIN64 #pragma const_seg() #else #pragma data_seg() #endif2.3 编写TLS回调函数TLS回调函数的原型如下void NTAPI TLS_CALLBACK( PVOID DllHandle, DWORD Reason, PVOID Reserved ) { switch (Reason) { case DLL_PROCESS_ATTACH: // 进程初始化时执行 break; case DLL_THREAD_ATTACH: // 线程创建时执行 break; case DLL_THREAD_DETACH: // 线程终止时执行 break; case DLL_PROCESS_DETACH: // 进程终止时执行 break; } }3. 挂钩LdrLoadDll实现模块监控3.1 为什么选择LdrLoadDllWindows模块加载的调用链LoadLibrary → LoadLibraryEx → LdrLoadDll直接挂钩LdrLoadDll可以捕获所有模块加载请求实现细粒度的控制避免被上层API钩子绕过3.2 64位系统下的Hook技术现代64位系统对代码段有严格的保护机制我们需要特殊的技术来实现Hookunsigned char trampoline[] { 0x49, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r11, address 0x41, 0xFF, 0xE3 // jmp r11 };Hook实现步骤备份原始函数头修改内存保护属性写入跳转指令恢复内存保护VOID HookLoadDll(LPVOID lpAddr) { DWORD oldProtect; unsigned char boing[] { 0x49, 0xBB, 0xDE, 0xAD, 0xC0, 0xDE, 0xDE, 0xAD, 0xC0, 0xDE, 0x41, 0xFF, 0xE3 }; *(void**)(boing 2) _LdrLoadDll; // 设置跳转地址 VirtualProtect(lpAddr, sizeof(boing), PAGE_EXECUTE_READWRITE, oldProtect); memcpy(lpAddr, boing, sizeof(boing)); VirtualProtect(lpAddr, sizeof(boing), oldProtect, oldProtect); }4. 完整实现DLL加载监控系统4.1 安全获取函数地址避免使用可能被Hook的GetProcAddressFARPROC WINAPI MyGetProcAddress(PVOID lpBaseAddress, LPCSTR FunName) { if (!lpBaseAddress || !FunName) return 0; PIMAGE_DOS_HEADER dosHeader (PIMAGE_DOS_HEADER)lpBaseAddress; PIMAGE_NT_HEADERS ntHeader (PIMAGE_NT_HEADERS)((BYTE*)lpBaseAddress dosHeader-e_lfanew); PIMAGE_EXPORT_DIRECTORY exports (PIMAGE_EXPORT_DIRECTORY)((BYTE*)lpBaseAddress ntHeader-OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); DWORD* nameTable (DWORD*)((BYTE*)lpBaseAddress exports-AddressOfNames); DWORD* addressTable (DWORD*)((BYTE*)lpBaseAddress exports-AddressOfFunctions); WORD* ordinalTable (WORD*)((BYTE*)lpBaseAddress exports-AddressOfNameOrdinals); for (DWORD i 0; i exports-NumberOfNames; i) { PCHAR pFuncName (PCHAR)((BYTE*)lpBaseAddress nameTable[i]); if (!_stricmp(pFuncName, FunName)) { return (FARPROC)((BYTE*)lpBaseAddress addressTable[ordinalTable[i]]); } } return 0; }4.2 实现LdrLoadDll钩子函数NTSTATUS __stdcall _LdrLoadDll( PWSTR SearchPath, PULONG DllCharacteristics, PUNICODE_STRING DllName, PVOID* BaseAddress ) { CHAR cDllName[MAX_PATH] {0}; sprintf_s(cDllName, %S, DllName-Buffer); // 检查DLL黑名单 for (int i 0; i dwNotAllowDllCount; i) { if (strstr(cDllName, cNotAllowDlls[i])) { printf(Blocked DLL: %s\n, cDllName); return STATUS_ACCESS_DENIED; } } // 恢复原始函数执行 memcpy(lpAddr, OriginalBytes, sizeof(OriginalBytes)); NTSTATUS status ((LdrLoadDll_)lpAddr)(SearchPath, DllCharacteristics, DllName, BaseAddress); HookLoadDll(lpAddr); // 重新安装Hook printf(Loaded DLL: %s\n, cDllName); return status; }4.3 完整代码结构项目应包含以下关键部分TLS回调初始化在进程启动时安装Hook安全的函数地址解析绕过可能的API Hook跳板代码生成实现函数重定向策略执行模块实现黑白名单逻辑原始函数调用临时恢复原始功能实际部署时可以考虑以下优化使用二分查找加速导出表搜索实现更复杂的模块加载策略添加日志记录和审计功能保护Hook代码免受篡改在开发这类底层工具时特别需要注意x86和x64架构的差异以及不同Windows版本的系统调用变化。建议在实际项目中使用前进行全面测试特别是在生产环境中部署前应在各种Windows版本上进行验证。