任何挂钩函数的单一接口

Single interface for any hooked function

我正在编写一个简单的 Dll 注入和挂钩程序,当我为 CreateFileA 手动声明要挂钩的函数时一切正常:

HANDLE WINAPI Hook_CreateFileA(
    LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile
)
{
    ...

    return CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
        dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}

但是现在我需要编写一个函数来处理来自 kernel32.dll 的任何函数,例如。这意味着除了函数的名称和地址之外,我对函数一无所知。

我对调用约定略知一二 - 函数参数按顺序压入 (__stdcall),然后调用函数。我试着写了一个 __declspec(naked) 函数,看起来像这样:

PVOID __declspec(naked) HookAnyFunction()
    {
        /* do something */

        __asm {
            mov ebx, [esp]
            add esp, 4
            call pfnFuncAddr
            sub esp, 4
            mov[esp], ebx
            ret
        }
    }

pfnFuncAddr - 是原始函数的地址。但是它会使注入了 Dll 的应用程序崩溃。我想我的代码损坏了堆栈或其他东西。我究竟做错了什么?希望我的解释有道理。

ebx 不是易变的,你不能只写它,你必须 save/restore 原始值。

编写一个通用的钩子函数会很困难,因为 32 位 Windows ABI 有 3 个调用约定; stdcall(被调用者清理堆栈),cdecl(调用者清理堆栈)和fastcall(寄存器中的前两个参数但在public API中用得不多)。

如果您只想记录函数调用,您可以这样做:

push esp
push pfnFuncAddr
call mylogger ; assumed to be stdcall in this case, it can also change the jump
jmp eax

FARPROC __stdcall mylogger(FARPROC function, SIZE_T stackaddress) { ...; return function; }(记住esp已经改变了,所以如果你想记录堆栈的内容,你必须将堆栈参数调整8+4个字节。)如果你关心fastcall 你还必须推送寄存器但是通用记录器无法知道第一个参数是在堆栈上还是在寄存器中。