HeapAlloc 与 miniHook 挂钩,在 Windows 10 上死锁,在 Windows 7 上工作

HeapAlloc hooking with miniHook, deadlock on Windows 10, works on Windows 7

我有一个最奇怪的错误...我挂钩 HeapAlloc 以记录所有调用并获取调用 API 的 DLL 的名称。该代码适用于 Windows 7,但不适用于 Windows 10。我使用 miniHook 进行挂钩。使用 Visual Studio 2019,v142.

编译的所有内容
// BasicTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <windows.h>
#include <iostream>
#include <intrin.h>
#include <psapi.h>
#include "minihook.h"

#pragma intrinsic(_ReturnAddress)
#pragma comment(lib, "libMinHook.x64.lib") //v142

LPVOID(WINAPI* OldHeapAlloc)(HANDLE, DWORD, SIZE_T);

template <typename T>
inline MH_STATUS MH_CreateHookApiEx(
    LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, T** ppOriginal)
{
    return MH_CreateHookApi(
        pszModule, pszProcName, pDetour, reinterpret_cast<LPVOID*>(ppOriginal));
}

BOOL intercept = FALSE;
int iMbox = 0;
int iTotal = 0;

LPVOID HookedHeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes) {
    iTotal++;
    LPVOID ptr = OldHeapAlloc(hHeap, dwFlags, dwBytes);
    if (intercept) {
        return ptr;
    }

    intercept = TRUE;
    iMbox++;

    HMODULE hModule;
    char lpBaseName[32];

    if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)_ReturnAddress(), &hModule) != 0) {
        if (GetModuleBaseNameA(GetCurrentProcess(), hModule, lpBaseName, sizeof(lpBaseName)) != 0) {
            printf("Reserved %d at %08x from %s\n", dwBytes, ptr, lpBaseName);
        }
    }
    intercept = FALSE;

    return ptr;
}

int main()
{
    if (MH_Initialize() != MH_OK)
    {
        return 1;
    }

    if (MH_CreateHookApiEx(
        L"ntdll.dll", "RtlAllocateHeap", &HookedHeapAlloc, &OldHeapAlloc) != MH_OK)
    {
        return 1;
    }

    if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK)
    {
        return 1;
    }

    MessageBoxA(NULL, "This is a test", "Test", MB_OK);

    MH_DisableHook(MH_ALL_HOOKS);
    printf("RtlAllocateHeap calls for MessageBoxA = %d\n", iMbox);
    printf("RtlAllocateHeap total calls  = %d\n", iTotal);

    return 0;
}

intercept是为了防止HookedHeapAlloc里面再次发生。因为 GetModule* 类函数调用 HeapAlloc。重要的是要注意,如果您自己调用 HeapAlloc,即使有重复(HeapAlloc -> HeapAlloc -> HeapAlloc),代码也能正常工作。您可以调用 HeapAlloc 5000 次,并在 HookedHeapAlloc 中放置深度为 5 的递归(拦截可防止任何崩溃)。

但是当使用MessageBoxA时,程序挂在Windows 10(在20H2和21H1测试)。

Windows 7 的输出是这样的

[.......]
Reserved 24 at 004528f0 from KERNELBASE.dll
Reserved 40 at 0046afb0 from KERNELBASE.dll
Reserved 520 at 02aae4f0 from uxtheme.dll
Reserved 512 at 0046dd90 from IMM32.DLL
Reserved 48 at 00468960 from ntdll.dll
Reserved 48 at 004689a0 from ntdll.dll
Reserved 512 at 004612a0 from USER32.dll
Reserved 48 at 004689e0 from ntdll.dll
Reserved 48 at 00468a20 from ntdll.dll
Reserved 48 at 00468a60 from ntdll.dll
RtlAllocateHeap calls for MessageBoxA = 828
RtlAllocateHeap total calls  = 1657

在 Windows 10,程序在几个输出后挂起。在调试器内部时,它挂在 ZwWaitForAlertByThreadId.

感谢您的帮助:)

所以,回答我自己的问题。我发现了问题。 GetModuleHandleExA 增加模块的引用计数。事实证明,如果过多地增加引用计数,就会出现死锁。我不知道为什么...添加标志 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT 解决了问题。