System wide Windows CBT hook not working properly
Asked Answered
D

1

2

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

I've read many threads talking about this issue, but none has solved my problem. The application runs well; the hook is installed and I can see some notifications coming.

Actually the problems arised is that the application is not notified about CBT hook of other processes running on the same machine.

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's going on? What have I missed?

Note that I'm running with the Administratos group.


I've found this interesting section in this page

Global hooks are not supported in the .NET Framework You cannot implement global hooks in Microsoft .NET Framework. To install a global hook, a hook must have a native DLL export to insert itself in another process that requires a valid, consistent function to call into. This behavior requires a DLL export. The .NET Framework does not support DLL exports. Managed code has no concept of a consistent value for a function pointer because these function pointers are proxies that are built dynamically.

I thought to do the trick by implementing a native DLL containing the hook callback, which calls the managed callback. However, the managed callback is called only in the process which calls the SetWindowsHookEx routine and not called by other processes.

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.

Every process in the same desktop has to be monitored, indepently by the archiveture (32 or 64 bit) and by the underlying framework (native or managed).

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).

Dzungaria answered 26/6, 2010 at 0:20 Comment(1)
Hi Luca, 4 years later, I am trying to achieve the same thing here. Can you share what was your solution eventually? Did you actually do something like support.microsoft.com/kb/828736 after having your HOOKPROC in native c++? Thanks.Ponderable
L
7

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

You should understand, that the method which you use as the callback of system wide CBT hook (parameter of 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. A 32-bit DLL cannot be injected into a 64-bit process, and a 64-bit DLL cannot be injected into a 32-bit process. If an application requires the use of hooks in other processes, it is required that a 32-bit application call 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. The 32-bit and 64-bit DLLs must have different names.

Moreover you write in your question about system wide hook and use not 0 as the last parameter of SetWindowsHookEx. One more problem: as the third parameter of SetWindowsHookEx (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.

Sorry for the bad news, but in my opinion your current code should be full rewritten.

UPDATED based on update in the question: One more time I repeat, that if you call in one process SetWindowsHookEx to set a CBT hook, you should give as a parameter the module instance (the start address) of a DLL and the address of a function in the DLL which implement the hook. It is not important from which process you call SetWindowsHookEx function. The DLL used as a parameter will be loaded (injected) in all processes of the same windows station which use User32.dll. So you have some native restrictions. If you want support both 32-bit and 64-bit platforms you have to implement two dlls: one 32-bit and 64-bit DLL. Moreover there are a problem with the usage of different .NET versions in the same process. It should be theoretically possible to do this only with .NET 4.0. In general it is very complex problem. And you should understand it I write about a DLL I mean 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 (one 32-bit and 64-bit) and set install the hook inside of two processes (one 32-bit and 64-bit). So do exact what is described in the remark of the SetWindowsHookEx documentation http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx (see above quote). I see no more easier ways.

Liken answered 26/6, 2010 at 14:48 Comment(5)
Last parameter is set to 0, since thid = 0 when global is true. The DLL design is based on ManagedWinApi project, and confirmed by other discussion threads found by googling. Reimplement the hook implementation is not possible, since the wrapped managed routine (called by the native method) is the only one able to access to complex data structures (that shall be marshalled to interop with a native method implementation that you suggest. Actually not useful as answer, since there is no possible solution for the actual code.Dzungaria
In all cases an EXE has no relocation table so is is impossible to inject this EXE in the address space another exe. The function which you use as a parameter of SetWindowsHookEx must be inside a DLL. If you need to have so complex data inside of CBT hook can you explain me why you need inject it in ALL windows processes. Probably you need inject it in some spetial process? Is is a .NET Process? Use this process the same version of .NET?Liken
See edit. Found new information and added some specification.Dzungaria
Ok, now I got it. The essential point is that the DLL function is called within the external process context, which cannot access to the managed one (and probably it can't execute it). Probably it is a wrong way to achieve the result. Thank you for support.Dzungaria
You welcome! If we find out what it wrong what we do - it is a half of the solution. Best regards and I which you all the best.Liken

© 2022 - 2024 — McMap. All rights reserved.