NanoCore: Raw Input Keylogging — Detection
NanoCore registers a raw input device for the keyboard with RIDEV_INPUTSINK, so it
receives every keystroke even while it has no focus, and it never installs a hook. Hook-based
keylogger detections that watch SetWindowsHookEx do not see this at all.
MITRE ATT&CK: T1056.001
Technique write-up: NanoCore_Keylogging
Detection Indicators
| Indicator | Value |
|---|---|
| Log path | KB_<Environment.TickCount>.dat — under C:\Users\Public\ in this build, configuration-dependent |
| Naming | literal KB_ prefix, decimal tick count, .dat extension |
| Location | any user-writable staging directory — Public, %APPDATA%, %TEMP%, ProgramData |
| Window | hidden — the form is minimised and Visible forced to false on every change |
| API | user32!RegisterRawInputDevices, usage page 0x01, usage 0x06 (keyboard) |
| Flag | RIDEV_INPUTSINK (0x00000100) — deliver input while unfocused |
| Message | WM_INPUT (0x00FF) handled in WndProc |
| Context | the focused window title is recorded alongside the keystrokes |
The KB_<ticks>.dat name is shared by all three NanoCore collection TTPs, so one broadened
file rule covers keystrokes, clipboard and DNS — but see the caveat below before weighting it.
⚠️ The output path is not a reliable indicator on its own. The recreated test case hardcodes
C:\Users\Public\KB_<TickCount>.dat, but where NanoCore stages collected data is configuration-dependent —%APPDATA%is at least as common in the wild, and the naming convention changes between builds. The file rule below is deliberately broadened across every plausible staging directory, and it is supporting evidence. The API-level rules are what actually identify the technique.
Log Sources
- Microsoft Defender XDR (
DeviceFileEvents) - Sysmon EID 11 (file create), EID 23/26 (file delete)
- Microsoft Defender XDR (
DeviceProcessEvents) - Sysmon EID 1 (process create)
- Windows Security 4688 with command line inclusion
Detection Engineering with Defender XDR
The log file is the reliable artefact; the API call itself is not audited.
// Supporting evidence, not proof - see the caveat above.
let staging = dynamic([@"\users\public\", @"\appdata\roaming\", @"\appdata\local\",
@"\programdata\", @"\windows\temp\"]);
DeviceFileEvents
| where tolower(FolderPath) has_any (staging)
| where FileName matches regex @"^KB_[0-9]+\.dat$"
or (FileName endswith ".dat" and tolower(InitiatingProcessFolderPath) has_any (staging))
| project Timestamp, DeviceName, AccountName, ActionType, FileName, FolderPath,
InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine
| order by Timestamp desc
Detection Engineering with Sysmon
Sysmon file-create on the same path, with the writing image.
WindowsEvent //(sysmon)
| where EventID == 11 // FileCreate
| extend TargetFilename = tostring(EventData.TargetFilename), Image = tostring(EventData.Image)
| where TargetFilename has_any (@"\Users\Public\", @"\AppData\", @"\ProgramData\")
| where TargetFilename endswith ".dat" or TargetFilename endswith ".log"
| where Image has_any (@"\AppData\", @"\Temp\", @"\Users\Public\")
| project TimeGenerated, Computer, Image, TargetFilename
Detection Engineering with SIGMA
Full rule: sigma_nanocore_collector_staging_file.yml
detection:
selection_name:
TargetFilename|re: '(?i)\\KB_[0-9]+\.dat$'
selection_location:
TargetFilename|contains:
- '\Users\Public\'
- '\AppData\Roaming\'
- '\AppData\Local\'
- '\ProgramData\'
condition: all of selection_*
Detection Engineering with YARA
The API surface is the durable half. RegisterRawInputDevices paired with GetRawInputData
and a hidden form is the technique regardless of where the output lands.
Rule: nanocore_rawinput_keylogger.yar
$api1 = "RegisterRawInputDevices" $api2 = "GetRawInputData"
$sink = "RIDEV_INPUTSINK" $struct = "RAWINPUTDEVICE"
Raw input registration produces no event at run time, so for live telemetry this is a scanning rule for files and process memory, not an alert source.
Notes and tuning
There is no event for RegisterRawInputDevices. Raw input registration is a user-mode
call into user32 with no audit path, which is exactly why this technique is attractive. The
output file is the detection.
Complementary signals worth pairing:
- A GUI process running with no visible window and no taskbar entry, writing to
Public. user32.dllloaded by a console-subsystem binary that has no UI reason to need it.
If you want prevention rather than detection, an application-control policy that stops
unsigned binaries executing from %APPDATA% breaks the delivery chain earlier.
Disclaimer
For educational purposes only!!!