Adding a keyboard logger is a great way to learn more about the Windows operating system. The previous filter examples (network and file system filtering) simply intercepted traffic, but keyboard logging not only adds the complexity of an I/O completion routine, it also covers logging to a file from a rootkit, something suspiciously missing up to this point. On the bright side, the rootkit we have been developing in this book is already set up to create new devices and process device I/O, so the foundation for a keyboard logger is already in place.
As already mentioned, a completion routine is required for keyboard I/O. This is simply a callback routine to sneak a peek at key data before it is passed back up the stack to the application receiving keys. To receive a key, a high-level driver must send down an empty IRP and wait for a key. When a key is pressed, the IRP is completed and key data is sent back up the stack. This would be great if we were writing an actual driver, but as a filter this design requires special consideration.
The keyboard driver stack design requires a keyboard logger to attach to the stack and wait for an empty IRP from a higher-level driver. The logger must then register its own callback routine before passing the empty IRP down the stack. Then, when called, the keyboard logging callback routine can peek at key data before it is sent up to the device that created the original IRP. This must be done (usually twice - once for key down and once for key up) for every key passed up the keyboard driver stack.
This IRP intercept method presents a special problem when the rootkit is unloaded. When unloading, the rootkit’s registered callback routine will be removed from memory, but chances are good that an empty IRP has already registered this routine to be called when the next key is pressed. This will most likely result in a system crash when the next key is pressed. If you’re not familiar with the term, “blue screen of death” (BSOD) is the common convention for describing this state. To avoid this problem, the rootkit will need to create its own special IRPs, associate them the intercepted IRPs, and send the special IRPs in place of the intercepted IRPs. This enables the rootkit to cancel any pending IRPs when the rootkit is unloaded. Figure 8-1 shows a key logger insertion.
There is also the problem of logging the key. As mentioned earlier, the callback routine registered by the keyboard logger can be called at the dispatch processing level, but file I/O can only be performed at the passive processing level. This requires the callback routine to send the key to a temporary storage area, monitored by a thread running at IRQL = PASSIVE_LEVEL. The reading thread also requires synchronization with the writing completion routine. This is why it was stated earlier that “a keyboard logger is a great way to learn more about the Windows operating system.” Key logger synchronization is shown in Figure 8-2.