Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Minimal Key Logger Using RAWINPUT

4.71/5 (7 votes)
8 Mar 2013CPOL5 min read 40K   2.6K  
This is a VB.NET and C# version of Minimal Key Logger Using RAWINPUT.

Introduction

If you are looking for something to read you should visit the primary article. This is short alternative article which includes downloads not available anywhere but here. A full C# and a full VB.Net version of the software is provided. 

This is the only managed minimalist Raw Input key logger I am aware of on the planet, if there are others out there please comment where they are for download. Also this is the only Raw Input library that compiles property for 64-bit platforms that I know of; again if you know of another please comment. 

I know this article is short, but it's all been said in the main article. I'm writing this article to release source code you simply will not find anywhere else but here. 

I know many VB.Net and C# coders may have tried unsuccessfully to recreate this project from the unmanaged versions in the original article. Most raw input libraries for C# contain multiple flaws and information on creating minimal message only windows in C# or VB.NET is not intuitive.

Background

I have not come across a C# Raw Input library that compiles properly for 64-bit versions. I have not come across any for VB.Net; please comment and leave the download link if you know of any. These Raw Input classes are in both languages and do compile properly for the 64-bit platform as well as the 32-bit platform. If you read the original article you will be able to draw many parallels to the managed code.

Key logging is monitoring and logging keys being pressed and released on your keyboard(s). The application that has the focus of the keyboard receives a message each time you press a key down. And a separate message when you release the key (key up). When you hold a key down the application may receive multiple key down messages. An application receives key down and key up messages (along with other forms of input messages like mouse input) through a very specifically named method called "WndProc" every window that is capable of receiving these messages has a "WndProc" method. 

A traditional key logger (one that does not use Raw Input) installs a global hook. You can research global hooks if you are unfamiliar with them. But let us just admit it can affect your machines performance, especially if done improperly. Raw Input is a different technique altogether. Raw Input is especially nice when you may have multiple keyboards or input devices and you are trying to keep your log decipherable. Knowing which device the input is coming from is very handy. 

Instead of installing a global hook, the raw input technique only requires you to create a minimal window. It requires you to do so because you need to have a window handle to a window that has a "WndProc". You can then "register" that handle with RegisterRawInputDevices and  once you do so; that window will receive all input messages that you registered to receive (parameters you pass to it determine which input messages you will receive). In the case of this key logger it registers to receive all keyboard input. It would be fun to try with multiple keyboards. This Raw Input technique is very elegant compared to the traditional key logging scenario.

The hard part in doing all this is to have the proper structures and parameters needed to make the various Raw Input Native calls. It can be important to keep things minimal and proper; the reason most managed Raw Input libraries do not compile properly for 64-bit versions is because of improper structures get bloated (offsets thrown off) by an additional four bytes or are four bytes to small to store a pointer. The pointers for 64-bit are 8 bytes; the pointers for 32-bit are 4 bytes. Which is why it is important to use pointers (System.IntPtr) in the correct spot for parameters and structures and integers in their correct spots and not intermingle the two. Often you may intermingle the two on 32-bit versions because they all are four bytes. But when you compile your 64-bit version the calls become unpredictable. 

The fact is I can write a billion paragraphs here; but you will learn more by using the code and wrapping your own mind around the source code. Set break points, see for yourself what is going on. Even better; change it, modify it, improve it, add to it, comment about it and have fun with it. 

Using the code  

The code below demonstrates one of the most difficult challenges for beginners who may have wanted to port the code themselves. Which is how we can Marshal an hInstance  required for WNDCLASS and also note that New IntPtr<code>(-3) is HWND_MESSAGE (which tells CreateWindowEx to create a message only window).  

VB.NET
Dim msg As New MSG
Dim wc As New WNDCLASS
Dim hInstance As System.IntPtr = New System.IntPtr(System.Runtime.InteropServices.Marshal.GetHINSTANCE(_
    System.Reflection.Assembly.GetExecutingAssembly.GetModules()(0)).ToInt32())
wc.lpfnWndProc = AddressOf WndProcCallback
wc.hInstance = hInstance
wc.lpszClassName = "SimpleMessageWindow"
RegisterClass(wc)
m_handle = CreateWindowEx(0, wc.lpszClassName, Nothing, 0, 0, 0, 0, 0, New IntPtr(-3), IntPtr.Zero, hInstance, 0)
C#
MSG msg = new MSG();
WNDCLASS wc = new WNDCLASS();
System.IntPtr hInstance = new System.IntPtr(System.Runtime.InteropServices.Marshal.GetHINSTANCE(
   System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]).ToInt32());
wc.lpfnWndProc = WndProcCallback;
wc.hInstance = hInstance;
wc.lpszClassName = "SimpleMessageWindow";
RegisterClass(wc);
m_handle = CreateWindowEx(0, wc.lpszClassName, null, 0, 0, 0, 0, 0, 
           new System.IntPtr(-3), System.IntPtr.Zero, hInstance, 0);

The downloadable demo applications are complete examples of the entire project. 

Points of Interest

Using Raw Input is fun, however it does not provide a way to detect which application or control has focus for the input. In other words, we know a key was pressed or released but which window (application) was the original message sent to?  For that determination there are a few techniques available. The technique I use myself is to use SetWinEventHook but don't confuse that with SetWinEventHookEx. If I can ever get this article done I will post an tip about how to use SetWinEventHook to listen to system events (directly in your managed applications; which is suppose to be "impossible"). Anyway there is a system event (EVENT_OBJECT_FOCUS = 32773) which you can hook into and be notified when the keyboard focus changes. This will eliminate the need to constantly poll GetForegroundWindow every time you want to know which application was the original destination for the keyboard input message. Hopefully I will be able get that tip published and link to it here when it is. 

History  

I hope this stimulates more VB.Net and C# coders to utilize Raw Input. I look forward to hearing comments from VB.Net and C# coders; all others can comment on the original article; this is only for VB.Net and C# coders.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)