代码之家  ›  专栏  ›  技术社区  ›  Luca

System wide Windows CBT hook not working properly

  •  2
  • Luca  · 技术社区  · 15 年前

    I'm trying to hook a CBT hook on Windows OSes. I'm currently using Windows 7 x64.

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

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

    The application is written in C# (using Microsoft .NET). Here is a running sample:

    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 and FreeHookWrapper are imported routines from a DLL (Native_x64.dll) compiler for x64 platform, located at the same directory of the application. AllocHookWrapper stores the function pointer (of the managed routine) and returns the DLL routine calling the function pointer).

    Here is the code of the 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);
    }
    

    Actually I'm interested about window related events (creation, destruction) on a certain system, but I'm actually unable to being notified by the OS...

    发生了什么事?What have I missed?

    请注意,我正在管理组中运行。


    I've found this interesting section in this page

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

    我想通过实现一个包含hook回调的本地dll来实现这个技巧,它调用托管回调。但是,托管回调只在调用setWindowsHookEx例程的进程中调用,而不是由其他进程调用。

    What are the possible workarounds?

    Maybe allocating heap memory storing a process id (the managed one), and sending user messages describing the hooked functions?


    What I'm trying to achieve is a system wide monitor, which detect new processes executed, detect created windows position and size, as well closed windows, moved windows, minimized/maximized windows. Successively the monitor shall detect mouse and keyboard events (always system-wide) and also it has to "emulate" mouse and keyboard events.

    同一桌面上的每个进程都必须由架构(32或64位)和底层框架(本机或托管)独立地进行监视。

    The monitor shall force process windows position, size and movements, and shall be able to act as local user in order to allow remote user to act as a local user (something like VNC).

    1 回复  |  直到 13 年前
        1
  •  7
  •   Oleg    15 年前

    Sorry but I don't understand the sense of "wrapping" unmanaged DLL and usage of ManagedCallback as the hook inside of managed EXE.

    您应该理解,您使用的方法作为系统范围CBT hook的回调(参数 SetWindowsHookEx ) must be loaded in the address space of all process (it will be done a DLL injection of the module where hook function is implemented). In Windows SDK (MSDN) you can read following (see remark on http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx ):

    SetWindowsHookEx can be used to inject a DLL into another process. 32位 DLL cannot be injected into a 64-bit 进程,64位DLL不能 injected into a 32-bit process. 如果一个 application requires the use of hooks in other processes, it is required 一个32位应用程序调用 SetWindowsHookEx to inject a 32-bit DLL into 32-bit processes, and a 64-bit application call SetWindowsHookEx to inject a 64-bit DLL into 64-bit processes. 32位 and 64-bit DLLs must have different 名字。

    Moreover you write in your question about system wide hook and use not 0 as the last parameter of 键盘钩子 . One more problem: as the third parameter of 键盘钩子 (HINSTANCE hMod) you use an instance of not the dll with the code of hook (the code of hook you have currently in the EXE).

    So my suggestion: you have to write a new native code for implementation of system wide CBT hook and place it inside a DLL. I recommend you also to choose a base address (linker switch) for the DLL, which is not a standard value to reduce DLL rebasing. It is not mandatory but this will save memory resources.

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

    更新的 based on update in the question: One more time I repeat, that if you call in one process 键盘钩子 要设置一个cbt钩子,您应该将一个dll的模块实例(起始地址)和实现钩子的dll中函数的地址作为一个参数给出。从哪个过程调用并不重要 键盘钩子 功能。用作参数的dll将加载(注入)到使用user32.dll的同一Windows工作站的所有进程中。所以你有一些本土的限制。如果要同时支持32位和64位平台,则必须实现 DLL:一个32位和64位的DLL。此外,在同一进程中使用不同的.NET版本也存在问题。理论上,只有使用.NET 4.0才能做到这一点。一般来说,这是一个非常复杂的问题。你应该理解我写的一个动态链接库我是说 not only the DLL, but all its dependencies . So if you implement a native DLL which call a managed DLL (.NET DLL) it would be not possible.

    So if you want use global CBT hook you have to implement if as a two native DLLs (一个32位和64位)并将钩子安装在两个进程(一个32位和64位)内。所以要准确描述 键盘钩子 文档 http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx (see above quote). I see no more easier ways.