PowerShell PSReflect Script
Detects PowerShell script block content containing PSReflect-style helper indicators, such as Add-Win32Type, New-InMemoryModule, or DllImport patterns, that may support dynamic Win32 API invocation from PowerShell.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2021/10/15"
3integration = ["windows"]
4maturity = "production"
5updated_date = "2026/05/03"
6
7[rule]
8author = ["Elastic"]
9description = """
10Detects PowerShell script block content containing PSReflect-style helper indicators, such as Add-Win32Type,
11New-InMemoryModule, or DllImport patterns, that may support dynamic Win32 API invocation from PowerShell.
12"""
13false_positives = [
14 "Legitimate PSReflect use when reconstructed content, imported API set, script origin, launcher, user/host scope, and same-host effects align with an approved workflow"
15]
16from = "now-9m"
17index = ["logs-windows.powershell*", "winlogbeat-*"]
18language = "kuery"
19license = "Elastic License v2"
20name = "PowerShell PSReflect Script"
21references = [
22 "https://github.com/mattifestation/PSReflect/blob/master/PSReflect.psm1",
23 "https://github.com/atc-project/atc-data/blob/master/docs/Logging_Policies/LP_0109_windows_powershell_script_block_log.md",
24]
25risk_score = 73
26rule_id = "56f2e9b5-4803-4e44-a0a4-a52dc79d57fe"
27severity = "high"
28tags = [
29 "Domain: Endpoint",
30 "OS: Windows",
31 "Use Case: Threat Detection",
32 "Tactic: Execution",
33 "Resources: Investigation Guide",
34 "Data Source: PowerShell Logs",
35]
36timestamp_override = "event.ingested"
37type = "query"
38
39query = '''
40event.category:process and host.os.type:windows and
41 powershell.file.script_block_text:(
42 "New-InMemoryModule" or
43 "Add-Win32Type" or
44 psenum or
45 DefineDynamicAssembly or
46 DefineDynamicModule or
47 "Reflection.TypeAttributes" or
48 "Reflection.Emit.OpCodes" or
49 "Reflection.Emit.CustomAttributeBuilder" or
50 "Runtime.InteropServices.DllImportAttribute"
51 ) and
52 not user.id : "S-1-5-18"
53'''
54
55note = """## Triage and analysis
56
57### Investigating PowerShell PSReflect Script
58#### Possible investigation steps
59
60- Does the reconstructed script block show active PSReflect native-API wiring?
61 - Why: script block logging can split one script; PSReflect is meaningful only after imports and follow-on logic are visible.
62 - Focus: reconstruct with `powershell.file.script_block_id`, `powershell.sequence`, and `powershell.total`, then read `powershell.file.script_block_text` in `host.id` / `user.id` scope. $investigate_0
63 - Implication: escalate when the full script defines New-InMemoryModule, Add-Win32Type, psenum, Reflection.Emit, DefineDynamicAssembly, DefineDynamicModule, or DllImportAttribute and calls wrapped APIs; lower suspicion when it is inert reference text, repository content, or a bounded lab example.
64
65- What native API objective does the script express?
66 - Focus: Imported DLL/function pairs and call sites in reconstructed `powershell.file.script_block_text`.
67 - Hint: renamed wrappers or helper-free variants can show the same behavior when DllImportAttribute, Reflection.Emit, VirtualAlloc, WriteProcessMemory, CreateRemoteThread, OpenProcessToken, or AdjustTokenPrivileges reveal native-API invocation.
68 - Implication: escalate when call sites map to memory manipulation, token or privilege changes, service tampering, registry changes, or network control; lower urgency when imports stay limited to bounded diagnostics or application interop and later evidence does not contradict that use.
69
70- If endpoint process telemetry exists, how was PowerShell launched?
71 - Why: script block events preserve deobfuscated content, not the command line or parent.
72 - Focus: Recover the matching process via `host.id + process.pid` before interpreting `process.*` or `process.parent.*`; read `process.entity_id`, `process.command_line`, `process.parent.executable`, and `process.parent.command_line`. $investigate_1
73 - Hint: expand the window if no process start appears; without process telemetry, keep later review scoped to `host.id`, `user.id`, `process.pid`, and alert time.
74 - Implication: escalate when a document, browser, chat client, remote session, scheduled task, or user-writable script path launched the helper; lower suspicion when launcher and command fit the same recognized build, admin, interop, or assessment workflow as the script.
75
76- Does script origin fit the recovered workflow?
77 - Focus: `file.path`, `file.directory`, `file.name`, missing file-origin fields, and recovered launch context.
78 - Implication: escalate when fileless on a workstation or sourced from temp, download, mounted-share, or user-writable paths that do not fit the user; lower suspicion when it resolves to a stable repository, deployment path, or test harness matching the same workflow.
79
80- Do same-host effects prove the wrapped APIs were used?
81 - Why: PSReflect is a helper pattern; the decisive follow-up is whether host activity matches the imported API family.
82 - Focus: API-matched effects: child execution for process APIs, staged payloads for write/load APIs, persistence or configuration changes for service/registry APIs, and outbound activity for network APIs. Use same-PID events around `@timestamp` to reduce PID-reuse ambiguity.
83 - $investigate_4
84 - $investigate_5
85 - $investigate_6
86 - Implication: escalate when effects appear in recovered process scope or the `host.id`, `user.id`, `process.pid`, and time fallback scope; lower suspicion only when they align with the same bounded workflow. Missing network or endpoint effect telemetry is unresolved, not benign.
87
88- Does related alert scope change urgency?
89 - Focus: 48-hour alerts for the same `user.id` showing repeated PowerShell, execution, defense-evasion, privilege-escalation, or credential-access activity; use same-`host.id` alerts only to confirm repeated script substrings or the same imported API set.
90 - $investigate_2
91 - $investigate_3
92 - Implication: broaden the case when related activity shows repeated native-API abuse, follow-on execution, or spread outside the original host; keep scope local when repetition stays confined to one exact benign workflow already supported by local evidence.
93
94- What disposition is supported?
95 - Implication: escalate when content, origin, launch, effects, or scope align on injection, privilege manipulation, persistence, remote activity, or egress that does not fit the user; close only when same-host telemetry proves one exact benign workflow and no contradictory findings remain. Outside confirmation may corroborate but not replace telemetry; if mixed or incomplete, preserve artifacts and escalate.
96
97### False positive analysis
98
99- Internal build, admin, interop, diagnostics, compatibility, or security-testing helpers can use PSReflect-style code. Confirm only when reconstructed `powershell.file.script_block_text`, imported API set, `file.path` or fileless delivery, launcher, `user.id`, `host.id`, and same-host effects all fit one bounded workflow without injection, persistence, egress, or privilege abuse. Change records, repository history, test schedules, or recurrence can support closure but not replace telemetry.
100- Before an exception, validate recurrence of the same `user.id`, `host.id`, stable `file.path` when file-backed, recovered launcher, and distinctive `powershell.file.script_block_text` substrings. Avoid exceptions on PSReflect helper strings, `user.name`, or host alone.
101
102### Response and remediation
103
104- If confirmed benign, document reconstructed script text, imported DLL/function set, origin, `user.id`, `host.id`, launch context, and same-host effect review before reversing containment. Create exceptions only for recurring workflows.
105- If suspicious but unconfirmed, preserve script block IDs/sequences, reconstructed text, API list, origin, process-start evidence, and same-host effect artifacts before response. Apply reversible containment such as destination restrictions or session limits; isolate only when corroboration shows likely injection, persistence, lateral movement, or credential misuse and the host role permits. Avoid destructive cleanup until scope is clearer.
106- If confirmed malicious, preserve the same artifacts plus related child-process, file-system, persistence, credential, and destination evidence before containment. Isolate the host and terminate PowerShell or child payloads after evidence capture; if direct response is unavailable, escalate with preserved artifacts. Block malicious scripts, payload hashes, and destinations, then review related users/hosts for the same script substrings, API set, or launch chain before eradication.
107- After containment, remove only scripts or payloads identified during investigation, undo persistence or configuration changes tied to the API objective, and remediate the delivery path or automation that launched the helper. If imports or same-host effects suggest credential theft, token abuse, or remote execution, reset affected credentials and review related auth/admin sessions before cleanup.
108- Post-incident hardening: restrict PowerShell interop and unsigned script distribution to controlled build, admin, or test systems; retain script block logging and endpoint process telemetry for `host.id + process.pid` recovery; document renamed-wrapper or helper-free reflection variants.
109"""
110
111setup = """## Setup
112
113PowerShell Script Block Logging must be enabled to generate the events used by this rule (e.g., 4104).
114Setup instructions: https://ela.st/powershell-logging-setup
115"""
116
117[[rule.filters]]
118
119[rule.filters.meta]
120negate = true
121[rule.filters.query.wildcard."file.path"]
122case_insensitive = true
123value = "?:\\\\ProgramData\\\\MaaS360\\\\Cloud Extender\\\\AR\\\\Scripts\\\\ASModuleCommon.ps1"
124
125[rule.investigation_fields]
126field_names = [
127 "@timestamp",
128 "user.name",
129 "user.id",
130 "user.domain",
131 "powershell.file.script_block_text",
132 "powershell.file.script_block_id",
133 "powershell.sequence",
134 "powershell.total",
135 "file.path",
136 "file.directory",
137 "file.name",
138 "process.pid",
139 "host.name",
140 "host.id",
141 "powershell.file.script_block_length"
142]
143
144[transform]
145
146[[transform.investigate]]
147label = "Script block fragments for the same script"
148description = ""
149providers = [
150 [
151 { excluded = false, field = "powershell.file.script_block_id", queryType = "phrase", value = "{{powershell.file.script_block_id}}", valueType = "string" },
152 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
153 ]
154]
155relativeFrom = "now-1h"
156relativeTo = "now"
157
158[[transform.investigate]]
159label = "Process events for the PowerShell instance"
160description = ""
161providers = [
162 [
163 { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" },
164 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
165 { excluded = false, field = "event.category", queryType = "phrase", value = "process", valueType = "string" }
166 ]
167]
168relativeFrom = "now-1h"
169relativeTo = "now"
170
171[[transform.investigate]]
172label = "Alerts associated with the user"
173description = ""
174providers = [
175 [
176 { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
177 { excluded = false, field = "user.id", queryType = "phrase", value = "{{user.id}}", valueType = "string" }
178 ]
179]
180relativeFrom = "now-48h/h"
181relativeTo = "now"
182
183[[transform.investigate]]
184label = "Alerts associated with the host"
185description = ""
186providers = [
187 [
188 { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
189 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
190 ]
191]
192relativeFrom = "now-48h/h"
193relativeTo = "now"
194
195[[transform.investigate]]
196label = "Child process events for the PowerShell PID"
197description = ""
198providers = [
199 [
200 { excluded = false, field = "event.category", queryType = "phrase", value = "process", valueType = "string" },
201 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
202 { excluded = false, field = "process.parent.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" }
203 ]
204]
205relativeFrom = "now-1h"
206relativeTo = "now"
207
208[[transform.investigate]]
209label = "File and registry events for the PowerShell PID"
210description = ""
211providers = [
212 [
213 { excluded = false, field = "event.category", queryType = "phrase", value = "file", valueType = "string" },
214 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
215 { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" }
216 ],
217 [
218 { excluded = false, field = "event.category", queryType = "phrase", value = "registry", valueType = "string" },
219 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
220 { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" }
221 ]
222]
223relativeFrom = "now-1h"
224relativeTo = "now"
225
226[[transform.investigate]]
227label = "Network events for the PowerShell PID"
228description = ""
229providers = [
230 [
231 { excluded = false, field = "event.category", queryType = "phrase", value = "network", valueType = "string" },
232 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
233 { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" }
234 ]
235]
236relativeFrom = "now-1h"
237relativeTo = "now"
238
239[[rule.threat]]
240framework = "MITRE ATT&CK"
241
242[[rule.threat.technique]]
243id = "T1059"
244name = "Command and Scripting Interpreter"
245reference = "https://attack.mitre.org/techniques/T1059/"
246
247[[rule.threat.technique.subtechnique]]
248id = "T1059.001"
249name = "PowerShell"
250reference = "https://attack.mitre.org/techniques/T1059/001/"
251
252[[rule.threat.technique]]
253id = "T1106"
254name = "Native API"
255reference = "https://attack.mitre.org/techniques/T1106/"
256
257[rule.threat.tactic]
258id = "TA0002"
259name = "Execution"
260reference = "https://attack.mitre.org/tactics/TA0002/"
Triage and analysis
Investigating PowerShell PSReflect Script
Possible investigation steps
-
Does the reconstructed script block show active PSReflect native-API wiring?
- Why: script block logging can split one script; PSReflect is meaningful only after imports and follow-on logic are visible.
- Focus: reconstruct with
powershell.file.script_block_id,powershell.sequence, andpowershell.total, then readpowershell.file.script_block_textinhost.id/user.idscope. $investigate_0 - Implication: escalate when the full script defines New-InMemoryModule, Add-Win32Type, psenum, Reflection.Emit, DefineDynamicAssembly, DefineDynamicModule, or DllImportAttribute and calls wrapped APIs; lower suspicion when it is inert reference text, repository content, or a bounded lab example.
-
What native API objective does the script express?
- Focus: Imported DLL/function pairs and call sites in reconstructed
powershell.file.script_block_text. - Hint: renamed wrappers or helper-free variants can show the same behavior when DllImportAttribute, Reflection.Emit, VirtualAlloc, WriteProcessMemory, CreateRemoteThread, OpenProcessToken, or AdjustTokenPrivileges reveal native-API invocation.
- Implication: escalate when call sites map to memory manipulation, token or privilege changes, service tampering, registry changes, or network control; lower urgency when imports stay limited to bounded diagnostics or application interop and later evidence does not contradict that use.
- Focus: Imported DLL/function pairs and call sites in reconstructed
-
If endpoint process telemetry exists, how was PowerShell launched?
- Why: script block events preserve deobfuscated content, not the command line or parent.
- Focus: Recover the matching process via
host.id + process.pidbefore interpretingprocess.*orprocess.parent.*; readprocess.entity_id,process.command_line,process.parent.executable, andprocess.parent.command_line. $investigate_1 - Hint: expand the window if no process start appears; without process telemetry, keep later review scoped to
host.id,user.id,process.pid, and alert time. - Implication: escalate when a document, browser, chat client, remote session, scheduled task, or user-writable script path launched the helper; lower suspicion when launcher and command fit the same recognized build, admin, interop, or assessment workflow as the script.
-
Does script origin fit the recovered workflow?
- Focus:
file.path,file.directory,file.name, missing file-origin fields, and recovered launch context. - Implication: escalate when fileless on a workstation or sourced from temp, download, mounted-share, or user-writable paths that do not fit the user; lower suspicion when it resolves to a stable repository, deployment path, or test harness matching the same workflow.
- Focus:
-
Do same-host effects prove the wrapped APIs were used?
- Why: PSReflect is a helper pattern; the decisive follow-up is whether host activity matches the imported API family.
- Focus: API-matched effects: child execution for process APIs, staged payloads for write/load APIs, persistence or configuration changes for service/registry APIs, and outbound activity for network APIs. Use same-PID events around
@timestampto reduce PID-reuse ambiguity.- $investigate_4
- $investigate_5
- $investigate_6
- Implication: escalate when effects appear in recovered process scope or the
host.id,user.id,process.pid, and time fallback scope; lower suspicion only when they align with the same bounded workflow. Missing network or endpoint effect telemetry is unresolved, not benign.
-
Does related alert scope change urgency?
- Focus: 48-hour alerts for the same
user.idshowing repeated PowerShell, execution, defense-evasion, privilege-escalation, or credential-access activity; use same-host.idalerts only to confirm repeated script substrings or the same imported API set.- $investigate_2
- $investigate_3
- Implication: broaden the case when related activity shows repeated native-API abuse, follow-on execution, or spread outside the original host; keep scope local when repetition stays confined to one exact benign workflow already supported by local evidence.
- Focus: 48-hour alerts for the same
-
What disposition is supported?
- Implication: escalate when content, origin, launch, effects, or scope align on injection, privilege manipulation, persistence, remote activity, or egress that does not fit the user; close only when same-host telemetry proves one exact benign workflow and no contradictory findings remain. Outside confirmation may corroborate but not replace telemetry; if mixed or incomplete, preserve artifacts and escalate.
False positive analysis
- Internal build, admin, interop, diagnostics, compatibility, or security-testing helpers can use PSReflect-style code. Confirm only when reconstructed
powershell.file.script_block_text, imported API set,file.pathor fileless delivery, launcher,user.id,host.id, and same-host effects all fit one bounded workflow without injection, persistence, egress, or privilege abuse. Change records, repository history, test schedules, or recurrence can support closure but not replace telemetry. - Before an exception, validate recurrence of the same
user.id,host.id, stablefile.pathwhen file-backed, recovered launcher, and distinctivepowershell.file.script_block_textsubstrings. Avoid exceptions on PSReflect helper strings,user.name, or host alone.
Response and remediation
- If confirmed benign, document reconstructed script text, imported DLL/function set, origin,
user.id,host.id, launch context, and same-host effect review before reversing containment. Create exceptions only for recurring workflows. - If suspicious but unconfirmed, preserve script block IDs/sequences, reconstructed text, API list, origin, process-start evidence, and same-host effect artifacts before response. Apply reversible containment such as destination restrictions or session limits; isolate only when corroboration shows likely injection, persistence, lateral movement, or credential misuse and the host role permits. Avoid destructive cleanup until scope is clearer.
- If confirmed malicious, preserve the same artifacts plus related child-process, file-system, persistence, credential, and destination evidence before containment. Isolate the host and terminate PowerShell or child payloads after evidence capture; if direct response is unavailable, escalate with preserved artifacts. Block malicious scripts, payload hashes, and destinations, then review related users/hosts for the same script substrings, API set, or launch chain before eradication.
- After containment, remove only scripts or payloads identified during investigation, undo persistence or configuration changes tied to the API objective, and remediate the delivery path or automation that launched the helper. If imports or same-host effects suggest credential theft, token abuse, or remote execution, reset affected credentials and review related auth/admin sessions before cleanup.
- Post-incident hardening: restrict PowerShell interop and unsigned script distribution to controlled build, admin, or test systems; retain script block logging and endpoint process telemetry for
host.id + process.pidrecovery; document renamed-wrapper or helper-free reflection variants.
References
Related rules
- Potential Process Injection via PowerShell
- Potential PowerShell HackTool Script by Function Names
- PowerShell Suspicious Discovery Related Windows API Functions
- Suspicious .NET Reflection via PowerShell
- Suspicious Portable Executable Encoded in Powershell Script