Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
186 views
in Technique[技术] by (71.8m points)

c# - System wide Windows CBT hook not working properly

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...