使用Windows API行为检测键盘记录程序
在本文中,我们将介绍今年添加到Elastic Defend(从8.12版本开始)的键盘记录器和键盘记录检测功能,它负责Elastic Security中的端点保护。这篇文章也有日文版本。
介绍
从Elastic Defend 8.12开始,我们通过监控和记录键盘记录程序对代表性Windows api的调用,增强了对键盘记录程序和具有键盘记录功能的恶意软件(例如窃取信息的恶意软件或远程访问木马,通常称为rat)的检测。本出版物将重点介绍这个新特性的详细技术背景。此外,我们将介绍与此功能一起创建的新的预构建行为检测规则。
什么是键盘记录器,它们的风险是什么?
键盘记录器是一种监视和记录计算机上键入的按键的软件。虽然键盘记录程序可以用于合法目的,如用户监控,但它们经常被恶意行为者滥用。具体来说,它们被用来窃取敏感信息,如身份验证凭证、信用卡详细信息和通过键盘输入的各种机密数据。(※1:虽然有硬件键盘记录程序可以通过USB直接连接到PC上,但本文主要介绍软件键盘记录程序。)
通过键盘记录程序获得的敏感信息可以被用于金钱盗窃或作为进一步网络攻击的垫脚石。因此,尽管键盘记录本身不会直接损坏计算机,但早期发现对于防止后续更具侵入性的网络攻击至关重要。
有许多类型的恶意软件具有键盘记录功能,特别是rat、信息窃取器和银行恶意软件。一些知名的带有键盘记录功能的恶意软件包括Agent Tesla、LokiBot和SnakeKeylogger。
按键是如何被窃取的?
接下来,让我们从技术角度解释键盘记录程序如何在不被检测到的情况下工作。虽然键盘记录程序可以在各种操作系统环境(Windows/Linux/macOS和移动设备)中使用,但本文将重点关注Windows键盘记录程序。具体来说,我们将描述使用Windows api和函数捕获击键的四种不同类型的键盘记录程序(※2)。
顺便说一句,这里解释键盘记录方法的原因是为了加深对本文后半部分引入的新检测特性的理解。因此,所提供的示例代码仅用于说明目的,并不打算按原样执行(※3)。
(※2:在Windows上运行的键盘记录程序可以大致分为安装在内核空间(操作系统侧)和安装在常规应用程序相同空间(用户空间)的键盘记录程序。(※3:如果基于下面提供的示例代码创建并滥用键盘记录程序,Elastic将不对任何后果负责。)
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
)捕获按键状态的方法不仅是一种众所周知的经典键盘记录技术,而且今天也经常被恶意软件使用。
2.Hooking-based键盘记录器
与基于轮询的键盘记录程序一样,基于钩子的键盘记录程序是一种已经存在了很长时间的经典类型。让我们先解释一下什么是“钩子”。
钩子是一种机制,允许您将自定义处理(自定义代码)插入到应用程序的特定操作中。使用钩子插入自定义处理称为“挂钩”。
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);
3.使用原始输入模型的键盘记录器
这种类型的键盘记录器捕获并记录直接从键盘等输入设备获得的原始输入数据。在深入研究这类键盘记录器的细节之前,有必要了解Windows中的“原始输入模型”和“原始输入模型”。以下是每种输入法的解释:
- 原始输入模式:从键盘等输入设备输入的数据在交付给应用程序之前由操作系统处理。
- 原始输入模式:从输入设备输入的数据直接由应用程序接收,无需操作系统进行任何中间处理。
最初,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
用于注册将从中接收原始输入数据的输入设备。在这里,它被设置为接收来自键盘的原始输入数据。
4.使用DirectInput
的键盘记录器
最后,让我们讨论一个使用DirectInput
的键盘记录器。简单地说,这个键盘记录器滥用了Microsoft DirectX的功能。DirectX是用于处理多媒体任务(如游戏和视频)的api(库)的集合。
由于从用户那里获得各种输入在游戏中是必不可少的,DirectX还提供了处理用户输入的api。在DirectX版本8之前提供的api被称为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调用来检测键盘记录程序
弹性防御使用Windows事件跟踪(ETW※4)来检测上述键盘记录器类型。这是通过监视对相关Windows api的调用和记录特别的异常行为来实现的。下面是正在监视的Windows api以及与这些api关联的新创建的键盘记录器检测规则。(※4:简而言之,ETW是微软提供的一种机制,用于跟踪和记录Windows中应用程序和系统组件(如设备驱动程序)的执行情况。)
受监控的Windows api:
新的键盘记录器端点检测规则:
- GetAsyncKeyState API Call from Suspicious Process
- GetAsyncKeyState API Call from Unusual Process
- Keystroke Input Capture via DirectInput
- Keystroke Input Capture via RegisterRawInputDevices
- Keystroke Messages Hooking via SetWindowsHookEx
- Keystrokes Input Capture from a Managed Application
- Keystrokes Input Capture from a Suspicious Module
- Keystrokes Input Capture from Suspicious CallStack
- Keystrokes Input Capture from Unsigned DLL
- Keystrokes Input Capture via SetWindowsHookEx
有了这套新的功能,Elastic Defend可以提供对键盘记录活动的全面监控和检测,增强Windows端点的安全性和保护,以抵御这些威胁。
检测Windows键盘记录程序
接下来,让我们通过一个示例了解检测在实践中是如何工作的。我们将使用带有弹性防御的原始输入模型来检测键盘记录器。对于这个例子,我们准备了一个简单的PoC键盘记录器,名为keylogger.exe
,它使用RegisterRawInputDevices
API,并在我们的测试环境※5中执行它。(※5:执行环境为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来捕获击键时,该规则会引发警报。更具体地说,Elastic Defend监视传递给RegisterRawInputDevices
API的参数,特别是RAWINPUTDEVICE
结构的成员,这是该API的第一个参数。
当这些参数值表明试图捕获键盘输入时,会引发警报。RegisterRawInputDevices
API的日志也可以在Kibana中查看。
在Windows API调用期间收集的数据
由于篇幅限制,本文没有涵盖添加的所有检测规则和API细节。但是,我们将简要描述Elastic Defend在调用相关Windows api期间收集的数据。对于每个项目的进一步解释,请参考custom_api.yml
中详细的弹性公共模式(ECS)映射。
API 名称 | Field | 描述 | 例子 |
---|---|---|---|
GetAsyncKeyState | process.Ext.api.metadata.ms_since_last_keyevent | 该参数指示最后一次GetAsyncKeyState事件之间经过的时间(以毫秒为单位)。 | 94 |
GetAsyncKeyState | process.Ext.api.metadata.background_callcount | 该参数表示在最后一次成功的GetAsyncKeyState调用之间的所有GetAsyncKeyState api调用的数量,包括不成功的调用。 | 6021 |
SetWindowsHookEx | process.Ext.api.parameters.hook_type | 要安装的hook子程类型。 | "WH_KEYBOARD_LL" |
SetWindowsHookEx | process.Ext.api.parameters.hook_module | 包含hook子程的DLL。 | "c:\windows\system32\taskbar.dll" |
SetWindowsHookEx | process.Ext.api.parameters.procedure | 过程或函数的内存地址。 | 2431737462784 |
SetWindowsHookEx | process.Ext.api.metadata.procedure_symbol | hook子程的摘要。 | "taskbar.dll" |
RegisterRawInputDevices | process.Ext.api.metadata.return_value | RegisterRawInputDevices API调用的返回值。 | 1 |
RegisterRawInputDevices | process.Ext.api.parameters.usage_page | 该参数表示设备的顶层采集(使用页面)。第一个成员RAWINPUTDEVICE结构。 | "GENERIC" |
RegisterRawInputDevices | process.Ext.api.parameters.usage | 该参数表示“使用情况”页面中的具体设备(“使用情况”)。第二个成员RAWINPUTDEVICE结构。 | "KEYBOARD" |
RegisterRawInputDevices | process.Ext.api.parameters.flags | 模式标志,指定如何解释UsagePage和Usage提供的信息。第三个成员RAWINPUTDEVICE结构。 | "INPUTSINK" |
RegisterRawInputDevices | process.Ext.api.metadata.windows_count | 调用者线程拥有的窗口数。 | 2 |
RegisterRawInputDevices | process.Ext.api.metadata.visible_windows_count | 调用线程拥有的可见窗口数。 | 0 |
RegisterRawInputDevices | process.Ext.api.metadata.thread_info_flags | 线程信息标志。 | 16 |
RegisterRawInputDevices | process.Ext.api.metadata.start_address_module | 与线程的起始地址相关联的模块名。 | "C:\Windows\System32\DellTPad\ApMsgFwd.exe" |
RegisterRawInputDevices | process.Ext.api.metadata.start_address_allocation_protection | 与线程起始地址相关联的内存保护属性。 | "RCX" |
结论
在本文中,我们介绍了从Elastic Defend 8.12开始为Windows环境添加的键盘记录程序和键盘记录检测功能。具体来说,通过监控对与键盘记录相关的代表性Windows api的调用,我们集成了一种不依赖于签名的行为键盘记录检测方法。为了确保准确性和减少误报率,我们基于几个月的研究创造了这个功能和新规则。
除了与键盘记录相关的api外,Elastic Defend还监视恶意行为者常用的其他api,例如用于内存操作的api,提供多层保护。如果您对Elastic Security和Elastic Defend感兴趣,请查看产品页面和文档。