<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Asuka Nakajima - Elastic Security Labs]]></title>
    <description><![CDATA[Trusted security news & research from the team at Elastic.]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Asuka Nakajima - Elastic Security Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2c6b841aff36df4/6a88d9784acc96e3f324863d/security-labs-thumbnail.png</url>
      <link>https://www.elastic.co/security-labs/author/asuka-nakajima</link>
    </image>
    <link>https://www.elastic.co/security-labs/author/asuka-nakajima</link>
    <atom:link href="https://www.elastic.co/security-labs/rss/author/asuka-nakajima.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Tue, 15 Sep 2026 05:14:44 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Detecting Hotkey-Based Keyloggers Using an Undocumented Kernel Data Structure]]></title>
    <description><![CDATA[In this article, we explore what hotkey-based keyloggers are and how to detect them. Specifically, we explain how these keyloggers intercept keystrokes, then present a detection technique that leverages an undocumented hotkey table in kernel space.]]></description>
    <content:encoded><![CDATA[<p>In May 2024, Elastic Security Labs published <a href="https://www.elastic.co/security-labs/protecting-your-devices-from-information-theft-keylogger-protection">an article</a> highlighting new features added in <a href="https://www.elastic.co/guide/en/integrations/current/endpoint.html">Elastic Defend</a> (starting with 8.12) to enhance the detection of keyloggers running on Windows. In that post, we covered four types of keyloggers commonly employed in cyberattacks — polling-based keyloggers, hooking-based keyloggers, keyloggers using the Raw Input Model, and keyloggers using DirectInput — and explained our detection methodology. In particular, we introduced a behavior-based detection method using the Microsoft-Windows-Win32k provider within <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-">Event Tracing for Windows</a> (ETW).</p>
<p>Shortly after publication, we were honored to have our article noticed by <a href="https://jonathanbaror.com/">Jonathan Bar Or</a>, Principal Security Researcher at Microsoft. He provided invaluable feedback by pointing out the existence of hotkey-based keyloggers and even shared proof-of-concept (PoC) code with us. Leveraging his PoC code <a href="https://github.com/yo-yo-yo-jbo/hotkeyz">Hotkeyz</a> as a starting point, this article presents one potential method for detecting hotkey-based keyloggers.</p>
<h2 id="overviewofhotkeybasedkeyloggers">Overview of Hotkey-based Keyloggers</h2>
<h3 id="whatisahotkey">What Is a Hotkey?</h3>
<p>Before delving into hotkey-based keyloggers, let’s first clarify what a hotkey is. A hotkey is a type of keyboard shortcut that directly invokes a specific function on a computer by pressing a single key or a combination of keys. For example, many Windows users press <strong>Alt + Tab</strong> to switch between tasks (or, in other words, windows). In this instance, <strong>Alt + Tab</strong> serves as a hotkey that directly triggers the task-switching function. </p>
<p><em>(Note: Although other types of keyboard shortcuts exist, this article focuses solely on hotkeys. Also, <strong>all information herein is based on Windows 10 version 22H2 OS Build 19045.5371 without virtualization based security</strong>. Please note that the internal data structures and behavior may differ in other versions of Windows.)</em></p>
<h3 id="abusingcustomhotkeyregistrationfunctionality">Abusing Custom Hotkey Registration Functionality</h3>
<p>In addition to using the pre-configured hotkeys in Windows as shown in the previous example, you can also register your own custom hotkeys. There are various methods to do this, but one straightforward approach is to use the Windows API function <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey"><strong>RegisterHotKey</strong></a>, which allows a user to register a specific key as a hotkey. For instance, the following code snippet demonstrates how to use the <strong>RegisterHotKey</strong> API to register the <strong>A</strong> key (with a <a href="https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes">virtual-key code</a> of 0x41) as a global hotkey:</p>
<pre><code>/*
BOOL RegisterHotKey(
  [in, optional] HWND hWnd, 
  [in]           int  id,
  [in]           UINT fsModifiers,
  [in]           UINT vk
);
*/
RegisterHotKey(NULL, 1, 0, 0x41);
</code></pre>
<p>After registering a hotkey, when the registered key is pressed, a <a href="https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-hotkey"><strong>WM_HOTKEY</strong></a> message is sent to the message queue of the window specified as the first argument to the <strong>RegisterHotKey</strong> API (or to the thread that registered the hotkey if <strong>NULL</strong> is used). The code below demonstrates a message loop that uses the <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage"><strong>GetMessage</strong></a> API to check for a <strong>WM_HOTKEY</strong> message in the <a href="https://learn.microsoft.com/en-us/windows/win32/winmsg/about-messages-and-message-queues">message queue</a>, and if one is received, it extracts the virtual-key code (in this case, 0x41) from the message.</p>
<pre><code>MSG msg = { 0 };
while (GetMessage(&amp;msg, NULL, 0, 0)) {
    if (msg.message == WM_HOTKEY) {
        int vkCode = HIWORD(msg.lParam);
        std::cout &lt;&lt; "WM_HOTKEY received! Virtual-Key Code: 0x"
            &lt;&lt; std::hex &lt;&lt; vkCode &lt;&lt; std::dec &lt;&lt; std::endl;
    }
}
</code></pre>
<p>In other words, imagine you're writing something in a notepad application. If the A key is pressed, the character won't be treated as normal text input — it will be recognized as a global hotkey instead.</p>
<p>In this example, only the A key is registered as a hotkey. However, you can register multiple keys (like B, C, or D) as separate hotkeys at the same time. This means that any key (i.e., any virtual-key code) that can be registered with the <strong>RegisterHotKey</strong> API can potentially be hijacked as a global hotkey. A hotkey-based keylogger abuses this capability to capture the keystrokes entered by the user.</p>
<p>Based on our testing, we found that not only alphanumeric and basic symbol keys, but also those keys when combined with the SHIFT modifier, can all be registered as hotkeys using the <strong>RegisterHotKey</strong> API. This means that a keylogger can effectively monitor every keystroke necessary to steal sensitive information.</p>
<h3 id="capturingkeystrokesstealthily">Capturing Keystrokes Stealthily</h3>
<p>Let's walk through the actual process of how a hotkey-based keylogger captures keystrokes, using the Hotkeyz hotkey-based keylogger as an example.</p>
<p>In Hotkeyz, it first registers each alphanumeric virtual-key code — and some additional keys,  such as <strong>VK_SPACE</strong> and <strong>VK_RETURN</strong> — as individual hotkeys by using the <strong>RegisterHotKey</strong> API. </p>
<p>Then, inside the keylogger's message loop, the <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-peekmessagew"><strong>PeekMessageW</strong></a> API is used to check whether any <strong>WM_HOTKEY</strong> messages from these registered hotkeys have appeared in the message queue. When a <strong>WM_HOTKEY</strong> message is detected, the virtual-key code it contains is extracted and eventually saved to a text file. Below is an excerpt from the message loop code, highlighting the most important parts.</p>
<pre><code>while (...)
{
    // Get the message in a non-blocking manner and poll if necessary
    if (!PeekMessageW(&amp;tMsg, NULL, WM_HOTKEY, WM_HOTKEY, PM_REMOVE))
    {
        Sleep(POLL_TIME_MILLIS);
        continue;
    }
....
   // Get the key from the message
   cCurrVk = (BYTE)((((DWORD)tMsg.lParam) &amp; 0xFFFF0000) &gt;&gt; 16);

   // Send the key to the OS and re-register
   (VOID)UnregisterHotKey(NULL, adwVkToIdMapping[cCurrVk]);
   keybd_event(cCurrVk, 0, 0, (ULONG_PTR)NULL);
   if (!RegisterHotKey(NULL, adwVkToIdMapping[cCurrVk], 0, cCurrVk))
   {
       adwVkToIdMapping[cCurrVk] = 0;
       DEBUG_MSG(L"RegisterHotKey() failed for re-registration (cCurrVk=%lu,    LastError=%lu).", cCurrVk, GetLastError());
       goto lblCleanup;
   }
   // Write to the file
  if (!WriteFile(hFile, &amp;cCurrVk, sizeof(cCurrVk), &amp;cbBytesWritten, NULL))
  {
....
</code></pre>
<p>One important detail is this: to avoid alerting the user to the keylogger's presence, once the virtual-key code is extracted from the message, the key's hotkey registration is temporarily removed using the <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregisterhotkey"><strong>UnregisterHotKey</strong></a> API. After that, the key press is simulated with <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-keybd_event"><strong>keybd_event</strong></a> so that it appears to the user as if the key was pressed normally. Once the key press is simulated, the key is re-registered using the <strong>RegisterHotKey</strong> API to wait for further input. This is the core mechanism behind how a hotkey-based keylogger operates.</p>
<h2 id="detectinghotkeybasedkeyloggers">Detecting Hotkey-Based Keyloggers</h2>
<p>Now that we understand what hotkey-based keyloggers are and how they operate, let's explain how to detect them.</p>
<h3 id="etwdoesnotmonitortheregisterhotkeyapi">ETW Does Not Monitor the RegisterHotKey API</h3>
<p>Following the approach described in an earlier article, we first investigated whether <a href="https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing">Event Tracing for Windows</a> (ETW) could be used to detect hotkey-based keyloggers. Our research quickly revealed that ETW currently does not monitor the <strong>RegisterHotKey</strong> or <strong>UnregisterHotKey</strong> APIs. In addition to reviewing the manifest file for the Microsoft-Windows-Win32k provider, we reverse-engineered the internals of the <strong>RegisterHotKey</strong> API — specifically, the <strong>NtUserRegisterHotKey</strong> function in win32kfull.sys. Unfortunately, we found no evidence that these APIs trigger any ETW events when executed.</p>
<p>The image below shows a comparison between the decompiled code for <strong>NtUserGetAsyncKeyState</strong> (which is monitored by ETW) and <strong>NtUserRegisterHotKey</strong>. Notice that at the beginning of <strong>NtUserGetAsyncKeyState</strong>, there is a call to <strong>EtwTraceGetAsyncKeyState</strong> — a function associated with  logging ETW events — while <strong>NtUserRegisterHotKey</strong> does not contain such a call.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc4cc53801b91a28/6a7c7cd7e88c65287b005793/image3.png" alt="Figure 1: Comparison of the Decompiled Code for **NtUserGetAsyncKeyState** and **NtUserRegisterHotKey**" title="Figure 1: Comparison of the Decompiled Code for **NtUserGetAsyncKeyState** and **NtUserRegisterHotKey**" />
　<br />
 Although we also considered using ETW providers other than Microsoft-Windows-Win32k to indirectly monitor calls to the <strong><code>RegisterHotKey</code></strong> API, we found that the detection method using the "hotkey table" — which will be introduced next and does not rely on ETW — achieves results that are comparable to or even better than monitoring the <strong><code>RegisterHotKey</code></strong> API. In the end, we chose to implement this method.</p>
<h3 id="detectionusingthehotkeytablegphkhashtable">Detection Using the Hotkey Table (<strong>gphkHashTable</strong>)</h3>
<p>After discovering that ETW cannot directly monitor calls to the <strong>RegisterHotKey</strong> API, we started exploring detection methods that don't rely on ETW. During our investigation, we wondered, "Isn't the information for registered hotkeys stored somewhere? And if so, could that data be used for detection?" Based on that hypothesis, we quickly found a hash table labeled <strong>gphkHashTable</strong> within <strong>NtUserRegisterHotKey</strong>. Searching Microsoft's online documentation revealed no details on <strong>gphkHashTable</strong>, suggesting that it's an undocumented kernel data structure.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce869e51a2db145b/6a7c7cda6c6eac2c17f0e341/image1.png" alt="Figure 2: The hotkey table (**gphkHashTable**), discovered within the **RegisterHotKey** function called inside **NtUserRegisterHotKey**" title="Figure 2: The hotkey table (**gphkHashTable**), discovered within the **RegisterHotKey** function called inside **NtUserRegisterHotKey**" /></p>
<p>Through reverse engineering, we discovered that this hash table stores objects containing information about registered hotkeys. Each object holds details such as the virtual-key code and modifiers specified in the arguments to the <strong>RegisterHotKey</strong> API. The right side of Figure 3 shows part of the structure definition for a hotkey object (named <strong>HOT_KEY</strong>), while the left side displays how the registered hotkey objects appear when accessed via WinDbg.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ffda8b3aea81b3f/6a7c7cdd2f00b2039def8d94/image4.png" alt="Figure 3: Hotkey Object Details. WinDbg view (left) and HOT_KEY structure details (right)" title="Figure 3: Hotkey Object Details. WinDbg view (left) and HOT_KEY structure details (right)" /></p>
<p>We also determined that <strong>ghpkHashTable</strong> is structured as shown in Figure 4.  Specifically, it uses the result of the modulo operation (with 0x80) on the virtual-key code (specified by the RegisterHotKey API) as the index into the hash table. Hotkey objects sharing the same index are linked together in a list, which allows the table to store and manage hotkey information even when the virtual-key codes are identical but the modifiers differ. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt772148e5fb13ba5c/6a7c7ce051156a77052bc82d/image6.png" alt="Figure 4: Structure of **gphkHashTable**" title="Figure 4: Structure of **gphkHashTable**" />  </p>
<p>In other words, by scanning all HOT_KEY objects stored in <strong>ghpkHashTable</strong>, we can retrieve details about every registered hotkey. If we find that every main key — for example, each individual alphanumeric key — is registered as a separate hotkey, that strongly indicates the presence of an active hotkey-based keylogger.</p>
<h2 id="implementingthedetectiontool">Implementing the Detection Tool</h2>
<p>Now, let's move on to implementing the detection tool. Since <strong>gphkHashTable</strong> resides in the kernel space, it cannot be accessed by a user-mode application. For this reason, it was necessary to develop a device driver for detection. More specifically, we decided to develop a device driver that obtains the address of <strong>gphkHashTable</strong> and scans through all the hotkey objects stored in the hash table. If the number of alphanumeric keys registered as hotkeys exceeds a predefined threshold, it will alert us to the potential presence of a hotkey-based keylogger.</p>
<h3 id="howtoobtaintheaddressofgphkhashtable">How to Obtain the Address of <strong>gphkHashTable</strong></h3>
<p>While developing the detection tool, one of the first challenges we faced was how to obtain the address of <strong>gphkHashTable</strong>. After some consideration, we decided to extract the address directly from an instruction in the <strong>win32kfull.sys</strong> driver that accesses <strong>gphkHashTable</strong>.</p>
<p>Through reverse engineering, we discovered that within the IsHotKey function — right at the beginning — there is a lea instruction (lea rbx, <strong>gphkHashTable</strong>) that accesses <strong>gphkHashTable</strong>. We used the opcode byte sequence (0x48, 0x8d, 0x1d) from that instruction as a signature to locate the corresponding line, and then computed the address of <strong>gphkHashTable</strong> using the obtained 32-bit (4-byte) offset.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce4609703613fb11/6a7c7ce33ce8e25bc2cef636/image5.png" alt="Figure 5: Inside the **IsHotKey** function" title="Figure 5: Inside the **IsHotKey** function" /></p>
<p>Additionally, since <strong>IsHotKey</strong> is not an exported function, we also need to know its address before looking for <strong>gphkHashTable</strong>. Through further reverse engineering, we discovered that the exported function <strong>EditionIsHotKey</strong> calls the <strong>IsHotKey</strong> function. Therefore, we decided to compute the address of IsHotKey within the <strong>EditionIsHotKey</strong> function using the same method described earlier. (For reference, the base address of <strong>win32kfull.sys</strong> can be found using the <strong>PsLoadedModuleList</strong> API.) </p>
<h3 id="accessingthememoryspaceofwin32kfullsys">Accessing the Memory Space of <strong>win32kfull.sys</strong></h3>
<p>Once we finalized our approach to obtaining the address of <strong>gphkHashTable</strong>, we began writing code to access the memory space of <strong>win32kfull.sys</strong> to retrieve that address. One challenge we encountered at this stage was that win32kfull.sys is a <em>session driver</em>. Before proceeding further, here’s a brief, simplified explanation of what a <em>session</em> is.</p>
<p>In Windows, when a user logs in, a separate session (with session numbers starting from 1) is assigned to each user. Simply put, the first user to log in is assigned <strong>Session 1</strong>. If another user logs in while that session is active, that user is assigned <strong>Session 2</strong>, and so on. Each user then has their own desktop environment within their assigned session.</p>
<p>Kernel data that must be managed separately for each session (i.e., per logged-in user) is stored in an isolated area of kernel memory called <em>session space</em>. This includes GUI objects managed by win32k drivers, such as windows and mouse/keyboard input data, ensuring that the screen and input remain properly separated between users.</p>
<p><em>(This is a simplified explanation. For a more detailed discussion on sessions, please refer to <a href="https://googleprojectzero.blogspot.com/2016/01/raising-dead.html">James Forshaw’s blog post</a>.)</em></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb40174c8d0f42e9e/6a7c7ce6448e4e50cc5baac2/image2.png" alt="Figure 6: Overview of Sessions. Session 0 is dedicated exclusively to service processes" title="Figure 6: Overview of Sessions. Session 0 is dedicated exclusively to service processes" />  </p>
<p>Based on the above, <strong>win32kfull.sys</strong> is known as a <em>session driver</em>. This means that, for example, hotkey information registered in the session of the first logged-in user (Session 1) can only be accessed from within that same session. So, how can we work around this limitation? In such cases, <a href="https://eversinc33.com/posts/kernel-mode-keylogging.html">it is known</a> that <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-kestackattachprocess"><strong>KeStackAttachProcess</strong></a> can be used.</p>
<p><strong>KeStackAttachProcess</strong> allows the current thread to temporarily attach to the address space of a specified process. If we can attach to a GUI process in the target session — more precisely, a process that has loaded <strong>win32kfull.sys</strong> — then we can access <strong>win32kfull.sys</strong> and its associated data within that session. For our implementation, assuming that only one user is logged in, we decided to locate and attach to <strong>winlogon.exe</strong>, the process responsible for handling user logon operations.</p>
<h3 id="enumeratingregisteredhotkeys">Enumerating Registered Hotkeys</h3>
<p>Once we have successfully attached to the winlogon.exe process and determined the address of <strong>gphkHashTable</strong>, the next step is simply scanning <strong>gphkHashTable</strong> to check the registered hotkeys. Below is an excerpt of that code:</p>
<pre><code>BOOL CheckRegisteredHotKeys(_In_ const PVOID&amp; gphkHashTableAddr)
{
-[skip]-
    // Cast the gphkHashTable address to an array of pointers.
    PVOID* tableArray = static_cast&lt;PVOID*&gt;(gphkHashTableAddr);
    // Iterate through the hash table entries.
    for (USHORT j = 0; j &lt; 0x80; j++)
    {
        PVOID item = tableArray[j];
        PHOT_KEY hk = reinterpret_cast&lt;PHOT_KEY&gt;(item);
        if (hk)
        {
            CheckHotkeyNode(hk);
        }
    }
-[skip]-
}

VOID CheckHotkeyNode(_In_ const PHOT_KEY&amp; hk)
{
    if (MmIsAddressValid(hk-&gt;pNext)) {
        CheckHotkeyNode(hk-&gt;pNext);
    }

    // Check whether this is a single numeric hotkey.
    if ((hk-&gt;vk &gt;= 0x30) &amp;&amp; (hk-&gt;vk &lt;= 0x39) &amp;&amp; (hk-&gt;modifiers1 == 0))
    {
        KdPrint(("[+] hk-&gt;id: %u hk-&gt;vk: %x\n", hk-&gt;id, hk-&gt;vk));
        hotkeyCounter++;
    }
    // Check whether this is a single alphabet hotkey.
    else if ((hk-&gt;vk &gt;= 0x41) &amp;&amp; (hk-&gt;vk &lt;= 0x5A) &amp;&amp; (hk-&gt;modifiers1 == 0))
    {
        KdPrint(("[+] hk-&gt;id: %u hk-&gt;vk: %x\n", hk-&gt;id, hk-&gt;vk));
        hotkeyCounter++;
    }
-[skip]-
}
....
if (CheckRegisteredHotKeys(gphkHashTableAddr) &amp;&amp; hotkeyCounter &gt;= 36)
{
   detected = TRUE;
   goto Cleanup;
}
</code></pre>
<p>The code itself is straightforward: it iterates through each index of the hash table, following the linked list to access every <strong>HOT_KEY</strong> object, and checks whether the registered hotkeys correspond to alphanumeric keys without any modifiers. In our detection tool, if every alphanumeric key is registered as a hotkey, an alert is raised, indicating the possible presence of a hotkey-based keylogger. For simplicity, this implementation only targets alphanumeric key hotkeys, although it would be easy to extend the tool to check for hotkeys with modifiers such as <strong>SHIFT</strong>.</p>
<h3 id="detectinghotkeyz">Detecting Hotkeyz</h3>
<p>The detection tool (Hotkey-based Keylogger Detector) has been released below. Detailed usage instructions are provided as well. Additionally, this research was presented at <a href="https://nullcon.net/goa-2025/speaker-windows-keylogger-detection">NULLCON Goa 2025</a>, and the <a href="https://docs.google.com/presentation/d/1B0Gdfpo-ER2hPjDbP_NNoGZ8vXP6X1_BN7VZCqUgH8c/edit?usp=sharing">presentation slides</a> are available. </p>
<p><a href="https://github.com/AsuNa-jp/HotkeybasedKeyloggerDetector">https://github.com/AsuNa-jp/HotkeybasedKeyloggerDetector</a></p>
<p>The following is a demo video showcasing how the Hotkey-based Keylogger Detector detects Hotkeyz.</p>
<p><a href="https://drive.google.com/file/d/1koGLqA5cPlhL8C07MLg9VDD9-SW2FM9e/view?usp=drive_link">DEMO_VIDEO.mp4</a></p>
<h2 id="acknowledgments">Acknowledgments</h2>
<p>We would like to express our heartfelt gratitude to Jonathan Bar Or for reading our previous article, sharing his insights on hotkey-based keyloggers, and generously publishing the PoC tool <strong>Hotkeyz</strong>.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/threat-command/detecting-hotkey-based-keyloggers</link>
    <guid isPermaLink="false">detecting-hotkey-based-keyloggers</guid>
    <category><![CDATA[Platform Internals]]></category>
    <dc:creator><![CDATA[Asuka Nakajima]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc22a2b9b9707cc9/6a7c7ce896b5a6527e8754dc/Security_Labs_Images_12.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Protecting your devices from information theft]]></title>
    <description><![CDATA[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.]]></description>
    <content:encoded><![CDATA[<p>In this article, we will introduce the keylogger and keylogging detection features added this year to Elastic Defend (starting from <a href="https://www.elastic.co/guide/en/security/8.12/release-notes-header-8.12.0.html#enhancements-8.12.0">version 8.12</a>), which is responsible for endpoint protection in Elastic Security. This article is also available in <a href="https://www.elastic.co/jp/security-labs/blog/protecting-your-devices-from-information-theft-keylogger-protection">Japanese</a>.</p>
<h2 id="introduction">Introduction</h2>
<p>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.</p>
<h3 id="whatisakeyloggerandwhataretheirrisks">What is a keylogger and what are their risks?</h3>
<p>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.)</p>
<p>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.</p>
<p>There are many types of malware with keylogging capabilities, particularly RATs, information stealers, and banking malware. Some well-known malware with keylogging functionality includes <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.agent_tesla">Agent Tesla</a>, <a href="https://malpedia.caad.fkie.fraunhofer.de/details/apk.lokibot">LokiBot</a>, and <a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.404keylogger">SnakeKeylogger</a>.</p>
<h3 id="howarekeystrokesstolen">How are keystrokes stolen?</h3>
<p>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).</p>
<p>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).</p>
<p>(※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.)</p>
<ol>
<li>Polling-based keylogger</li>
</ol>
<p>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.</p>
<p>Polling-based keyloggers are implemented using Windows APIs that check the state of key inputs, with the <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate"><code>GetAsyncKeyState</code></a> 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 <code>GetAsyncKeyState</code> API:</p>
<pre><code>while(true)
{
    for (int key = 1; key &lt;= 255; key++)
    {
        if (GetAsyncKeyState(key) &amp; 0x01)
        {
            SaveTheKey(key, "log.txt");
        }
    }
    Sleep(50);
}
</code></pre>
<p>The method of polling (<code>GetAsyncKeyState</code>) to capture key press states is not only a well-known, classic keylogging technique, but it is also commonly used by malware today.</p>
<ol>
<li>Hooking-based keylogger</li>
</ol>
<p>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.</p>
<p>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."</p>
<p>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 <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw"><code>SetWindowsHookEx</code></a> API. Below is a simple example of a hooking-based keylogger using the <code>SetWindowsHookEx</code> API:</p>
<pre><code>HMODULE hHookLibrary = LoadLibraryW(L"hook.dll");
FARPROC hookFunc = GetProcAddress(hHookLibrary, "SaveTheKey");

HHOOK keyboardHook = NULL;

keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
                (HOOKPROC)hookFunc,
                hHookLibrary,
                0);
</code></pre>
<ol>
<li>Keylogger using the Raw Input Model</li>
</ol>
<p>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:</p>
<ul>
<li><strong>Original Input Model</strong>: The data entered from input devices like keyboards is processed by the OS before being delivered to the application.</li>
<li><strong>Raw Input Model</strong>: The data entered from input devices is received directly by the application without any intermediate processing by the OS.</li>
</ul>
<p>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 <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerrawinputdevices"><code>RegisterRawInputDevices</code></a> API is used to register the input devices from which you want to receive raw data directly. Subsequently, the <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getrawinputdata"><code>GetRawInputData</code></a> API is used to obtain the raw data.</p>
<p>Below is a simple example of a keylogger using the Raw Input Model and these APIs:</p>
<pre><code>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(&amp;rid, 1, sizeof(rid));
        break;
    case WM_INPUT:
        GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &amp;dwSize, sizeof(RAWINPUTHEADER));

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

        if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, buffer, &amp;dwSize, sizeof(RAWINPUTHEADER)))
        {
            if (buffer-&gt;header.dwType == RIM_TYPEKEYBOARD)
            {
                SaveTheKey(buffer, "log.txt");
            }
        }
        HeapFree(GetProcessHeap(), 0, buffer);
        break;
    default:
        return DefWindowProc(hWnd, uMessage, wParam, lParam);
    }
    return 0;
}
</code></pre>
<p>In this example, <code>RegisterRawInputDevices</code> 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.</p>
<ol>
<li>Keylogger using <code>DirectInput</code></li>
</ol>
<p>Finally, let's discuss a keylogger that uses <code>DirectInput</code>. 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.</p>
<p>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 <code>DirectInput</code>. Below is a simple example of a keylogger using related APIs. As a side note, when acquiring key states using <code>DirectInput</code>, the <code>RegisterRawInputDevices</code> API is called in the background.</p>
<pre><code>LPDIRECTINPUT8        lpDI = NULL;
LPDIRECTINPUTDEVICE8    lpKeyboard = NULL;

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

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

while(true)
{
    HRESULT ret = lpKeyboard-&gt;GetDeviceState(sizeof(key), key);
    if (FAILED(ret)) {
        lpKeyboard-&gt;Acquire();
        lpKeyboard-&gt;GetDeviceState(sizeof(key), key);
    }
  SaveTheKey(key, "log.txt");    
    Sleep(50);
}
</code></pre>
<h2 id="detectingkeyloggersbymonitoringwindowsapicalls">Detecting keyloggers by monitoring Windows API calls</h2>
<p>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.)</p>
<h3 id="monitoredwindowsapis">Monitored Windows APIs:</h3>
<ul>
<li><a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate">GetAsyncKeyState</a></li>
<li><a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw">SetWindowsHookEx</a></li>
<li><a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerrawinputdevices">RegisterRawInputDevice</a></li>
</ul>
<h3 id="newkeyloggerendpointdetectionrules">New keylogger endpoint detection rules:</h3>
<ul>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_getasynckeystate_api_call_from_suspicious_process.toml">GetAsyncKeyState API Call from Suspicious Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_getasynckeystate_api_call_from_unusual_process.toml">GetAsyncKeyState API Call from Unusual Process</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystroke_input_capture_via_directinput.toml">Keystroke Input Capture via DirectInput</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystroke_input_capture_via_registerrawinputdevices.toml">Keystroke Input Capture via RegisterRawInputDevices</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystroke_messages_hooking_via_setwindowshookex.toml">Keystroke Messages Hooking via SetWindowsHookEx</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystrokes_input_capture_from_a_managed_application.toml">Keystrokes Input Capture from a Managed Application</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystrokes_input_capture_from_a_suspicious_module.toml">Keystrokes Input Capture from a Suspicious Module</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystrokes_input_capture_from_suspicious_callstack.toml">Keystrokes Input Capture from Suspicious CallStack</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystrokes_input_capture_from_unsigned_dll.toml">Keystrokes Input Capture from Unsigned DLL</a></li>
<li><a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystrokes_input_capture_via_setwindowshookex.toml">Keystrokes Input Capture via SetWindowsHookEx</a></li>
</ul>
<p>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.</p>
<h3 id="detectingwindowskeyloggers">Detecting Windows keyloggers</h3>
<p>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 <code>Keylogger.exe</code> that uses the <code>RegisterRawInputDevices</code> 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.)</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte0adb003ad038acd/6a8f2a1b36492a137485fc7c/protecting-your-devices-from-information-theft-keylogger-protection-image1.png" alt="Elastic Security alert" title="Elastic Security alert" />
　
Shortly after the keylogger was executed, a detection rule  (<a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystroke_input_capture_via_registerrawinputdevices.toml">Keystroke Input Capture via RegisterRawInputDevices</a>) was triggered on the endpoint, showing an alert.  The further details of this alert can be viewed within Kibana.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted30cf88590e4af1/6a8f2a1ba8b323347fcc2525/protecting-your-devices-from-information-theft-keylogger-protection-image3.png" alt="Elastic Security alert dashboard" title="Elastic Security alert dashboard" /></p>
<p>Here are the details of the detection rule, note the specific API referenced in the example. </p>
<pre><code>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")
'''
</code></pre>
<p>This rule raises an alert when an unsigned process, or a process signed by an untrusted signer, calls the <code>RegisterRawInputDevices</code> API to capture keystrokes. More specifically, Elastic Defend monitors the arguments passed to the <code>RegisterRawInputDevices</code> API, particularly the members of the <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinputdevice"><code>RAWINPUTDEVICE</code> structure</a>, which is the first argument of this API.</p>
<p>This raises an alert when these argument values indicate an attempt to capture keyboard input. The logs of the <code>RegisterRawInputDevices</code> API can also be viewed within Kibana.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f1f7333f11e2770/6a8f2a1bda6aeaad0a37b7ab/protecting-your-devices-from-information-theft-keylogger-protection-image2.png" alt="&lt;code&gt;RegisterRawInputDevices&lt;/code&gt; API logs displayed in Kibana" title="&lt;code&gt;RegisterRawInputDevices&lt;/code&gt; API logs displayed in Kibana" /></p>
<h3 id="datacollectedduringwindowsapicalls">Data Collected During Windows API Calls</h3>
<p>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 <a href="https://github.com/elastic/endpoint-package/blob/main/custom_schemas/custom_api.yml"><code>custom_api.yml</code></a>.</p>
<p>| API Name | Field | Description | Example |
| --- | --- | --- | --- |
| GetAsyncKeyState | process.Ext.api.metadata.ms_since_last_keyevent | This parameter indicates an elapsed time in milliseconds between the last GetAsyncKeyState event. | 94 |
| GetAsyncKeyState | process.Ext.api.metadata.background_callcount | This parameter indicates a number of all GetAsyncKeyState api calls, including unsuccessful calls, between the last successful GetAsyncKeyState call. | 6021 |
| SetWindowsHookEx | process.Ext.api.parameters.hook_type | Type of hook procedure to be installed. | "WH_KEYBOARD_LL"
| SetWindowsHookEx | process.Ext.api.parameters.hook_module | DLL containing the hook procedure. | "c:\windows\system32\taskbar.dll"
| SetWindowsHookEx | process.Ext.api.parameters.procedure | The memory address of the procedure or function. | 2431737462784 |
| SetWindowsHookEx | process.Ext.api.metadata.procedure_symbol | Summary of the hook procedure. | "taskbar.dll" |
| RegisterRawInputDevices | process.Ext.api.metadata.return_value | Return value of RegisterRawInputDevices API call. | 1 |
| RegisterRawInputDevices | process.Ext.api.parameters.usage_page | This parameter indicates the top-level collection (Usage Page) of the device. First member RAWINPUTDEVICE structure. | "GENERIC" |
| RegisterRawInputDevices | process.Ext.api.parameters.usage | This parameter indicates the specific device (Usage) within the Usage Page. Second member RAWINPUTDEVICE structure. | "KEYBOARD" |
| RegisterRawInputDevices | process.Ext.api.parameters.flags | Mode flag that specifies how to interpret the information provided by UsagePage and Usage. Third member RAWINPUTDEVICE structure. | "INPUTSINK" |
| RegisterRawInputDevices | process.Ext.api.metadata.windows_count | Number of windows owned by the caller thread. | 2 |
| RegisterRawInputDevices | process.Ext.api.metadata.visible_windows_count | Number of visible windows owned by the caller thread. | 0 |
| RegisterRawInputDevices | process.Ext.api.metadata.thread_info_flags | Thread info flags. | 16 |
| RegisterRawInputDevices | process.Ext.api.metadata.start_address_module | Name of the module associated with the starting address of a thread. | "C:\Windows\System32\DellTPad\ApMsgFwd.exe" |
| RegisterRawInputDevices | process.Ext.api.metadata.start_address_allocation_protection | Memory protection attributes associated with the starting address of a thread. | "RCX" |</p>
<h2 id="conclusion">Conclusion</h2>
<p>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.</p>
<p>In addition to keylogging-related APIs, Elastic Defend also monitors <a href="https://www.elastic.co/security-labs/doubling-down-etw-callstacks">other APIs commonly used by malicious actors, such as those for memory manipulation</a>, providing multi-layered protection. If you are interested in Elastic Security and Elastic Defend, please check out the <a href="https://www.elastic.co/security">product page</a> and <a href="https://www.elastic.co/videos/intro-elastic-security">documentation</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/blog/protecting-your-devices-from-information-theft-keylogger-protection</link>
    <guid isPermaLink="false">protecting-your-devices-from-information-theft-keylogger-protection</guid>
    <category><![CDATA[Endpoint Protection & Security]]></category>
    <dc:creator><![CDATA[Asuka Nakajima]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd91d559e47ec132c/6a8f2918a1b20be0a1872562/Security_Labs_Images_10.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 30 May 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>