<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elastic Security Labs - Articles by Asuka Nakajima</title>
        <link>https://www.elastic.co/security-labs</link>
        <description>Trusted security news &amp; research from the team at Elastic.</description>
        <lastBuildDate>Thu, 05 Mar 2026 22:21:01 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Elastic Security Labs - Articles by Asuka Nakajima</title>
            <url>https://www.elastic.co/security-labs/assets/security-labs-thumbnail.png</url>
            <link>https://www.elastic.co/security-labs</link>
        </image>
        <copyright>© 2026. Elasticsearch B.V. All Rights Reserved</copyright>
        <item>
            <title><![CDATA[Detecting Hotkey-Based Keyloggers Using an Undocumented Kernel Data Structure]]></title>
            <link>https://www.elastic.co/security-labs/detecting-hotkey-based-keyloggers</link>
            <guid>detecting-hotkey-based-keyloggers</guid>
            <pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate>
            <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[<h1>Detecting Hotkey-Based Keyloggers Using an Undocumented Kernel Data Structure</h1>
<p>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.</p>
<h2>Introduction</h2>
<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>Overview of Hotkey-based Keyloggers</h2>
<h3>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>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 class="language-c">/*
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 class="language-c">MSG msg = { 0 };
while (GetMessage(&amp;msg, NULL, 0, 0)) {
    if (msg.message == WM_HOTKEY) {
        int vkCode = HIWORD(msg.lParam);
        std::cout &lt;&lt; &quot;WM_HOTKEY received! Virtual-Key Code: 0x&quot;
            &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>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 class="language-c">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&quot;RegisterHotKey() failed for re-registration (cCurrVk=%lu,    LastError=%lu).&quot;, 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>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>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://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image3.png" alt="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 &quot;hotkey table&quot; — 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>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, &quot;Isn't the information for registered hotkeys stored somewhere? And if so, could that data be used for detection?&quot; 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://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image1.png" alt="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://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image4.png" alt="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://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image6.png" alt="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>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>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://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image5.png" alt="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>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://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image2.png" alt="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>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 class="language-c">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((&quot;[+] hk-&gt;id: %u hk-&gt;vk: %x\n&quot;, 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((&quot;[+] hk-&gt;id: %u hk-&gt;vk: %x\n&quot;, 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>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>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>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/Security Labs Images 12.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[未公開のカーネルデータ構造を使ったホットキー型キーロガーの検知]]></title>
            <link>https://www.elastic.co/security-labs/detecting-hotkey-based-keyloggers-jp</link>
            <guid>detecting-hotkey-based-keyloggers-jp</guid>
            <pubDate>Tue, 04 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[本記事では、ホットキー型キーロガーとは何かについてと、その検知方法について紹介します。具体的には、ホットキー型キーロガーがどのようにしてキー入力を盗み取るのかを解説した後、カーネルレベルに存在する未公開(Undocumented)のホットキーテーブルを活用した検知手法について説明します。]]></description>
            <content:encoded><![CDATA[<h2>未公開のカーネルデータ構造を使った</h2>
<h2>ホットキー型キーロガーの検知</h2>
<p>本記事では、ホットキー型キーロガーとは何かについてと、その検知方法について紹介します。具体的には、ホットキー型キーロガーがどのようにしてキー入力を盗み取るのかを解説した後、カーネルレベルに存在する未公開(Undocumented)のホットキーテーブルを活用した検知手法について説明します。</p>
<h2>はじめに</h2>
<p>　Elastic Security Labsでは2024年5月、<a href="https://www.elastic.co/guide/en/integrations/current/endpoint.html">Elastic Defend</a>のバージョン 8.12 より追加された、Windows上で動作するキーロガーの検知を強化する新機能を紹介する<a href="https://www.elastic.co/security-labs/protecting-your-devices-from-information-theft-keylogger-protection-jp">記事</a>を公開しました 。具体的には、サイバー攻撃で一般的に使われる4種類のキーロガー(ポーリング型キーロガー、フッキング型キーロガー、Raw Input Modelを用いたキーロガー、DirectInputを用いたキーロガー)を挙げ、それらに対する私たちが提供した検知手法についてを解説しました。具体的には<a href="https://learn.microsoft.com/ja-jp/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-">Event Tracing for Windows</a> (ETW)における、Microsoft-Windows-Win32kプロバイダを用いた振る舞い検知の方法についてを紹介しました。<br />
　記事公開後、大変光栄なことに記事がMicrosoft社のPrincipal Security Researcherである<a href="https://jonathanbaror.com/">Jonathan Bar Or</a>氏の目に留まり、「ホットキー型キーロガーもある」といった貴重なご意見とともに、そのPoCコードも公開してくださりました。そこで本記事では、氏が公開したホットキー型キーロガーのPoCコードである「<a href="https://github.com/yo-yo-yo-jbo/hotkeyz">Hotkeyz</a>」 をもとに、本キーロガーの検知手法の一案についてを述べたいと思います。</p>
<h2>ホットキー型キーロガーの概要</h2>
<h3>そもそもホットキーとは何か？</h3>
<p>　ホットキー型キーロガーについて説明する前に、まずホットキーとは何かを解説します。ホットキーとは、キーボードショートカットの一種であり、コンピュータにおいて、特定の機能を直接呼び出して実行させるキーまたはキーの組み合わせのことを指します。例えばWindowsにおいてタスク(ウィンドウ)を切り替える際に「<strong>Alt + Tab</strong>」を押している人も多いかと思います。この時使っているこの「<strong>Alt + Tab</strong>」が、タスク切り替え機能を直接呼び出す「ホットキー」にあたります。</p>
<p><em>(注: ホットキー以外にも、キーボードショートカットは存在しますが、本記事ではそれらは対象外です。また本記事に記載の事項はすべて、筆者が検証に利用した環境である、仮想化ベースのセキュリティが動作していないWindows 10 version 22H2 OS Build 19045.5371が前提になります。他のWindowsのバージョンではまた内部の構造や挙動が違う場合があること、ご注意ください。)</em></p>
<h3>任意のホットキーが登録できることを悪用する</h3>
<p>　先ほどの例のようにWindowsで予め設定されたホットキーを使う以外にも、実は自分で任意のホットキーを登録することも可能です。登録方法は様々ありますが、<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-registerhotkey">RegisterHotKey</a>というWindows APIを使えば、指定のキーをホットキーとして登録することができます。例えば、以下が<code>RegisterHotKey</code> APIを使って「A」(<a href="https://learn.microsoft.com/ja-jp/windows/win32/inputdev/virtual-key-codes">virtual-key code</a>で0x41)キーを、グローバルなホットキーとして登録するためのコードの例です。</p>
<pre><code class="language-c">/*
BOOL RegisterHotKey(
  [in, optional] HWND hWnd, 
  [in]           int  id,
  [in]           UINT fsModifiers,
  [in]           UINT vk
);
*/
RegisterHotKey(NULL, 1, 0, 0x41);
</code></pre>
<p>　ホットキーとして登録後、登録されたキーが押下された場合、<code>RegisterHotKey</code> APIの第一引数で指定したウィンドウ(NULLの場合はホットキー登録時のスレッド)の<a href="https://learn.microsoft.com/ja-jp/windows/win32/winmsg/about-messages-and-message-queues">メッセージキュー</a>に、<a href="https://learn.microsoft.com/ja-jp/windows/win32/inputdev/wm-hotkey">WM_HOTKEYメッセージ</a>が届くようになります。以下は実際に、メッセージキューにWM_HOTKEY メッセージが来ていないかを<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getmessage">GetMessage</a> APIを使って確認し、届いていた場合、WM_HOTKEYメッセージに内包されていた virtual-key code(今回の場合「0x41」)を取り出しているコード(メッセージループ)になります。</p>
<pre><code class="language-c">MSG msg = { 0 };
while (GetMessage(&amp;msg, NULL, 0, 0)) {
    if (msg.message == WM_HOTKEY) {
        int vkCode = HIWORD(msg.lParam);
        std::cout &lt;&lt; &quot;WM_HOTKEY received! Virtual-Key Code: 0x&quot;
            &lt;&lt; std::hex &lt;&lt; vkCode &lt;&lt; std::dec &lt;&lt; std::endl;
    }
}
</code></pre>
<p>　これは言い換えると、例えばメモ帳アプリに文章を書く際、Aキーから入力された文字は、文字としての入力ではなく、グローバルなホットキーとして認識されることになります。</p>
<p>　今回は「A」のみをホットキーとして登録しましたが、複数のキー(BやCやD)を同時に個々のホットキーとして登録することも可能です。これはつまり、<code>RegisterHotKey</code> APIでホットキーとして登録可能な範囲の任意のキー(virtual-key code)の入力は、すべてグローバルなホットキーとして横取りすることも可能であるということです。そしてホットキー型キーロガーはこの性質を悪用して、ユーザから入力されたキーを盗み取ります。<br />
　筆者が手元の環境で試した限りは、英数字と基本的な記号キーだけでなく、それらにSHIFT修飾子をつけたすべてキーが<code>RegisterHotKey</code> APIでホットキーとして登録可能でした。そのため、キーロガーとして問題なく、情報の窃取に必要なキーの監視ができると言えるでしょう。</p>
<h3>密かにキーを盗み取る</h3>
<p>　ホットキー型キーロガーがキーを盗み取る実際の流れについてを、Hotkeyzを例に紹介します。<br />
Hotkeyzでは最初に、各英数字キーに加えて、一部のキー(VK_SPACEやVK_RETURNなど)のvirtual-key codeを、<code>RegisterHotKey</code> APIを使い個々のホットキーとして登録します。その後キーロガー内のメッセージループにて、登録されたホットキーのWM_HOTKEYメッセージが、メッセージキューに到着していないかを<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-peekmessagew">PeekMessageW</a> APIを使って確認します。そしてWM_HOTKEYメッセージが来ていた場合、メッセージに内包されているvirtual-key codeを取り出して、最終的にはそれをテキストファイルに保存します。以下がメッセージループ内のコードのコードです。特に重要な部分を抜粋して掲載しています。</p>
<pre><code class="language-c">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&quot;RegisterHotKey() failed for re-registration (cCurrVk=%lu,    LastError=%lu).&quot;, cCurrVk, GetLastError());
       goto lblCleanup;
   }
   // Write to the file
  if (!WriteFile(hFile, &amp;cCurrVk, sizeof(cCurrVk), &amp;cbBytesWritten, NULL))
  {
....
</code></pre>
<p>　ここで特筆するべき点としては、ユーザにキーロガーの存在を気取られないため、メッセージからvirtual-key codeを取り出した時点で、いったんそのキーのホットキー登録を<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-unregisterhotkey">UnregisterHotKey</a> APIを使って解除し、その上で<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-keybd_event">keybd_event</a>を使ってキーを送信することです。これにより、ユーザからは問題無くキーが入力出来ているように見え、キーが裏で窃取されていることに気が付かれにくくなります。そしてキーを送信した後は再びそのキーを<code>RegisterHotKey</code> APIを使ってホットキーとして登録し、再びユーザからの入力を待ちます。以上が、ホットキー型キーロガーの仕組みです。</p>
<h2><strong>ホットキー型キーロガーの検知手法</strong></h2>
<p>　ホットキー型キーロガーとは何かやその仕組みについて理解したところで、次にこれをどのように検知するかについてを説明します。</p>
<h3>ETWではRegisterHotKey APIは監視していない</h3>
<p>　以前の記事で書いた方法と同様に、まずはホットキー型キーロガーも<a href="https://learn.microsoft.com/ja-jp/windows/win32/etw/about-event-tracing">Event Tracing for Windows</a> (ETW) を利用して検知が出来ないかを検討・調査しました。その結果、ETWでは<code>RegisterHotKey</code> APIや<code>UnRegisterHotKey</code> APIを監視していないことがすぐに判明しました。Microsoft-Windows-Win32k プロダイバーのマニフェストファイルの調査に加えて、<code>RegisterHotKey</code>のAPIの内部(具体的にはwin32kfull.sysにある<code>NtUserRegisterHotKey</code>)をリバースエンジニアリングをしたものの、これらのAPIが実行される際、ETWのイベントを送信しているような形跡は残念ながら見つかりませんでした。<br />
　以下の図は、ETWで監視対象となっている<code>GetAsyncKeyState</code>(<code>NtUserGetAsyncKeyState</code>)と、<code>NtUserRegisterHotKey</code>の逆コンパイル結果を比較したものを示しています。<code>NtUserGetAsyncKeyState</code>の方には関数の冒頭に、<code>EtwTraceGetAsyncKeyState</code>というETWのイベント書き出しに紐づく関数が存在しますが、<code>NtUserRegisterHotKey</code>には存在しないのが見て取れます。</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image3.png" alt="図1: NtUserGetAsyncKeyStateとNtUserRegisterHotKeyの逆コンパイル結果の比較" />
　<br />
　Microsoft-Windows-Win32k 以外のETWプロバイダーを使って、間接的に<code>RegisterHotKey</code> APIを呼び出しを監視する案もでたものの、次に紹介する、ETWを使わず「ホットキーテーブル」を利用した検知手法が、<code>RegisterHotKey</code> APIを監視するのと同様かそれ以上の効果が得られることが分かり、最終的にはこの案を採用することにしました。</p>
<h3>ホットキーテーブル(gphkHashTable)を利用した検知</h3>
<p>　ETWでは<code>RegisterHotKey</code> APIの呼び出しを直接監視出来ないことが判明した時点で、ETWを利用せずに検知する方法を検討することにしました。検討の最中、「そもそも登録されたホットキーの情報がどこかに保存されているのではないか？」「もし保存されているとしたら、その情報が検知に使えるのではないか？」という考えに至りました。その仮説をもとに調査した結果、すぐに<code>NtUserRegisterHotkey</code>内にて<code>gphkHashTable</code>というラベルがつけられたハッシュテーブルを発見することが出来ました。Microsoft社が公開しているオンラインのドキュメント類を調査しても<code>gphkHashTable</code>についての情報はなかったため、これは未公開(undocumented)のカーネルデータ構造のようです。</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image1.png" alt="図2: ホットキーテーブルgphkHashTable。NtUserRegisterHotKey内で呼ばれたRegisterHotKey関数内にて発見" /></p>
<p>　リバースエンジニアリングをした結果、このハッシュテーブルは、登録されたホットキーの情報を持つオブジェクトを保存しており、各オブジェクトは<code>RegisterHotKey</code> APIの引数にて指定されたvirtual-key codeや修飾子の情報を保持していることが分かりました。以下の図(右)がホットキーのオブジェクト(<strong>HOT_KEY</strong>と命名)の構造体の定義の一部と、図(左)が実際にwindbg上で<code>gphkHashTable</code>にアクセスした上で、登録されたホットキーのオブジェクトを見た時の様子です。</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image4.png" alt="図3: ホットキーオブジェクトの詳細。Windbg画面(図左)とHOT_KEY構造体の詳細" /></p>
<p>　リバースエンジニアリングをした結果をまとめると、ghpkHashTableは図4のような構造になっていることがわかりました。具体的には、<code>RegisterHotKey</code> APIで指定されたvirtual-key codeに対して0x80の余剰演算をした結果をハッシュテーブルのインデックスにしていました。そして同じインデックスを持つホットキーオブジェクトを連結リストで結ぶことで、virtual-key codeが同じでも、修飾子が違うホットキーの情報も保持・管理出来るようになっています。</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image6.png" alt="図4: gphkHashTableの構造" /></p>
<p>　つまり<code>gphkHashTable</code>で保持している全てのHOT_KEYオブジェクトを走査すれば、登録されている全ホットキーの情報が取得できるということになります。取得した結果、主要なキー(例えば単体の英数字キー）全てが個々のホットキーとして登録されていれば、ホットキー型キーロガーが動作していることを示す強い根拠となります。</p>
<h2>検知ツールを作成する</h2>
<p>　では次に、実際に検知ツールの方を実装していきます。<code>gphkHashTable</code>自体はカーネル空間に存在するため、ユーザモードのアプリケーションからはアクセス出来ません。そのため検知のために、デバイスドライバを書くことにしました。具体的には<code>gphkHashTable</code>のアドレスを取得した後、ハッシュテーブルに保存されている全オブジェクトを走査した上で、ホットキーとして登録されている英数字キーの数が一定数以上ならば、ホットキー型キーロガーが存在する可能性がある事を知らせてくるデバイスドライバを作成することにしました。</p>
<h3>gphkHashTableのアドレスを取得する方法</h3>
<p>　検知ツールを作成するにあたり、最初に直面した課題としては「gphkHashTableのアドレスをどのようにして取得すればよいのか？」ということです。悩んだ結果、<strong>win32kfull.sys</strong>のメモリ空間内でgphkHashTableにアクセスしている命令から直接gphkHashTableのアドレスを取得することにしました。<br />
　リバースエンジニアリングした結果、<code>IsHotKey</code>という関数内では、関数の冒頭部分にあるlea命令(lea rbx, gphkHashTable)にて、gphkHashTableのアクセスしていることがわかりました。この命令のオプコードバイト(0x48, 0x8d, 0x1d)部分をシグネチャに該当行を探索して、得られた32bit(4バイト)のオフセットからgphkHashTableのアドレスを算出することにしました。</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image5.png" alt="図5: IsHotKey関数内 " /></p>
<p>　加えて、IsHotKey関数自体もエクスポート関数でないため、そのアドレスも何らかの方法で取得しなければいけません。そこでさらなるリバースエンジニアリングの結果、<code>EditionIsHotKey</code>というエクスポートされた関数内で、<code>IsHotKey</code>関数が呼ばれていることがわかりました。そこでEditionIsHotKey関数から前述と同様の方法で、IsHotKey関数のアドレスを算出することにしました。(補足ですが、<strong>win32kfull.sys</strong>のベースアドレスに関しては<code>PsLoadedModuleList</code>というAPIで探せます。)</p>
<p>　## <strong>win32kfull.sys</strong>のメモリ空間にアクセスするには</p>
<p>　<strong>gphkHashTable</strong>のアドレスを取得する方法について検討が終わったところで、実際に<strong>win32kfull.sys</strong>のメモリ空間にアクセスして、<strong>gphkHashTable</strong>のアドレスを取得するためのコードを書き始めました。この時直面した課題としては、<strong>win32kfull.sys</strong>は「セッションドライバ」であるという点ですが、ここではまず「セッション」とは何かについて、簡単に説明します。<br />
　Windowsでは一般的にユーザがログインした際、ユーザ毎に個別に「セッション」(1番以降のセッション番号)が割り当てられます。かなり大雑把に説明すると、最初にログインしたユーザには「セッション１」が割り当てられ、その状態で別のユーザがログインした場合今度は「セッション２」が割り当てられます。そして各ユーザは個々のセッション内で、それぞれのデスクトップ環境を持ちます。<br />
　この時、セッション別(ログインユーザ別)に管理するべきカーネルのデータは、カーネルメモリ内の「セッション空間」というセッション別の分離したメモリ空間で管理され、win32k ドライバが管理しているようなGUIオブジェクト(ウィンドウ、マウス・キーボード入力の情報等)もこれに該当します。これにより、ユーザ間で画面や入力情報が混ざることがないのです。(かなり大まかな説明のため、より詳しくセッションについて知りたい方はJames Forshaw氏の<a href="https://googleprojectzero.blogspot.com/2016/01/raising-dead.html">こちらのブログ記事</a>を読むことをおすすめします。)</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers/image2.png" alt="図6: セッションの概要。 セッション0はサービスプロセス専用のセッション" />
　　<br />
以上の背景から、<strong>win32kfull.sys</strong>は「セッションドライバ」と呼ばれています。つまり、例えば最初のログインユーザのセッション(セッション1)内で登録されたホットキーの情報は、同じセッション内からしかアクセスできないということです。ではどうすれば良いのかというと、このような場合、<a href="https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/ntifs/nf-ntifs-kestackattachprocess">KeStackAttachProcess</a>が利用できることが<a href="https://eversinc33.com/posts/kernel-mode-keylogging.html">知られています</a>。<br />
　KeStackAttachProcessは、現在のスレッドを指定のプロセスのアドレス空間に一時的にアタッチすることが出来ます。この時、対象のセッションにいるGUIプロセス、より正確には<strong>win32kfull.sys</strong>をロードしているプロセスにアタッチすることが出来れば、対象セッションの<strong>win32kfull.sys</strong>やそのデータにアクセスすることが出来ます。今回は、ログインユーザが１ユーザであることを仮定して、各ユーザのログオン操作を担うプロセスである<strong>winlogon.exe</strong>を探してアタッチすることにしました。</p>
<h3>登録されているホットキーを確認する</h3>
<p>　<strong>winlogon.exe</strong>のプロセスにアタッチし、<strong>gphkHashTable</strong>のアドレスを特定出来た後は、後は<strong>gphkHashTable</strong>をスキャンして登録されたホットキーを確認するだけです。以下がその抜粋版のコードです。</p>
<pre><code class="language-c">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((&quot;[+] hk-&gt;id: %u hk-&gt;vk: %x\n&quot;, 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((&quot;[+] hk-&gt;id: %u hk-&gt;vk: %x\n&quot;, hk-&gt;id, hk-&gt;vk));
        hotkeyCounter++;
    }
-[skip]-
}
....
if (CheckRegisteredHotKeys(gphkHashTableAddr) &amp;&amp; hotkeyCounter &gt;= 36)
{
   detected = TRUE;
   goto Cleanup;
}
</code></pre>
<p>　コード自体は難しくなく、ハッシュテーブルの各インデックスの先頭から順に、連結リストをたどりながらすべての<strong>HOT_KEY</strong>オブジェクトにアクセスして、登録されているホットキーが単体の英数字キーか否かを確認しています。作成した検知ツールでは、すべての単体英数字キーがホットキーとして登録<br />
されていた場合、ホットキー型キーロガーが存在するとしてアラートを挙げます。また、今回実装の簡略化のため、英数字単体キーのホットキーのみを対象としていますが、SHIFTなどの修飾子付きのホットキーも容易に調べることが可能です。</p>
<h3>Hotkeyzを検知する</h3>
<p>　検知ツール(Hotkey-based Keylogger Detector)は以下にて公開しました。使い方も以下に記載していますので、興味ある方はぜひご覧ください。加えて本研究は<a href="https://nullcon.net/goa-2025/speaker-windows-keylogger-detection">NULLCON Goa 2025</a>でも発表しましたので、その<a href="https://docs.google.com/presentation/d/1B0Gdfpo-ER2hPjDbP_NNoGZ8vXP6X1_BN7VZCqUgH8c/edit?usp=sharing">発表スライド</a>も併せてご覧いただけます。</p>
<p>*<a href="https://github.com/AsuNa-jp/HotkeybasedKeyloggerDetector">https://github.com/AsuNa-jp/HotkeybasedKeyloggerDetector</a></p>
<p>　最後に、本ツールを用いて実際にHotkeyzを検知する様子を収録したデモ動画が以下になります。</p>
<p><a href="https://drive.google.com/file/d/1koGLqA5cPlhL8C07MLg9VDD9-SW2FM9e/view?usp=drive_link">DEMO_VIDEO.mp4</a></p>
<h2>謝辞</h2>
<p>　<a href="https://www.elastic.co/security-labs/protecting-your-devices-from-information-theft-keylogger-protection-jp">前回の記事</a>を読んで下さり、その上でホットキー型キーロガーの手法について教えてくださり、その上そのPoCとなるHotkeyzを公開してくださった、Jonathan Bar Or氏に心より感謝致します。</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/security-labs/assets/images/detecting-hotkey-based-keyloggers-jp/Security Labs Images 12.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[情報窃取から端末を守る]]></title>
            <link>https://www.elastic.co/security-labs/protecting-your-devices-from-information-theft-keylogger-protection-jp</link>
            <guid>protecting-your-devices-from-information-theft-keylogger-protection-jp</guid>
            <pubDate>Thu, 30 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[本記事ではElastic Securityにおいて、エンドポイント保護を担っているElastic Defendに今年(バージョン8.12より)新たに追加された、キーロガーおよびキーロギング検出機能について紹介します。]]></description>
            <content:encoded><![CDATA[<p>本記事ではElastic Securityにおいて、エンドポイント保護を担っているElastic Defendに今年(バージョン<a href="https://www.elastic.co/guide/en/security/8.12/release-notes-header-8.12.0.html#enhancements-8.12.0">8.12</a>より)新たに追加された、キーロガーおよびキーロギング検出機能について紹介します。</p>
<h2>はじめに</h2>
<p>Elastic Defend 8.12より、Windows上で動作するキーロガーおよび、キーロギング機能を備えたマルウェア(情報窃取型マルウェアや、リモートアクセス型トロイの木馬、通称RAT)の検知の強化を目的に、キーロガーが使用する代表的なWindows API群の呼び出しを監視・記録する機能が追加されました。本記事ではこの新機能に焦点を当て、その技術的な詳細を解説します。加えて、本機能に付随して新たに作成された振る舞い検知ルール(Prebuilt rule)についても紹介します。</p>
<h3>キーロガーとはなにか？どのような危険性があるのか？</h3>
<p>キーロガーとは、コンピュータ上で入力されたキーの内容を監視および記録(キーロギング)するソフトウェアの一種です(※1)。キーロガーは、ユーザのモニタリングなどの正当な理由で利用されることもありますが、攻撃者によって頻繁に悪用されるソフトウェアです。具体的には、ユーザがキーボード経由で入力した認証情報やクレジットカード情報、各種機密情報などのセンシティブな情報の窃取などに際に使われます。(※1: パソコンにUSB等で直接取り付けるようなハードウェア型のキーロガーもありますが、本記事ではソフトウェア型のキーロガーに焦点を当てます。)</p>
<p>キーロガーを通じて入手したセンシティブな情報は、金銭の窃取やさらなるサイバー攻撃の足がかりに悪用されます。それゆえに、キーロギング行為自体は直接的にコンピュータに被害をおよばさないものの、続くサイバー攻撃の被害を食い止めるためにも、早期の検知が非常に重要だと言えます。</p>
<p>キーロギング機能を持つマルウェアは多々あり、特にRAT、情報窃取型マルウェア、バンキングマルウェアといった種類のマルウェアにキーロギング機能が搭載されている場合があることが確認されています。有名なマルウェアでキーロギング機能を有するものとしては<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">Lokibit</a>、そして<a href="https://malpedia.caad.fkie.fraunhofer.de/details/win.404keylogger">SnakeKeylogger</a>などが挙げられます。</p>
<h3>いかにして入力した文字を盗み取っているのか？</h3>
<p>では次に、キーロガーはいかにしてユーザがキーボードから入力した文字を、ユーザに気づかれること無く盗み取っているのかを、技術的な観点から説明していきます。キーロガー自体は、あらゆるOS環境(Windows/Linux/macOSやモバイルデバイス)で存在しうるものではありますが、本記事ではWindowsのキーロガーに焦点を絞って解説します。特にWindows APIや機能を使用してキー入力を取得する4つの異なるタイプのキーロガーについて解説します。</p>
<p>一点補足としては、ここでキーロギングの手法について説明しているのは、あくまで本記事後半で紹介している、新しい検知機能についての理解を深めていただくためです。そのため、例として掲載しているコードはあくまで単なる例であり、実際にそのまま動くコードが掲載されている訳ではありません(※3)。</p>
<p>(※2:  Windows上で動作するキーロガーは、カーネル空間(OS)側に設置されるものと、通常のアプリケーションと同じ領域(ユーザ空間)に設置されるものに大別されます。本記事では、後者のタイプを取り上げます。 )
(※3: 以下に掲載されている例のコードを元にキーロガーを作成し悪用した場合、弊社では対応、および、責任について負いかねます 。)</p>
<ol>
<li>ポーリング型キーロガー</li>
</ol>
<p>このタイプのキーロガーは、キーボードの各キーの状態(キーが押された否か)を短い間隔(1秒よりはるかに短い間隔)で定期的に確認します。そして前回の確認以降に、新たに押されたキーがあることが判明した場合、その押されたキーの文字の情報を記録・保存します。この一連の流れを繰り返すことで、キーロガーは、ユーザが入力した文字列の情報を取得しているのです。</p>
<p>ポーリング型のキーロガーは、キーの入力状態をチェックするWindowsのAPIを利用して実装されており、代表的には <a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getasynckeystate"><code>GetAsyncKeyState</code></a> APIが利用されます。このAPIは、特定のキーが現在押されているか否かに加えて、その特定のキーが前回のAPI呼び出し以降押されたか否かの情報を取得することが出来ます。以下が<code>GetAsyncKeyState</code> APIを使ったポーリング型キーロガーの簡単な例です。</p>
<pre><code class="language-C">while(true)
{
    for (int key = 1; key &lt;= 255; key++)
    {
        if (GetAsyncKeyState(key) &amp; 0x01)
        {
            SaveTheKey(key, &quot;log.txt&quot;);
        }
    }
    Sleep(50);
}
</code></pre>
<p>ポーリング(<code>GetAsyncKeyState</code>)を用いてキー押下状態を取得する手法は、古くから存在する典型的なキーロギングの手法として知られているだけでなく、今でもマルウェアによって使われていることが確認されています。</p>
<ol start="2">
<li>フッキング型キーロガー</li>
</ol>
<p>フッキング型キーロガーは、ポーリング型キーロガーと同じく、古くから存在する典型的な種類のキーロガーです。ここではまず「そもそもフックとは何か？」について説明します。</p>
<p>フックとは大雑把に言うと「アプリケーションの特定の処理に、独自の処理を割り込ませる仕組み」のことを指す言葉です。そして、フックを使って独自の処理を割り込ませることを「フックする」とも言います。Windowsでは、アプリケーションに対するキー入力などのメッセージ(イベント)をフックすることが出来る仕組みが用意されており、この仕組みは<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-setwindowshookexa">SetWindowsHookEx</a> APIを通じて利用することが出来ます。以下が<code>SetWindowsHookEx</code> APIを使ったポーリング型キーロガーの簡単な例です。</p>
<pre><code class="language-C">HMODULE hHookLibrary = LoadLibraryW(L&quot;hook.dll&quot;);
FARPROC hookFunc = GetProcAddress(hHookLibrary, &quot;SaveTheKey&quot;);

HHOOK keyboardHook = NULL;
    
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
                (HOOKPROC)hookFunc,
                hHookLibrary,
                0);
</code></pre>
<ol start="3">
<li>Raw Input Modelを用いたキーロガー</li>
</ol>
<p>このタイプのキーロガーは、キーボードなどの入力デバイスから得られた、生の入力データ(Raw Input)を取得し、それを保存・記録します。このキーロガーの詳細について説明する前に、まずWindowsにおける入力方式である「Original Input Model」と「Raw Input Model」について理解する必要があります。以下がそれぞれの入力方式についての説明です。</p>
<ul>
<li><strong>Original Input Model</strong>:  キーボードなどの入力デバイスから入力されたデータを、一度OSを介して必要な処理をした後、アプリケーション側に届ける方式</li>
<li><strong>Raw Input Model</strong>:  キーボードなどの入力デバイスから入力されたデータを、そのままアプリケーション側が直接受け取る方式</li>
</ul>
<p>Windowsでは当初、Original Input Modelのみが使われていました。しかしWindows XP以降に、おそらくは入力デバイスの多様化などの要因から、Raw Input Modelが導入されました。Raw Input Modelでは、<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-registerrawinputdevices"><code>RegisterRawInputDevices</code></a> APIを使い、入力データを直接受け取りたい入力デバイスを登録します。そしてその後、<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getrawinputdata"><code>GetRawInputData</code></a>) APIを用いて生データを取得します。
以下がこれらのAPIを使った、Raw Input Modelを用いたキーロガーの簡単な例です。</p>
<pre><code class="language-C">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, &quot;log.txt&quot;);
            }
        }
        HeapFree(GetProcessHeap(), 0, buffer);
        break;
    default:
        return DefWindowProc(hWnd, uMessage, wParam, lParam);
    }
    return 0;
}
</code></pre>
<p>この例では、最初に生入力を受け取りたい入力デバイスを<code>RegisterRawInputDevices</code>を用いて、登録します。ここでは、キーボードの生入力データを受け取るように設定・登録しています。</p>
<ol start="4">
<li><code>DirectInput</code>を用いたキーロガー</li>
</ol>
<p>最後に、<code>DirectInput</code>を用いたキーロガーについて説明します。このキーロガーは簡単に言えばMicrosoft DirectXの機能を悪用したキーロガーです。DirectXとは、ゲームや動画などのマルチメディア関連の処理を扱うためのAPI群の総称(ライブラリ)です。</p>
<p>ゲームにおいて、ユーザから各種入力が取得できることは必須機能と言って良いことから、DirectXにおいてもユーザの入力を処理するAPI群が提供されています。そして、DirectXのバージョン8以前に提供されていたそれらAPI群のことを「DirectInput」と呼びます。以下が<code>DirectInput</code>に関連するAPIを使ったキーロガーの簡単な例です。補足ですが、<code>DirectInput</code>を用いてキーを取得する際、裏では<code>RegisterRawInputDevices</code> APIが呼ばれています。</p>
<pre><code class="language-C">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, &quot;log.txt&quot;);	
    Sleep(50);
}
</code></pre>
<h2>Windows API呼び出しを監視してキーロガーを検出する</h2>
<p>Elastic Defendでは、Event Tracing for Windows (ETW ※4)を用いて、前述の種類のキーロガーを検知しています。具体的には、関連するWindows API群の呼び出しを監視し、その挙動のログを取得することで実現しています。監視するWindows API群と、付随して新規に作成したキーロガーの検知ルールは以下です。(※4 一言でいうとWindowsが提供する、アプリケーションやデバイスドライバなどのシステム側のコンポーネントを、トレースおよびロギングする仕組み。)</p>
<h3>監視するWindows API群:</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>追加したキーロガー検知ルール一覧:</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>新規に追加した機能および検知ルールにより、Elastic Defendにてキーロガー・キーロギングの包括的な監視と検出が可能となり、これらの脅威に対するWindowsエンドポイントのセキュリティと保護の強化を実現しました。</p>
<h3>Windowsのキーロガーを検知する</h3>
<p>次に実際の検知の様子をお見せします。例として、Raw Input Modelを用いたキーロガーをElastic Defendで検出してみます。ここでは<code>RegisterRawInputDevices</code> APIを用いた簡易的なキーロガー「Keylogger.exe」を用意し、テスト環境で実行してみました※5。(※5 実行環境はWindows 10の執筆時点の最新版であるWindows 10 Version 22H2 19045.4412です。)</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/protecting-your-devices-from-information-theft-keylogger-protection/image1.png" alt="Elastic Securityのアラート" /></p>
<p>キーロガーを実行した直後に、検知ルール(<a href="https://github.com/elastic/protections-artifacts/blob/main/behavior/rules/collection_keystroke_input_capture_via_registerrawinputdevices.toml">Keystroke Input Capture via <code>RegisterRawInputDevices</code></a>)が発動し、エンドポイント側でアラートが上がりました。このアラートのさらなる詳細はKibana上から見ることが出来ます。</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/protecting-your-devices-from-information-theft-keylogger-protection/image3.png" alt="Elastic Securityのアラートダッシュボード" /></p>
<p>以下が検知ルールの詳細です。検知に使われているAPIの部分を中心に説明します。</p>
<pre><code class="language-sql">query = '''
api where
 process.Ext.api.name == &quot;RegisterRawInputDevices&quot; and not process.code_signature.status : &quot;trusted&quot; and
 process.Ext.api.parameters.usage : (&quot;HID_USAGE_GENERIC_KEYBOARD&quot;, &quot;KEYBOARD&quot;) and
 process.Ext.api.parameters.flags : &quot;*INPUTSINK*&quot; and process.thread.Ext.call_stack_summary : &quot;?*&quot; 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 :
                         (&quot;*\\program files*&quot;, &quot;*\\windows\\system32\\*&quot;, &quot;*\\windows\\syswow64\\*&quot;,
                          &quot;*\\windows\\systemapps\\*&quot;,
                          &quot;*\\users\\*\\appdata\\local\\*\\kumospace.exe&quot;,
                          &quot;*\\users\\*\\appdata\\local\\microsoft\\teams\\current\\teams.exe&quot;) and 
 not process.executable : (&quot;?:\\Program Files\\*.exe&quot;, &quot;?:\\Program Files (x86)\\*.exe&quot;)
'''
</code></pre>
<p>このアラートは簡単に言うと「署名されていないプロセス」または「署名されているが、その署名者が信頼できないプロセス」が、キー入力を取得する目的で<code>RegisterRawInputDevices</code>  APIを呼び出した時に発せられるアラートです。<code>RegisterRawInputDevices</code> APIが呼び出された際の引数の情報に着目しており、より具体的にはAPIの第一引数である、<a href="https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/ns-winuser-rawinputdevice">RAWINPUTDEVICE</a>構造体のメンバの情報を検知に用いています。</p>
<p>この引数の値が、キーボード入力の取得を試みていることを示している場合、キーロガーが実行されたと見なして、アラートを上げるようになっています。 <code>RegisterRawInputDevices</code> APIのログはKibana上でも確認できます。</p>
<p><img src="https://www.elastic.co/security-labs/assets/images/protecting-your-devices-from-information-theft-keylogger-protection/image2.png" alt="Kibana上で確認できるRegisterRawInputDevices APIログ" /></p>
<h3>各Windows APIの呼び出しの際に取得しているデータ</h3>
<p>分量の都合で、追加したすべての検知ルールとAPIの詳細については本記事では説明しません。ですが最後に、対象のWindows APIの呼び出しの際にElastic Defend側で取得しているデータについて、簡単にご紹介します。各項目についてさらに知りたい方は、<a href="https://github.com/elastic/endpoint-package/blob/main/custom_schemas/custom_api.yml">custom_api.yml</a>に記載されているElastic Common Schema（ECS）とのマッピングをご参照ください。</p>
<table>
<thead>
<tr>
<th>API名</th>
<th>フィールド</th>
<th>説明(原文を日本語訳したもの)</th>
<th>例</th>
</tr>
</thead>
<tbody>
<tr>
<td>GetAsyncKeyState</td>
<td>process.Ext.api.metadata.ms_since_last_keyevent</td>
<td>このパラメーターは、最後の GetAsyncKeyState イベントからの経過時間をミリ秒で示します。</td>
<td>94</td>
</tr>
<tr>
<td>GetAsyncKeyState</td>
<td>process.Ext.api.metadata.background_callcount</td>
<td>このパラメーターは、最後に成功した GetAsyncKeyState 呼び出しからの間に行われた、失敗した呼び出しも含めたすべての GetAsyncKeyState API 呼び出しの回数を示します。</td>
<td>6021</td>
</tr>
<tr>
<td>SetWindowsHookEx</td>
<td>process.Ext.api.parameters.hook_type</td>
<td>Tインストールするフックの種類</td>
<td>&quot;WH_KEYBOARD_LL&quot;</td>
</tr>
<tr>
<td>SetWindowsHookEx</td>
<td>process.Ext.api.parameters.hook_module</td>
<td>フック先の処理を保有するDLL</td>
<td>&quot;c:\windows\system32\taskbar.dll&quot;</td>
</tr>
<tr>
<td>SetWindowsHookEx</td>
<td>process.Ext.api.parameters.procedure</td>
<td>フック先となる処理や関数のメモリアドレス</td>
<td>2431737462784</td>
</tr>
<tr>
<td>SetWindowsHookEx</td>
<td>process.Ext.api.metadata.procedure_symbol</td>
<td>フック先の処理の要約</td>
<td>&quot;taskbar.dll&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.return_value</td>
<td>RegisterRawInputDevices API 呼び出しの戻り値</td>
<td>1</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.parameters.usage_page</td>
<td>このパラメーターはデバイスのトップレベルコレクション（Usage Page）を示す。RAWINPUTDEVICE 構造体の最初のメンバ</td>
<td>&quot;GENERIC&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.parameters.usage</td>
<td>このパラメーターは、Usage Page 内の特定のデバイス（Usage）を示します。RAWINPUTDEVICE 構造体の２番目のメンバ</td>
<td>&quot;KEYBOARD&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.parameters.flags</td>
<td>UsagePageとUsageによって提供される情報をどのように解釈するかを指定するモードフラグ。RAWINPUTDEVICE 構造体の３番目のメンバ</td>
<td>&quot;INPUTSINK&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.windows_count</td>
<td>呼び出し元スレッドが所有するウィンドウの数</td>
<td>2</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.visible_windows_count</td>
<td>呼び出し元スレッドが所有する表示されているウィンドウの数</td>
<td>0</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.thread_info_flags</td>
<td>スレッドの情報を表すフラグ</td>
<td>16</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.start_address_module</td>
<td>スレッドの開始アドレスに紐づくモジュールの名前</td>
<td>&quot;C:\Windows\System32\DellTPad\ApMsgFwd.exe&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.start_address_allocation_protection</td>
<td>スレッドの開始アドレスに紐づくメモリ保護属性</td>
<td>&quot;RCX&quot;</td>
</tr>
</tbody>
</table>
<h2>まとめ</h2>
<p>本記事では、Elastic Defend 8.12にて導入された、Windows環境におけるキーロガーおよびキーロギング検知機能についてご紹介しました。具体的には、キーロギングに関連する代表的なWindows API群の呼び出しを監視することで、シグネチャに依存しない、振る舞い検知によるキーロガー検出を実現しました。精度を高め、誤検知率を減らすために、数ヶ月にわたる研究・調査をもとにこの機能と新しいルールを開発しました。</p>
<p>Elastic Defendではキーロガー関連のAPI以外にも、攻撃者に一般的に利用されるメモリ操作等の<a href="https://www.elastic.co/security-labs/doubling-down-etw-callstacks">API群なども監視すること</a>で、多層的な防御を実現しております。Elastic Security および Elastic Defendについて気になった方はぜひ<a href="https://www.elastic.co/jp/security">製品ページ</a>や<a href="https://www.elastic.co/jp/videos/intro-elastic-security">ドキュメント</a>を御覧頂ければ幸いです。</p>
]]></content:encoded>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/security-labs/assets/images/protecting-your-devices-from-information-theft-keylogger-protection-jp/Security Labs Images 10.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Protecting your devices from information theft]]></title>
            <link>https://www.elastic.co/security-labs/protecting-your-devices-from-information-theft-keylogger-protection</link>
            <guid>protecting-your-devices-from-information-theft-keylogger-protection</guid>
            <pubDate>Thu, 30 May 2024 00:00:00 GMT</pubDate>
            <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/security-labs/protecting-your-devices-from-information-theft-keylogger-protection-jp">Japanese</a>.</p>
<h2>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>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>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 class="language-c">while(true)
{
    for (int key = 1; key &lt;= 255; key++)
    {
        if (GetAsyncKeyState(key) &amp; 0x01)
        {
            SaveTheKey(key, &quot;log.txt&quot;);
        }
    }
    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 start="2">
<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 &quot;hook&quot; 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 &quot;hooking.&quot;</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 class="language-c">HMODULE hHookLibrary = LoadLibraryW(L&quot;hook.dll&quot;);
FARPROC hookFunc = GetProcAddress(hHookLibrary, &quot;SaveTheKey&quot;);

HHOOK keyboardHook = NULL;
    
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
                (HOOKPROC)hookFunc,
                hHookLibrary,
                0);
</code></pre>
<ol start="3">
<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 &quot;Original Input Model&quot; and &quot;Raw Input Model&quot; 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 class="language-c">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, &quot;log.txt&quot;);
            }
        }
        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 start="4">
<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 class="language-c">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, &quot;log.txt&quot;);	
    Sleep(50);
}
</code></pre>
<h2>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>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>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>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://www.elastic.co/security-labs/assets/images/protecting-your-devices-from-information-theft-keylogger-protection/image1.png" alt="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://www.elastic.co/security-labs/assets/images/protecting-your-devices-from-information-theft-keylogger-protection/image3.png" alt="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 class="language-sql">query = '''
api where
 process.Ext.api.name == &quot;RegisterRawInputDevices&quot; and not process.code_signature.status : &quot;trusted&quot; and
 process.Ext.api.parameters.usage : (&quot;HID_USAGE_GENERIC_KEYBOARD&quot;, &quot;KEYBOARD&quot;) and
 process.Ext.api.parameters.flags : &quot;*INPUTSINK*&quot; and process.thread.Ext.call_stack_summary : &quot;?*&quot; 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 :
                         (&quot;*\\program files*&quot;, &quot;*\\windows\\system32\\*&quot;, &quot;*\\windows\\syswow64\\*&quot;,
                          &quot;*\\windows\\systemapps\\*&quot;,
                          &quot;*\\users\\*\\appdata\\local\\*\\kumospace.exe&quot;,
                          &quot;*\\users\\*\\appdata\\local\\microsoft\\teams\\current\\teams.exe&quot;) and 
 not process.executable : (&quot;?:\\Program Files\\*.exe&quot;, &quot;?:\\Program Files (x86)\\*.exe&quot;)
'''
</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://www.elastic.co/security-labs/assets/images/protecting-your-devices-from-information-theft-keylogger-protection/image2.png" alt="RegisterRawInputDevices API logs displayed in Kibana" /></p>
<h3>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>
<table>
<thead>
<tr>
<th>API Name</th>
<th>Field</th>
<th>Description</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>GetAsyncKeyState</td>
<td>process.Ext.api.metadata.ms_since_last_keyevent</td>
<td>This parameter indicates an elapsed time in milliseconds between the last GetAsyncKeyState event.</td>
<td>94</td>
</tr>
<tr>
<td>GetAsyncKeyState</td>
<td>process.Ext.api.metadata.background_callcount</td>
<td>This parameter indicates a number of all GetAsyncKeyState api calls, including unsuccessful calls, between the last successful GetAsyncKeyState call.</td>
<td>6021</td>
</tr>
<tr>
<td>SetWindowsHookEx</td>
<td>process.Ext.api.parameters.hook_type</td>
<td>Type of hook procedure to be installed.</td>
<td>&quot;WH_KEYBOARD_LL&quot;</td>
</tr>
<tr>
<td>SetWindowsHookEx</td>
<td>process.Ext.api.parameters.hook_module</td>
<td>DLL containing the hook procedure.</td>
<td>&quot;c:\windows\system32\taskbar.dll&quot;</td>
</tr>
<tr>
<td>SetWindowsHookEx</td>
<td>process.Ext.api.parameters.procedure</td>
<td>The memory address of the procedure or function.</td>
<td>2431737462784</td>
</tr>
<tr>
<td>SetWindowsHookEx</td>
<td>process.Ext.api.metadata.procedure_symbol</td>
<td>Summary of the hook procedure.</td>
<td>&quot;taskbar.dll&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.return_value</td>
<td>Return value of RegisterRawInputDevices API call.</td>
<td>1</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.parameters.usage_page</td>
<td>This parameter indicates the top-level collection (Usage Page) of the device. First member RAWINPUTDEVICE structure.</td>
<td>&quot;GENERIC&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.parameters.usage</td>
<td>This parameter indicates the specific device (Usage) within the Usage Page. Second member RAWINPUTDEVICE structure.</td>
<td>&quot;KEYBOARD&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.parameters.flags</td>
<td>Mode flag that specifies how to interpret the information provided by UsagePage and Usage. Third member RAWINPUTDEVICE structure.</td>
<td>&quot;INPUTSINK&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.windows_count</td>
<td>Number of windows owned by the caller thread.</td>
<td>2</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.visible_windows_count</td>
<td>Number of visible windows owned by the caller thread.</td>
<td>0</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.thread_info_flags</td>
<td>Thread info flags.</td>
<td>16</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.start_address_module</td>
<td>Name of the module associated with the starting address of a thread.</td>
<td>&quot;C:\Windows\System32\DellTPad\ApMsgFwd.exe&quot;</td>
</tr>
<tr>
<td>RegisterRawInputDevices</td>
<td>process.Ext.api.metadata.start_address_allocation_protection</td>
<td>Memory protection attributes associated with the starting address of a thread.</td>
<td>&quot;RCX&quot;</td>
</tr>
</tbody>
</table>
<h2>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>
            <category>security-labs</category>
            <enclosure url="https://www.elastic.co/security-labs/assets/images/protecting-your-devices-from-information-theft-keylogger-protection/Security Labs Images 10.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>