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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 13:58:15