Skip to content
Martin Drab edited this page Apr 12, 2020 · 9 revisions

In this tutorial, we will use IRPMon to monitor keyboard events in order to discover how Windows handles keyboard at the level of the Keyboard Class Driver (kbdclass.sys).

As Figure 1 shows, the Keyboard Class Driver is registered as an upper filter for the Keyboard device class which means it gets notified whenever a new keyboard is connected to the computer regardless of the connecting interface. Since the main goal of the driver consists of presenting an unified keyboard interface to upper layers of the operating system, it never misses a chance to attach to and monitor events emitted by the newly connected device.

The Keyboard Class Driver is an upper filter for the Keyboard device setup class
Figure 1: The Keyboard Class Driver is an upper filter for the Keyboard device setup class

We will use IRPMon to monitor how the upper layers of the system communicate with kbdclass to obtain information about user's keyboard activity. This can be seen as key logging, an activity usually performed by malware. However, nobody ever claimed that IRPmon can be used only to accomplish clearly good things only. In addition, kbdclass is not a good place for keyloggers, if the authors need accurate results.

Since keyboards play an important role in communication between the user and her machine, it would be best to have at least two of these devices connected and use IRPMon to monitor only one of them. This way, we will still be able to work with PC even if something goes wrong with the keyboard being monitored. Probably the best option is to use a laptop with an extra USb keyboard plugged in.

Reaching the window for selection drivers and devices to monitor
Figure 2: Reaching the window for selection drivers and devices to monitor

To start monitoring keyboard events, we need to run IRPMon and select the Select drivers / devices... menu item (Figure 2) and search for the kbdclass driver (\Driver\kbdclass) in the list of drivers we may monitor. In my case, we see that the driver has placed its two devices into two device stacks. In more precise terms, the \Device\KeyboardClass0 represents the main keyboard of my laptop that is displayed as connected via PS/2 interface, and the \Device\Keyboardclass114 device represents an extra USb keyboard. As mentioned above, we will go for the PS/2 keyboard, so we mark it as hooked and press Ok to start the job (Figure 3).

Hooking laptop's PS/2 keyboard
Figure 3: Hooking laptop's PS/2 keyboard

To actually receive events, we also need to check the Capture Events menu item in the Monitoring submenu. After that, the kernel driver enumerates all running processes and reports them in form of individual events. We use the Clear menu item to get rid of them. Also, I have extensively used the Columns submenu to hide columns that are not useful for our case.

Now, let's press a key X on the PS/2 keyboard. We can see that kbdclass is queried for keyboard events by an IRP_MJ_READ IRP. An interesting fact is that the driver pends such an incomming IRP until a keyboard event occurs. That explains why the last event reported by IRPMon is an incomming read IRP - this IRP waits for a future keyboard event. This explains why the first event IRPMon reports is IRP completion.

Monitoring press and release of the X key
Figure 4: Monitoring press and release of the X key

Note: Since IRPMon can catch the completion event only for IRPs it intercepts on their way down the device stack, it can even miss completion of the first read IRP since this IRP was sent to kbdclass way before the monitoring started. Figure 4 does not cover this case.

As we can see, key press and key releases are treated as distinct events. That allows the software to easily detect when multiple keys are pressed at once. The hardware (the PS/2 interface at least) reports these events as distinct too.

There exist some documentation around kbdclass. It is also possible to find driver's source code inside the Windows Driver Development Kit (DDK). Examination of the code reveals an interesting fact - as a reaction to an IRP_MJ_READ request, the driver does not send any IRPs down the device stack. It actually performs no action at all. So, how it gets notified when the user hits a key?

We can either extract this information from kbdclass source code, deduce it from the documentation, or use IRPMon to monitor what kbdclass does early after attaching to a newly connected keyboard device. We can accomplish this by monitoring requests to the driver we see just below kbdclass. Since we are interested in monitoring of a device that does not exist yet, we need to instruct IRPMon to monitor requests sent to devices not known to the tool. This can be accomplished by simply checking the New devices context menu item as Figure 5 shows.

Watching for currently non-existent devices
Figure 5: Watching for currently non-existent devices

As for connecting a new keyboard to the machine, we have two options: disconnect and reconnect the USB keyboard (which means we needd to monitor the kbdhid driver), or disable and enable again the PS/2 keyboard device in Device Manager. I decided to use the former approach.

After connecting the keyboard (or enabling it in the Device Manager), we may se quite a lot of requests detected by IRPMon. Most of them are rleated to the fact that keyboards are Plug&Play devices and the system uses these requests to retrieve their various details. For our case, only the IOCTL_INTERNAL_KEYBOARD_CONNECT request is interesting. We can even look into documentation to see what it actually does.

The IOCTL sent by kbdclass to register for receiving keyboard-related events from the kbdhid driver
Figure 6: The IOCTL sent by kbdclass to register for receiving keyboard-related events from the kbdhid driver

Kbdclass actually uses the request to register itself within a driver responsible for receiving keyboard events from the hardware (kbdhid in our case). When an event occurs, that driver gathers details about it and calls the callback provided by kbdclass. This way, kbdclass gets informed on anyhting related to keyboard.

similar approach can be taken to research how the Mouse Class Driver does its job. It works more or less the same way - far the biggest difference is that it works with mice, not keyboards.

General

For Users-Developers

Tutorial

Public API

Functions

Types

Clone this wiki locally