如何为挂钩创建蹦床功能

问题描述 投票:12回答:2

我对挂钩很感兴趣,因此决定看看是否可以挂钩某些功能。我对使用绕道而建的库不感兴趣,因为我想拥有自己做的经验。通过在互联网上找到的一些资源,我能够创建以下代码。这是基本的,但可以正常工作。但是,当钩住由多个线程调用的函数时,它被证明是极其不稳定的。如果几乎同时拨打两个电话,它将崩溃。经过一些研究,我认为我需要创建一个蹦床功能。几个小时后,除了蹦床的一般描述,我什么都找不到。我找不到关于编写蹦床功能或它们如何工作的任何具体信息。如果有人可以帮助我写一篇文章,发布一些资料或者通过推荐一些文章,网站,书籍等使我指向正确的方向,我将不胜感激。

下面是我编写的代码。这确实很基本,但我希望其他人可以从中学到。

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;
}
c++ winapi hook tramp trampolines
2个回答
9
投票

如果您希望钩子在被多个线程调用时是安全的,那么您就不想一直解钩并重钩原始API。

蹦床只是您生成的一些代码,它复制了原始API的前几个字节的功能(您用跳转重写了该字节),然后在重写了字节之后跳转到了API。

而不是取消对API的调用,对其进行重新调用而不是对其进行钩接,您只需调用蹦床即可。

这在x86上相当复杂,因为您需要(相当少的)反汇编程序来查找指令边界。您还需要检查复制到蹦床中的代码相对于指令指针没有任何作用(例如,jmp,分支或调用)。

这足以使钩子线程安全调用,但是如果多个线程正在使用API​​,则无法创建钩子。为此,您需要将函数与两个字节的近跳转挂钩(可以自动编写)。 Windows API经常在前面加上一些NOP(可以用远距离跳转来覆盖)以提供此近距离跳转的目标。

在x64上执行此操作更复杂。您不能简单地通过64位远距离跳接对该函数进行修补(因为没有一个,并且模拟它的指令通常太长)。并且,根据您的蹦床功能,您可能需要将其添加到操作系统的堆栈展开信息中。我希望这不是太笼统。


0
投票
事实上的标准挂钩教程来自jbremer,here可用
© www.soinside.com 2019 - 2024. All rights reserved.