How to hook an application?

19,826

Solution 1

One issue with your code is that hhookProc can be garbage collected while your native code still needs it. Use GC.KeepAlive or put in in a static variable.

The hMod param should probably be null, since you specified a thread in your own process:

hMod [in]

Type: HINSTANCE

A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.


But I think this only works for windows on the thread you specify.

In theory you can specify threads in other applications or even a global hook. The specified callback then gets called on the corresponding thread, even if that thread is in another process, in which case your dll gets injected into that process(that's the reason you need to specify the module handle in the first place).

But I believe that's not possible with .net code, because the mechanism for injecting into other processes and calling the hook method over there doesn't work with JIT compiled code.

Solution 2

Like most SetWindowsHookEx hooks, WH_CBT hooks require that the hook callback lives in a separate Win32 DLL that will get loaded into the target process. This essentially requires that the hook is written in C/C++, C# won't work here.

(Low-level mouse and keyboard hooks are two exceptions to this rule. It might also be possible to use other hooks in C# but only if you are hooking one of your own threads, so dwThreadId is the id of a thread in the current process, not 0. I haven't confirmed this, though. And you need to make sure you're using the Win32 threadid, so using GetCurrentThreadId is probably the best bet here.)

If you want to watch for new windows appearing from another app, an alternative C#-friendly approach is to use the SetWinEventHook API instead, specify WINEVENT_OUTOFCONTEXT (which is the magic flag that gets the events delivered to your own process, removing the need for a DLL and making C# usable here) and hook for the EVENT_OBJECT_CREATE and EVENT_OBJECT_SHOW events. You can listen to either your own process/thread's events, or to all processes/threads on the current desktop.

This will get you all sorts of "create" and show notifications, including those for child HWNDs within dialog, and even items within listboxes and similar; so you'll need to filter to extract only those for top-level HWNDs: eg. check that idObject==OBJID_WINDOW and idChild==0; that hwnd is visible (IsVisible()) and is top-level.

Note that using WinEvents requires that the thread that calls SetWinEventHook is pumping messages - which is typically the case anyway if it's a thread with UI. If not, you may need to add a message loop (GetMessage/TranslateMessage) manually. And you'll also want to use GC.KeepAlive() with the callback here also to prevent it from getting collected until after you call UnhookWinEvents.

Solution 3

This won't work in C#

Scope: Thread

If the application installs a hook procedure for a thread of a different application, the procedure must be in a DLL.

(Documentation of SetWindowsHookEx)

Scope: Global

To install a global hook, a hook must have a native DLL export to inject 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.

(Source)

Share:
19,826
Arnaud F.
Author by

Arnaud F.

(c) Feel good Inc.

Updated on June 04, 2022

Comments

  • Arnaud F.
    Arnaud F. about 2 years

    I'm trying to hook the creation of a windows in my C# app.

    static IntPtr hhook = IntPtr.Zero;
    static NativeMethods.HookProc hhookProc;
    
    static void Main(string[] args)
    {
        // Dummy.exe is a form with a button that opens a MessageBox when clicking on it.
        Process dummy = Process.Start(@"Dummy.exe");
    
        try
        {
            hhookProc = new NativeMethods.HookProc(Hook);
            IntPtr hwndMod = NativeMethods.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
            hhook = NativeMethods.SetWindowsHookEx(HookType.WH_CBT, hhookProc, hwndMod, (uint)AppDomain.GetCurrentThreadId());
    
            Console.WriteLine("hhook valid? {0}", hhook != IntPtr.Zero);
    
            while (!dummy.HasExited)
                dummy.WaitForExit(500);                
        }
        finally
        {
            if(hhook != IntPtr.Zero)
                NativeMethods.UnhookWindowsHookEx(hhook);
        }
    }
    
    static int Hook(int nCode, IntPtr wParam, IntPtr lParam)
    {
        Console.WriteLine("Hook()");
        return NativeMethods.CallNextHookEx(hhook, nCode, wParam, lParam);
    }
    

    Problem is, when clicking on my button (in Dummy.exe), I never enter in my Hook, what am I doing wrong?

    Thanks.


    EDIT

    Program.cs

    using System;
    using System.Diagnostics;
    
    namespace Hooker
    {
        class Program
        {
            static IntPtr hhook = IntPtr.Zero;
            static NativeMethods.HookProc hhookProc;
    
            static void Main(string[] args)
            {
                Process dummy = Process.Start(@"Dummy.exe");
    
                try
                {
                    hhookProc = new NativeMethods.HookProc(Hook);
                    hhook = NativeMethods.SetWindowsHookEx(HookType.WH_CBT, hhookProc, IntPtr.Zero, 0);
    
                    Console.WriteLine("hhook valid? {0}", hhook != IntPtr.Zero);
    
                    while (!dummy.HasExited)
                        dummy.WaitForExit(500);                
                }
                finally
                {
                    if(hhook != IntPtr.Zero)
                        NativeMethods.UnhookWindowsHookEx(hhook);
                }
            }
    
            static int Hook(int nCode, IntPtr wParam, IntPtr lParam)
            {
                Console.WriteLine("Hook()");
                return NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
            }
        }
    }
    

    NativeMethods.cs

    namespace Hooker
    {
        using System;
        using System.Runtime.InteropServices;
    
        internal static class NativeMethods
        {
            public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);
    
            [DllImport("user32.dll", SetLastError = true)]
            public static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, int dwThreadId);
            [DllImport("user32.dll")]
            public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
            [DllImport("user32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool UnhookWindowsHookEx(IntPtr hhk);
    
            [DllImport("user32.dll", SetLastError = true)]
            public static extern int GetWindowThreadProcessId(IntPtr hwnd, ref int pid);
    
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr GetModuleHandle(string lpModuleName);
        }
    }
    

    For dummy, do a new Form with :

        public Form1()
        {
            InitializeComponent();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("CONTENT", "TITLE");
        }
    
    • Arnaud F.
      Arnaud F. over 12 years
      Why use a Keyboard hook? I want to detect when a exe (that as no GUI) calls another exe (that has a GUI). What's the link with keyboard?
    • CodesInChaos
      CodesInChaos over 12 years
      I misremembered what WH_CBT meant.
    • CodesInChaos
      CodesInChaos over 12 years
      Why are you prefixing a module handle with hwnd? It's no window handle.
    • CodesInChaos
      CodesInChaos over 12 years
      Write this in unmanaged code.
    • Christian Larsson
      Christian Larsson almost 4 years
  • corylulu
    corylulu over 12 years
    @ArnaudF. Well I would need more code regarding the NativeMethod stuff or a copy of the project to test further. Not enough for me to work with.
  • corylulu
    corylulu over 12 years
    At what point are you getting the dummys handle? From the looks of it (if I'm following correctly) you are giving it the current applications handle only.
  • CodesInChaos
    CodesInChaos over 12 years
    @Arnaud Your new code is still broken. You either need to specify a 0 hModule and a thread inside your own process. In which case you can only observe windows in your own process. Or you specify 0 or an external thread and a non null hModule. But as I wrote, the second approach is likely impossible in .net.
  • CodesInChaos
    CodesInChaos over 12 years
    A hModule isn't a window handle.
  • Arnaud F.
    Arnaud F. over 12 years
    Yes, the dummy handle comes from a Process launched inside the current application.
  • Arnaud F.
    Arnaud F. over 12 years
    Actually, I don't known which value I should pass to SetWindowsHookEx to hook an application I launched from inside the current application...
  • CodesInChaos
    CodesInChaos over 12 years
    @Arnaud You would need to pass your current dll hModule as hMod and the thread associated with the created window as threadId. Then once an event happens, your dll gets injected into the target process and your hook method gets called. But the hook method must be at a constant offset from your dll, i.e. it can't be JIT compiled.
  • Arnaud F.
    Arnaud F. over 12 years
    Thanks, actually I read this : klocwork.com/products/documentation/current/How_kwinject_wor‌​ks, in this article it is written On Windows, kwinject starts the user process in debug mode and listens to CREATE_PROCESS debug events., how is this done, this can be done with SetWindowsHook, right?
  • corylulu
    corylulu over 12 years
    Okay, but from this code, the dummy application seems irrelevant. You could launch it externally and produce the same results. The Dummy's information is never used outside of it being started. This hook seems to be attempting a global hook. But listen to CodeInChaos's response, he seems to know more about this topic.
  • CodesInChaos
    CodesInChaos over 12 years
    Unlikely. According to the text it acts as a debugger for the process it wants to monitor.
  • Arnaud F.
    Arnaud F. over 12 years
    Ok, never mind, will delete the discussion then because was the aim of my question. Thanks for patience and explanations.
  • ordag
    ordag over 12 years
    The only possible windows hooks implemented in C# are global low level hooks and local thread hooks.
  • Cody Gray
    Cody Gray over 12 years
    The real answer is hidden at the bottom. You might want to move it to the top. You can't install a WH_CBT hook from a C# application. You must have the hook procedure live in an unmanaged DLL.