在EXE中使用Detours替换LoadLibrary函数遇ERROR_INVALID_BLOCK问题求助
首先咱们得先搞明白ERROR_INVALID_BLOCK这个错误的核心原因:Detours找不到对应已经挂载的钩子实例,所以没法完成卸载操作。结合你是在自身进程内Hook LoadLibrary系列函数(而非注入其他进程)的场景,大概率是以下几个问题导致的:
1. 未执行对应DetourAttach,或Real_LoadLibraryA指针不匹配
Detours有个硬性要求:必须先调用DetourAttach挂载钩子,才能调用DetourDetach卸载,而且DetourDetach的第一个参数必须是你当初DetourAttach时用的同一个PVOID*变量(也就是保存原始函数地址的那个指针)。
给你贴个正确的完整流程示例,你可以对照自己的代码排查:
// 先定义原始函数指针和自定义钩子函数 typedef HMODULE(WINAPI* pLoadLibraryA)(LPCSTR lpLibFileName); pLoadLibraryA Real_LoadLibraryA = nullptr; HMODULE WINAPI Mine_LoadLibraryA(LPCSTR lpLibFileName) { // 你的黑名单检查逻辑 if (isBlacklisted(lpLibFileName)) { return nullptr; // 阻止加载黑名单DLL } return Real_LoadLibraryA(lpLibFileName); // 调用原始函数 } // 挂载钩子的正确代码 void AttachLoadLibraryHooks() { // 注意:优先从kernelbase.dll获取,避免系统转发导致地址不匹配 Real_LoadLibraryA = (pLoadLibraryA)GetProcAddress(GetModuleHandleA("kernelbase.dll"), "LoadLibraryA"); DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); LONG attachResult = DetourAttach(&(PVOID&)Real_LoadLibraryA, Mine_LoadLibraryA); if (attachResult != NO_ERROR) { __debugbreak(); } LONG commitResult = DetourTransactionCommit(nullptr); if (commitResult != NO_ERROR) { __debugbreak(); } } // 卸载钩子的正确代码 void DetachLoadLibraryHooks() { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); LONG detachResult = DetourDetach(&(PVOID&)Real_LoadLibraryA, Mine_LoadLibraryA); if (detachResult != NO_ERROR) { __debugbreak(); } PVOID* ppbFailedPointer = nullptr; LONG commitResult = DetourTransactionCommitEx(&ppbFailedPointer); if (commitResult != NO_ERROR) { __debugbreak(); } }
特别注意:DetourAttach执行后,Real_LoadLibraryA会被Detours内部修改为真正的原始函数跳转地址,所以你绝对不能在卸载钩子前重新给这个指针赋值(比如再次调用GetProcAddress),否则指针不匹配就会直接返回ERROR_INVALID_BLOCK。
2. 线程上下文的潜在问题
你调用DetourUpdateThread(GetCurrentThread())是对当前线程生效,虽然在自身进程内操作时,只要是进程内的活跃线程都可以,但最好确保执行钩子操作(挂载/卸载)的是进程的主线程,或者没有被挂起的线程,避免线程状态异常导致的问题。
3. 是不是搞反了挂载和卸载的顺序?
你贴的代码直接调用了DetourDetach,如果你的代码执行流程里,根本没先成功执行过对应的DetourAttach,那肯定会触发这个错误。先确认你的代码是先挂载钩子,再执行卸载逻辑。
额外小提示
因为LoadLibrary系列函数在不同Windows版本下可能存在导出转发(比如从kernel32.dll转发到kernelbase.dll),所以获取原始函数地址时,直接从kernelbase.dll获取能避免很多地址不匹配的问题,这也是我上面示例里这么写的原因。
如果还是排查不出问题,可以在挂载钩子后打印Real_LoadLibraryA的地址,卸载前再打印一次,确认两个地址完全一致,这样就能快速排除指针不匹配的问题。
内容的提问来源于stack exchange,提问作者M. Twombley




