PowerShell Invoke-NinjaCopy script
Detects PowerShell script block content containing Invoke-NinjaCopy or related Stealth* functions used for direct volume file access. Attackers use NinjaCopy to read locked system files such as NTDS.dit or registry hives for credential dumping.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2023/01/23"
3integration = ["windows"]
4maturity = "production"
5updated_date = "2026/09/18"
6
7[rule]
8author = ["Elastic"]
9description = """
10Detects PowerShell script block content containing Invoke-NinjaCopy or related Stealth* functions used for direct volume
11file access. Attackers use NinjaCopy to read locked system files such as NTDS.dit or registry hives for credential
12dumping.
13"""
14from = "now-9m"
15index = ["logs-windows.powershell*", "winlogbeat-*"]
16language = "kuery"
17license = "Elastic License v2"
18name = "PowerShell Invoke-NinjaCopy script"
19references = [
20 "https://github.com/BC-SECURITY/Empire/blob/main/empire/server/data/module_source/collection/Invoke-NinjaCopy.ps1",
21]
22risk_score = 73
23rule_id = "b8386923-b02c-4b94-986a-d223d9b01f88"
24severity = "high"
25tags = [
26 "Domain: Endpoint",
27 "OS: Windows",
28 "Use Case: Threat Detection",
29 "Tactic: Credential Access",
30 "Data Source: PowerShell Logs",
31 "Resources: Investigation Guide",
32 "Noise: Low",
33 "Performance: Normal",
34 "Rule Type: Custom Query (KQL)",
35 "Platform: Windows",
36]
37timestamp_override = "event.ingested"
38type = "query"
39
40query = '''
41event.category:process and host.os.type:windows and
42 powershell.file.script_block_text : (
43 "StealthReadFile" or
44 "StealthReadFileAddr" or
45 "StealthCloseFileDelegate" or
46 "StealthOpenFile" or
47 "StealthCloseFile" or
48 "Invoke-NinjaCopy"
49 ) and
50 not powershell.file.script_block_text : (
51 "sentinelbreakpoints" and "Set-PSBreakpoint" and "PowerSploitIndicators"
52 )
53'''
54
55note = """## Triage and analysis
56
57### Investigating PowerShell Invoke-NinjaCopy script
58
59#### Possible investigation steps
60
61- Does the alert-preserved script content show active NinjaCopy direct-volume access rather than inert reference text?
62 - Focus: `powershell.file.script_block_text`, `powershell.file.script_block_length`, and file-backed `file.path` or `file.name`.
63 - Implication: escalate sooner when the fragment shows `Invoke-NinjaCopy`, `StealthOpenFile`, `StealthReadFile`, credential-store targets, output destinations, or cleanup logic; lower concern only for inert example or recognized validation content that later recovery does not contradict.
64
65- Does full script reconstruction reveal target, destination, or cleanup behavior that the matching fragment did not show?
66 - Why: Script Block Logging can split one script across events; later fragments often contain output paths, loops, or cleanup logic that change urgency.
67 - Focus: reconstruct on `host.id` + `powershell.file.script_block_id`, ordering by `powershell.sequence` / `powershell.total`; read `powershell.file.script_block_text` for "-Path", "-ComputerName", "-LocalDestination", or "-RemoteDestination". $investigate_2
68 - Hint: if `powershell.sequence` is missing or never reaches `powershell.total`, record the gap before treating the script as understood.
69 - Implication: escalate when reconstruction adds remote targets, credential stores (`NTDS.dit`, `SAM`, `SYSTEM`, `SECURITY`), temp/share destinations, large copy buffers, archive paths, encoding, or delete-after-copy logic; lower concern when the full script is confirmed lab, forensic, or validation content with no hostile follow-on behavior.
70
71- Does the script provenance and reconstructed target fit a recognized workflow?
72 - Focus: `file.path`, `file.name`, `user.id`, `host.id`, and reconstructed "-Path", "-ComputerName", "-LocalDestination", or "-RemoteDestination".
73 - Hint: if `file.path` is absent, treat content as inline, generated, interactive, or remote; do not close on path absence alone.
74 - Implication: escalate when source is temp, downloads, or user-writable shares, or parameters name remote servers, `NTDS.dit`, registry hives, or staging destinations; lower concern when source, targets, and destinations match one recognized forensic, IR, lab, or validation workflow.
75
76- Do surrounding script blocks from the same user and host show staging, retries, or variant logic?
77 - Focus: manually search the same execution window for `user.id`, `host.id`, `powershell.file.script_block_text`, and adjacent `powershell.file.script_block_id` values.
78 - Hint: renamed wrappers may omit `Invoke-NinjaCopy`; still check `Stealth*` helpers and reconstructed target or destination values.
79 - Implication: escalate when adjacent fragments show retries, renamed helpers, alternative raw-volume logic, output-file reuse, or follow-on compression and cleanup.
80
81- Can process telemetry recover the PowerShell process and explain how it was launched?
82 - Focus: match the PowerShell PID on `host.id`, `process.pid`, and `@timestamp`; record `process.entity_id`, then interpret `process.command_line`, `process.parent.command_line`, and `process.Ext.token.integrity_level_name`. $investigate_3
83 - Hint: recover the matching process via `host.id + process.pid` before using `process.*` or `process.parent.*`. If no process start event appears, expand the window because PowerShell can predate the script block; if still absent, bound file review to `host.id`, `user.id`, `process.pid`, and alert time.
84 - Implication: escalate when recovery shows encoded commands, remote-admin launchers, Office/browser ancestry, or unexpected high-integrity execution; unresolved process recovery does not make direct-volume script content benign.
85
86- Do file events confirm copied credential stores or staging artifacts?
87 - Focus: endpoint file events scoped to `host.id`, `process.pid`, and alert time; review `file.path`, `file.directory`, and `file.name`. $investigate_4
88 - Implication: escalate when file activity confirms copied hives, `NTDS.dit`, archives, or renamed staging files; missing file telemetry is unresolved, not benign.
89
90- If the local script, launch, or artifact answers remain suspicious or unresolved, is this script block isolated, or part of broader suspicious activity for the same user or host?
91 - Focus: related `user.id` alerts for repeated credential access, execution, or post-compromise behavior. $investigate_0
92 - Hint: compare `host.id` alerts for credential access, persistence, or staging. $investigate_1
93 - Implication: broaden scope when either view shows adjacent credential-access or post-compromise behavior; keep it local only when both are quiet and the local evidence fits one recognized workflow.
94
95- Escalate when script content, reconstruction, target parameters, launch context, or artifacts show unauthorized direct-volume access or credential-store targeting; close only when source path, targets, destinations, launch context, and artifacts align with one recognized forensic, IR, lab, or validation workflow; preserve and escalate if mixed or incomplete.
96
97### False positive analysis
98
99- Recognized red-team, security-validation, forensic-acquisition, or IR workflows can legitimately include NinjaCopy-derived code. Confirm only when `powershell.file.script_block_text`, reconstructed targets/destinations, `file.path`, `user.id`, `host.id`, and recovered launch context align with the same workflow. If records are unavailable, prior recurrence can support but not replace local telemetry proof; first occurrences stay candidate exceptions.
100- Build exceptions from `user.id`, `host.id`, stable `file.path`, recovered parent-launch pattern when available, and the `powershell.file.script_block_text` pattern. Avoid exceptions on "Invoke-NinjaCopy", `user.name`, or host alone.
101
102### Response and remediation
103
104- If confirmed benign, reverse containment and document the recognized workflow: `user.id`, `host.id`, `file.path`, `powershell.file.script_block_id`, recovered launch context, and reconstructed target or destination values. Create an exception only if the same pattern recurs across prior alerts.
105- If suspicious but unconfirmed, preserve reconstructed `powershell.file.script_block_text`, `powershell.file.script_block_id`, `powershell.sequence`, `process.pid`, recovered launch context, target or destination values, and `file.path` before cleanup. Apply reversible containment first; escalate to host isolation only if copied credential artifacts, staging, or broader post-compromise activity appears.
106- If confirmed malicious, preserve the reconstructed script, launch context, targets, destinations, copied stores, and related alerts first. Then use reversible endpoint response to isolate the host when needed; if unavailable, escalate with the preserved artifact set to the team that can act. Record all evidence before terminating processes or deleting files.
107- If "NTDS.dit", "SAM", "SYSTEM", or "SECURITY" targeting is confirmed with copied artifacts, follow the organization's credential-exposure playbook and prioritize privileged-account hygiene.
108- Before eradicating, review related hosts for the same script pattern, `file.path` destinations, and recovered parent-launch pattern. Then remove unauthorized scripts, copied stores, archives, and persistence mechanisms and remediate the delivery path.
109- Post-incident hardening: restrict direct-volume-copy tooling to controlled acquisition hosts, retain PowerShell Script Block Logging and endpoint file/process telemetry, and document the recognized workflow pattern for future analysts.
110"""
111
112setup = """## Setup
113
114PowerShell Script Block Logging must be enabled to generate the events used by this rule (e.g., 4104).
115Setup instructions: https://ela.st/powershell-logging-setup
116"""
117
118[rule.investigation_fields]
119field_names = [
120 "@timestamp",
121 "user.name",
122 "user.id",
123 "user.domain",
124 "powershell.file.script_block_text",
125 "powershell.file.script_block_id",
126 "powershell.sequence",
127 "powershell.total",
128 "file.path",
129 "file.directory",
130 "file.name",
131 "process.pid",
132 "host.name",
133 "host.id",
134 "powershell.file.script_block_length"
135]
136
137[transform]
138
139[[transform.investigate]]
140label = "Alerts associated with the user"
141description = ""
142providers = [
143 [
144 { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
145 { excluded = false, field = "user.id", queryType = "phrase", value = "{{user.id}}", valueType = "string" }
146 ]
147]
148relativeFrom = "now-48h/h"
149relativeTo = "now"
150
151[[transform.investigate]]
152label = "Alerts associated with the host"
153description = ""
154providers = [
155 [
156 { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
157 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
158 ]
159]
160relativeFrom = "now-48h/h"
161relativeTo = "now"
162
163[[transform.investigate]]
164label = "Script block fragments for the same script"
165description = ""
166providers = [
167 [
168 { excluded = false, field = "powershell.file.script_block_id", queryType = "phrase", value = "{{powershell.file.script_block_id}}", valueType = "string" },
169 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
170 ]
171]
172relativeFrom = "now-1h"
173relativeTo = "now"
174
175[[transform.investigate]]
176label = "Process events for the PowerShell instance"
177description = ""
178providers = [
179 [
180 { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" },
181 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
182 { excluded = false, field = "event.category", queryType = "phrase", value = "process", valueType = "string" }
183 ]
184]
185relativeFrom = "now-1h"
186relativeTo = "now"
187
188[[transform.investigate]]
189label = "File events for the PowerShell process"
190description = ""
191providers = [
192 [
193 { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" },
194 { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
195 { excluded = false, field = "event.category", queryType = "phrase", value = "file", valueType = "string" }
196 ]
197]
198relativeFrom = "now-1h"
199relativeTo = "now"
200
201[[rule.threat]]
202framework = "MITRE ATT&CK"
203
204[[rule.threat.technique]]
205id = "T1003"
206name = "OS Credential Dumping"
207reference = "https://attack.mitre.org/techniques/T1003/"
208
209[[rule.threat.technique.subtechnique]]
210id = "T1003.002"
211name = "Security Account Manager"
212reference = "https://attack.mitre.org/techniques/T1003/002/"
213
214[[rule.threat.technique.subtechnique]]
215id = "T1003.003"
216name = "NTDS"
217reference = "https://attack.mitre.org/techniques/T1003/003/"
218
219[[rule.threat.technique.subtechnique]]
220id = "T1003.004"
221name = "LSA Secrets"
222reference = "https://attack.mitre.org/techniques/T1003/004/"
223
224[[rule.threat.technique.subtechnique]]
225id = "T1003.005"
226name = "Cached Domain Credentials"
227reference = "https://attack.mitre.org/techniques/T1003/005/"
228
229[rule.threat.tactic]
230id = "TA0006"
231name = "Credential Access"
232reference = "https://attack.mitre.org/tactics/TA0006/"
233
234[[rule.threat]]
235framework = "MITRE ATT&CK"
236
237[[rule.threat.technique]]
238id = "T1059"
239name = "Command and Scripting Interpreter"
240reference = "https://attack.mitre.org/techniques/T1059/"
241
242[[rule.threat.technique.subtechnique]]
243id = "T1059.001"
244name = "PowerShell"
245reference = "https://attack.mitre.org/techniques/T1059/001/"
246
247[rule.threat.tactic]
248id = "TA0002"
249name = "Execution"
250reference = "https://attack.mitre.org/tactics/TA0002/"
251
252[[rule.threat]]
253framework = "MITRE ATT&CK"
254
255[[rule.threat.technique]]
256id = "T1006"
257name = "Direct Volume Access"
258reference = "https://attack.mitre.org/techniques/T1006/"
259
260[rule.threat.tactic]
261id = "TA0005"
262name = "Defense Evasion"
263reference = "https://attack.mitre.org/tactics/TA0005/"
Triage and analysis
Investigating PowerShell Invoke-NinjaCopy script
Possible investigation steps
-
Does the alert-preserved script content show active NinjaCopy direct-volume access rather than inert reference text?
- Focus:
powershell.file.script_block_text,powershell.file.script_block_length, and file-backedfile.pathorfile.name. - Implication: escalate sooner when the fragment shows
Invoke-NinjaCopy,StealthOpenFile,StealthReadFile, credential-store targets, output destinations, or cleanup logic; lower concern only for inert example or recognized validation content that later recovery does not contradict.
- Focus:
-
Does full script reconstruction reveal target, destination, or cleanup behavior that the matching fragment did not show?
- Why: Script Block Logging can split one script across events; later fragments often contain output paths, loops, or cleanup logic that change urgency.
- Focus: reconstruct on
host.id+powershell.file.script_block_id, ordering bypowershell.sequence/powershell.total; readpowershell.file.script_block_textfor "-Path", "-ComputerName", "-LocalDestination", or "-RemoteDestination". $investigate_2 - Hint: if
powershell.sequenceis missing or never reachespowershell.total, record the gap before treating the script as understood. - Implication: escalate when reconstruction adds remote targets, credential stores (
NTDS.dit,SAM,SYSTEM,SECURITY), temp/share destinations, large copy buffers, archive paths, encoding, or delete-after-copy logic; lower concern when the full script is confirmed lab, forensic, or validation content with no hostile follow-on behavior.
-
Does the script provenance and reconstructed target fit a recognized workflow?
- Focus:
file.path,file.name,user.id,host.id, and reconstructed "-Path", "-ComputerName", "-LocalDestination", or "-RemoteDestination". - Hint: if
file.pathis absent, treat content as inline, generated, interactive, or remote; do not close on path absence alone. - Implication: escalate when source is temp, downloads, or user-writable shares, or parameters name remote servers,
NTDS.dit, registry hives, or staging destinations; lower concern when source, targets, and destinations match one recognized forensic, IR, lab, or validation workflow.
- Focus:
-
Do surrounding script blocks from the same user and host show staging, retries, or variant logic?
- Focus: manually search the same execution window for
user.id,host.id,powershell.file.script_block_text, and adjacentpowershell.file.script_block_idvalues. - Hint: renamed wrappers may omit
Invoke-NinjaCopy; still checkStealth*helpers and reconstructed target or destination values. - Implication: escalate when adjacent fragments show retries, renamed helpers, alternative raw-volume logic, output-file reuse, or follow-on compression and cleanup.
- Focus: manually search the same execution window for
-
Can process telemetry recover the PowerShell process and explain how it was launched?
- Focus: match the PowerShell PID on
host.id,process.pid, and@timestamp; recordprocess.entity_id, then interpretprocess.command_line,process.parent.command_line, andprocess.Ext.token.integrity_level_name. $investigate_3 - Hint: recover the matching process via
host.id + process.pidbefore usingprocess.*orprocess.parent.*. If no process start event appears, expand the window because PowerShell can predate the script block; if still absent, bound file review tohost.id,user.id,process.pid, and alert time. - Implication: escalate when recovery shows encoded commands, remote-admin launchers, Office/browser ancestry, or unexpected high-integrity execution; unresolved process recovery does not make direct-volume script content benign.
- Focus: match the PowerShell PID on
-
Do file events confirm copied credential stores or staging artifacts?
- Focus: endpoint file events scoped to
host.id,process.pid, and alert time; reviewfile.path,file.directory, andfile.name. $investigate_4 - Implication: escalate when file activity confirms copied hives,
NTDS.dit, archives, or renamed staging files; missing file telemetry is unresolved, not benign.
- Focus: endpoint file events scoped to
-
If the local script, launch, or artifact answers remain suspicious or unresolved, is this script block isolated, or part of broader suspicious activity for the same user or host?
- Focus: related
user.idalerts for repeated credential access, execution, or post-compromise behavior. $investigate_0 - Hint: compare
host.idalerts for credential access, persistence, or staging. $investigate_1 - Implication: broaden scope when either view shows adjacent credential-access or post-compromise behavior; keep it local only when both are quiet and the local evidence fits one recognized workflow.
- Focus: related
-
Escalate when script content, reconstruction, target parameters, launch context, or artifacts show unauthorized direct-volume access or credential-store targeting; close only when source path, targets, destinations, launch context, and artifacts align with one recognized forensic, IR, lab, or validation workflow; preserve and escalate if mixed or incomplete.
False positive analysis
- Recognized red-team, security-validation, forensic-acquisition, or IR workflows can legitimately include NinjaCopy-derived code. Confirm only when
powershell.file.script_block_text, reconstructed targets/destinations,file.path,user.id,host.id, and recovered launch context align with the same workflow. If records are unavailable, prior recurrence can support but not replace local telemetry proof; first occurrences stay candidate exceptions. - Build exceptions from
user.id,host.id, stablefile.path, recovered parent-launch pattern when available, and thepowershell.file.script_block_textpattern. Avoid exceptions on "Invoke-NinjaCopy",user.name, or host alone.
Response and remediation
- If confirmed benign, reverse containment and document the recognized workflow:
user.id,host.id,file.path,powershell.file.script_block_id, recovered launch context, and reconstructed target or destination values. Create an exception only if the same pattern recurs across prior alerts. - If suspicious but unconfirmed, preserve reconstructed
powershell.file.script_block_text,powershell.file.script_block_id,powershell.sequence,process.pid, recovered launch context, target or destination values, andfile.pathbefore cleanup. Apply reversible containment first; escalate to host isolation only if copied credential artifacts, staging, or broader post-compromise activity appears. - If confirmed malicious, preserve the reconstructed script, launch context, targets, destinations, copied stores, and related alerts first. Then use reversible endpoint response to isolate the host when needed; if unavailable, escalate with the preserved artifact set to the team that can act. Record all evidence before terminating processes or deleting files.
- If "NTDS.dit", "SAM", "SYSTEM", or "SECURITY" targeting is confirmed with copied artifacts, follow the organization's credential-exposure playbook and prioritize privileged-account hygiene.
- Before eradicating, review related hosts for the same script pattern,
file.pathdestinations, and recovered parent-launch pattern. Then remove unauthorized scripts, copied stores, archives, and persistence mechanisms and remediate the delivery path. - Post-incident hardening: restrict direct-volume-copy tooling to controlled acquisition hosts, retain PowerShell Script Block Logging and endpoint file/process telemetry, and document the recognized workflow pattern for future analysts.
References
Related rules
- Potential Invoke-Mimikatz PowerShell Script
- PowerShell Kerberos Ticket Dump
- PowerShell Kerberos Ticket Request
- PowerShell MiniDump Script
- PowerShell Script with Veeam Credential Access Capabilities