14

I'm interested in hooking and I decided to see if I could hook some functions. I wasn't interested in using a library like detours because I want to have the experience of doing it on my own. With some sources I found on the internet, I was able to create the code below. It's basic, but it works alright. However when hooking functions that are called by multiple threads it proves to be extremely unstable. If two calls are made at nearly the same time, it'll crash. After some research I think I need to create a trampoline function. After looking for hours all I was not able to find anything other that a general description on what a trampoline was. I could not find anything specifically about writing a trampoline function, or how they really worked. If any one could help me write one, post some sources, or at least point me in the right direction by recommending some articles, sites, books, etc. I would greatly appreciate it.

Below is the code I've written. It's really basic but I hope others might learn from it.

test.cpp

#include "stdafx.h"

Hook hook;

typedef int (WINAPI *tMessageBox)(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

DWORD hMessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
    hook.removeHook();
    tMessageBox oMessageBox = (tMessageBox)hook.funcPtr; 
    int ret =oMessageBox(hWnd, lpText, "Hooked!", uType);
    hook.applyHook(&hMessageBox);

    return ret;
}

void hookMessageBox()
{
    printf("Hooking MessageBox...\n");
    if(hook.findFunc("User32.dll", "MessageBoxA")) 
    {
        if(hook.applyHook(&hMessageBox))
        {
            printf("hook applied! \n\n");
        } else printf("hook could not be applied\n");
    }   
}

hook.cpp

#include "stdafx.h"

bool Hook::findFunc(char* libName, char* funcName) 
{
    Hook::funcPtr = (void*)GetProcAddress(GetModuleHandleA(libName), funcName); 
    return (Hook::funcPtr != NULL);
}

bool Hook::removeHook() 
{
    DWORD dwProtect;
    if(VirtualProtect(Hook::funcPtr, 6, PAGE_EXECUTE_READWRITE, &dwProtect))
        {
        WriteProcessMemory(GetCurrentProcess(), (LPVOID)Hook::funcPtr, Hook::origData, 6, 0);
        VirtualProtect(Hook::funcPtr, 6, dwProtect, NULL);
        return true;
    } else return false;
}

bool Hook::reapplyHook() 
{
    DWORD dwProtect;
    if(VirtualProtect(funcPtr, 6, PAGE_EXECUTE_READWRITE, &dwProtect)) 
        {
        WriteProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, Hook::hookData, 6, 0);
        VirtualProtect(funcPtr, 6, dwProtect, NULL);
        return true;
    } else return false;
}

bool Hook::applyHook(void* hook) 
{ 
    return setHookAtAddress(Hook::funcPtr, hook);
}

bool Hook::setHookAtAddress(void* funcPtr, void* hook) 
{
    Hook::funcPtr = funcPtr;
    BYTE jmp[6] = { 0xE9, //jmp
                   0x00, 0x00, 0x00, 0x00,  //address
                   0xC3 //retn 
                 };

    DWORD dwProtect;

    if(VirtualProtect(funcPtr, 6, PAGE_EXECUTE_READWRITE, &dwProtect)) // make memory writable
    {

        ReadProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, Hook::origData, 6, 0); // save old data
        DWORD offset = ((DWORD)hook - (DWORD)funcPtr - 5);  //((to)-(from)-5)
        memcpy(&jmp[1], &offset, 4); // write address into jmp
        memcpy(Hook::hookData, jmp, 6); // save hook data
        WriteProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, jmp, 6, 0); // write jmp
        VirtualProtect(funcPtr, 6, dwProtect, NULL); // reprotect

        return true;
    } else return false;
}
4

2 に答える 2

11

複数のスレッドによって呼び出されたときにフックを安全にしたい場合は、元の API を常にフック解除して再フックする必要はありません。

トランポリンは、元の API の最初の数バイト (ジャンプで上書きしたもの) の機能を複製し、上書きしたバイトの後に API にジャンプする、生成する簡単なコードです。

API をアンフックして呼び出し、再度フックするのではなく、単純にトランポリンを呼び出します。

命令の境界を見つけるために (かなり最小限の) 逆アセンブラーが必要なため、これを x86 で行うのはやや複雑です。また、トランポリンにコピーするコードが、命令ポインター (jmp、分岐、呼び出しなど) に関連して何もしないことを確認する必要があります。

フックをスレッドセーフに呼び出すにはこれで十分ですが、複数のスレッドが API を使用している場合はフックを作成できません。このためには、関数を 2 バイトのニア ジャンプ (アトミックに記述できる) でフックする必要があります。Windows API の前には、このニア ジャンプのターゲットを提供するために、いくつかの NOP (ファー ジャンプで上書きできる) が先行することがよくあります。

x64 でこれを行うのは、はるかに複雑です。関数に 64 ビットの far jump を単純にパッチすることはできません (それがなく、それをシミュレートする命令が長すぎることが多いため)。また、トランポリンの機能によっては、トランポリンを OS のスタック アンワインド情報に追加する必要がある場合があります。

これが一般的すぎないことを願っています。

于 2012-02-17T22:49:50.370 に答える