【问题标题】:How do I unhook a global hook from another process?如何从另一个进程中取消挂钩全局挂钩?
【发布时间】:2018-10-15 20:26:48
【问题描述】:

这是我当前的设置:我有一个 C++ DLL,它将它的一个函数全局挂钩到计算机上运行的每个进程。挂钩是在DLLMain 中使用SetWindowsHookEx winapi 函数完成的,我正在挂钩WH_CBTWH_SHELL 事件。我还有一个 C# 应用程序,它使用 p/invoke (LoadLibrary()) 加载 DLL,这会触发从 DLLMain 安装挂钩。 DLL 中的处理程序通过命名管道将事件信息发送到 C# 应用程序。

根据我在microsoft documentary 上阅读的内容,这些事件将在目标进程的线程上处理,并且必须由独立的 C++ DLL 安装(与任何应用程序都可以安装的 WH_MOUSE_LL 和 WH_KEYBOARD_LL 不同,甚至straight from a C# app using p/invoke)。

到目前为止一切正常;托管应用程序正在接收应有的数据。当我关闭应用程序时出现问题,因为处理程序仍然被挂钩,因此 DLL 文件正在使用中,无法删除。

由于处理程序不在我的应用程序中运行,而是注入到我计算机上运行的其他进程中,C# 应用程序不能简单地调用UnhookWindowsHookExFreeLibrary,因为事件处理程序的指针属于到其他进程。

问题:

如何从托管应用程序中触发取消挂钩例程,以确保 DLL 不再被任何进程使用?

这是我尝试过的:

我想出的唯一解决方案是创建一个退出事件(使用CreateEvent),每次处理程序收到WH_CBTWH_SHELL 消息时,它都会检查是否设置了退出事件,在这种情况下,它会从它所属的进程中脱钩并在处理消息之前返回。

这种方法的问题是,在我关闭我的应用程序并卸载 DLL 后,我必须等到其余进程至少收到一次 WH 事件,这样属于它们的处理程序才能自行解除挂钩。

这是 DLL 的代码:

#include <windows.h>
#include <sstream>

HANDLE hTERM;
HHOOK hCBT;
HHOOK hShell;

void __declspec(dllexport) InstallHooks(HMODULE h);
void __declspec(dllexport) RemoveHooks();

int Continue()
{
    return WAIT_TIMEOUT == WaitForSingleObject(hTERM, 0);
}

LRESULT FAR PASCAL _cbtProc(int c, WPARAM w, LPARAM l)
{
    if (!Continue()) { RemoveHooks(); return 0; }   
    // Handling the message ...
    return CallNextHookEx(0, c, w, l);
}

LRESULT FAR PASCAL _shellProc(int c, WPARAM w, LPARAM l)
{
    if (!Continue()) { RemoveHooks(); return 0; }
    // Handling the message ...
    return CallNextHookEx(0, c, w, l);
}

void InstallHooks(HMODULE h)
{
    hTERM = OpenEvent(EVENT_ALL_ACCESS, 0, __TEXT("{0C3ED513-F38C-4996-8130-F9A3C93D890B}"));
    if (!Continue())
        return;
    hCBT = SetWindowsHookEx(WH_CBT, _cbtProc, h, 0);
    hShell = SetWindowsHookEx(WH_SHELL, _shellProc, h, 0);
}

void RemoveHooks()
{
    UnhookWindowsHookEx(hCBT);
    UnhookWindowsHookEx(hShell);
    if (hTERM) CloseHandle(hTERM); hTERM = 0;
}

int FAR PASCAL DllMain(HMODULE h, DWORD r, void* p)
{
    switch (r)
    {
        case DLL_PROCESS_ATTACH: InstallHooks(h); break;
        case DLL_PROCESS_DETACH: RemoveHooks(); break;
        default: break;
    }
    return 1;
}

托管 C# 应用程序的源代码没有什么特别之处,因为它唯一做的就是在启动时调用 LoadLibrary,处理来自管道的消息,最后用另一个设置退出事件p/invoke 需要时调用。

【问题讨论】:

  • "挂钩在 DLLMain 中完成" - 这是完全错误的处理方法。每次将 DLL 注入一个新进程时,它都会安装一个新的钩子。 正确的 解决方案是让您的 DLL 导出其 InstallHooks()RemoveHooks() 函数,然后只有您的 C# 应用程序在将 DLL 加载到自身后调用它们。然后,这组挂钩将根据需要将 DLL 注入所有正在运行的进程中,而不必每次都调用 SetWindowsHookEx。而且你不应该从 inside 钩子回调本身调用UnhookWindowsHookEx()
  • 谢谢,我现在就试试。
  • @RemyLebeau 您推荐哪种方法在 C# 中加载 DLL?我应该 p/invoke LoadLibrary,使用 GetProcAddress 获取方法的指针并以这种方式调用它,还是应该使用 DllImport 属性?
  • 使用DllImport,它是LoadLibrary()GetProcAddress()的封装,所以不需要直接调用。
  • 如何/为什么将您的 dll 加载到另一个进程?你以某种方式手动注入它?还是因为您在流程中调用SetWindowsHookEx 而加载到流程中?在这种情况下,您当然不需要在目标进程中第二次调用SetWindowsHookEx - 因为它已加载,因为挂钩已经安装

标签: c++ winapi hook


【解决方案1】:

"挂钩是在 DLLMain 中完成的" - 这是完全错误的地方来处理这个问题。

每次将 DLL 加载到新进程中时,它都会安装一组新的 Shell/CBT 挂钩,这是您不希望/不需要发生的。您只需 1 套。

正确的解决方案是让您的 DLL 导出其 InstallHooks()RemoveHooks() 函数,然后在将 DLL 加载到自身之后只让您的 C# 应用调用它们。这组钩子将根据需要将 DLL 加载到所有正在运行的进程中,而无需您每次都调用 SetWindowsHookEx()

另外,不要在钩子回调本身内部调用UnhookWindowsHookEx()。在 C# 应用程序退出之前,它应该调用RemoveHooks(),然后它可以在调用UnhookWindowsHookEx() 之前发出hTerm 事件的信号。如果Continue() 返回false,回调应该简单地退出,仅此而已。但不要跳过调用CallNextHookEx(),即使Continue() 返回false,因为可能有其他应用程序安装了额外的钩子,你不想破坏它们。

试试类似的方法:

#include <windows.h>

HMODULE hModule = NULL;
HANDLE hTERM = NULL;
HHOOK hCBT = NULL;
HHOOK hShell = NULL;

static bool Continue()
{
    return (WAIT_TIMEOUT == WaitForSingleObject(hTERM, 0));
}

LRESULT CALLBACK _cbtProc(int code, WPARAM wParam, LPARAM lParam)
{
    if (Continue()) {
        // Handle the message ...
    }
    return CallNextHookEx(NULL, code, wParam, lParam);
}

LRESULT CALLBACK _shellProc(int code, WPARAM wParam, LPARAM lParam)
{
    if (Continue()) {
        // Handle the message ...
    }
    return CallNextHookEx(NULL, code, wParam, lParam);
}

__declspec(dllexport) BOOL WINAPI InstallHooks()
{
    if (!Continue())
        return FALSE;

    if (!hCBT)
        hCBT = SetWindowsHookEx(WH_CBT, _cbtProc, hModule, 0);

    if (!hShell)
        hShell = SetWindowsHookEx(WH_SHELL, _shellProc, hModule, 0);

    return ((hCBT) && (hShell)) ? TRUE : FALSE;
}

__declspec(dllexport) void WINAPI RemoveHooks()
{
    if (hTERM)
        SetEvent(hTERM);

    if (hCBT) {
        UnhookWindowsHookEx(hCBT);
        hCBT = NULL;
    }

    if (hShell) {
        UnhookWindowsHookEx(hShell);
        hShell = NULL;
    }
}

BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, void* lpvReserved)
{
    hModule = hinstDLL;

    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            hTERM = CreateEvent(NULL, TRUE, FALSE, TEXT("{0C3ED513-F38C-4996-8130-F9A3C93D890B}"));
            if (!hTERM) return FALSE;
            break;

        case DLL_PROCESS_DETACH:
            if (hTERM) {
                CloseHandle(hTERM);
                hTERM = NULL;
            }
            break;
    }

    return TRUE;
}

然后,您的 C# 应用程序可以简单地加载 DLL 并在需要时调用 InstallHooks()RemoveHooks()。例如,分别在应用启动和关闭时使用 PInvoke 调用。

【讨论】:

  • 你是救生员!感谢您的回答! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-04
  • 1970-01-01
  • 1970-01-01
  • 2014-12-24
相关资源
最近更新 更多