Как создать функцию батута для крючка

Я заинтересован в подключении, и я решил посмотреть, могу ли я использовать некоторые функции. Меня не интересовало использование библиотеки, такой как обходные пути, потому что я хочу иметь опыт самостоятельного использования. С некоторыми источниками, которые я нашел в Интернете, мне удалось создать код ниже. Это основное, но все работает хорошо. Однако при подключении функций, вызываемых несколькими потоками, это оказывается крайне неустойчивым. Если два вызова выполняются почти в одно и то же время, это сработает. После некоторых исследований я думаю, что мне нужно создать функцию батута. После долгих часов я не смог найти ничего другого, кроме общего описания того, что такое батут. Я не мог найти ничего конкретного о написании функции батута или о том, как они действительно работали. Если кто-нибудь может помочь мне написать один, опубликовать некоторые источники или, по крайней мере, указать мне в правильном направлении, рекомендуя некоторые статьи, сайты, книги и т.д. Я был бы очень признателен.

Ниже приведен код, который я написал. Это действительно базовое, но я надеюсь, что другие могут учиться на нем.

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;
}

Ответ 1

Если вы хотите, чтобы ваш крючок был безопасным при вызове несколькими потоками, вы не хотите постоянно отцепляться и переименовывать оригинальный API.

Батут - это всего лишь немного кода, который вы создаете, который реплицирует функциональность первых нескольких байтов исходного API (который вы переписали с помощью вашего перехода), а затем перескакивает в API после перезаписанных байтов.

Вместо того, чтобы отцеплять API, вызывая его и перетягивая, вы просто вызываете батут.

Это довольно сложно сделать на x86, потому что вам нужен (довольно минимальный) дизассемблер, чтобы найти границы команд. Вам также необходимо проверить, что код, который вы копируете в батуте, не делает ничего относительно указателя инструкции (например, jmp, ветвь или вызов).

Этого достаточно, чтобы делать вызовы в потокобезопасном режиме, но вы не можете создать hook, если несколько потоков используют API. Для этого вам нужно подключить функцию с двухбайтным скачком (который можно записать атомарно). API Windows часто предшествует несколько NOP (которые могут быть перезаписаны с помощью даун-прыжка), чтобы обеспечить цель для этого ближайшего прыжка.

Выполнение этого на x64 более значительно. Вы не можете просто исправить эту функцию с помощью 64-битного даун-прыжка (потому что его нет, а инструкции по его симуляции часто слишком длинны). И, в зависимости от того, что делает ваш батут, вам может потребоваться добавить его в информацию об освобождении стека ОС.

Надеюсь, это не слишком общее.