Windows Keylogging - Part I



S'goin' on guys, dtm here with a write-up on Windows keylogging.

As a promotional gift, I will be detailing a type of keylogger on the code level written in MSVC++ utilising the WinAPI.

Disclaimer: The following document is not newbie-friendly and requires a fair amount of knowledge of both general programming concepts and Windows internals:

Proficiency in C/C++
Knowledge of the WinAPI and its documentation
Knowledge of Windows messages and message queues
Knowledge of Windows hooks
Some understanding of multi-threading
Recommended knowledge of Mutual Exclusions
Recommended knowledge of FTP

General Overview of Keyloggers

A keylogger is an object which, in its simplest state, tracks key presses registered by the keyboard. Note that this object can be hardware as well as software but this article will focus more on the latter. When a key is pressed, the keylogger is able to capture it and then pass it along to its original destination. Keyloggers are not always considered malware since it is dependant on the situation in which it is used. Consider the following: If a parent is concerned about their child's activity on the computer, they can easily monitor them through their keyboard input; or in a company, the employer wishes to monitor the activity of his employees to make sure that they aren't doing what they aren't supposed to do. in fact, this probably goes for all "malware". Such applications may be referred to as Possibly Unwanted Programs (PUP) or Possibly Unwanted Applications (PUA).

In a standard console application which takes keyboard input, for example, using the getchar function from the standard C library, the input from the keyboard gets buffered into the stdin where the program can fetch the character. A keylogger is not limited to its own active console window (if it even has one at all) so how are we able to globally capture keystrokes? The answer is pretty simple, actually, in fact, Microsoft has already done most of the heavy lifting for us.

Callback Functions

Windows applications all work in harmony using something called messages. These messages are all passed through the message queue and each application can detect and process its own messages. Within the applications are callback functions which may be called in the event of retrieving certain messages. For example, pressing ALT+F4 in a GUI window will send a WM_CLOSE message into the message queue where the corresponding application's message loop will detect it and pass it to a WindowProc callback function for processing. What happens inside this function is that the message is filtered through some switch cases and in the case of a WM_CLOSE message, it will handle any clean ups and whatever else it needs, then it will destroy the window and also possibly exit the program.

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	switch (uMsg) {
		case WM_CLOSE:
			// ...

Additional message information may also be passed into the callback function under WPARAM and LPARAM arguments. Note that these arguments will not always be the same and can change under different callback functions.


There exists a callback function called LowLevelKeyboardProc and what this function does is it responds to key state changes either within a thread scope or a global scope - of course we're wanting to have a global scope for our purposes. But before we can use this function, we first need to learn about Windows hooks.

Windows Hooks

From the MSDN documentation on Windows Hooks, "A hook is a mechanism by which an application can intercept events, such as messages, mouse actions, and keystrokes. A function that intercepts a particular type of event is known as a hook procedure. A hook procedure can act on each event it receives, and then modify or discard the event." As we can see from this description, applications are able to "intercept ... keystrokes" and that is exactly what we are aiming to do with a keylogger.

Installing Hooks

To install a hook procedure, we must use the SetWindowsHookEx function to install a hook like so:

LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam) {
	// ...
	return CallNextHookEx(NULL, nCode, wParam, lParam);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	// ...
	SetWindowsHookEx(idHook, (HOOKPROC)HookProc, hMod, dwThreadId);
	// this is our message loop to receive messages from the queue
	MSG msg;
	BOOL bRet;
	while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
		if (bRet == -1) {
			// handle error
		} else {
	return 0;

Here, we would define a callback function HookProc and use a call to SetWindowsHookEx to install a hook so that our function can intercept and handle the hook procedure of type idHook. In the HookProc, we are highly recommended to call CallNextHookEx to be able to pass the hooked information onwards to the next existing hook procedure.

Let's finally get our hands dirty by writing a prototype.

Keylogger Prototyping

Disclaimer: The following code snippets will not contain any error checking for the sake of readability.

We now know what we need to set up a function to intercept keystrokes from the information provided above. It should look a little something like this:

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
	// ...
	return CallNextHookEx(NULL, nCode, wParam, lParam);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	// ...
	SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)LowLevelKeyboardProc, hModule, 0);
	// message loop here

For our SetWindowsHookEx, we require the idHook parameter to be WH_KEYBOARD_LL since it has a global scope. For our dwThreadId parameter, we set it to 0 since this will allow our hook procedure to be associated will all existing threads running on the same desktop as our application. As of yet, our keylogger is useless to us since we have not implemented any method to store the keystrokes.

Writing Keystrokes to Disk

First, we need to know about the arguments which are passed into the LoweLevelKeyboardProc function.

According to the MSDN documentation for the LowLevelKeyboardProc function, nCode will hold the value HC_ACTION if " wParam and lParam parameters contain information about a keyboard message". So only when nCode is equal to HC_ACTION will we need to write to the file, else we do not care. Once we have such a situation, we will need to check the wParam and lParam for information about the keypress. wParam contains information about how the key was pressed such as a key down or key up. lParam is documented as a pointer to a KBDLLHOOKSTRUCT which contains the respective vkCode of the keypress.

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
	if (nCode == HC_ACTION) {
		if (wParam == WM_KEYDOWN) {
			DWORD vkCode = kbd->vkCode;
			if (vkCode >= 0x30 && vkCode <= 0x5A) {
				FILE *fp = fopen(KEYLOG_FILE, "a+");
				fputc(vkCode, fp);
	return CallNextHookEx(NULL, nCode, wParam, lParam);

We can typecase the lParam as a pointer to a KBDLLHOOKSTRUCT and assign it to a variable kbd. For this example, we only test for basic input which includes keys ranging from 0-9 and A-Z. Of course, right now there is a lot missing detail with regards to other keys such as ALT, CTRL and SHIFT and also that it cannot differentiate upper and lower case and symbols. We can easily fix this by manually adding in some tests and switch statements until it reaches our satisfaction. Once we have extracted the vkCode member from kbd and it satisfies the key range, we will open our file defined by KEYLOG_FILE with an append mode and simply add on the vkCode to the end of the file.

After we've finished what we intended to do, we can then push the keypress onwards to the next hook procedure.


In the next part I will showcase my keylogger code and briefly run through its functionality and how it's achieved. Thanks for reading and stay tuned!

-- dtm

Windows Keylogging - Part II
Our First "1337 of the W33k"
(Command-Line Ninja) #2

Nice article, although only the first paragraph and link to the real article original article on the main page should be supplied.


I think I prefer posting onto the forum from now on. You could just modify the one on the front page or remove it entirely.


Great article DTM! I'm sure it'll help a lot of people out!

(Command-Line Ninja) #5

This really changed how we do things! The old way was cool! :stuck_out_tongue:


Hello @dtm I’ve been reading your Security-Oriented C tutorial since Null Byte didn’t change their theme.Since they changed the website your tutorials is lacking some parts.Like this article :

Not just your tutorials,OccupyTheWeb and the others’ articles have same problem.

Where can I find your full Security-Oriented C tutorials?


I’ve discontinued that series.

@oaktree @unh0lys0da @pry0cc


They were great series.I want to read all you’ve written but not in Null Byte.So do you have word documents of these written series.If yes,can you send me please?


I don’t have a back up of them but somebody else might. Which ones were you specifically looking for?


Especially from 0x0c tutorial to end tutorial.But if someone backed up of them I need all of them.Because they were absolutely excellent articles.I’ve really liked them.


Thanks for the support but the series has been concluded. There are no missing articles there, only missing information on the higher numbered ones.


Thanks for your help.


Hello @dtm
I could find the original articles from Security-Oriented C series using “”.
And I wanted to ask you a question (sorry if I’m posting this at the wrong place).

In tutorial 0xFE (It lives), I tried to use append binary “ab” mode instead of “wb” mode so that the infected file could run itself and then the virus, but only the victim file executed.

Is it related to EOF at the end of the (original) infected file?
How can I make it work, something like Win32.Sality?

Thanks for the series and I’m eagerly willing to see your answer :blush:

(Command-Line Ninja) #14

I don’t know if he even remembers the exact content of each article. If you can link to the article on it might give you a better chance of reply.


Of course (and thanks for the advice)


Please PM me with Win32.Sality’s code and I will get back to you on the NB article.


Well by mentioning Sality I just wanted to give an example of a virus which doesn’t overwrite and destroy the victim file but appends to it and as the result, when you execute the infected victim file, both the victim file and the virus run.

But if you wish I will try to find out exactly how Sality works :slight_smile:

(Command-Line Ninja) #18

Hmm this indeed is an interesting concept. You could be mega dirty and just filebind everything. (This would break a lot too though).

You might be best off backdooring the program that opens those files, perhaps every executable on the system? Then when you open a text file, command prompt, or a spreadsheet, you are opening the payload too.


Okay, I see what you mean by using mode ab but unfortunately, it’s not quite as simple as you think it is.

I’ve already documented a method for this in my paper PE File Infection and have done an analysis on a simple appending virus Understanding a Win32 Virus: Code Analysis with background material Understanding a Win32 Virus: Background Material.


Thank you both of you. I will study the papers and see if I can put them into practice.
Maybe I would go mega-dirty!