Asuka Najakima

保护设备免受信息窃取

使用 Windows API 行为检测键盘记录器

情報窃取から端末を守る

本文介绍了 Elastic Security 中,负责终端保护的 Elastic Defend 今年(版本 8.12 起)新增的键盘记录器和键盘记录检测功能。

前言

自 Elastic Defend 8.12 起,为了加强对 Windows 上运行的键盘记录器和具有键盘记录功能的恶意软件(信息窃取恶意软件、远程访问木马,又称 RAT)的检测,新增了监视和记录键盘记录器使用的典型 Windows API 群调用的功能。本文将重点介绍此新功能,并解释其技术细节。此外,还将介绍与此功能一起新创建的行为检测规则(预置规则)。

什么是键盘记录器?它有哪些危险?

键盘记录器是一种监视和记录计算机上输入的按键内容的软件(※1)。键盘记录器有时会被用于用户监控等正当理由,但也经常被攻击者恶意利用。具体而言,它们被用于窃取用户通过键盘输入的身份验证信息、信用卡信息、各种机密信息等敏感信息。(※1:也有直接通过 USB 等连接到电脑的硬件型键盘记录器,但本文将重点介绍软件型键盘记录器。)

通过键盘记录器获得的敏感信息会被用于窃取金钱或为进一步的网络攻击奠定基础。因此,尽管键盘记录行为本身不会直接对计算机造成损害,但为了阻止后续的网络攻击,尽早检测非常重要。

有许多恶意软件具有键盘记录功能,特别是 RAT、信息窃取恶意软件和银行恶意软件等类型的恶意软件中,有时会发现搭载了键盘记录功能。具有键盘记录功能的著名恶意软件包括 Agent TeslaLokibitSnakeKeylogger 等。

如何盗取输入的文字?

接下来,我们将从技术角度解释键盘记录器如何在用户不知情的情况下盗取用户从键盘输入的文字。键盘记录器本身可能存在于任何操作系统环境(Windows/Linux/macOS 和移动设备)中,但本文将重点介绍 Windows 的键盘记录器。特别是,我们将解释使用 Windows API 或功能获取键盘输入的四种不同类型的键盘记录器。

需要补充一点,这里解释键盘记录方法仅仅是为了加深对本文后半部分介绍的新检测功能的理解。因此,作为示例列出的代码仅仅是示例,并非实际可运行的代码(※3)。

(※2:在 Windows 上运行的键盘记录器可以大致分为设置在内核空间 (OS) 侧的和设置在与普通应用程序相同区域(用户空间)的两种。本文将介绍后一种类型。)(※3:如果根据以下示例代码创建键盘记录器并恶意使用,本公司概不负责,也不承担任何责任。)

  1. 轮询型键盘记录器

这种类型的键盘记录器会定期以较短的时间间隔(远小于 1 秒)检查键盘上每个键的状态(是否被按下)。如果发现自上次检查以来有新按下的键,则会记录并保存该按键的字符信息。通过重复这一系列操作,键盘记录器即可获取用户输入的字符串信息。

轮询型键盘记录器是使用 Windows API 来检查按键的输入状态实现的,典型的是使用 GetAsyncKeyState API。此 API 不仅可以获取特定按键当前是否被按下,还可以获取该特定按键自上次 API 调用以来是否被按下的信息。以下是使用 GetAsyncKeyState API 的轮询型键盘记录器的简单示例。

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

使用轮询(GetAsyncKeyState)获取按键按下状态的方法不仅是众所周知的古老典型键盘记录方法,而且已被确认为仍被恶意软件使用。

  1. 挂钩型键盘记录器

挂钩型键盘记录器与轮询型键盘记录器一样,是古老且典型的键盘记录器类型。在这里,我们首先说明一下“什么是钩子?”

简而言之,钩子是指“将自己的处理插入到应用程序的特定处理中的机制”。并且,使用钩子插入自己的处理也被称为“挂钩”。Windows 提供了一种可以挂钩应用程序的按键输入等消息(事件)的机制,可以通过 SetWindowsHookEx API 来使用此机制。以下是使用 SetWindowsHookEx API 的轮询型键盘记录器的简单示例。

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

HHOOK keyboardHook = NULL;
    
keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,
                (HOOKPROC)hookFunc,
                hHookLibrary,
                0);
  1. 使用原始输入模型的键盘记录器

这种类型的键盘记录器获取并保存、记录从键盘等输入设备获得的原始输入数据 (Raw Input)。在详细解释这种键盘记录器之前,首先需要理解 Windows 中的输入方式“原始输入模型 (Original Input Model)”和“原始输入模型 (Raw Input Model)”。以下是对各自输入方式的说明:

  • 原始输入模型 (Original Input Model):从键盘等输入设备输入的数据,在通过 OS 进行必要的处理后,传递到应用程序侧的方式
  • 原始输入模型 (Raw Input Model):从键盘等输入设备输入的数据,直接由应用程序侧接收的方式

在 Windows 中,最初仅使用原始输入模型。但是,在 Windows XP 之后,可能是由于输入设备多样化等因素,引入了原始输入模型。在原始输入模型中,使用 RegisterRawInputDevices API 注册要直接接收输入数据的输入设备。之后,使用 GetRawInputData) API 获取原始数据。 以下是使用这些 API 的基于原始输入模型的键盘记录器的简单示例。

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

    UINT dwSize = 0;
    RAWINPUT* buffer = NULL;

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

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

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

在此示例中,首先使用 RegisterRawInputDevices 注册要接收原始输入的输入设备。在这里,设置和注册为接收键盘的原始输入数据。

  1. 使用 DirectInput 的键盘记录器

最后,我们将介绍使用 DirectInput 的键盘记录器。简而言之,这种键盘记录器是滥用 Microsoft DirectX 功能的键盘记录器。DirectX 是用于处理游戏和视频等多媒体相关处理的 API 群的统称(库)。

在游戏中,从用户处获取各种输入是必不可少的功能,因此 DirectX 也提供了处理用户输入的 API 群。并且,DirectX 版本 8 之前提供的这些 API 群被称为“DirectInput”。以下是使用与 DirectInput 相关的 API 的键盘记录器的简单示例。补充一点,在使用 DirectInput 获取按键时,底层会调用 RegisterRawInputDevices API。

LPDIRECTINPUT8		lpDI = NULL;
LPDIRECTINPUTDEVICE8	lpKeyboard = NULL;

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

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

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

监视 Windows API 调用以检测键盘记录器

Elastic Defend 使用 Windows 事件跟踪 (ETW ※4) 来检测上述类型的键盘记录器。具体而言,它通过监视相关的 Windows API 群的调用并获取其行为日志来实现。以下是监视的 Windows API 群和附带新创建的键盘记录器检测规则。(※4 简而言之,ETW 是 Windows 提供的一种用于跟踪和记录应用程序和设备驱动程序等系统侧组件的机制。)

监视的 Windows API 群

添加的键盘记录器检测规则列表

通过新添加的功能和检测规则,Elastic Defend 能够全面监视和检测键盘记录器和键盘记录行为,从而增强 Windows 终端的安全性并防止这些威胁。

检测 Windows 键盘记录器

接下来,我们将展示实际的检测情况。作为示例,我们将尝试在 Elastic Defend 中检测使用原始输入模型的键盘记录器。这里,我们准备了一个使用 RegisterRawInputDevices API 的简单键盘记录器“Keylogger.exe”,并在测试环境中执行了它※5。(※5 执行环境为 Windows 10 的撰写时最新版本 Windows 10 Version 22H2 19045.4412。)

在执行键盘记录器后,检测规则(通过 RegisterRawInputDevices 捕获按键输入)被触发,并在终端侧发出警报。可以在 Kibana 上查看此警报的更多详细信息。

以下是检测规则的详细信息。重点说明检测中使用的 API 部分。

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")
'''

简而言之,此警报是在“未签名的进程”或“已签名但其签名者不可信的进程”为获取按键输入的目的而调用 RegisterRawInputDevices API 时发出的警报。它关注 RegisterRawInputDevices API 被调用时的参数信息,更具体地说,使用了 API 的第一个参数 RAWINPUTDEVICE 结构的成员信息进行检测。

如果此参数的值表明尝试获取键盘输入,则会认为执行了键盘记录器并发出警报。 RegisterRawInputDevices API 的日志也可以在 Kibana 上查看。

调用各个 Windows API 时获取的数据

由于篇幅所限,本文不介绍添加的所有检测规则和 API 的详细信息。但最后,我们将简要介绍在调用目标 Windows API 时 Elastic Defend 侧获取的数据。如果想了解每个项目的更多信息,请参阅 custom_api.yml 中记录的与 Elastic Common Schema (ECS) 的映射。

API 名称字段描述(原文的中文翻译)示例
GetAsyncKeyStateprocess.Ext.api.metadata.ms_since_last_keyevent此参数表示自上次 GetAsyncKeyState 事件以来经过的时间(以毫秒为单位)。94
GetAsyncKeyStateprocess.Ext.api.metadata.background_callcount此参数表示自上次成功的 GetAsyncKeyState 调用以来执行的所有 GetAsyncKeyState API 调用的次数,包括失败的调用。6021
SetWindowsHookExprocess.Ext.api.parameters.hook_typeT 要安装的钩子的类型"WH_KEYBOARD_LL"
SetWindowsHookExprocess.Ext.api.parameters.hook_module包含钩子目标处理的 DLL"c:\windows\system32\taskbar.dll"
SetWindowsHookExprocess.Ext.api.parameters.procedure作为钩子目标的处理或函数的内存地址2431737462784
SetWindowsHookExprocess.Ext.api.metadata.procedure_symbol钩子目标处理的摘要"taskbar.dll"
RegisterRawInputDevicesprocess.Ext.api.metadata.return_valueRegisterRawInputDevices API 调用的返回值1
RegisterRawInputDevicesprocess.Ext.api.parameters.usage_page此参数表示设备的顶级集合(使用页面)。RAWINPUTDEVICE 结构的第一个成员"GENERIC"
RegisterRawInputDevicesprocess.Ext.api.parameters.usage此参数指示 Usage Page 内的特定设备(Usage)。RAWINPUTDEVICE 结构的第二个成员"键盘"
RegisterRawInputDevicesprocess.Ext.api.parameters.flags指定如何解释 UsagePage 和 Usage 提供的信息的模式标志。RAWINPUTDEVICE 结构的第三个成员"INPUTSINK"
RegisterRawInputDevicesprocess.Ext.api.metadata.windows_count调用线程拥有的窗口数2
RegisterRawInputDevicesprocess.Ext.api.metadata.visible_windows_count调用线程拥有的可见窗口数0
RegisterRawInputDevicesprocess.Ext.api.metadata.thread_info_flags表示线程信息的标志16
RegisterRawInputDevicesprocess.Ext.api.metadata.start_address_module与线程起始地址关联的模块名称"C:\Windows\System32\DellTPad\ApMsgFwd.exe"
RegisterRawInputDevicesprocess.Ext.api.metadata.start_address_allocation_protection与线程起始地址关联的内存保护属性"RCX"

总结

本文介绍了 Elastic Defend 8.12 中引入的 Windows 环境下的键盘记录器和键盘记录检测功能。具体而言,通过监控与键盘记录相关的典型 Windows API 调用,实现了不依赖于签名、通过行为检测来检测键盘记录器。为了提高精度并减少误报率,我们基于数月的研究和调查开发了此功能和新规则。

Elastic Defend 除了键盘记录器相关的 API 之外,还监控攻击者常用的内存操作等API 群,从而实现多层防御。如果您对 Elastic Security 和 Elastic Defend 感兴趣,请务必查看产品页面文档