Crazy Hacker Tricks
July 26th, 2007

Let me be the first to say that this is probably a really bad idea, unless you are very desperate.

On the other hand, this was really fun to write.

Today I wrote some code that patches the first few bytes of user32!MessageBoxExW, in order to keep a pesky third-party library from showing a prompt when nobody was around to click on it. This overwrites the start of that function with a relative jump to one of my own, which then forwards to a managed function that logs what the message box was trying to tell us.

Actually, this was for a friend’s program. I would probably shy away from doing this if I weren’t only indirectly responsible.


    void InstallHook()
    {
        HMODULE hUser32 = LoadLibrary(L"user32.dll");
        PROC pMsgBox = GetProcAddress(hUser32, "MessageBoxExW");
        FailIf(pMsgBox == NULL);
    
        // Need to change the memory protection for the
        // (read/execute) page in user32.dll before we
        //write to it.
        MEMORY_BASIC_INFORMATION mbi = {0};
        VirtualQuery(pMsgBox, &mbi,
            sizeof(MEMORY_BASIC_INFORMATION));
        FailIf(!VirtualProtect(mbi.BaseAddress, mbi.RegionSize,
            PAGE_READWRITE, &mbi.Protect));
    
        // Copy in the jump instruction... you could of course
        // opt to not write this in assembly, but if we've gone this
        // far, what's the difference?
        __asm
        {
             // edi <- user32!MessageBoxExW
            mov edi, pMsgBox;              
    
            // write the opcode for JMP rel32
            mov byte ptr [edi], 0xe9;       
    
            // eax <- _MessageBoxEx, then
            // eax <- _MessageBoxEx - user32!MessageBoxExW
            mov eax, offset _MessageBoxEx;
            sub eax, edi;                   
    
            // One byte for jmp, four for the address.
            sub eax, 5;
            inc edi;
            stosd;
        };
    
        // Restore the old protection
        DWORD oldProtect = 0;
        FailIf(!VirtualProtect(mbi.BaseAddress,
            mbi.RegionSize, mbi.Protect, &oldProtect));
    }

  

With that in place it’s not hard to erect some managed scaffolding around it, and nobody needs to know your secret. Until they try to run it on an x64, I guess:

static void Main(string[] args)
{
    HookMonkey.Install(delegate(MessageBoxInfo mbi)
    {
        Console.WriteLine(mbi.Text);
        return DialogResult.OK;
    });
    MessageBox.Show("foo bar");
}

You can download some sample code here.