【问题标题】:System wide Windows CBT hook not working properly系统范围的 Windows CBT 挂钩无法正常工作
【发布时间】:2011-03-08 12:35:04
【问题描述】:

我正在尝试在 Windows 操作系统上挂钩 CBT 挂钩。我目前使用的是 Windows 7 x64。

我读过很多讨论这个问题的帖子,但没有一个能解决我的问题。应用程序运行良好;挂钩已安装,我可以看到一些通知。

实际上出现的问题是应用程序没有收到关于在同一台机器上运行的其他进程的CBT钩子的通知。

应用程序是用 C# 编写的(使用 Microsoft .NET)。这是一个运行示例:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsHook
{
    class Program
    {
    [STAThread]
    static void Main(string[] args)
    {
        uint thid = (uint)AppDomain.GetCurrentThreadId();
        bool global = true;

        mHookDelegate = Marshal.GetFunctionPointerForDelegate(new HookProc(ManagedCallback));

        if (global == true) {
            mNativeWrapperInstance = LoadLibrary("Native_x64.dll");
            thid = 0;
        } else {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                mNativeWrapperInstance = GetModuleHandle(curModule.ModuleName);
            }
        }

        mNativeWrappedDelegate = AllocHookWrapper(mHookDelegate);
        mHookHandle = SetWindowsHookEx(/*WH_CBT*/5, mNativeWrappedDelegate, mNativeWrapperInstance, thid);

        if (mHookHandle == IntPtr.Zero)
            throw new Win32Exception(Marshal.GetLastWin32Error());

        Application.Run(new Form());

        if (FreeHookWrapper(mNativeWrappedDelegate) == false)
            throw new Win32Exception("FreeHookWrapper has failed");
        if (FreeLibrary(mNativeWrapperInstance) == false)
            throw new Win32Exception("FreeLibrary has failed");

        if (UnhookWindowsHookEx(mHookHandle) == false)
            throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    static int ManagedCallback(int code, IntPtr wParam, IntPtr lParam)
    {
        Trace.TraceInformation("Code: {0}", code);
        if (code >= 0) {
            return (0);
        } else {
            return (CallNextHookEx(mHookHandle, code, wParam, lParam));
        }
    }

    delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

    static IntPtr mHookHandle;

    static IntPtr mHookDelegate;

    static IntPtr mNativeWrapperInstance = IntPtr.Zero;

    static IntPtr mNativeWrappedDelegate = IntPtr.Zero;

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int hook, IntPtr callback, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll")]
    internal static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool FreeLibrary(IntPtr hModule);

    [DllImport("Native_x64.dll")]
    private static extern IntPtr AllocHookWrapper(IntPtr callback);

    [DllImport("Native_x64.dll")]
    private static extern bool FreeHookWrapper(IntPtr wrapper);

    [DllImport("Native_x64.dll")]
    private static extern int FreeHooksCount();
}

}

AllocHookWrapper 和 FreeHookWrapper 是从用于 x64 平台的 DLL (Native_x64.dll) 编译器导入的例程,位于应用程序的同一目录中。 AllocHookWrapper 存储函数指针(托管例程的)并返回调用函数指针的 DLL 例程。

这是DLL的代码:

#include "stdafx.h"
#include "iGecko.Native.h"

#ifdef _MANAGED
#pragma managed(push, off)
#endif

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#define WRAPPER_NAME(idx)   Wrapper ## idx

#define WRAPPER_IMPLEMENTATION(idx)                                                 \
LRESULT WINAPI WRAPPER_NAME(idx)(int code, WPARAM wparam, LPARAM lparam)        \
{                                                                               \
    if (sHooksWrapped[idx] != NULL)                                             \
        return (sHooksWrapped[idx])(code, wparam, lparam);                      \
    else                                                                        \
        return (0);                                                             \
}

#define WRAPPER_COUNT       16

HOOKPROC sHooksWrapped[WRAPPER_COUNT] = { NULL };

WRAPPER_IMPLEMENTATION(0x00);
WRAPPER_IMPLEMENTATION(0x01);
WRAPPER_IMPLEMENTATION(0x02);
WRAPPER_IMPLEMENTATION(0x03);
WRAPPER_IMPLEMENTATION(0x04);
WRAPPER_IMPLEMENTATION(0x05);
WRAPPER_IMPLEMENTATION(0x06);
WRAPPER_IMPLEMENTATION(0x07);
WRAPPER_IMPLEMENTATION(0x08);
WRAPPER_IMPLEMENTATION(0x09);
WRAPPER_IMPLEMENTATION(0x0A);
WRAPPER_IMPLEMENTATION(0x0B);
WRAPPER_IMPLEMENTATION(0x0C);
WRAPPER_IMPLEMENTATION(0x0D);
WRAPPER_IMPLEMENTATION(0x0E);
WRAPPER_IMPLEMENTATION(0x0F);

const HOOKPROC sHookWrappers[] = {
    WRAPPER_NAME(0x00),
    WRAPPER_NAME(0x01),
    WRAPPER_NAME(0x02),
    WRAPPER_NAME(0x03),
    WRAPPER_NAME(0x04),
    WRAPPER_NAME(0x05),
    WRAPPER_NAME(0x06),
    WRAPPER_NAME(0x07),
    WRAPPER_NAME(0x08),
    WRAPPER_NAME(0x09),
    WRAPPER_NAME(0x0A),
    WRAPPER_NAME(0x0B),
    WRAPPER_NAME(0x0C),
    WRAPPER_NAME(0x0D),
    WRAPPER_NAME(0x0E),
    WRAPPER_NAME(0x0F)
};

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    return (TRUE);
}

#ifdef _MANAGED
#pragma managed(pop)
#endif

extern "C" IGECKONATIVE_API HOOKPROC WINAPI AllocHookWrapper(HOOKPROC wrapped)
{
    for(int i = 0; i < WRAPPER_COUNT; i++) {
        if (sHooksWrapped[i] == NULL) {
            sHooksWrapped[i] = wrapped;
            return sHookWrappers[i];
        }
    }

    return (NULL);
}

extern "C" IGECKONATIVE_API BOOL WINAPI FreeHookWrapper(HOOKPROC wrapper)
{
    for(int i = 0; i < WRAPPER_COUNT; i++) {
        if (sHookWrappers[i] == wrapper) {
            sHooksWrapped[i] = NULL;
            return TRUE;
        }
    }

    return (FALSE);
}

extern "C" IGECKONATIVE_API INT WINAPI FreeHooksCount()
{
    int c = 0;

    for(int i = 0; i < WRAPPER_COUNT; i++) {
        if (sHooksWrapped[i] == NULL)
            c++;
    }

    return (c);
}

实际上我对某个系统上的窗口相关事件(创建、销毁)感兴趣,但实际上我无法收到操作系统的通知...

发生了什么事?我错过了什么?

请注意,我使用的是 Administratos 组。


我在page 中发现了这个有趣的部分

.NET Framework 不支持全局挂钩 您不能在 Microsoft .NET Framework 中实现全局挂钩。要安装全局挂钩,挂钩必须具有本机 DLL 导出,才能将自身插入另一个需要有效、一致的函数才能调用的进程中。此行为需要 DLL 导出。 .NET Framework 不支持 DLL 导出。托管代码没有函数指针一致值的概念,因为这些函数指针是动态构建的代理。

我想通过实现一个包含挂钩回调的本机 DLL 来解决这个问题,它调用托管回调。但是,托管回调仅在调用 SetWindowsHookEx 例程的进程中调用,其他进程不调用。

有哪些可能的解决方法?

也许分配堆内存来存储进程 id(托管的),并发送描述挂钩函数的用户消息?


我想要实现的是一个系统范围的监视器,它检测执行的新进程,检测创建的​​窗口位置和大小,以及关闭的窗口、移动的窗口、最小化/最大化的窗口。监视器将依次检测鼠标和键盘事件(始终在系统范围内),并且它必须“模拟”鼠标和键盘事件。

必须单独监控同一桌面中的每个进程,由归档(32 位或 64 位)和底层框架(本机或托管)独立监控。

监视器应强制进程窗口的位置、大小和移动,并应能够充当本地用户,以允许远程用户充当本地用户(类似于 VNC)。

【问题讨论】:

  • 嗨,Luca,4 年后,我正试图在这里实现同样的目标。你能分享一下你最终的解决方案是什么吗?在本机 c++ 中使用 HOOKPROC 之后,你真的做了类似support.microsoft.com/kb/828736 的事情吗?谢谢。

标签: c# windows


【解决方案1】:

抱歉,我不明白“包装”非托管 DLL 的意义以及将 ManagedCallback 用作托管 EXE 内部的钩子。

你应该明白,你用作系统范围CBT钩子回调的方法(SetWindowsHookEx的参数)必须加载到all process的地址空间中(它将完成模块的DLL注入实现钩子函数的地方)。在 Windows SDK (MSDN) 中,您可以阅读以下内容(请参阅http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx 上的备注):

SetWindowsHookEx 可用于注入 一个DLL到另一个进程。一个 32 位 DLL 不能注入到 64 位 进程,并且不能使用 64 位 DLL 注入32位进程。如果 应用程序需要使用钩子 在其他过程中,需要 一个 32 位应用程序调用 SetWindowsHookEx 注入 32 位 DLL 转换为 32 位进程,以及 64 位应用程序调用 SetWindowsHookEx 注入 64 位 DLL 转换为 64 位进程。 32 位 和 64 位 DLL 必须有不同的 名字。

此外,您在有关系统范围挂钩的问题中写了,并使用非 0 作为SetWindowsHookEx 的最后一个参数。还有一个问题:作为SetWindowsHookEx (HINSTANCE hMod) 的第三个参数,您使用 不是带有钩子代码的 dll 的实例(您当前在 EXE 中的钩子代码)。

所以我的建议是:你必须编写一个新的本地代码来实现系统范围的 CBT 钩子,并将它放在一个 DLL 中。我建议您还为 DLL 选择一个基地址(链接器开关),这不是减少 DLL 变基的标准值。这不是强制性的,但这会节省内存资源。

很抱歉这个坏消息,但我认为你当前的代码应该被完全重写。

更新 基于问题中的更新:我再重复一次,如果您调用一个进程 SetWindowsHookEx 来设置 CBT 挂钩,您应该将模块实例作为参数提供( DLL 的起始地址)和 DLL 中实现挂钩的函数的地址。从哪个进程调用SetWindowsHookEx 函数并不重要。用作参数的 DLL 将在使用 User32.dll 的同一 windows 站的所有进程中加载​​(注入)。所以你有一些本地限制。如果您想同时支持 32 位和 64 位平台,您必须实现 两个 dll:一个 32 位和 64 位 DLL。此外,在同一进程中使用不同的 .NET 版本存在问题。理论上应该只能使用 .NET 4.0 来做到这一点。一般来说,这是一个非常复杂的问题。你应该明白我写的关于 DLL 我的意思是不仅是 DLL,还有它的所有依赖项。因此,如果您实现一个调用托管 DLL (.NET DLL) 的本机 DLL,这是不可能的。

因此,如果您想使用全局 CBT 挂钩,您必须将 if 实现为两个本机 DLL(一个 32 位和一个 64 位)并设置在两个进程(一个 32位和 64 位)。因此,请严格按照SetWindowsHookEx 文档http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx 的注释中描述的内容进行操作(参见上面的引用)。我看不到更简单的方法了。

【讨论】:

  • 最后一个参数设置为 0,因为当 global 为 true 时 thid = 0。 DLL 设计基于 ManagedWinApi 项目,并由谷歌搜索发现的其他讨论线程确认。重新实现钩子实现是不可能的,因为包装的托管例程(由本机方法调用)是唯一能够访问复杂数据结构的程序(应编组以与您建议的本机方法实现互操作。实际上没有用作为答案,因为实际代码没有可能的解决方案。
  • 在所有情况下,EXE 都没有重定位表,因此不可能将此 EXE 注入另一个 exe 的地址空间。用作SetWindowsHookEx 参数的函数必须在DLL 中。如果你需要在 CBT 钩子中拥有如此复杂的数据,你能解释一下为什么你需要在所有 Windows 进程中注入它。可能你需要在一些特殊的过程中注入它?是.NET 进程吗?这个过程使用相同版本的.NET吗?
  • 查看编辑。发现了新信息并添加了一些规范。
  • 好的,现在我明白了。要点是DLL函数是在外部进程上下文中调用的,它不能访问托管的(并且可能无法执行它)。可能这是实现结果的错误方法。感谢您的支持。
  • 欢迎您!如果我们发现我们做错了什么 - 这是解决方案的一半。最好的问候,我祝你一切顺利。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-18
  • 1970-01-01
  • 2018-01-27
相关资源
最近更新 更多