Hook MessageBoxA触发Access Violation错误的技术求助
解决MessageBoxA内存钩子的访问冲突问题
核心问题分析
- 64位程序指令不兼容:你当前编译的是64位程序,但使用了32位钩子指令(
0x68+ 32位地址 +0xC3)。64位地址是8字节,0x68仅能推送32位值,导致地址被截断,执行时跳转到无效内存地址(错误信息里的0xFFFFFFFF94D81290就是截断后的无效地址)。 - VirtualProtect使用错误:你传入了空指针作为
oldProtect参数,系统无法保存原内存保护属性,且未检查函数返回值,无法确认权限修改是否成功。 - 调用约定不匹配:64位Windows默认调用约定是
__fastcall,你使用的__stdcall会导致栈平衡异常。 - 冗余的跨进程内存操作:修改当前进程内存无需调用
ReadProcessMemory/WriteProcessMemory,直接用指针操作即可。
修正步骤与代码
1. 适配64位钩子指令
64位下需使用兼容的跳转指令,这里采用mov rax, 函数地址 + jmp rax的组合,指令长度为14字节。
2. 正确使用VirtualProtect
传入有效的DWORD*变量接收原保护属性,修改后可选择恢复原属性,同时必须检查函数返回值。
3. 匹配调用约定
64位下钩子函数默认使用__fastcall,无需显式声明。
修正后的完整代码
#include <iostream> #include <string> #include <Windows.h> #include <cstring> BYTE originalBytes[14] = {0}; FARPROC targetProc = nullptr; int MessageBoxHook(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { // 恢复原指令,避免递归调用 DWORD oldProtect; VirtualProtect(targetProc, sizeof(originalBytes), PAGE_EXECUTE_READWRITE, &oldProtect); memcpy(targetProc, originalBytes, sizeof(originalBytes)); VirtualProtect(targetProc, sizeof(originalBytes), oldProtect, &oldProtect); // 修改钩子后的参数 const char* newCaption = "Process hooked"; const char* newText = "Child window hooked (Message Box)"; // 控制台输出调试信息 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hConsole, 0x02); std::cout << "Successfully hooked!\n"; SetConsoleTextAttribute(hConsole, 0x07); std::cout << "Original caption: " << lpCaption << "\n"; std::cout << "Original body: " << lpText << "\n"; SetConsoleTextAttribute(hConsole, 0x02); std::cout << "Hooked caption: " << newCaption << "\n"; std::cout << "Hooked body: " << newText << "\n"; SetConsoleTextAttribute(hConsole, 0x07); // 调用原始MessageBoxA return MessageBoxA(hWnd, newText, newCaption, uType); } int main() { std::string caption, body; std::cout << "Message caption: "; std::cin >> caption; std::cout << "Message body: "; std::cin >> body; // 加载user32.dll并获取MessageBoxA地址 HINSTANCE hUser32 = LoadLibraryA("user32.dll"); if (!hUser32) { std::cerr << "LoadLibraryA failed, error: " << GetLastError() << "\n"; return 1; } targetProc = GetProcAddress(hUser32, "MessageBoxA"); if (!targetProc) { std::cerr << "GetProcAddress failed, error: " << GetLastError() << "\n"; FreeLibrary(hUser32); return 1; } // 保存原指令 memcpy(originalBytes, targetProc, sizeof(originalBytes)); // 构造64位钩子指令:mov rax, MessageBoxHook; jmp rax BYTE hookBytes[14] = {0}; hookBytes[0] = 0x48; // mov rax, imm64 hookBytes[1] = 0xB8; memcpy(&hookBytes[2], &MessageBoxHook, 8); // 写入函数地址 hookBytes[10] = 0xFF; // jmp rax hookBytes[11] = 0xE0; // 修改内存权限并写入钩子指令 DWORD oldProtect; if (!VirtualProtect(targetProc, sizeof(hookBytes), PAGE_EXECUTE_READWRITE, &oldProtect)) { std::cerr << "VirtualProtect failed, error: " << GetLastError() << "\n"; FreeLibrary(hUser32); return 1; } memcpy(targetProc, hookBytes, sizeof(hookBytes)); VirtualProtect(targetProc, sizeof(hookBytes), oldProtect, &oldProtect); // 触发钩子 MessageBoxA(NULL, body.c_str(), caption.c_str(), MB_OK); FreeLibrary(hUser32); return 0; }
额外注意事项
- 编译时确保目标平台为x64(VS22默认是x64,若修改过需改回)。
- 钩子函数必须先恢复原指令再调用原始函数,否则会触发无限递归。
- 修改当前进程内存无需管理员权限,权限问题并非触发错误的原因。
- 若需兼容32位程序,需单独编写32位版本的钩子指令(如
0xE9+ 4位相对偏移的jmp指令)。
内容的提问来源于stack exchange,提问作者badatcpp
相关产品推荐
相关产品推荐

