Asuka Najakima

Protecting your devices from information theft

Keylogger detection using Windows API behaviors

Protecting your devices from information theft

In this article, we will introduce the keylogger and keylogging detection features added this year to Elastic Defend (starting from version 8.12), which is responsible for endpoint protection in Elastic Security. This article is also available in Japanese.

Introduction

Starting with Elastic Defend 8.12, we have enhanced the detection of keyloggers and malware with keylogging capabilities (such as information-stealing malware or remote access trojans, better known as RATs) on Windows by monitoring and recording the calls to representative Windows APIs used by keyloggers. This publication will focus on providing a detailed technical background of this new feature. Additionally, we will introduce the new prebuilt behavioral detection rules created in conjunction with this feature.

What is a keylogger and what are their risks?

A keylogger is a type of software that monitors and records the keystrokes entered on a computer (※1). While keyloggers can be used for legitimate purposes such as user monitoring, they are frequently abused by malicious actors. Specifically, they are used to steal sensitive information such as authentication credentials, credit card details, and various confidential data entered through the keyboard. (※1: While there are hardware keyloggers that can be attached directly to a PC via USB, this article focuses on software keyloggers.)

The sensitive information obtained through keyloggers can be exploited for monetary theft or as a stepping stone for further cyber attacks. Therefore, although keylogging itself does not directly damage the computer, early detection is crucial to preventing subsequent, more invasive cyber attacks.

There are many types of malware with keylogging capabilities, particularly RATs, information stealers, and banking malware. Some well-known malware with keylogging functionality includes Agent Tesla, LokiBot, and SnakeKeylogger.

How are keystrokes stolen?

Next, let's explain from a technical perspective how keyloggers function without being detected. While keyloggers can be used within various operating system environments (Windows/Linux/macOS and mobile devices), this article will focus on Windows keyloggers. Specifically, we will describe four distinct types of keyloggers that capture keystrokes using Windows APIs and functions (※2).

As a side note, the reason for explaining keylogging methods here is to deepen the understanding of the new detection features introduced in the latter half of this article. Therefore, the example code provided is for illustrative purposes only and is not intended to be executable as is (※3).

(※2: Keyloggers running on Windows can be broadly divided into those installed in kernel space (OS side) and those installed in the same space as regular applications (user space). This article focuses on the latter type.) (※3: If a keylogger is created and misused based on the example code provided below, Elastic will not be responsible for any consequences.)

  1. Polling-based keylogger

This type of keylogger polls or periodically checks the state of each key on the keyboard (whether the key is pressed) at short intervals (much shorter than one second). If a keylogger detects that a new key has been pressed since the last check, it records and saves the information of the pressed key. By repeating this process, the keylogger captures the characters entered by the user.

Polling-based keyloggers are implemented using Windows APIs that check the state of key inputs, with the GetAsyncKeyState API being a representative example. This API can determine whether a specific key is currently pressed and whether that key has been pressed since the last API call. Below is a simple example of a polling-based keylogger using the GetAsyncKeyState API:

while(true)
{
    for (int key = 1; key <= 255; key++)
    {
        if (GetAsyncKeyState(key) & 0x01)
        {
            SaveTheKey(key, "log.txt");
        }
    }
    Sleep(50);
}

The method of polling (GetAsyncKeyState) to capture key press states is not only a well-known, classic keylogging technique, but it is also commonly used by malware today.

  1. Hooking-based keylogger

Hooking-based keyloggers, like polling-based keyloggers, are a classic type that has been around for a long time. Let's first explain what a "hook" is.

A hook is a mechanism that allows you to insert custom processing (custom code) into specific operations of an application. Using a hook to insert custom processing is known as "hooking."

Windows provides a mechanism that allows you to hook messages (events) such as key inputs to an application, and this can be utilized through the SetWindowsHookEx API. Below is a simple example of a hooking-based keylogger using the SetWindowsHookEx API:

HMODULE hHookLibrary = LoadLibraryW(L"hook.dll");
FARPROC hookFunc = GetProcAddress(hHookLibrary, "SaveTheKey");

HHOOK keyboardHook = NULL;
    
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
                (HOOKPROC)hookFunc,
                hHookLibrary,
                0);
  1. Keylogger using the Raw Input Model

This type of keylogger captures and records raw input data obtained directly from input devices like keyboards. Before delving into the details of this type of keylogger, it's essential to understand the "Original Input Model" and "Raw Input Model" in Windows. Here's an explanation of each input method:

  • Original Input Model: The data entered from input devices like keyboards is processed by the OS before being delivered to the application.
  • Raw Input Model: The data entered from input devices is received directly by the application without any intermediate processing by the OS.

Initially, Windows only used the Original Input Model. However, with the introduction of Windows XP, the Raw Input Model was added, likely due to the increasing diversity of input devices. In the Raw Input Model, the RegisterRawInputDevices API is used to register the input devices from which you want to receive raw data directly. Subsequently, the GetRawInputData API is used to obtain the raw data.

Below is a simple example of a keylogger using the Raw Input Model and these APIs:

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{

    UINT dwSize = 0;
    RAWINPUT* buffer = NULL;

    switch (uMessage)
    {
    case WM_CREATE:
        RAWINPUTDEVICE rid;
        rid.usUsagePage = 0x01;  // HID_USAGE_PAGE_GENERIC
        rid.usUsage = 0x06;      // HID_USAGE_GENERIC_KEYBOARD
        rid.dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK;
        rid.hwndTarget = hWnd;
        RegisterRawInputDevices(&rid, 1, sizeof(rid));
        break;
    case WM_INPUT:
        GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));

        buffer = (RAWINPUT*)HeapAlloc(GetProcessHeap(), 0, dwSize);

        if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, buffer, &dwSize, sizeof(RAWINPUTHEADER)))
        {
            if (buffer->header.dwType == RIM_TYPEKEYBOARD)
            {
                SaveTheKey(buffer, "log.txt");
            }
        }
        HeapFree(GetProcessHeap(), 0, buffer);
        break;
    default:
        return DefWindowProc(hWnd, uMessage, wParam, lParam);
    }
    return 0;
}

In this example, RegisterRawInputDevices is used to register the input devices from which raw input data is to be received. Here, it is set to receive raw input data from the keyboard.

  1. Keylogger using DirectInput

Finally, let's discuss a keylogger that uses DirectInput. In simple terms, this keylogger abuses the functionalities of Microsoft DirectX. DirectX is a collection of APIs (libraries) used for handling multimedia tasks such as games and videos.

Since obtaining various inputs from users is essential in gaming, DirectX also provides APIs for processing user inputs. The APIs provided before DirectX version 8 are known as DirectInput. Below is a simple example of a keylogger using related APIs. As a side note, when acquiring key states using DirectInput, the RegisterRawInputDevices API is called in the background.

LPDIRECTINPUT8		lpDI = NULL;
LPDIRECTINPUTDEVICE8	lpKeyboard = NULL;

BYTE key[256];
ZeroMemory(key, sizeof(key));

DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&lpDI, NULL);
lpDI->CreateDevice(GUID_SysKeyboard, &lpKeyboard, NULL);
lpKeyboard->SetDataFormat(&c_dfDIKeyboard);
lpKeyboard->SetCooperativeLevel(hwndMain, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE | DISCL_NOWINKEY);

while(true)
{
    HRESULT ret = lpKeyboard->GetDeviceState(sizeof(key), key);
    if (FAILED(ret)) {
        lpKeyboard->Acquire();
        lpKeyboard->GetDeviceState(sizeof(key), key);
    }
  SaveTheKey(key, "log.txt");	
    Sleep(50);
}

Detecting keyloggers by monitoring Windows API calls

Elastic Defend uses Event Tracing for Windows (ETW ※4) to detect the aforementioned keylogger types. This is achieved by monitoring calls to related Windows APIs and logging particularly anomalous behavior. Below are the Windows APIs being monitored and the newly created keylogger detection rules associated with these APIs. (※4: In short, ETW is a mechanism provided by Microsoft for tracing and logging the execution of applications and system components in Windows, such as device drivers.)

Monitored Windows APIs:

New keylogger endpoint detection rules:

With this new set of capabilities, Elastic Defend can provide comprehensive monitoring and detection of keylogging activity, enhancing the security and protection of Windows endpoints against these threats.

Detecting Windows keyloggers

Next, let’s walk through an example of how the detection works in practice. We'll detect a keylogger using the Raw Input Model with Elastic Defend. For this example, we prepared a simple PoC keylogger named Keylogger.exe that uses the RegisterRawInputDevices API and executed it in our test environment ※5. (※5:The execution environment is Windows 10 Version 22H2 19045.4412, the latest version available at the time of writing.)

  Shortly after the keylogger was executed, a detection rule (Keystroke Input Capture via RegisterRawInputDevices) was triggered on the endpoint, showing an alert. The further details of this alert can be viewed within Kibana.

Here are the details of the detection rule, note the specific API referenced in the example.

query = '''
api where
 process.Ext.api.name == "RegisterRawInputDevices" and not process.code_signature.status : "trusted" and
 process.Ext.api.parameters.usage : ("HID_USAGE_GENERIC_KEYBOARD", "KEYBOARD") and
 process.Ext.api.parameters.flags : "*INPUTSINK*" and process.thread.Ext.call_stack_summary : "?*" and
 process.thread.Ext.call_stack_final_user_module.hash.sha256 != null and process.executable != null and
 not process.thread.Ext.call_stack_final_user_module.path :
                         ("*\\program files*", "*\\windows\\system32\\*", "*\\windows\\syswow64\\*",
                          "*\\windows\\systemapps\\*",
                          "*\\users\\*\\appdata\\local\\*\\kumospace.exe",
                          "*\\users\\*\\appdata\\local\\microsoft\\teams\\current\\teams.exe") and 
 not process.executable : ("?:\\Program Files\\*.exe", "?:\\Program Files (x86)\\*.exe")
'''

This rule raises an alert when an unsigned process, or a process signed by an untrusted signer, calls the RegisterRawInputDevices API to capture keystrokes. More specifically, Elastic Defend monitors the arguments passed to the RegisterRawInputDevices API, particularly the members of the RAWINPUTDEVICE structure, which is the first argument of this API.

This raises an alert when these argument values indicate an attempt to capture keyboard input. The logs of the RegisterRawInputDevices API can also be viewed within Kibana.

Data Collected During Windows API Calls

Due to space constraints, this article does not cover all of the detection rules and API details that were added. However, we will briefly describe the data that Elastic Defend collects during calls to the relevant Windows APIs. For further explanations for each item, please refer to the Elastic Common Schema (ECS) mapping detailed in custom_api.yml.

API NameFieldDescriptionExample
GetAsyncKeyStateprocess.Ext.api.metadata.ms_since_last_keyeventThis parameter indicates an elapsed time in milliseconds between the last GetAsyncKeyState event.94
GetAsyncKeyStateprocess.Ext.api.metadata.background_callcountThis parameter indicates a number of all GetAsyncKeyState api calls, including unsuccessful calls, between the last successful GetAsyncKeyState call.6021
SetWindowsHookExprocess.Ext.api.parameters.hook_typeType of hook procedure to be installed."WH_KEYBOARD_LL"
SetWindowsHookExprocess.Ext.api.parameters.hook_moduleDLL containing the hook procedure."c:\windows\system32\taskbar.dll"
SetWindowsHookExprocess.Ext.api.parameters.procedureThe memory address of the procedure or function.2431737462784
SetWindowsHookExprocess.Ext.api.metadata.procedure_symbolSummary of the hook procedure."taskbar.dll"
RegisterRawInputDevicesprocess.Ext.api.metadata.return_valueReturn value of RegisterRawInputDevices API call.1
RegisterRawInputDevicesprocess.Ext.api.parameters.usage_pageThis parameter indicates the top-level collection (Usage Page) of the device. First member RAWINPUTDEVICE structure."GENERIC"
RegisterRawInputDevicesprocess.Ext.api.parameters.usageThis parameter indicates the specific device (Usage) within the Usage Page. Second member RAWINPUTDEVICE structure."KEYBOARD"
RegisterRawInputDevicesprocess.Ext.api.parameters.flagsMode flag that specifies how to interpret the information provided by UsagePage and Usage. Third member RAWINPUTDEVICE structure."INPUTSINK"
RegisterRawInputDevicesprocess.Ext.api.metadata.windows_countNumber of windows owned by the caller thread.2
RegisterRawInputDevicesprocess.Ext.api.metadata.visible_windows_countNumber of visible windows owned by the caller thread.0
RegisterRawInputDevicesprocess.Ext.api.metadata.thread_info_flagsThread info flags.16
RegisterRawInputDevicesprocess.Ext.api.metadata.start_address_moduleName of the module associated with the starting address of a thread."C:\Windows\System32\DellTPad\ApMsgFwd.exe"
RegisterRawInputDevicesprocess.Ext.api.metadata.start_address_allocation_protectionMemory protection attributes associated with the starting address of a thread."RCX"

Conclusion

In this article, we introduced the keylogger and keylogging detection features for Windows environments that were added starting from Elastic Defend 8.12. Specifically, by monitoring calls to representative Windows APIs related to keylogging, we have integrated a behavioral keylogging detection approach that does not rely on signatures. To ensure accuracy and reduce the false positive rate, we have created this feature and new rules based on months of research.

In addition to keylogging-related APIs, Elastic Defend also monitors other APIs commonly used by malicious actors, such as those for memory manipulation, providing multi-layered protection. If you are interested in Elastic Security and Elastic Defend, please check out the product page and documentation.