PowerShell Keylogging Script
Detects PowerShell script block content that references Win32 keylogging primitives such as key state polling or low-level input hooks. Adversaries use keylogging to capture credentials and other sensitive user input.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2021/10/15"
3integration = ["windows"]
4maturity = "production"
5updated_date = "2026/03/30"
6
7[rule]
8author = ["Elastic"]
9description = """
10Detects PowerShell script block content that references Win32 keylogging primitives such as key state polling or
11low-level input hooks. Adversaries use keylogging to capture credentials and other sensitive user input.
12"""
13from = "now-9m"
14index = ["logs-windows.powershell*", "winlogbeat-*"]
15language = "kuery"
16license = "Elastic License v2"
17name = "PowerShell Keylogging Script"
18references = [
19 "https://github.com/EmpireProject/Empire/blob/master/data/module_source/collection/Get-Keystrokes.ps1",
20 "https://github.com/MojtabaTajik/FunnyKeylogger/blob/master/FunnyLogger.ps1",
21]
22risk_score = 73
23rule_id = "bd2c86a0-8b61-4457-ab38-96943984e889"
24severity = "high"
25tags = [
26 "Domain: Endpoint",
27 "OS: Windows",
28 "Use Case: Threat Detection",
29 "Tactic: Collection",
30 "Resources: Investigation Guide",
31 "Data Source: PowerShell Logs",
32]
33timestamp_override = "event.ingested"
34type = "query"
35
36query = '''
37event.category:process and host.os.type:windows and
38 (
39 powershell.file.script_block_text : (GetAsyncKeyState or NtUserGetAsyncKeyState or GetKeyboardState or "Get-Keystrokes") or
40 powershell.file.script_block_text : (
41 (SetWindowsHookEx or SetWindowsHookExA or SetWindowsHookExW or NtUserSetWindowsHookEx) and
42 (
43 GetForegroundWindow or GetWindowTextA or GetWindowTextW or "WM_KEYBOARD_LL" or "WH_MOUSE_LL" or
44 "WH_KEYBOARD_LL" or "LowLevelKeyboardProc" or "CallNextHookEx"
45 )
46 )
47 ) and not user.id : "S-1-5-18" and
48 not powershell.file.script_block_text : (
49 "sentinelbreakpoints" and "Set-PSBreakpoint"
50 )
51'''
52
53note = """## Triage and analysis
54
55### Investigating PowerShell Keylogging Script
56
57#### Possible investigation steps
58
59- Does the preserved script content show active keylogging intent rather than inert reference text?
60 - Focus: the preserved script text on the alert and any associated `file.path`.
61 - Implication: supports concern when the content invokes polling loops, hook registration, window-labeling routines, output formatting, or commodity functions such as "Get-Keystrokes"; carries less weight when the text is clearly documentation, training content, or inert reference with no adjacent execution evidence.
62
63- Does reconstructing the full script reveal logging, labeling, staging, or transmission behavior that changes urgency?
64 - Why: script block logging can split one script across multiple records; later fragments often reveal output paths, timer loops, or exfiltration.
65 - Focus: `powershell.file.script_block_id`, `powershell.sequence`, `powershell.total`, and `powershell.file.script_block_length` to rebuild adjacent fragments, then the reconstructed content for foreground-window labeling, output files, archives, remote destinations, or cleanup logic. $investigate_0
66 - Implication: supports active collection when reconstruction shows continuous polling, registered hooks, keystroke formatting, saved logs, compression, upload logic, or cleanup after collection.
67
68- Does the user-host pairing fit recognized accessibility tooling, kiosk automation, or security assessment?
69 - Focus: the `user.id` and `host.id` pairing, whether the host role supports input-capture tooling, and any prior alert recurrence for the same pairing and launcher.
70 - Hint: if workflow documentation is unavailable, require the same pairing and launcher to recur across prior alerts.
71 - Implication: escalate when the user has no recurring pattern of input capture, the host handles privileged workflows, or the timing falls outside scheduled testing.
72
73- Can you recover the PowerShell process and explain how it was launched?
74 - Focus: the matching process start event via `process.pid` and `host.id`, recovering `process.command_line`, `process.parent.executable`, `process.parent.command_line`, and `process.Ext.session_info.logon_type`. $investigate_1
75 - Hint: if the process event cannot be found, keep later file, network, and authentication review bounded to the same host and alert time.
76 - Implication: supports unauthorized use when the recovered process is launched by a document, browser, chat client, scheduled task, remote session, or user-writable script path.
77
78- Do file events show keystroke logs, staged archives, or renamed artifacts?
79 - Focus: file events for the same `process.entity_id`, with attention to `file.path`, `file.extension`, `file.Ext.header_bytes`, and `file.Ext.original.path` when logs or archives are renamed for staging.
80 - Implication: supports active collection when log files, archives, or renamed artifacts appear in user-writable or hidden paths, or when header bytes do not match the visible extension.
81
82- Do network events show credential exfiltration, webhook delivery, or remote staging?
83 - Focus: network events for the same `process.entity_id`, separating DNS `lookup_result` events (`dns.question.name`, `dns.resolved_ip`) from connection events (`destination.ip`, `destination.port`).
84 - Implication: suggests exfiltration when the process reaches rare public destinations, messaging or webhook services, or cloud storage. Missing network telemetry is unresolved, not benign.
85
86- Do authentication events show the session came from an unusual origin or that captured credentials were reused?
87 - Why: keylogging becomes higher priority when post-capture authentication shows new logons or explicit-credential use that could reflect captured input being used.
88 - Focus: if `process.Ext.authentication_id` was recovered, bridge to `winlog.event_data.TargetLogonId` for session origin (`source.ip`, `winlog.event_data.AuthenticationPackageName`). Also check post-alert 4624 or 4648 events on the same `host.id` for accounts that do not match the alert user.
89 - Hint: "4648" explicit-credential events do not use `winlog.event_data.TargetLogonId`; search `winlog.event_data.SubjectLogonId` instead.
90 - Implication: suggests captured-input abuse when the session has an unexpected origin or when later logons show new remote, privileged, or explicit-credential activity.
91
92- If the local evidence stays suspicious, do related alerts suggest broader compromise?
93 - Focus: related alerts for the same `user.id` to find repeated collection or defense-evasion activity. $investigate_2
94 - Hint: compare related alerts for the same `host.id` for persistence, repeated collection, or renamed input-capture variants. $investigate_3
95 - Implication: broaden when either view shows collection, defense-evasion, persistence, or transfer activity outside the expected workflow; keep the case local when surrounding alerts stay confined to one recognized workflow.
96
97- Escalate when script intent, launch context, artifacts, network, or authentication evidence align on unauthorized input capture; close only when all evidence supports a recognized benign workflow; if mixed or incomplete, preserve and escalate.
98
99### False positive analysis
100
101- Accessibility, kiosk, security-testing, or malware-analysis workflows can legitimately trigger this rule. Confirm by matching the same `process.executable`, signer, and `host.id` pattern across prior alerts or against workflow records.
102- Before creating an exception, validate that the same `user.id`, `host.id`, `file.path`, and a stable `powershell.file.script_block_text` substring recur across prior alerts. Avoid exceptions on hook-function strings alone, `user.name` alone, or the host alone.
103
104### Response and remediation
105
106- If confirmed benign, reverse any temporary containment and document the script content, recovered launch chain, user-host scope, and any benign artifact or destination pattern that proved the confirmed workflow. Create an exception only if the same workflow recurs consistently across prior alerts from this rule.
107- If suspicious but unconfirmed, preserve the reconstructed script content, recovered `process.entity_id`, related `file.path` artifacts, any `dns.question.name` or `destination.ip` values linked to transfer, and authentication events around the alert. Apply reversible containment such as session restrictions or temporary destination blocking. Escalate to host isolation only when active collection, credential reuse, or transfer evidence is strong and the host role can tolerate it. Avoid destructive cleanup until scope is clearer.
108- If confirmed malicious, document the recovered `process.entity_id`, `process.command_line`, `process.parent.executable`, written `file.path` artifacts, any confirmed `dns.question.name` or `destination.ip` values, and logon session details before initiating response actions. Prefer host isolation over process termination for initial containment when the asset can tolerate it, then contain affected accounts, block malicious destinations and scripts, and terminate recovered processes only after evidence capture.
109- If keystroke logs, archives, or staging artifacts are identified, preserve them as sensitive evidence. Review related users and hosts for the same `powershell.file.script_block_text` content, `file.path` pattern, or `dns.question.name` destinations before eradicating. Then remove the artifacts and any persistence or automation identified during reconstruction or host-scoping.
110- If follow-on authentication review suggests captured credentials were used, prioritize credential resets for the affected user and any additional accounts identified during the post-alert authentication timeline, then hunt for related sessions or privilege changes on the same host and other assets.
111- After containment, restrict the execution path that allowed the script to run, such as tightening PowerShell execution policies or script-path allowlists. Retain PowerShell script block logging and related endpoint telemetry.
112"""
113
114setup = """## Setup
115
116PowerShell Script Block Logging must be enabled to generate the events used by this rule (e.g., 4104).
117Setup instructions: https://ela.st/powershell-logging-setup
118"""
119
120[rule.investigation_fields]
121field_names = [
122 "@timestamp",
123 "user.name",
124 "user.id",
125 "user.domain",
126 "powershell.file.script_block_text",
127 "powershell.file.script_block_id",
128 "powershell.sequence",
129 "powershell.total",
130 "file.path",
131 "file.directory",
132 "file.name",
133 "process.pid",
134 "host.name",
135 "host.id",
136 "powershell.file.script_block_length"
137]
138
139[[transform.investigate]]
140label = "Script block fragments for the same script"
141description = ""
142providers = [
143 [
144 { excluded = false, field = "powershell.file.script_block_id", queryType = "phrase", value = "{{powershell.file.script_block_id}}", valueType = "string" },
145 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
146 ]
147]
148relativeFrom = "now-1h"
149relativeTo = "now"
150
151[[transform.investigate]]
152label = "Process events for the PowerShell instance"
153description = ""
154providers = [
155 [
156 { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" },
157 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
158 { excluded = false, field = "event.category", queryType = "phrase", value = "process", valueType = "string" }
159 ]
160]
161relativeFrom = "now-1h"
162relativeTo = "now"
163
164[[transform.investigate]]
165label = "Alerts associated with the user"
166description = ""
167providers = [
168 [
169 { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
170 { excluded = false, field = "user.id", queryType = "phrase", value = "{{user.id}}", valueType = "string" }
171 ]
172]
173relativeFrom = "now-48h/h"
174relativeTo = "now"
175
176[[transform.investigate]]
177label = "Alerts associated with the host"
178description = ""
179providers = [
180 [
181 { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
182 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
183 ]
184]
185relativeFrom = "now-48h/h"
186relativeTo = "now"
187
188[[rule.threat]]
189framework = "MITRE ATT&CK"
190[[rule.threat.technique]]
191id = "T1056"
192name = "Input Capture"
193reference = "https://attack.mitre.org/techniques/T1056/"
194[[rule.threat.technique.subtechnique]]
195id = "T1056.001"
196name = "Keylogging"
197reference = "https://attack.mitre.org/techniques/T1056/001/"
198
199[rule.threat.tactic]
200id = "TA0009"
201name = "Collection"
202reference = "https://attack.mitre.org/tactics/TA0009/"
203[[rule.threat]]
204framework = "MITRE ATT&CK"
205[[rule.threat.technique]]
206id = "T1059"
207name = "Command and Scripting Interpreter"
208reference = "https://attack.mitre.org/techniques/T1059/"
209[[rule.threat.technique.subtechnique]]
210id = "T1059.001"
211name = "PowerShell"
212reference = "https://attack.mitre.org/techniques/T1059/001/"
213
214[[rule.threat.technique]]
215id = "T1106"
216name = "Native API"
217reference = "https://attack.mitre.org/techniques/T1106/"
218
219[rule.threat.tactic]
220id = "TA0002"
221name = "Execution"
222reference = "https://attack.mitre.org/tactics/TA0002/"
Triage and analysis
Investigating PowerShell Keylogging Script
Possible investigation steps
-
Does the preserved script content show active keylogging intent rather than inert reference text?
- Focus: the preserved script text on the alert and any associated
file.path. - Implication: supports concern when the content invokes polling loops, hook registration, window-labeling routines, output formatting, or commodity functions such as "Get-Keystrokes"; carries less weight when the text is clearly documentation, training content, or inert reference with no adjacent execution evidence.
- Focus: the preserved script text on the alert and any associated
-
Does reconstructing the full script reveal logging, labeling, staging, or transmission behavior that changes urgency?
- Why: script block logging can split one script across multiple records; later fragments often reveal output paths, timer loops, or exfiltration.
- Focus:
powershell.file.script_block_id,powershell.sequence,powershell.total, andpowershell.file.script_block_lengthto rebuild adjacent fragments, then the reconstructed content for foreground-window labeling, output files, archives, remote destinations, or cleanup logic. $investigate_0 - Implication: supports active collection when reconstruction shows continuous polling, registered hooks, keystroke formatting, saved logs, compression, upload logic, or cleanup after collection.
-
Does the user-host pairing fit recognized accessibility tooling, kiosk automation, or security assessment?
- Focus: the
user.idandhost.idpairing, whether the host role supports input-capture tooling, and any prior alert recurrence for the same pairing and launcher. - Hint: if workflow documentation is unavailable, require the same pairing and launcher to recur across prior alerts.
- Implication: escalate when the user has no recurring pattern of input capture, the host handles privileged workflows, or the timing falls outside scheduled testing.
- Focus: the
-
Can you recover the PowerShell process and explain how it was launched?
- Focus: the matching process start event via
process.pidandhost.id, recoveringprocess.command_line,process.parent.executable,process.parent.command_line, andprocess.Ext.session_info.logon_type. $investigate_1 - Hint: if the process event cannot be found, keep later file, network, and authentication review bounded to the same host and alert time.
- Implication: supports unauthorized use when the recovered process is launched by a document, browser, chat client, scheduled task, remote session, or user-writable script path.
- Focus: the matching process start event via
-
Do file events show keystroke logs, staged archives, or renamed artifacts?
- Focus: file events for the same
process.entity_id, with attention tofile.path,file.extension,file.Ext.header_bytes, andfile.Ext.original.pathwhen logs or archives are renamed for staging. - Implication: supports active collection when log files, archives, or renamed artifacts appear in user-writable or hidden paths, or when header bytes do not match the visible extension.
- Focus: file events for the same
-
Do network events show credential exfiltration, webhook delivery, or remote staging?
- Focus: network events for the same
process.entity_id, separating DNSlookup_resultevents (dns.question.name,dns.resolved_ip) from connection events (destination.ip,destination.port). - Implication: suggests exfiltration when the process reaches rare public destinations, messaging or webhook services, or cloud storage. Missing network telemetry is unresolved, not benign.
- Focus: network events for the same
-
Do authentication events show the session came from an unusual origin or that captured credentials were reused?
- Why: keylogging becomes higher priority when post-capture authentication shows new logons or explicit-credential use that could reflect captured input being used.
- Focus: if
process.Ext.authentication_idwas recovered, bridge towinlog.event_data.TargetLogonIdfor session origin (source.ip,winlog.event_data.AuthenticationPackageName). Also check post-alert 4624 or 4648 events on the samehost.idfor accounts that do not match the alert user. - Hint: "4648" explicit-credential events do not use
winlog.event_data.TargetLogonId; searchwinlog.event_data.SubjectLogonIdinstead. - Implication: suggests captured-input abuse when the session has an unexpected origin or when later logons show new remote, privileged, or explicit-credential activity.
-
If the local evidence stays suspicious, do related alerts suggest broader compromise?
- Focus: related alerts for the same
user.idto find repeated collection or defense-evasion activity. $investigate_2 - Hint: compare related alerts for the same
host.idfor persistence, repeated collection, or renamed input-capture variants. $investigate_3 - Implication: broaden when either view shows collection, defense-evasion, persistence, or transfer activity outside the expected workflow; keep the case local when surrounding alerts stay confined to one recognized workflow.
- Focus: related alerts for the same
-
Escalate when script intent, launch context, artifacts, network, or authentication evidence align on unauthorized input capture; close only when all evidence supports a recognized benign workflow; if mixed or incomplete, preserve and escalate.
False positive analysis
- Accessibility, kiosk, security-testing, or malware-analysis workflows can legitimately trigger this rule. Confirm by matching the same
process.executable, signer, andhost.idpattern across prior alerts or against workflow records. - Before creating an exception, validate that the same
user.id,host.id,file.path, and a stablepowershell.file.script_block_textsubstring recur across prior alerts. Avoid exceptions on hook-function strings alone,user.namealone, or the host alone.
Response and remediation
- If confirmed benign, reverse any temporary containment and document the script content, recovered launch chain, user-host scope, and any benign artifact or destination pattern that proved the confirmed workflow. Create an exception only if the same workflow recurs consistently across prior alerts from this rule.
- If suspicious but unconfirmed, preserve the reconstructed script content, recovered
process.entity_id, relatedfile.pathartifacts, anydns.question.nameordestination.ipvalues linked to transfer, and authentication events around the alert. Apply reversible containment such as session restrictions or temporary destination blocking. Escalate to host isolation only when active collection, credential reuse, or transfer evidence is strong and the host role can tolerate it. Avoid destructive cleanup until scope is clearer. - If confirmed malicious, document the recovered
process.entity_id,process.command_line,process.parent.executable, writtenfile.pathartifacts, any confirmeddns.question.nameordestination.ipvalues, and logon session details before initiating response actions. Prefer host isolation over process termination for initial containment when the asset can tolerate it, then contain affected accounts, block malicious destinations and scripts, and terminate recovered processes only after evidence capture. - If keystroke logs, archives, or staging artifacts are identified, preserve them as sensitive evidence. Review related users and hosts for the same
powershell.file.script_block_textcontent,file.pathpattern, ordns.question.namedestinations before eradicating. Then remove the artifacts and any persistence or automation identified during reconstruction or host-scoping. - If follow-on authentication review suggests captured credentials were used, prioritize credential resets for the affected user and any additional accounts identified during the post-alert authentication timeline, then hunt for related sessions or privilege changes on the same host and other assets.
- After containment, restrict the execution path that allowed the script to run, such as tightening PowerShell execution policies or script-path allowlists. Retain PowerShell script block logging and related endpoint telemetry.
References
Related rules
- PowerShell Suspicious Script with Audio Capture Capabilities
- PowerShell Suspicious Script with Screenshot Capabilities
- Exchange Mailbox Export via PowerShell
- PowerShell Suspicious Discovery Related Windows API Functions
- PowerShell Share Enumeration Script