Как объединить функции Hook и Trampoline для перехвата WinAPI

Итак, я узнал о концепции перехвата и использования батутов для обхода / выполнения данных в функции перехвата WinAPI (в другом исполняемом файле с использованием инъекции DLL). Пока я знаю, как сделать это (батут и крюк), используя смесь сборки и C, но, похоже, я не могу сделать это просто с помощью C, так как мне что-то не хватает. Буду признателен, если кто-нибудь скажет мне, что я делаю не так и как это исправить.

Прямо сейчас мой код:

#include <Windows.h>

unsigned char* address = 0;

__declspec(naked) int __stdcall MessageBoxAHookTrampoline(HWND Window, char* Message, char* Title, int Type) {
    __asm
    {
        push ebp
        mov ebp, esp
        mov eax, address
        add eax, 5
        jmp eax
    }
}

int __stdcall MessageBoxAHook(HWND Window, char* Message, char* Title, int Type) {
    wchar_t* WMessage = L"Hooked!";
    wchar_t* WTitle = L"Success!";
    MessageBoxW(0, WMessage, WTitle, 0);
    return MessageBoxAHookTrampoline(Window, Message, Title, Type);
}

unsigned long __stdcall Thread(void* Context) {
    address = (unsigned char*)GetProcAddress(LoadLibraryA("user32"), "MessageBoxA");
    ULONG OP = 0;
    if (VirtualProtect(address, 1, PAGE_EXECUTE_READWRITE, &OP)) {
        memset(address, 0x90, 5);
        *address = 0xE9;
        *(unsigned long*)(address + 1) = (unsigned long)MessageBoxAHook - (unsigned long)address - 5;
    }
    else {
        MessageBoxA(0, "Failed to change protection", "RIP", 0);
    }
    return 1;
}

// Entry point.
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) {
    if (fdwReason == DLL_PROCESS_ATTACH) {
        CreateThread(0, 0, Thread, 0, 0, 0);
    }
    else if (fdwReason == DLL_PROCESS_DETACH) {

    }
    return true;
}

Итак, вопрос: как мне заставить функцию сказать InstallHook, которая установит крючок и вернет батут, чтобы я мог легко его использовать? Прототипом функции, вероятно, будет: void* InstallHook(void* originalFunc, void* targetFunc, int jumpsize), или так я понял, читая онлайн, но не уверен, для чего jumpsize будет использоваться.

Пока я знаю, что первые 5 байтов должны быть сохранены и восстановлены, а затем происходит переход к адресу исходной перехваченной функции. Поэтому мне пришлось бы использовать malloc для выделения памяти, memcpy для копирования байтов, 0xE9 - это значение инструкции перехода и тому подобное, но я просто не знаю, как реализовать это, используя только чистый C.

person Rivasa    schedule 11.07.2017    source источник
comment
Хук и батут не могут быть написаны только на чистом C, требуется некоторая низкоуровневая сборка, так как стек вызовов должен управляться напрямую. Этого не избежать. Доступно множество подключаемых библиотек, которые справятся с подобными вещами за вас. Почему бы не воспользоваться одним из них?   -  person Remy Lebeau    schedule 12.07.2017
comment
@RemyLebeau Мне показалось, что это можно сделать с помощью memcpy и memset? По крайней мере, один из программистов, с которыми я разговаривал, имел в виду это.   -  person Rivasa    schedule 12.07.2017
comment
Конечно, если вы выделите блок памяти и запишите в него необработанные инструкции сборки во время выполнения. Но это не меняет того факта, что хук и трансполин все еще используют инструкции сборки во время выполнения, они не написаны на чистом C.   -  person Remy Lebeau    schedule 12.07.2017
comment
@RemyLebeau Это, по сути, то, что я пытаюсь сделать, да.   -  person Rivasa    schedule 12.07.2017


Ответы (1)


Если я правильно понял вопрос, вы хотите избежать «жесткого кодирования» функции батутов в сборке, по-видимому, чтобы вы могли использовать несколько батутов одновременно без дублирования кода. Вы можете добиться этого, используя VirtualAlloc (malloc не будет работать, поскольку возвращенная память не будет исполняемой).

Я написал это по памяти без доступа к компилятору, поэтому в нем могут быть небольшие ошибки, но общая идея здесь. Обычно вы также можете использовать VirtualProtect, чтобы изменить права доступа к странице на r-x вместо rwx после того, как вы закончите его изменять, но я оставил это для простоты:

void *CreateTrampoline(void *originalFunc)
{
    /* Allocate the trampoline function */
    uint8_t *trampoline = VirtualAlloc(
        NULL,
        5 + 5, /* 5 for the prologue, 5 for the JMP */
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE); /* Make trampoline executable */

    /* Copy the original function's prologue */
    memcpy(trampoline, originalFunc, 5);

    /* JMP rel/32 opcode */
    trampoline[5] = 0xE9;

    /* JMP rel/32 operand */
    uint32_t jmpDest = (uint32_t)originalFunc + 5; /* Skip original prologue */
    uint32_t jmpSrc = (uint32_t)trampoline + 10; /* Starting after the JMP */
    uint32_t delta = jmpDest - jmpSrc;
    memcpy(trampoline + 6, &delta, 4);

    return trampoline;
}

Ваша InstallHook функция тогда просто вызовет CreateTrampoline для создания трамплина, а затем исправит первые 5 байтов исходной функции с помощью JMP rel/32 к вашему хуку.

Имейте в виду, это работает только с функциями WinAPI, потому что Microsoft требует, чтобы у них был 5-байтовый пролог для включения горячего исправления (это то, что вы здесь делаете). Обычные функции не имеют этого требования - обычно они начинаются только с push ebp; mov ebp, esp, что составляет всего 3 байта (а иногда даже не с этого, если компилятор решает оптимизировать его).

Изменить: вот как работает математика:

                          _______________delta______________
                         |                                  |
trampoline               |                  originalFunc    |
    |                    |                        |         |
    v                    |                        v         v
    [prologue][jmp delta]                         [prologue][rest of func]
    |________||_________|                         |________|
         5    +    5                                   5
person Andrew Sun    schedule 12.07.2017
comment
Я проверю это. Спасибо. - person Rivasa; 12.07.2017
comment
Не могли бы вы объяснить строки uint32_t jmpDest = (uint32_t)originalFunc + 5; /* Skip original prologue */ uint32_t jmpSrc = (uint32_t)trampoline + 10; /* Starting after the JMP */ Я не совсем уверен, как вы получили +10 на батуте? - person Rivasa; 12.07.2017
comment
@Annabelle Смещение инструкции JMP относительно EIP после выполнения инструкции (см. здесь). +10 - это расстояние в байтах от начала функции трамплина до конца инструкции JMP. (Или вы можете просто представить его как общий размер батута, 5+5) - person Andrew Sun; 12.07.2017
comment
@Annabelle Я добавил диаграмму к ответу, надеюсь, это проясняет, что происходит. - person Andrew Sun; 12.07.2017
comment
Правильно! Хорошо, что +10 имеет смысл. Я знал, что это 5 + 5 - это размер батута, но по какой-то причине я не стал связывать это, что +10 - это 5 + 5, о которых я уже знал. - person Rivasa; 12.07.2017
comment
Не могли бы вы взглянуть на мой код и объяснить, почему он не позволяет запускать исходную исправленную функцию после этого и как это исправить? Принято кстати! - person Rivasa; 13.07.2017
comment
@Annabelle Похоже, вы пропустили несколько звездочек - unsigned char должно быть unsigned char * (по этой причине я предпочитаю просто использовать uint32_t) - person Andrew Sun; 13.07.2017
comment
Позвольте нам продолжить это обсуждение в чате. - person Rivasa; 13.07.2017