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/04/26"
  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]
 33timestamp_override = "event.ingested"
 34type = "query"
 35
 36query = '''
 37event.category:process and host.os.type:windows and
 38  powershell.file.script_block_text : (
 39    "StealthReadFile" or
 40    "StealthReadFileAddr" or
 41    "StealthCloseFileDelegate" or
 42    "StealthOpenFile" or
 43    "StealthCloseFile" or
 44    "Invoke-NinjaCopy"
 45   ) and
 46  not powershell.file.script_block_text : (
 47    "sentinelbreakpoints" and "Set-PSBreakpoint" and "PowerSploitIndicators"
 48  )
 49'''
 50
 51note = """## Triage and analysis
 52
 53### Investigating PowerShell Invoke-NinjaCopy script
 54
 55#### Possible investigation steps
 56
 57- Does the alert-preserved script content show active NinjaCopy direct-volume access rather than inert reference text?
 58  - Focus: `powershell.file.script_block_text`, `powershell.file.script_block_length`, and file-backed `file.path` or `file.name`.
 59  - 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.
 60
 61- Does full script reconstruction reveal target, destination, or cleanup behavior that the matching fragment did not show?
 62  - Why: Script Block Logging can split one script across events; later fragments often contain output paths, loops, or cleanup logic that change urgency.
 63  - 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
 64  - Hint: if `powershell.sequence` is missing or never reaches `powershell.total`, record the gap before treating the script as understood.
 65  - 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.
 66
 67- Does the script provenance and reconstructed target fit a recognized workflow?
 68  - Focus: `file.path`, `file.name`, `user.id`, `host.id`, and reconstructed "-Path", "-ComputerName", "-LocalDestination", or "-RemoteDestination".
 69  - Hint: if `file.path` is absent, treat content as inline, generated, interactive, or remote; do not close on path absence alone.
 70  - 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.
 71
 72- Do surrounding script blocks from the same user and host show staging, retries, or variant logic?
 73  - 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.
 74  - Hint: renamed wrappers may omit `Invoke-NinjaCopy`; still check `Stealth*` helpers and reconstructed target or destination values.
 75  - Implication: escalate when adjacent fragments show retries, renamed helpers, alternative raw-volume logic, output-file reuse, or follow-on compression and cleanup.
 76
 77- Can process telemetry recover the PowerShell process and explain how it was launched?
 78  - 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
 79  - 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.
 80  - 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.
 81
 82- Do file events confirm copied credential stores or staging artifacts?
 83  - Focus: endpoint file events scoped to `host.id`, `process.pid`, and alert time; review `file.path`, `file.directory`, and `file.name`. $investigate_4
 84  - Implication: escalate when file activity confirms copied hives, `NTDS.dit`, archives, or renamed staging files; missing file telemetry is unresolved, not benign.
 85
 86- 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?
 87  - Focus: related `user.id` alerts for repeated credential access, execution, or post-compromise behavior. $investigate_0
 88  - Hint: compare `host.id` alerts for credential access, persistence, or staging. $investigate_1
 89  - 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.
 90
 91- 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.
 92
 93### False positive analysis
 94
 95- 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.
 96- 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.
 97
 98### Response and remediation
 99
100- 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.
101- 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.
102- 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.
103- 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.
104- 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.
105- 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.
106"""
107
108setup = """## Setup
109
110PowerShell Script Block Logging must be enabled to generate the events used by this rule (e.g., 4104).
111Setup instructions: https://ela.st/powershell-logging-setup
112"""
113
114[rule.investigation_fields]
115field_names = [
116    "@timestamp",
117    "user.name",
118    "user.id",
119    "user.domain",
120    "powershell.file.script_block_text",
121    "powershell.file.script_block_id",
122    "powershell.sequence",
123    "powershell.total",
124    "file.path",
125    "file.directory",
126    "file.name",
127    "process.pid",
128    "host.name",
129    "host.id",
130    "powershell.file.script_block_length"
131]
132
133[transform]
134
135[[transform.investigate]]
136label = "Alerts associated with the user"
137description = ""
138providers = [
139  [
140    { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
141    { excluded = false, field = "user.id", queryType = "phrase", value = "{{user.id}}", valueType = "string" }
142  ]
143]
144relativeFrom = "now-48h/h"
145relativeTo = "now"
146
147[[transform.investigate]]
148label = "Alerts associated with the host"
149description = ""
150providers = [
151  [
152    { excluded = false, field = "event.kind", queryType = "phrase", value = "signal", valueType = "string" },
153    { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
154  ]
155]
156relativeFrom = "now-48h/h"
157relativeTo = "now"
158
159[[transform.investigate]]
160label = "Script block fragments for the same script"
161description = ""
162providers = [
163  [
164    { excluded = false, field = "powershell.file.script_block_id", queryType = "phrase", value = "{{powershell.file.script_block_id}}", valueType = "string" },
165    { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" }
166  ]
167]
168relativeFrom = "now-1h"
169relativeTo = "now"
170
171[[transform.investigate]]
172label = "Process events for the PowerShell instance"
173description = ""
174providers = [
175  [
176    { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" },
177    { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
178    { excluded = false, field = "event.category", queryType = "phrase", value = "process", valueType = "string" }
179  ]
180]
181relativeFrom = "now-1h"
182relativeTo = "now"
183
184[[transform.investigate]]
185label = "File events for the PowerShell process"
186description = ""
187providers = [
188  [
189    { excluded = false, field = "process.pid", queryType = "phrase", value = "{{process.pid}}", valueType = "string" },
190    { excluded = false, field = "host.id", queryType = "phrase", value = "{{host.id}}", valueType = "string" },
191    { excluded = false, field = "event.category", queryType = "phrase", value = "file", valueType = "string" }
192  ]
193]
194relativeFrom = "now-1h"
195relativeTo = "now"
196
197[[rule.threat]]
198framework = "MITRE ATT&CK"
199
200[[rule.threat.technique]]
201id = "T1003"
202name = "OS Credential Dumping"
203reference = "https://attack.mitre.org/techniques/T1003/"
204
205[[rule.threat.technique.subtechnique]]
206id = "T1003.002"
207name = "Security Account Manager"
208reference = "https://attack.mitre.org/techniques/T1003/002/"
209
210[[rule.threat.technique.subtechnique]]
211id = "T1003.003"
212name = "NTDS"
213reference = "https://attack.mitre.org/techniques/T1003/003/"
214
215[[rule.threat.technique.subtechnique]]
216id = "T1003.004"
217name = "LSA Secrets"
218reference = "https://attack.mitre.org/techniques/T1003/004/"
219
220[[rule.threat.technique.subtechnique]]
221id = "T1003.005"
222name = "Cached Domain Credentials"
223reference = "https://attack.mitre.org/techniques/T1003/005/"
224
225[rule.threat.tactic]
226id = "TA0006"
227name = "Credential Access"
228reference = "https://attack.mitre.org/tactics/TA0006/"
229
230[[rule.threat]]
231framework = "MITRE ATT&CK"
232
233[[rule.threat.technique]]
234id = "T1059"
235name = "Command and Scripting Interpreter"
236reference = "https://attack.mitre.org/techniques/T1059/"
237
238[[rule.threat.technique.subtechnique]]
239id = "T1059.001"
240name = "PowerShell"
241reference = "https://attack.mitre.org/techniques/T1059/001/"
242
243[rule.threat.tactic]
244id = "TA0002"
245name = "Execution"
246reference = "https://attack.mitre.org/tactics/TA0002/"
247
248[[rule.threat]]
249framework = "MITRE ATT&CK"
250
251[[rule.threat.technique]]
252id = "T1006"
253name = "Direct Volume Access"
254reference = "https://attack.mitre.org/techniques/T1006/"
255
256[rule.threat.tactic]
257id = "TA0005"
258name = "Defense Evasion"
259reference = "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-backed file.path or file.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.
  • 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 by powershell.sequence / powershell.total; read powershell.file.script_block_text for "-Path", "-ComputerName", "-LocalDestination", or "-RemoteDestination". $investigate_2
    • Hint: if powershell.sequence is missing or never reaches powershell.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.path is 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.
  • 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 adjacent powershell.file.script_block_id values.
    • Hint: renamed wrappers may omit Invoke-NinjaCopy; still check Stealth* 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.
  • 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; record process.entity_id, then interpret process.command_line, process.parent.command_line, and process.Ext.token.integrity_level_name. $investigate_3
    • 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.
    • 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.
  • Do file events confirm copied credential stores or staging artifacts?

    • Focus: endpoint file events scoped to host.id, process.pid, and alert time; review file.path, file.directory, and file.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.
  • 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.id alerts for repeated credential access, execution, or post-compromise behavior. $investigate_0
    • Hint: compare host.id alerts 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.
  • 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, 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.

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, 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.
  • 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.path destinations, 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

to-top