Intercept keybord on windows

Spoiler: Initially designed to prevent a child from using keyboard shortcuts, here is how to hijack events in Windows and to block some ones.

A few years ago, I discovered, like many young parents, that it is more effective to let my children play on my computer for 5 minutes on my lap (afterwards, they get bored and go play something else) than to tell them “no” for a quarter of an hour…

Unfortunately, I have also discovered, like many young parents, that children are quite efficients in discovering improbable, painful and above all impossible to anticipate keyboard shortcuts in their creative frenzy. Just like with other toys, we then spend a lot of time cleaning everything. Two examples.

So it was initially to block the keyboard while they had fun and avoid accidents that I looked into how Windows manages the keyboard. And quickly, after very few lines of code, I had a small application disabling selected keyboard keys; no more stress when my children were doing anything, no more need to be on the lookout.

Interception. KeithJJ @ pixabay

As you might expect, if we can block certain keys, we can also know which ones are used. We can then do statistics or even record the sequences in search of frequent patterns (i.e. identifiers).

So without having any illusions about the interest you will have in the matter – scientific and professional, I have no doubt – here is how to globally intercept the keyboard on Windows.

Understand the keyboard

Before getting to the heart of the matter, I would like to take a little time to describe the journey followed by the information from the keyboard to the applications. It won’t take long, I promise (for details, you can see the official documentation) and that will allow us to better understand the why and how.

Keyboard event’s journey

It all starts with a keyboard manipulation. A key is pressed, released and in some cases, a switch can be toggled (i.e. case of Caps Lock keys). It’s very technical. Too technical in fact because each keyboard has its layout, its conventions, its additional keys. The scan code provided by the keyboard to tell us which key it is is specific to it.

Hence the interest of the drivers who are responsible for interpreting this idiomatic scan code and translating it into a more common version in an event message which contains everything necessary to know what to do with it the following components.

The event is first inserted into the system events queue which, as its name suggests, contains all the events of which the system has learned and which it will be able to manage or transfer to the appropriate party.

In this second case, the event is inserted into the event queue of the recipient thread. It will then be processed in the event loop of the thread (or of the application if it only has one thread).

For what we need, we must therefore intercept the event early enough to be global and miss as few events as possible, but still late enough to remain simple and avoid too low-level code. That’s good, Windows provides native functions for just that.

Example of a bad idea: the BlockInput function which allows you to disable keyboard inputs . Because it occurs at the thread level, so too late. Unable to block the keyboard of other applications and certain global shortcuts.

Install a hook

Installing an event hook is done via the function SetWindowsHookExA() including here is the signature:

HHOOK SetWindowsHookExA(
  int       idHook,
  HOOKPROC  lpfn,
  HINSTANCE hmod,
  DWORD     dwThreadId
);

The 4 parameters have their own interest (although for us, only the first two will be useful):

  1. idHook specifies the type of interception (which events will be captured and when it will take place), there is a whole list, I will give you details right below.
  2. lpfn, a pointer to the function that will be called when capturing the event, its signature is always the same but the meaning of the parameters can change.
  3. hmod identifies the DLL containing the function and which must be injected into the processes so that the function can be executed there. When no injection is needed, this parameter is useless (passed to NULL).
  4. dwThreadId identifies the thread for which we wish to intercept events, if the interception is global it can be omitted (passed to 0).

Warnings: Injecting DLLs into threads usually poses some limitations…

  1. Your DLL will only be injected into processes whose architecture is compatible (32 vs 64 bits),
  2. If your application comes from the Windows Store, it will not be injected into other Windows Store applications and the Runtime Broker (which manages permissions).

If you make a desktop application (Desktop app in official language), it can, on the other hand, inject its DLL into other apps, including from the Windows Store (tested by capturing the keyboard in Minecraft). I haven’t looked to see if a Windows Store app can be injected into a normal app…

When the hook is installed, the function returns us an identifier (which we will use next). On failure, it returns NULL (and error information is available via GetLastError ).

Hooking. photoheuristic.info @ flickr

Regarding the keyboard, we can intercept events before and after they pass through the thread’s event queue:

  1. Before (idHook = WH_KEYBOARD_LL), the interception is then called low level and necessarily global since it takes place before the event is transferred to its destination, in this case, the code is executed in our application and there is no need to inject any DLL (the hmod and dwThreadId parameters can be omitted).
  2. After (idHook = WH_KEYBOARD), the interception is then done in the process which manages the event and can concern a thread or be global, in this case, the code is executed in the targeted process and the DLL will have to be injected (the hmod and dwThreadId parameters take on their full meaning here).

Since we want to capture all events, including system keyboard shortcuts (like ALT + TAB), we will place our hook before the queue via the constant WH_KEYBOARD_LL. Our code will therefore look something like this:

#include "windows.h"
#include "Winuser.h"

LRESULT CALLBACK MyKeyboardCB(int nCode, WPARAM wParam,LPARAM lParam) ;

int main(int argc, char *argv[])
{
    HHOOK h = SetWindowsHookExA(WH_KEYBOARD_LL, MyKeyboardKB, NULL,0) ;
    if (h == NULL) {
        return 1 ;
    }

    // Do Stuffs

Uninstall the hook

When it is no longer needed, or before your application is finished (which then amounts to the same thing), it is polite to uninstall the hook. Nothing is documented about what would happen if you don’t uninstall it, so we can only imagine horrible things.

To uninstall your hook, you should call the function UnhookWindowsHookEx() which takes as parameter the identifier of the previously installed hook. To continue the previous code, here is how it would end:

    // Do Stuffs
    
    UnhookWindowsHookEx(h) ;
    return 0 ;
}

The return value (boolean) tells us about the success of the process, in my case, since I left the application, I did not process it.

Block event

We declared it in the code example, it is now time to define it, the function which will manage the keyboard events. For your information, here is his signature:

LRESULT CALLBACK LowLevelKeyboardProc(
  _In_ int    nCode,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

By catching the low level event (since we used the WH_KEYBOARD_LL flag), the parameters have a special meaning (details are provided in the LowLevelKeyboardProc() of which here is the general meaning:

When we have finished processing the event and/or want to pass it to the following hooks, we are asked to call the function CallNextHookEx() – with the same parameters that were provided to us and return the result it provided, as if we did not exist. If you don’t do this, other horrible things could happen (to other applications that won’t have access to the events).

alseeger @ pixabay

We can also choose our own return value. 0 meaning that the event can continue its journey to the applications. Any other value (i.e. 1) meaning that the journey must stop here, the event cannot go any further.

So we have everything we need to block the keyboard with the following hook:

LRESULT CALLBACK MyKeyboardCB(int nCode, WPARAM wParam,LPARAM lParam)
{
    if (nCode >= 0) {
        // Block every keyboard event
        return 1 ;
    }
    
    return CallNextHookEx(NULL, nCode, wParam, lParam) ;
}

Limitations: Since this hook takes place around the thread queue (and not inside the Windows system kernel), it cannot block CTRL + ALT + DELETE and will be inactive on the login screen.

Filter event

Rather than blocking everything stupidly, which is not very useful I admit, I suggest to add a little filtering. We let certain keys pass, and we block others.

The problem, if we block all keyboard input, is that it quickly becomes apparent and the children who were happy to be able to “work like dad” are frustrated not to see any reaction on the screen (at this age, notepad++ is a very addictive game).

To do this, we will read the third parameter (lparam), a pointer to the structure KBDLLHOOKSTRUCT, which contains a vkCode field with the virtual code of the key (an identifier common to all keyboards). The official list will explain them all, you can also write them on the console to see which key gives what value…

Filtered is better. Free-Photos @ pixabay

In my case, I mainly focused on certain special keys which can have consequences (TAB, ALT,…). It’s up to you to adapt the code according to your needs…


LRESULT CALLBACK MyKeyboardCB(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode == 0) {
        // Cast of third parameter to its real type
        KBDLLHOOKSTRUCT * event = (KBDLLHOOKSTRUCT *) lParam ;
        
        switch (event->vkCode) {
        case VK_TAB:
        case VK_MENU:
        case VK_SNAPSHOT:
        case VK_HELP:
        case VK_LWIN:
        case VK_RWIN:
        case VK_APPS:
        case VK_SLEEP:
            return 1 ;
        }
    }

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

And There you go !

In about 40 lines of code, you have a small program that blocks arbitrary keyboard keys. It’s up to you to imagine what happens next 😉.

At our side, it’s been a long time since we no longer needed to block the keys. The children have grown and our games have evolved; we went from notepad++ to minetest then to CS GO…

While waiting for other articles on this theme, here are some articles that may interest you…

Shellcode for Windows 10

6 janvier 2020 Because there is not just Linux in life, we’re going to take a look at Windows 10 for a new article on making shellcodes.