Click here to Skip to main content
15,171,664 members
Articles / Desktop Programming / Win32
Posted 4 Aug 2014

Tagged as


50 bookmarked

Global Interceptable Program and System Hooks in .NET

Rate me:
Please Sign up or sign in to vote.
4.92/5 (21 votes)
7 Aug 2014MIT14 min read
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 don't want to read the whole documentation about hooks, please check out following articles before continuing:

  1. Global System Hooks in .NET
  2. Using Hooks from C#

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. Let's 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.

At 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 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: 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 associated 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 two 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 at 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 received 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 available process. This DLL cannot 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 parallel. 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 all hooks have in common and what extra information we want to send. Let's 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 on 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 structure 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 transferred 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 received 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 Intercept the hook chain (message will not be passed to the process). Some hooks are interceptable and some are not, we can't 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 tests have shown that only returning -1 does prevent the operation. (This can be used to prevent windows from showing or destroying. Basically you can't 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 that's 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 receive windows messages. As we have seen above, we want to receive 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 don't have all the overloads. Messageloop creates a window for us and completely hides it from the user. It also has an eventhandler for received 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 declaration, it's 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 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 associated 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 can't receive any new messages now because it is still blocked in SendMessage. It cannot receive anything so it cannot 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:

                        (process, PassData.lParam);

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

So, here's 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 Notepad process. (We will not use WH_KEYBOARD_LL).

We make a new form and then we hook a WH_GETMESSAGE hook. For this, we don't 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 an 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 our own character (character code is an IntPtr):

Image 1


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 that 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 translator 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 these values are the reference to other structs, those should be copied as well without writing to the wrong memory locations.

Compile Native DLL as Embedded Data

Having an extra file around your EXE isn't the prettiest solution and there certainly is a way to get rid of it. Maybe to embed 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 doesn't 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. E.g.,. to read passwords, as this is totally possible with WH_GETMASSAGE or drive the user insane when you intercept some mouse events.

If some piece 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.


  • 5th August, 2014: Initial version


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


About the Author

D. Infuehr
Austria Austria
I use my spare time to make C# and C++ applications.

Comments and Discussions

QuestionAwesome Code(!) and little Bug Pin
Michael Dewitz23-Sep-21 8:53
MemberMichael Dewitz23-Sep-21 8:53 
QuestionWell explained and very informative... How can I use this code to prevent launching a specific process like "Explorer.exe"? Pin
abdelhamed9-Feb-21 12:50
Memberabdelhamed9-Feb-21 12:50 
QuestionRead from any control Pin
Mohideenmeera18-Aug-20 5:35
MemberMohideenmeera18-Aug-20 5:35 
AnswerRe: Read from any control Pin
D. Infuehr22-Sep-21 2:16
MemberD. Infuehr22-Sep-21 2:16 
AnswerRe: Read from any control Pin
D. Infuehr22-Sep-21 2:16
MemberD. Infuehr22-Sep-21 2:16 
QuestionGlobal WH_GETMESSAGE Hook Pin
Member 129081715-Apr-18 1:36
MemberMember 129081715-Apr-18 1:36 
AnswerRe: Global WH_GETMESSAGE Hook Pin
Michael Dewitz17-Sep-21 0:10
MemberMichael Dewitz17-Sep-21 0:10 
Questioncan only hook a process that is launched by hooking process? Pin
Member 1164174026-Mar-17 4:12
MemberMember 1164174026-Mar-17 4:12 
AnswerRe: can only hook a process that is launched by hooking process? Pin
D. Infuehr13-Apr-17 14:38
MemberD. Infuehr13-Apr-17 14:38 
When i tested it 3 years ago it worked as expected.
Maybe after several new Windows 10 builds the API behaviour has changed.
It should work with any notepad Smile | :)
AnswerRe: can only hook a process that is launched by hooking process? Pin
Michael Dewitz22-Sep-21 12:35
MemberMichael Dewitz22-Sep-21 12:35 
GeneralMy vote of 1 Pin
2006 Flauzer11-Aug-14 12:31
professional2006 Flauzer11-Aug-14 12:31 
GeneralRe: My vote of 1 Pin
D. Infuehr18-Aug-14 2:07
MemberD. Infuehr18-Aug-14 2:07 
GeneralRe: My vote of 1 (changed) Pin
2006 Flauzer18-Aug-14 22:36
professional2006 Flauzer18-Aug-14 22:36 
GeneralRe: My vote of 1 (changed) Pin
D. Infuehr19-Aug-14 3:06
MemberD. Infuehr19-Aug-14 3:06 
GeneralRe: My vote of 1 (changed) Pin
2006 Flauzer20-Aug-14 1:49
professional2006 Flauzer20-Aug-14 1:49 
QuestionThanks and....question: Pin
2006 Flauzer5-Aug-14 3:57
professional2006 Flauzer5-Aug-14 3: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.