Creating a local hook

A local hook is a hook that targets the same process that your code is running within.

To install a local hook we need to do four things:

  1. Retrieve the address of the native method to be hooked, for this we will use LocalHook.GetProcAddress
  2. Define a delegate type that matches the native method calling convention and parameters
  3. Write a hook handler method that we want to run in place of the original native method
  4. Lastly we need to create the hook using LocalHook.Create, passing in the original method address and the replacement delegate.

The complete sample is provided below.

The full managed BeepHook tutorial source project can be found here.

1. Retrieving the native method's address

If that the method you wish to hook is exported by the DLL, we can use LocalHook.GetProcAddress to retrieve the method address. E.g. to retrieve the address of the user32!MessageBeep method exported by user32.dll we would use the following code:

LocalHook.GetProcAddress("user32.dll", "MessageBeep");

2. Creating a delegate type that matches the native method

When creating the delegate type it is important that we match the same calling convention as the original and use the correct parameter types and marshalling. Here is the native WINAPI method declaration for MessageBeep:

BOOL WINAPI MessageBeep(
  _In_ UINT uType
);

The corresponding delegate type would then look like:

[UnmanagedFunctionPointer(CallingConvention.StdCall, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
delegate bool MessageBeepDelegate(uint uType);

3. Write the hook handler

The hook handler method must be compatible with the delegate that was created.

A hook handler for the MessageBeep method might look like this:

[DllImport("user32.dll")]
static extern bool MessageBeep(uint uType);

static bool MessageBeepHook(uint uType)
{
    // Change the message beep to always be "Asterisk" (0x40)
    // see https://msdn.microsoft.com/en-us/library/windows/desktop/ms680356(v=vs.85).aspx
    return MessageBeep(0x40);
}

If you cannot use a DllImport and you still want to call the original method after retrieving the address in some other manner, you can use the original function address with Marshal.GetDelegateForFunctionPointer. To call the original method in this manner :

static bool MessageBeepHook(uint uType)
{
    // Calling the original method with no changes
    return Marshal.GetDelegateForFunctionPointer<MessageBeepDelegate>(origAddr)(uType);
}

Note: EasyHook implements the hook trampoline code in such a way that calling the original method directly (or indirectly) while still within the hook handler will bypass the hook handler and call the original method.

4. Create and enable the local hook

We now have everything we need to create the hook. This involves two steps:

  1. Create the LocalHook instance using LocalHook.Create, and
  2. Activate the hook by telling it which threads to include/exclude from the hook
// Create the local hook using our MessageBeepDelegate and MessageBeepHook handler
var hook = EasyHook.LocalHook.Create(
        EasyHook.LocalHook.GetProcAddress("user32.dll", "MessageBeep"),
        new MessageBeepDelegate(MessageBeepHook),
        null);
        
// Only hook this thread (threadId == 0 == GetCurrentThreadId)
hook.ThreadACL.SetInclusiveACL(new int[] { 0 });

Full MessageBeep hook example

Create a new console application, install the EasyHook NuGet package and then replace the existing Program.cs with the following code.

This example hooks the MessageBeep method in order to prevent it from being called.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System;
using System.Runtime.InteropServices;

namespace BeepHook
{
    class Program
    {
        // The matching delegate for MessageBeep
        [UnmanagedFunctionPointer(CallingConvention.StdCall, SetLastError = true)]
        delegate bool MessageBeepDelegate(uint uType);

        // Import the method so we can call it
        [DllImport("user32.dll")]
        static extern bool MessageBeep(uint uType);

        /// <summary>
        /// Our MessageBeep hook handler
        /// </summary>
        static private bool MessageBeepHook(uint uType)
        {
            // We aren't going to call the original at all
            // but we could using: return MessageBeep(uType);
            Console.Write("...intercepted...");
            return false;
        }
        
        /// <summary>
        /// Plays a beep using the native MessageBeep method
        /// </summary>
        static private void PlayMessageBeep()
        {
            Console.Write("    MessageBeep(BeepType.Asterisk) return value: ");
            Console.WriteLine(MessageBeep((uint)BeepType.Asterisk));
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Calling MessageBeep with no hook.");
            PlayMessageBeep();

            Console.Write("\nPress <enter> to call MessageBeep while hooked by MessageBeepHook:");
            Console.ReadLine();

            Console.WriteLine("\nInstalling local hook for user32!MessageBeep");
            // Create the local hook using our MessageBeepDelegate and MessageBeepHook function
            using (var hook = EasyHook.LocalHook.Create(
                    EasyHook.LocalHook.GetProcAddress("user32.dll", "MessageBeep"),
                    new MessageBeepDelegate(MessageBeepHook),
                    null))
            {
                // Only hook this thread (threadId == 0 == GetCurrentThreadId)
                hook.ThreadACL.SetInclusiveACL(new int[] { 0 });

                PlayMessageBeep();

                Console.Write("\nPress <enter> to disable hook for current thread:");
                Console.ReadLine();
                Console.WriteLine("\nDisabling hook for current thread.");
                // Exclude this thread (threadId == 0 == GetCurrentThreadId)
                hook.ThreadACL.SetExclusiveACL(new int[] { 0 });
                PlayMessageBeep();

                Console.Write("\nPress <enter> to uninstall hook and exit.");
                Console.ReadLine();
            } // hook.Dispose() will uninstall the hook for us
        }
        
        public enum BeepType : uint
        {
            /// <summary>
            /// A simple windows beep
            /// </summary>            
            SimpleBeep = 0xFFFFFFFF,
            /// <summary>
            /// A standard windows OK beep
            /// </summary>
            OK = 0x00,
            /// <summary>
            /// A standard windows Question beep
            /// </summary>
            Question = 0x20,
            /// <summary>
            /// A standard windows Exclamation beep
            /// </summary>
            Exclamation = 0x30,
            /// <summary>
            /// A standard windows Asterisk beep
            /// </summary>
            Asterisk = 0x40,
        }
        
    }
}

Running this code results in the following output:

Calling MessageBeep with no hook.
    MessageBeep(BeepType.Asterisk) return value: True

Press <enter> to call MessageBeep while hooked by MessageBeepHook:

Installing local hook for user32!MessageBeep
    MessageBeep(BeepType.Asterisk) return value: ...intercepted...False

Press <enter> to disable hook for current thread:

Disabling hook for current thread.
    MessageBeep(BeepType.Asterisk) return value: True

Press <enter> to uninstall hook and exit.