Click here to Skip to main content
12,559,868 members (50,817 online)
Click here to Skip to main content
Add your own
alternative version


40 bookmarked

Global Interceptable Program and System Hooks in .NET

, 7 Aug 2014 MIT
Rate this:
Please Sign up or sign in to vote.
An introduction on how to implement global interceptable hooks in .Net


This article is going to show a way to implement all global windows hooks types in managed .Net with the possibility to intercept and alter system messages before they get processed by the target application.


For everyone who does not know what hooks are and why you need them here is some quick information:


A hook is a point in the system message-handling mechanism where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure.

If you dont want to read the whole documentation about hooks please check out following articles before continuing:


My motivation about making this was that when I heard about hooks and windows messages I searched around the web to find an implementation of global hooks in C#. Most hooks were Mouse and Keyboard hooks because they don't require an unmanaged dll. Then I found the above articles which do something more, but still not everything. Lets Quote MSDN:


Global hooks are not supported in the .NET Framework You cannot implement global hooks in Microsoft .NET Framework.

That sounds an awful lot like a challenge.

On this spot I want to describe the nomeclature of the article's title:

  • Global:   The hook procedure is loaded into every process in the system.
  • System:  The hook procedure is called if a system event happens.
  • Program:The hook procedure is loaded into one specific process.

Most Useful Hooks

Some hooks have some special properties:

  • WH_CBT: Stands for Computer Based Training and can is called when: activating, creating, destroying, minimizing, maximizing, moving, or sizing a window, and when moving or pressing the mouse. It can also prevent those operations.
  • WH_GETMESSAGE: Is called before a message reaches a window and can intercept and change the message to the window (used in the sample app) WH_CALLWNDPROCRET can examine which return value the target application returned to this message.
  • WH_DEBUG: Can be very useful, called before any other hooktype gets called. If you set a global debug hook you can see every windows message and every system action (GUI, not network or file) the operating system performs.

The Target

Let's take a quick tour on where we are heading:

The .Net hook class should implement:

  • An event for each native windows hook type as well as all information assosiated with the hook.
  • Monitoring of a specific process or all processes in the system (global)
  • A translator .ToString() method which extracts all information from the hookproc callback to a single human readable string.
  • A way to hook and unhook the hook. As well as changing and intercepting windows messages.
  • Making the usage of the class as intuitive as possible and get rid of all balast.

How can all this be accomplished? Simple.

We need 2 Projects:

  1. A native C++ dll with some exports.
  2. A C# project which implements a wrapper around 1) and some Pinvoke functions.

First let us take a look on where we are heading:

var hook = new Hook(HookType.WH_CBT, true);
hook.HookTriggered += hook_HookTriggered;

void hook_HookTriggered(HookArguments Msg, ref bool Intercept)
     var msg = new WH_CBT(Msg);

And a generic hook:

var hook = new Hook<WH_CBT>(true);

void hook_HookTriggered(WH_CBT Message, ref bool Intercept)

1) Going Low Level

To hook to another window's messages there is no way around loading an unmanaged dll into the target process and redirecting/intercepting the message about to be recieved by the messageloop. Luckily the Windows Api has a rich function set which makes hooking very easy. The most important function is SetWindowsHookEx(). This function even has the possibility to hook globally. Global hooks load this dll into each availible process. This dll can not be written in managed code because most processes do not run the .Net framework.

Writing a native dll is not necessary for 2 types of hooks: The ones that you only use in your own application (local hooks) and the ones that hook to the keyboard or the mouse (WH_KEYBOARD_LL, WH_MOUSE_LL). Anyways this dll has to be designed to work in each process paralell. This is the key part of a functioning wrapper for .Net.

Let's get started.

The first thing to do is to define a dll export method for later use in .Net:

Keep in mind that this dll is loaded several times. This is why we need some shared data segments:

// Shared data among all dll instances.
#pragma comment(linker, "/SECTION:.SHARED,RWS")
#pragma data_seg(".SHARED")
HWND g_hWnd = NULL;            // Window handle
HHOOK g_hHook = NULL;        // Hook handle
INT hooktype = 0;
#pragma data_seg()

DWORD WINAPI SetHook(int HookType, BOOL bInstall, DWORD dwThreadId, HWND hWndCaller)
    BOOL bOk = FALSE;
    g_hWnd = hWndCaller;
    g_hHook = NULL;

    if (bInstall)
        g_hHook = ::SetWindowsHookEx(HookType, AllHookProc, ModuleFromAddress(AllHookProc), dwThreadId);
        hooktype = HookType;

        bOk = (g_hHook != NULL);
        bOk = ::UnhookWindowsHookEx(g_hHook);
        g_hHook = NULL;

    return GetLastError();

This function basically is a wrapper around Windows.h ::SetWindowsHookEx. The SetHook function saves the handle to our C# application window to a shared data segment. This dll reflects its own module with ModuleFromAdress and is loaded into one or more processes. This method returns the LastWin32Error to know if something and what exactly went wrong from the C# code.

The next step is to define the AllHookProc function which should process all windows hook callbacks. Invariant of the HookType specifier. First of all let us think of what do all hooks have in common and what extra information do we want to send. Lets see how every hook is defined: (


  int nCode,
  WPARAM wParam,
  LPARAM lParam
   // process event

   return CallNextHookEx(NULL, nCode, wParam, lParam);

Interesting. Every hook has a nCode and two pointers and a LRESULT (int or pointer) as a result. The LRESULT value should (we will break that rule to intercept) always be CallNextHookEx(NULL, nCode, wParam, lParam); The two pointers may be the actual information or they could be pointers to structures containing more pointers and data and so on. The pointers are named WideParam and LongParam because of historical reasons.

The HookProc() function gets called when the hook is triggered. But what now? We are not executing in our own process, you remember? Our dll is executed an a remote application so now how do we get data back to our application? Well I found a life saving windows message called WM_COPYDATA. This sounds an awful lot like inter process communication and voila:


An application sends the WM_COPYDATA message to pass data to another application.

A thing I have not known about IPC that there is something else than sockets, pipes and shared memory. This windows message is very handy as it automatically marshals any strucure to another process. We found a way to send data to another process. But what do we send?

As much as possible of course:

HOOKDLL_API typedef struct AllHookMSG
    INT HookType;

    INT nCode;
    WPARAM wParam;
    LPARAM lParam;
    DWORD Process;

    time_t Time;
    INT MilliSecond;

} HookMsg, *PHookMsg;

This structure can be extended by you if you like. This struct is very useful as can gather as much information as possible because you really execute your own code in another appdomain.

I chose to send all parameters of hookproc as well as the exact time and the ProcessId. The ProcessId is very important for the .Net project when you want to find out which process actually called this hook.

Now to the actual hookproc function:

// Hook callback called when hook triggers. 
LRESULT CALLBACK AllHookProc(int nCode, WPARAM wParam, LPARAM lParam)
    if (nCode < 0) //this is a must (see hookproc documentation)
        return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);


    HookMsg info;

    info.lParam = lParam;
    info.wParam = wParam;
    info.nCode = nCode;

    info.Time = time(0);
    info.MilliSecond = st.wMilliseconds;
    info.Process = GetCurrentProcessId();
    info.HookType = hooktype;


    InfoBoat.lpData = (PVOID)&info;
    InfoBoat.cbData = sizeof(info);
    InfoBoat.dwData = 0;

    //very important line:
    BOOL Intercept = SendMessage(g_hWnd, WM_COPYDATA, 0, (LPARAM)&InfoBoat); 

    if (Intercept)
        if (info.HookType == WH_CBT) return -1;
        return -1;

    return  ::CallNextHookEx(g_hHook, nCode, wParam, lParam);

The first 20 lines gather information and put it into the right form to be transfered with WM_COPYDATA. I called it Infoboat.

The line with SendMessage is very important. SendMessage blocks until the Message is processed (in contrast to PostMessage). SendMessage is exactly what we need. This is a guarantee that no message is recieved by the process and that the GUI thread is blocked and most importantly that there is a context switch to our C# application.

This line also gives us the opportunity to to Intercept the hook chain (message will not be passed to the process). Some hooks are interceptable and some are not, we cant change that. SendMessage will return a value and this value is defined by us in the C# project.

An advanced reader may notice that if we Intercept the HookProc it returns -1 and if the HookType is WH_CBT it also returns -1. In both cases we do not call the next hook. This is a proof that you always have to be sceptical even towards the most trusted sources as Microsoft documentation clearly states:


For operations corresponding to the following CBT hook codes, the return value must be 0 to allow the operation, or 1 to prevent it.

Empirical test have shown that only returning -1 does prevent the operation (This can be used to prevent windows from showing or destroying. Basically you cant open or exit any window in your target app)

Another thing you may notice is that wParam and lParam are copied by value. These pointers are definitely not valid in the .Net process. This issue will be resolved in the .Net project.

We already could write a C++ project which uses this dll to use all hooktypes. But thats not the target.

2) The .Net Project

First thing we have to do is to implement our own function and structs (in a static class called HookDll)

struct AllHookMSG
    public int HookType;

    public int nCode;
    public IntPtr wParam;
    public IntPtr lParam;
    public uint Process;

    public long Time;
    public int MilliSecond;

       DllImport("HookDll.dll", CharSet = CharSet.Auto,
       EntryPoint = "?SetHook@@YGKHHKPAUHWND__@@@Z",
       ExactSpelling = false, CallingConvention = CallingConvention.StdCall)
public static extern uint SetHook(int HookType, bool bInstall, [MarshalAs(UnmanagedType.U4)] UInt32 dwThreadId, IntPtr hWndCaller);

Second thing we need to know is that only forms (win32 windows) can recieve windows messages. As we have seen above we want to recieve our Inforboat (AllHookMsg). What if we want to use our class from a console application? Simple we define our own Messageloop class which derives from form.

There we can overload the msgproc method and filter for WM_COPYDATASTRUCT

class MessageLoop : Form
    int filter = 0;

    public IntPtr hWnd
        get { return base.Handle; }

    public MessageLoop(WindowsMessages Filter) : base ()
        filter = (int)Filter;

            base.FormBorderStyle = FormBorderStyle.FixedToolWindow;
            base.ShowInTaskbar = false;
            base.StartPosition = FormStartPosition.Manual;
            base.Location = new System.Drawing.Point(-2000, -2000);
            base.Size = new System.Drawing.Size(1, 1);

    protected override void WndProc(ref Message m)
        bool Intercept=false;

        if (m.Msg==filter&&MessageCallback!=null)
            MessageCallback(ref m,ref Intercept);

        base.WndProc(ref m);

        if (Intercept)
            m.Result = new IntPtr(1);

    public event dWndProc MessageCallback;
    public delegate void dWndProc(ref Message m,ref bool Intercept);

This is the whole Messageloop class implementation, so you could just copy and use it right away. We do not derive hook from form so we dont have all the overloads. Messageloop creates a window for us and completely hides it from the user. It also has an eventhandler for recieved windowsmessages. It also filters for our WH_COPYDATASTRUCT message. Very handy.

We also see the possibility to return 1 in WndProc(ref Message m). If you remember the dll decleration its stated that: Intercept=SendMessage(). So by setting m.Result to 1 we also tell the native dll to intercept the message.

The Hook Class

Now we have almost everything ready for our hook class. We need to define all hooktypes windows can hook to:

public enum HookType : int
     WH_CBT = 5,
     WH_MOUSE = 7,

Not everything is ready and we can build our final hook class:

MessageLoop MessageHandler;
public HookType HookType; 

        public Hook(HookType hooktype, Process ToWatch)
            MessageHandler = new MessageLoop(WindowsMessages.WM_COPYDATA);
            MessageHandler.MessageCallback += MessageHandler_WndProc;

            HookType = hooktype;

            uint TID = (uint)ToWatch.Threads[0].Id;

            if (HookType == HookType.WH_SYSMSGFILTER) { TID = 0; }

            uint HookEnabled = HookDll.SetHook((int)HookType, true, TID, MessageHandler.Handle);
            if (HookEnabled != 0) { throw new Win32Exception((int)HookEnabled); }

        public void Dispose()
            HookDll.SetHook(0, false, 0, IntPtr.Zero);

This is yet another wrapper around SetHook. All this abstraction is for making things as easy as possible. We can clearly see that we now can hook to a specific target process. The ThreadID parameter is the first thread of the process (which has to contain a window) and we also set the handle to our own defined messageloop. A global hook is another overload of this constructor and sets TID to 0. This will hook the native dll to every window currently open.

MessageCallback will only be triggered if a WM_COPYDATASTRUCT message enters the messageloop and we define our own messagecallback as follows:

void MessageHandler_WndProc(ref Message m, ref bool Intercept)
    if (HookTriggered == null) return;

    var InfoBoat = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));
    var HookInfo = (AllHookMSG)Marshal.PtrToStructure(InfoBoat.lpData, typeof(AllHookMSG));

    var time = new System.DateTime(1970, 1, 1).AddSeconds(HookInfo.Time).ToLocalTime().AddMilliseconds(HookInfo.MilliSecond);
    var process = Process.GetProcessById((int)HookInfo.Process);

    //All above variables go into one wrapper class (HookEvent)
    //This is our HookTriggered event which feeds everything to the user
    HookTriggered(HookEvent AllAboveVariables,ref Intercept);

Lucky for us, as as stated above, windows does marshal our struct to our own memory so everything is fine when we call Marshal.PtrToStructure. First we extract a pointer to our AllHookMsg and the we extract it. We now have everything we need. A global or specific hook, a way to intercept and all information assosiated with the hook. The time, the process and the information of hookproc. Of course

So what do we do now? Extract all information.

The Translator Classes

We now have one very big problem. We do not get any information from our HookTriggered event. There is lParam and wParam and nCode. But no information.

Luckily there is a workaround. We do know that our target application cant recieve any new messages now because it is still blocked in SendMessage. It can not recieve anything so it can not overwrite anything. lParam and wParam do point to a valid struct in our target (remote) process. These structs depend on the hooktype and all are documented in MSDN. Here is one example:

lParam [in]


A pointer to a CWPRETSTRUCT structure that contains details about the message.

The solution only works because we know that the target thread is blocked. We can just read the process memory of our target application. Beware: All translated messaged are not valid outside of our event method. This way we do not need the cooperation of our target application, no COM objects and no extra IPC.

        static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

        static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);

        const int PROCESS_VM_READ = 0x0010;
        const int PROCESS_VM_WRITE = 0x0020;
        const int PROCESS_VM_OPERATION = 0x0008;

These two functions copy a datablock from any process to our own process. We now need a way to cast it from byte[] to any struct. This can be done with generics:

public static T GetStructFromProcess<T>(Process Process,IntPtr Address) where T:struct
            IntPtr ProcessHandle = OpenProcess(PROCESS_VM_READ, false, Process.Id);

            int bytesrecieved = 0;
            byte[] buffer = new byte[Marshal.SizeOf(typeof(T))];
            bool Ok=ReadProcessMemory(ProcessHandle.ToInt32(), Address.ToInt32(), buffer, buffer.Length, ref bytesrecieved);
            if (!Ok) { throw new Win32Exception(Marshal.GetLastWin32Error()); }
            return MarshalHelper.DeserializeMsg<T>(buffer);

        static T DeserializeMsg<T>(Byte[] data) where T : struct
            int objsize = Marshal.SizeOf(typeof(T));
            IntPtr buff = Marshal.AllocHGlobal(objsize);
            Marshal.Copy(data, 0, buff, objsize);
            T retStruct = (T)Marshal.PtrToStructure(buff, typeof(T));
            return retStruct;

I have also written a WriteStructToProcess method which theoretically can change every windows message and struct in the target process even the ones that are tagged as not interceptable.

Now we put this code into a helperclass (MarshalHelper) and we can read the CWPRETSTRUCT from another process just by calling:

CWPRETSTRUCT IsWMCOPY = MarshalHelper.GetStructFromProcess<CWPRETSTRUCT>(process, PassData.lParam);

We are almost finished. I created one messagetranslator class (called like WH_HookType) for every single HookType and overrided the .ToString() to something a human can read. So every struct used by any hook is in the namespace System.Hooks

So heres the example translator of HookType.WH_MOUSE called WH_MOUSE

public class WH_MOUSE : IHook
    public int Code { get; private set; }
    public IntPtr wParam { get; private set; }
    public IntPtr lParam { get; private set; }
    public Process Caller { get; private set; }
    public DateTime Time { get; private set; }

    new public const string Description = "The system calls this function whenever an application calls the GetMessage or PeekMessage function and there is a mouse message to be processed. ";

    public override bool InterceptEffective
            return true;

    public INPUT_Messages Attachment
            return (INPUT_Messages)Code;

    public MouseMessages MouseMessage
        get { return (MouseMessages)wParam; }

    public bool KeyIsDown
        get { return !Convert.ToBoolean(lParam.ToInt32() & (1 << 30));}

    public Win32Window Above
        get { return new Win32Window(MouseData.hwnd);}

    public MOUSEHOOKSTRUCT MouseData
        get {return MarshalHelper.GetStructFromProcess<MOUSEHOOKSTRUCT>(Caller, lParam);}

    public override string ToString()
        return MouseMessage + " event @ " + + " above " + (HitTest)MouseData.wHitTestCode+" at "+Caller.ProcessName;
    public WH_MOUSE(HookArguments Msg): base(Msg)
        if (Msg == null) { return; }

        this.Code = Msg.nCode;
        this.wParam = Msg.wParam;
        this.lParam = Msg.lParam;
        this.Caller = Msg.Process;
        this.Time = Msg.TimeStamp;


I created 12 of these classes to make every hook callback human readable and easier to program or filter for certain events.

Here is a sample output of WH_CBT:

The Sample Application

If you just want to try it out for yourself please check out the download link.

Basically we want to use our features and intercept and change input we make to a remote notpad process. (We will not use WH_KEYBOARD_LL)

We make a new form and then we hook a WH_GETMESSAGE hook. For this we dont have to set Intercept to true but we can just set hook.message (speciality of this hooktype)

We will watch for a WM_CHAR event as this is a event which fires before the character pressed reaches the notepad.

using System.Hooks

var k = new Hook(HookType.WH_GETMESSAGE, NotepadProcess);
k.HookTriggered += k_HookTriggered;

void k_HookTriggered(HookArguments Msg, ref bool Intercept)
            var hook = new WH_GETMESSAGE(Msg);

            if (hook.Message.Msg == (int)WindowsMessages.WM_CHAR)
                IntPtr character = new IntPtr(HelloWorld());

                hook.Message = Message.Create(hook.Message.HWnd, hook.Message.Msg, character, hook.Message.LParam);

We change the char in WM_CHAR to out own character (character code is an IntPtr)


It is not possible to set a breakpoint when debugging if you set a global hook, as this will stop every window from refreshing its content. You have to kill the hooking application from taskmanager and everything will return to normal.


Here are some Ideas what we could do with hooks

  • Make any process crash
  • Intercept Keypress, Repaints and other events
  • Alter messages sent to the process (used in sample app)
  • Disallow certain processes from starting up (security)
  • Change the text in any GUI element
  • Stop a window from refreshing its content
  • Debug applications

Things Left Undone

Single bit information extraction

This class library was created against the native Win32 Api. I have copied most of the Pinvoke declaration to the namespace but there are some missing wrappers. Especially the translation of information coded into individual bits of the lParam or wParam are missing. For example the lParam of wh_keyboard callback.

Translation for the most common Windows messages

Most hook callbacks return a pointer to a msg struct. This contains information for a windows message like WM_CHAR or NTCHTEST. There should be a tranlator class which extracts information from a Msg struct.

Recursive struct to process reading/writing

As shown above there is a way to copy/read a native struct to/from a target process. Currently only the value of wparam or lparam can be copied. If this values are the reference to other structs those sould be copied as well without writing to the wrong memory locations.

Compile native dll as embedded data

Having an extra file around your exe isnt the prettiest solution and there certainly is a way to get rid of it. Maybe to embedd it as a resource and extract it at runtime.

Compile 64 and 32 bit native dll

The SetWindowsHookEx method injects 32 bit dlls into 32 bit processes and 64 bit into 64 bit processes. There would need to be two versions of the dll.

If you find a solution to any of those problems feel free to leave a comment.

Final Words

We saw how to make a hook class which doesn't need any previous knowledge of the user which can intercept change and watch global or local windowsmessages in a managed C# program.

Watch out what you do with these classes as they give you more power than the pure .Net framework delivers.

Especially with global hooks.

They give you the power to interfere with other processes. Some of the code does run in the other processes namespace and some functions directly write to process memory. Windows 8 (the one I have tested with) behaves well and doesnt inject the globalhook dll into taskmanager or explorer but still:

If you block or intercept the callback every process with a window will crash.

Please do not use this work for evil. Eg. to read passwords, as this is totally possible with WH_GETMASSAGE or drive the user insane when you intercept some mouse events.

If some pice of work is wrong or confusing/inconsistent please let me know. If you discover some bugs or have something interesting to show please leave a comment.



This article, along with any associated source code and files, is licensed under The MIT License


About the Author

D. Infuehr
Austria Austria
Currently i am 21 years old. I started making software when I was 13.
I use my spare time to make C# and C++ applications.

You may also be interested in...


Comments and Discussions

GeneralMy vote of 1 Pin
2006 Flauzer11-Aug-14 11:31
professional2006 Flauzer11-Aug-14 11:31 
GeneralRe: My vote of 1 Pin
D. Infuehr18-Aug-14 1:07
memberD. Infuehr18-Aug-14 1:07 
GeneralRe: My vote of 1 (changed) Pin
2006 Flauzer18-Aug-14 21:36
professional2006 Flauzer18-Aug-14 21:36 
GeneralRe: My vote of 1 (changed) Pin
D. Infuehr19-Aug-14 2:06
memberD. Infuehr19-Aug-14 2:06 
GeneralRe: My vote of 1 (changed) Pin
2006 Flauzer20-Aug-14 0:49
professional2006 Flauzer20-Aug-14 0:49 
QuestionThanks and....question: Pin
2006 Flauzer5-Aug-14 2:57
professional2006 Flauzer5-Aug-14 2:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.161026.1 | Last Updated 7 Aug 2014
Article Copyright 2014 by D. Infuehr
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid