Armorsteal: SEO-Poisoned Chocolatey and Massgrave Installers
Executive Summary
Armorsteal is a Python-based information stealer, remote access tool and clipboard hijacker that Zscaler Threat Hunting tracked through an SEO poisoning campaign first identified in September 2026 (report published 2026-10-22). Typosquatted sites impersonating the Chocolatey package manager (choclatey[.]org) and the Microsoft Activation Scripts project (massgravel[.]net) ranked in search results for install commands and showed a PowerShell one-liner for the victim to paste into an elevated prompt. The attacker objective is credential, session and cryptocurrency theft: browser data, Discord tokens, wallets, seed phrases, SSH keys and Steam data, plus keylogging, screenshots and wallet-address swapping.
The malware installs a standalone Python 3.11 runtime and Pyarmor-protected modules under C:\Windows\SystemHealth\Update\, persists with scheduled tasks SysCheck2 (user logon, highest privileges) and SysCheckX (system boot, SYSTEM, 45-second delay), and only runs C2 tasks whose RSA-2048 signature verifies against an embedded key. The highest-value defensive angle is behavioral and does not depend on rotating infrastructure: the fake SystemHealth\Update directory, the two task names, and browsers started with --remote-debugging-port=9222.
Defender priority: hunt the fake SystemHealth directory and the SysCheck2 / SysCheckX tasks first (Q4 to Q7), then block the lure and C2 domains. The campaign's two lure sites were reported down by early October 2026, so infrastructure hits are most likely historical: a clean domain sweep does not clear a host.
Source Review & Web Hunter Notes
| Tier | Source | Key finding | Carry forward |
|---|---|---|---|
| 1 (primary) | Zscaler Threat Hunting: SEO Poisoning Campaign Delivers New Armorsteal Malware (2026-10-22) | Full chain, persistence, module and C2 detail, infrastructure timeline, detection guidance and all atomic indicators | yes |
| 3 (secondary) | SecurityOnline: Armorsteal malware via Python SEO poisoning | Restates the Zscaler report and its guidance. No independent analysis and no indicator values | partial (corroborates guidance only) |
Single-source indicators. Every atomic IOC comes from the Zscaler report alone, and the second source is derivative. Under the pack's two-source rule, no indicator ships at prevent or critical severity: every Falcon IOC row is detect, severity high or lower.
- All IOC values were re-derived from the saved snapshot
01-zscaler-armorsteal.txt, not from search summaries. - Zscaler attributes both lures to one operation through shared malware components, a shared RSA-2048 task-signing key and shared C2 infrastructure. It names no threat actor and no known group, so this pack names none.
- Search results also surfaced vendor detection-rule pages. They were not fetched or used, so nothing in this pack derives from them.
- The lure sites were reported down on 2026-09-25 (Massgrave) and about 2026-10-01 (Chocolatey).
Hunt Brief & Attack Chain
Hypotheses (ordered by fidelity)
| # | Hypothesis | Technique | Telemetry (plain terms, then platform) | Conf | Queries |
|---|---|---|---|---|---|
| H1 | Armorsteal runtime or modules execute from the fake SystemHealth\Update directory | T1036.005, T1059.006 | Process creation with image path and command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents); file writes (CrowdStrike: PeFileWritten, NewExecutableWritten. Defender: DeviceFileEvents) | high | Q6, Q7 |
| H2 | Tasks SysCheck2 and SysCheckX are registered | T1053.005 | Scheduled task registration (CrowdStrike: ScheduledTaskRegistered. Defender: DeviceEvents ScheduledTaskCreated) plus process command line | high | Q4, Q5 |
| H3 | Endpoint resolves or contacts a lure, C2 or payload host | T1608.006, T1071.001, T1105 | DNS lookup with requesting host (CrowdStrike: DnsRequest. Defender: DeviceEvents DnsQueryResponse); outbound connection (CrowdStrike: NetworkConnectIP4. Defender: DeviceNetworkEvents) | high | Q1, Q2, Q8 |
| H4 | Known Armorsteal files appear by hash | T1059.001, T1059.006 | Process and file events carrying SHA256 (CrowdStrike: SHA256HashData. Defender: prefer SHA1 where SHA256 is empty) | high | Q3 |
| H5 | Victim ran a hidden-window PowerShell installer that downloaded a ZIP | T1204.004, T1059.001, T1564.003 | Process creation with command line | low | Q9 |
| H6 | Browser launched with remote debugging port 9222 for cookie theft | T1539, T1555.003 | Process creation with command line and parent | medium | Q10 |
| H7 | Defender exclusions added or present, especially for the Windows directory | T1562.001 | Process command line; registry value writes (CrowdStrike: RegGenericValueUpdate. Defender: DeviceRegistryEvents) | medium | Q11 |
Attack chain
| Step | Behavior | Telemetry | Hunt angle |
|---|---|---|---|
| 1 Lure | Typosquat sites rank for 'chocolatey install windows' and MAS/Massgrave searches | Proxy and DNS logs, browser history (outside endpoint telemetry for search ranking) | Q1, Q8 |
| 2 Paste | Victim pastes PowerShell one-liner into an elevated prompt | Process creation with command line | Q8, Q9 |
| 3 Install | install.ps1 requests elevation, hides the window, downloads a ZIP to C:\Windows\SystemHealth\Update\; the Chocolatey variant also runs the real installer | Process creation, file writes, network connections | Q2, Q3, Q7 |
| 4 Persist | Tasks SysCheck2 and SysCheckX launch the remote access module | Scheduled task registration, process creation | Q4, Q5, Q6 |
| 5 Evade | Public IP check via ipinfo[.]io; cloud-provider orgs get a fake memory error. Pyarmor 9.1.7 trial obfuscation | Network connection from a Python image | Q6 (indirect) |
| 6 Task | 60-second C2 polling over HTTPS; RSA-2048 signed Python tasks run in memory | DNS and connections; in-memory execution not visible | Q1, Q2 (execution is a gap) |
| 7 Collect | Keylogging, clipboard monitoring and swapping, screenshots, browser/Discord/wallet/SSH/Steam theft, CDP cookie extraction on 9222 | Process creation; in-process APIs not visible | Q10 (rest is a gap) |
Consolidated IOC Table
All values are from the Zscaler report (snapshot 01). Network indicators are defanged below and refanged in the import CSV in section 10. Confidence is medium for every row because the report is the only source.
| Type | Value | Confidence | Action | Context |
|---|---|---|---|---|
| sha256 | f3846ec2a6ff0a27d9ef2b9bf7908743b81d295ef29344acfb96ff83fff994f9 | medium | detect | install.ps1 (MAS lure installer) |
| sha256 | fb1181160e9f10ce1eb5e5dc9bc23e1498a39110ee9ea361fd150454cb01b292 | medium | detect | install.ps1 (Chocolatey lure installer) |
| sha256 | 3e3b62a9836697d978cbb620dc8af8f6eefdf8cb73fc30d16bb394d1c58f4c97 | medium | detect | Update.zip / Updatef.zip (remote access module) |
| sha256 | 237ba698d7dc8936c43e0e78e7644002375e51e8baf9b3a7d4e18683f871ee31 | medium | detect | nig20084.zip (surveillance module) |
| sha256 | be0329d0795dd62ae587faf97e704bb36413e5c98ddd8fe575f5077db3c84e5f | medium | detect | getthem20084.zip (stealer module) |
| sha256 | 04d2828155ed2a276cfd3c97d4f966d328391dd818e04e773212cd601e49fa74 | medium | detect | nig20083.zip (older stealer module per source) |
| sha256 | 56f05c4fb1c25c04e0dde60e2e5e41ac164a6dc53688d0f9eb52a55f82106389 | medium | detect | getthem20083.zip (older stealer module) |
| sha256 | 0ea99fbbe29af6f797e3866ed5f822c7a5d3e9cdb990a8c382a715069f156499 | medium | detect | SysMon.py (remote access module) |
| sha256 | 8fb7aaf2b9270b8b75f6844aaefd5242b4f926fb89cba10fff3d9b908a769788 | medium | detect | basrq.py (surveillance module) |
| sha256 | 5410c2f239c3649928383c4478dea182f90b21f5b258081fec07e9d297ae4b1c | medium | detect | got.py (stealer module) |
| domain | choclatey[.]org | medium | detect | Fake Chocolatey lure (typosquat), registered 2026-09-01 |
| domain | massgravel[.]net | medium | detect | Fake Massgrave/MAS lure (typosquat), registered 2026-08-30 |
| domain | workerstats[.]net | medium | detect | Primary C2 (registered 2026-01-28), became unavailable early October 2026 |
| domain | datamons[.]org | medium | detect | Standby C2, later active C2 |
| domain | supertrap[.]net | medium | detect | Standby C2 |
| domain | niceer[.]net | medium | detect | Standby C2 |
| domain | pymons[.]is | medium | detect | C2 domain used from early October 2026 |
| ipv4 | 82[.]153[.]135[.]80 | medium | detect | Payload host for lure sites (mid-September onward, AKILE LTD, UK) |
| ipv4 | 209[.]99[.]186[.]187 | medium | detect | Payload host for fake Chocolatey site (early September) |
| ipv4 | 186[.]2[.]175[.]183 | medium | detect | C2 IP (workerstats.net after leaving Cloudflare) |
| ipv4 | 186[.]2[.]163[.]185 | medium | detect | C2 IP |
| ipv4 | 190[.]115[.]31[.]69 | medium | detect | C2 IP |
Affected Surface & Telemetry Matrix
| Surface | Telemetry (plain terms, then platform) | Priority | Gap |
|---|---|---|---|
| Windows endpoints, elevated user session | Process creation with command line and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | P1 | None for launch; in-memory task code is outside endpoint telemetry |
| Windows persistence | Scheduled task registration (CrowdStrike: ScheduledTaskRegistered. Defender: DeviceEvents ScheduledTaskCreated) | P1 | Task event field names vary by sensor version: validate in tenant |
| Network egress | DNS lookup (CrowdStrike: DnsRequest. Defender: DeviceEvents DnsQueryResponse) and outbound connection (CrowdStrike: NetworkConnectIP4. Defender: DeviceNetworkEvents) | P1 | URL paths such as /receive_infof are outside endpoint telemetry: use proxy or TLS-inspecting gateway logs |
| File staging | Executable and archive writes (CrowdStrike: PeFileWritten, ZipFileWritten. Defender: DeviceFileEvents) | P2 | Script and Python module hashes are often not recorded |
| Browser data, wallets, Discord, SSH, Steam | Process creation for CDP launch; file reads are not recorded | P2 | Credential file access is outside endpoint telemetry |
| Keylogging, clipboard hijack, screenshots | None directly | P3 | In-process API use is outside endpoint telemetry; detect via the host and network footprint |
ATT&CK Mapping
| Tactic | Technique | Behavior | Query / control |
|---|---|---|---|
| Resource Development | T1608.006 SEO Poisoning | Typosquat lure sites ranked for install-command searches | Q1, Q8, DNS filtering |
| Execution | T1204.004 Malicious Copy and Paste | Victim pastes the displayed PowerShell command | Q8, Q9, training |
| Execution | T1059.001 PowerShell; T1059.006 Python | install.ps1; bundled Python 3.11 running Pyarmor modules | Q6, Q9 |
| Defense Evasion | T1564.003 Hidden Window | Installer hides the PowerShell console | Q9 |
| Defense Evasion | T1036.005 Match Legitimate Name or Location | C:\Windows\SystemHealth\Update\ and task names SysCheck2 / SysCheckX | Q4, Q5, Q6, Q7 |
| Defense Evasion | T1027 Obfuscated Files or Information | Pyarmor 9.1.7 trial obfuscation of modules | Q3 (hashes) |
| Defense Evasion | T1497 Virtualization/Sandbox Evasion | ipinfo[.]io organization check, fake memory error on cloud IPs | Gap (indirect via Q6) |
| Defense Evasion | T1562.001 Impair Defenses | Advisory: review and remove unauthorized Defender exclusions | Q11 |
| Persistence | T1053.005 Scheduled Task | SysCheck2 (logon) and SysCheckX (boot, SYSTEM, 45 s) | Q4, Q5 |
| Command and Control | T1071.001 Web Protocols; T1008 Fallback Channels | HTTPS polling every 60 s, hardcoded domain list, standby C2s | Q1, Q2 |
| Command and Control | T1105 Ingress Tool Transfer | ZIP modules fetched from C2 and payload hosts | Q2, Q3, Q7 |
| Collection | T1056.001 Keylogging; T1115 Clipboard Data; T1113 Screen Capture | Surveillance module (basrq.py) | Gap (host and network footprint only) |
| Impact | T1565.002 Transmitted Data Manipulation | Clipboard wallet-address replacement for nine currencies | Gap |
| Credential Access | T1555.003 Credentials from Web Browsers; T1555.005 Password Managers; T1539 Steal Web Session Cookie | Stealer module (got.py), CDP on port 9222, chrome-decryptor | Q10 |
| Credential Access | T1528 Steal Application Access Token; T1552.004 Private Keys | Discord tokens from 22 clients; SSH keys | Gap |
| Exfiltration | T1041 Exfiltration Over C2 Channel | Uploads to /infame/receive/, /black-watch/, /ws/, /pwd, /upload/st | Q1, Q2; paths via proxy logs |
Native Audit-Log Hunts
| Source | What to look for |
|---|---|
| Windows Security log, event 4698 | Scheduled task created with TaskName \SysCheck2 or \SysCheckX (needs Audit Other Object Access Events) |
| Microsoft-Windows-TaskScheduler/Operational, events 106 and 140 | Task registered or updated with the same names |
| Microsoft-Windows-PowerShell/Operational, event 4104 | Script blocks referencing choclatey, massgravel, SystemHealth\Update or a hidden-window ZIP download |
| Sysmon (if deployed): events 1, 3, 11, 22 | Process, network, file creation and DNS query matching Q1, Q2, Q6, Q7 |
| Proxy, secure web gateway or TLS-inspecting firewall | Requests to the lure and C2 domains, and URL paths /receive_infof, /get-global, /black-watch/, /infame/receive/, /get-onetime-code/ |
| Microsoft Defender (Get-MpPreference, Operational log event 5007) | Exclusion list contents and changes, particularly C:\Windows |
| Browser and shell history | Pasted install commands; users who searched for Chocolatey or MAS install commands in late 2026 |
Read-only triage checks (run in an elevated session on a suspect host)
Get-ScheduledTask -TaskName SysCheck2,SysCheckX -ErrorAction SilentlyContinue |
Select-Object TaskName, State, @{n='Run';e={$_.Actions.Execute + ' ' + $_.Actions.Arguments}}
Test-Path 'C:\Windows\SystemHealth\Update'
Get-ChildItem 'C:\Windows\SystemHealth\Update' -Force -ErrorAction SilentlyContinue |
Select-Object Name, Length, LastWriteTime
(Get-MpPreference).ExclusionPath
Get-Process | Where-Object { $_.Path -like 'C:\Windows\SystemHealth\*' } | Select-Object Id, ProcessName, Path
Hunt Queries: CrowdStrike CQL, SentinelOne PowerQuery and Defender XDR KQL
Use your tenant's own address. SentinelOne does not document a link that pre-fills a query, so each "Copy & open SentinelOne" button copies the query and opens this console: paste it into PowerQuery.
Pick your portal. Microsoft documents a Share link in advanced hunting but not its URL format, so each "Copy & open Defender" button copies the query and opens Advanced hunting: paste it into the editor.
Looks for: Any endpoint resolving a lure or C2 domain named in the Zscaler report (includes subdomains such as the community. host on the Chocolatey lure).
Known limitations and deployment notes
- PowerQuery: DNS resolved by a process the sensor did not observe is not captured.
- PowerQuery assumes: event.dns.request holds the requested name; some schema versions name it dns.request, so run the readiness test first.
- KQL – Defender XDR: Advanced Hunting keeps 30 days; the CQL tab covers the CQL window.
- KQL – Defender XDR: DNS answered from a cache the sensor did not observe is not captured.
- KQL – Defender XDR assumes: DnsQueryResponse is populated on the tenant and AdditionalFields carries the name under DnsQueryString or QueryName; the readiness test prints both so you can see which.
// HUNT: Armorsteal lure and C2 domain resolution // MITRE: T1608.006, T1204.004, T1071.001, T1008 // CONF: high FP: low COST: low // REQUIRES: DNS lookups with the requesting host (endpoint DNS telemetry) // FALSE POSITIVES: researchers or sandboxes resolving the published domains #event_simpleName=DnsRequest | DomainName=/(^|\.)(choclatey\.org|massgravel\.net|workerstats\.net|datamons\.org|supertrap\.net|niceer\.net|pymons\.is)$/i | groupBy([ComputerName, aid, DomainName], function=[count(as=lookups), min(@timestamp, as=firstSeen), max(@timestamp, as=lastSeen)]) | table([ComputerName, aid, DomainName, lookups, firstSeen, lastSeen])
2 required fields · View requirements
Required telemetry
- DNS Resolved
Required fields
event.type: Event typeevent.dns.request: Requested name
Recommended fields
endpoint.name: Hostevent.time: Event time
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'DNS Resolved' event.dns.request = * | limit 5 | columns event.type, event.dns.request, endpoint.name, event.time
// HUNT: Armorsteal lure and C2 domain resolution
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1608.006, T1204.004, T1071.001, T1008
// CONF: high | FP: low
// FALSE POSITIVES: researchers or sandboxes resolving the published domains
// EQUIVALENCE: equivalent-with-schema-assumptions - Same DNS predicate: an exact match on the seven published domains or any subdomain of them, grouped per host and domain with a lookup count and first and last seen. in:anycase and the default case-insensitive matches reproduce the case-insensitive intent.
// REQUIRES: SentinelOne DNS telemetry (DNS Resolved events) with the requested name populated
// TUNING: Exclude sandbox and research hosts by endpoint name after a baseline review.
// LOOKBACK: 7d; set the console time picker or LRQ window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: DNS resolved by a process the sensor did not observe is not captured.
// VALIDATION LEVEL: static-reviewed
event.type = 'DNS Resolved' (event.dns.request in:anycase ('choclatey.org', 'massgravel.net', 'workerstats.net', 'datamons.org', 'supertrap.net', 'niceer.net', 'pymons.is') or event.dns.request matches '^.*\\.(choclatey\\.org|massgravel\\.net|workerstats\\.net|datamons\\.org|supertrap\\.net|niceer\\.net|pymons\\.is)$')
| group lookups=count(), first_seen=min(event.time), last_seen=max(event.time) by agent.uuid, endpoint.name, event.dns.request
| sort -last_seen
| limit 1000Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceEvents
Required fields
Timestamp: Event timeActionType: Action typeAdditionalFields: DNS name carrier
Recommended fields
DeviceName: Host
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceEvents | where Timestamp > ago(1d) | where ActionType == "DnsQueryResponse" | project Timestamp, ActionType, AdditionalFields | take 5
// HUNT: Armorsteal lure and C2 domain resolution
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1608.006, T1204.004, T1071.001, T1008
// CONF: high | FP: low
// FALSE POSITIVES: researchers or sandboxes resolving the published domains
// EQUIVALENCE: equivalent-with-schema-assumptions - Same DNS predicate on DnsQueryResponse events: an exact match on the seven published domains or any subdomain, grouped per host and name. The name is read from AdditionalFields because Defender has no endpoint DNS table.
// REQUIRES: Defender for Endpoint DNS telemetry (DeviceEvents DnsQueryResponse)
// TUNING: Exclude sandbox and research hosts by DeviceName after a baseline review.
// LOOKBACK: 30d (Advanced Hunting retention; the CQL tab covers the CQL window)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Advanced Hunting keeps 30 days; the CQL tab covers the CQL window. DNS answered from a cache the sensor did not observe is not captured.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0
DeviceEvents
| where Timestamp >= ago(30d)
| where ActionType == "DnsQueryResponse"
| extend DnsName = tostring(coalesce(parse_json(AdditionalFields)["DnsQueryString"], parse_json(AdditionalFields)["QueryName"]))
| where DnsName in~ ("choclatey.org", "massgravel.net", "workerstats.net", "datamons.org", "supertrap.net", "niceer.net", "pymons.is") or DnsName matches regex @"(?i)\.(choclatey\.org|massgravel\.net|workerstats\.net|datamons\.org|supertrap\.net|niceer\.net|pymons\.is)$"
| summarize Lookups = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceId, DeviceName, DnsName
| sort by LastSeen desc
| take 1000Looks for: Outbound IPv4 connections to the two payload hosts and three C2 IPs. Hosting IPs can be reassigned, so treat a hit as a lead and confirm with Q1, Q6 and Q7.
Known limitations and deployment notes
- PowerQuery: Connections made before the sensor started, or from an unmonitored host, are not captured.
- PowerQuery assumes: On an IP Connect event the initiating process is src.process.*.
- KQL – Defender XDR: Advanced Hunting keeps 30 days; the CQL tab covers the CQL window.
- KQL – Defender XDR assumes: DeviceNetworkEvents records outbound connection attempts to these addresses with RemoteIP populated.
// HUNT: Armorsteal payload host and C2 IP connections // MITRE: T1105, T1071.001 // CONF: medium FP: low COST: low // REQUIRES: outbound network connections with the initiating process // FALSE POSITIVES: unrelated tenants later assigned the same hosting IPs #event_simpleName=NetworkConnectIP4 | in(field=RemoteAddressIP4, values=["82.153.135.80","209.99.186.187","186.2.175.183","186.2.163.185","190.115.31.69"]) | table([@timestamp, ComputerName, aid, ContextBaseFileName, RemoteAddressIP4, RemotePort, LocalAddressIP4])
2 required fields · View requirements
Required telemetry
- IP Connect
Required fields
event.type: Event typedst.ip.address: Remote address
Recommended fields
src.process.name: Initiating processdst.port.number: Remote port
Optional fields
src.ip.address: Local address
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'IP Connect' dst.ip.address = * | limit 5 | columns event.type, dst.ip.address, src.process.name, dst.port.number
// HUNT: Armorsteal payload host and C2 IP connections
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1105, T1071.001
// CONF: medium | FP: low
// FALSE POSITIVES: unrelated tenants later assigned the same hosting IPs
// EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: an outbound IP connection to one of the five published payload or C2 addresses. The initiating process on a non-creation event is src.process.*.
// REQUIRES: SentinelOne network telemetry (IP Connect events)
// TUNING: Hosting addresses can be reassigned; review the age of a hit against the advisory date before escalating.
// LOOKBACK: 7d; set the console time picker or LRQ window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Connections made before the sensor started, or from an unmonitored host, are not captured.
// VALIDATION LEVEL: static-reviewed
event.type = 'IP Connect' dst.ip.address in ('82.153.135.80', '209.99.186.187', '186.2.175.183', '186.2.163.185', '190.115.31.69')
| columns event.time, endpoint.name, src.process.name, dst.ip.address, dst.port.number, src.ip.address
| sort -event.time
| limit 1000Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceNetworkEvents
Required fields
Timestamp: Event timeRemoteIP: Remote address
Recommended fields
InitiatingProcessFileName: Initiating processRemotePort: Remote port
Optional fields
LocalIP: Local address
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceNetworkEvents | where Timestamp > ago(1d) | project Timestamp, RemoteIP, InitiatingProcessFileName, RemotePort | take 5
// HUNT: Armorsteal payload host and C2 IP connections
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1105, T1071.001
// CONF: medium | FP: low
// FALSE POSITIVES: unrelated tenants later assigned the same hosting IPs
// EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate: a network event whose remote address is one of the five published payload or C2 addresses. The initiating process is InitiatingProcessFileName.
// REQUIRES: Defender for Endpoint network telemetry (DeviceNetworkEvents)
// TUNING: Hosting addresses can be reassigned; review the age of a hit against the advisory date before escalating.
// LOOKBACK: 30d (Advanced Hunting retention; the CQL tab covers the CQL window)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Advanced Hunting keeps 30 days; the CQL tab covers the CQL window.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceNetworkEvents
| where Timestamp >= ago(30d)
| where RemoteIP in ("82.153.135.80", "209.99.186.187", "186.2.175.183", "186.2.163.185", "190.115.31.69")
| project Timestamp, ReportId, DeviceId, DeviceName, InitiatingProcessFileName, RemoteIP, RemotePort, LocalIP
| sort by Timestamp desc
| take 1000Looks for: SHA256 of the lure installers, module archives and Python modules. Sensor hash coverage of scripts and archives varies, so a miss does not clear a host: rely on Q4 to Q7 for behavior.
Known limitations and deployment notes
- PowerQuery: An event that carries neither hash field is not matched, and the hash of a process that is not the created or written object is not checked.
- PowerQuery assumes: tgt.process.image.sha256 and tgt.file.sha256 are populated on the tenant; hash population varies by event type and policy.
- KQL – Defender XDR: Started processes with these hashes are not matched here; DeviceProcessEvents would be needed.
- KQL – Defender XDR: SHA256 is often unpopulated, and the source offers no SHA1 to pivot on, so an empty result is not proof of absence.
- KQL – Defender XDR: Advanced Hunting keeps 30 days; the CQL tab covers the CQL window.
- KQL – Defender XDR assumes: SHA256 is populated in DeviceFileEvents on the tenant.
// HUNT: Armorsteal known file hashes // MITRE: T1059.001, T1059.006, T1105 // CONF: high FP: low COST: low // REQUIRES: file and process events carrying SHA256HashData // FALSE POSITIVES: none expected for these exact hashes #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|PeFileWritten|NewExecutableWritten|ZipFileWritten)$/ | in(field=SHA256HashData, values=[ "f3846ec2a6ff0a27d9ef2b9bf7908743b81d295ef29344acfb96ff83fff994f9", "fb1181160e9f10ce1eb5e5dc9bc23e1498a39110ee9ea361fd150454cb01b292", "3e3b62a9836697d978cbb620dc8af8f6eefdf8cb73fc30d16bb394d1c58f4c97", "237ba698d7dc8936c43e0e78e7644002375e51e8baf9b3a7d4e18683f871ee31", "be0329d0795dd62ae587faf97e704bb36413e5c98ddd8fe575f5077db3c84e5f", "04d2828155ed2a276cfd3c97d4f966d328391dd818e04e773212cd601e49fa74", "56f05c4fb1c25c04e0dde60e2e5e41ac164a6dc53688d0f9eb52a55f82106389", "0ea99fbbe29af6f797e3866ed5f822c7a5d3e9cdb990a8c382a715069f156499", "8fb7aaf2b9270b8b75f6844aaefd5242b4f926fb89cba10fff3d9b908a769788", "5410c2f239c3649928383c4478dea182f90b21f5b258081fec07e9d297ae4b1c"]) | table([@timestamp, ComputerName, aid, #event_simpleName, FileName, TargetFileName, SHA256HashData])
2 required fields · View requirements
Required telemetry
- Process Creation
- File Creation
Required fields
tgt.process.image.sha256: Created process hashtgt.file.sha256: File hash
Recommended fields
event.type: Event typetgt.file.path: File path
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
tgt.file.sha256 = * | limit 5 | columns event.type, tgt.process.image.sha256, tgt.file.sha256, tgt.file.path
// HUNT: Armorsteal known file hashes
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1059.001, T1059.006, T1105
// CONF: high | FP: low
// FALSE POSITIVES: none expected for these exact hashes
// EQUIVALENCE: approximate-analytic - Same ten SHA256 values, matched against the process image hash and the file hash on every event that carries them. The source limits itself to a few write and launch event types; SentinelOne has no per-event-type split here, so the match runs on any event with those hash fields.
// REQUIRES: SentinelOne process and file telemetry with SHA256 hashes populated
// TUNING: None expected for these exact hashes; treat any hit as confirmed until proven otherwise.
// LOOKBACK: 7d; set the console time picker or LRQ window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: An event that carries neither hash field is not matched, and the hash of a process that is not the created or written object is not checked.
// VALIDATION LEVEL: static-reviewed
(tgt.process.image.sha256 in:anycase ('f3846ec2a6ff0a27d9ef2b9bf7908743b81d295ef29344acfb96ff83fff994f9', 'fb1181160e9f10ce1eb5e5dc9bc23e1498a39110ee9ea361fd150454cb01b292', '3e3b62a9836697d978cbb620dc8af8f6eefdf8cb73fc30d16bb394d1c58f4c97', '237ba698d7dc8936c43e0e78e7644002375e51e8baf9b3a7d4e18683f871ee31', 'be0329d0795dd62ae587faf97e704bb36413e5c98ddd8fe575f5077db3c84e5f', '04d2828155ed2a276cfd3c97d4f966d328391dd818e04e773212cd601e49fa74', '56f05c4fb1c25c04e0dde60e2e5e41ac164a6dc53688d0f9eb52a55f82106389', '0ea99fbbe29af6f797e3866ed5f822c7a5d3e9cdb990a8c382a715069f156499', '8fb7aaf2b9270b8b75f6844aaefd5242b4f926fb89cba10fff3d9b908a769788', '5410c2f239c3649928383c4478dea182f90b21f5b258081fec07e9d297ae4b1c') or tgt.file.sha256 in:anycase ('f3846ec2a6ff0a27d9ef2b9bf7908743b81d295ef29344acfb96ff83fff994f9', 'fb1181160e9f10ce1eb5e5dc9bc23e1498a39110ee9ea361fd150454cb01b292', '3e3b62a9836697d978cbb620dc8af8f6eefdf8cb73fc30d16bb394d1c58f4c97', '237ba698d7dc8936c43e0e78e7644002375e51e8baf9b3a7d4e18683f871ee31', 'be0329d0795dd62ae587faf97e704bb36413e5c98ddd8fe575f5077db3c84e5f', '04d2828155ed2a276cfd3c97d4f966d328391dd818e04e773212cd601e49fa74', '56f05c4fb1c25c04e0dde60e2e5e41ac164a6dc53688d0f9eb52a55f82106389', '0ea99fbbe29af6f797e3866ed5f822c7a5d3e9cdb990a8c382a715069f156499', '8fb7aaf2b9270b8b75f6844aaefd5242b4f926fb89cba10fff3d9b908a769788', '5410c2f239c3649928383c4478dea182f90b21f5b258081fec07e9d297ae4b1c'))
| columns event.time, event.type, endpoint.name, src.process.user, tgt.process.name, tgt.process.image.sha256, tgt.file.path, tgt.file.sha256
| sort -event.time
| limit 1000Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceFileEvents
Required fields
Timestamp: Event timeSHA256: File hash
Recommended fields
FileName: File nameFolderPath: File folder
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceFileEvents | where Timestamp > ago(1d) | where isnotempty(SHA256) | project Timestamp, SHA256, FileName, FolderPath | take 5
// HUNT: Armorsteal known file hashes
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1059.001, T1059.006, T1105
// CONF: high | FP: low
// FALSE POSITIVES: none expected for these exact hashes
// EQUIVALENCE: telemetry-gap - Partial pivot: the ten SHA256 values matched against file events only. The launch-side half of the source, matching hashes of started processes, is not expressible in the same table, and SHA256 is often unpopulated in Advanced Hunting.
// REQUIRES: Defender for Endpoint file telemetry (DeviceFileEvents) with SHA256 populated
// TUNING: None expected for these exact hashes; treat any hit as confirmed until proven otherwise.
// LOOKBACK: 30d (Advanced Hunting retention; the CQL tab covers the CQL window)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Started processes with these hashes are not matched here; DeviceProcessEvents would be needed. SHA256 is often unpopulated, and the source offers no SHA1 to pivot on, so an empty result is not proof of absence. Advanced Hunting keeps 30 days; the CQL tab covers the CQL window.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceFileEvents
| where Timestamp >= ago(30d)
| where SHA256 in~ ("f3846ec2a6ff0a27d9ef2b9bf7908743b81d295ef29344acfb96ff83fff994f9", "fb1181160e9f10ce1eb5e5dc9bc23e1498a39110ee9ea361fd150454cb01b292", "3e3b62a9836697d978cbb620dc8af8f6eefdf8cb73fc30d16bb394d1c58f4c97", "237ba698d7dc8936c43e0e78e7644002375e51e8baf9b3a7d4e18683f871ee31", "be0329d0795dd62ae587faf97e704bb36413e5c98ddd8fe575f5077db3c84e5f", "04d2828155ed2a276cfd3c97d4f966d328391dd818e04e773212cd601e49fa74", "56f05c4fb1c25c04e0dde60e2e5e41ac164a6dc53688d0f9eb52a55f82106389", "0ea99fbbe29af6f797e3866ed5f822c7a5d3e9cdb990a8c382a715069f156499", "8fb7aaf2b9270b8b75f6844aaefd5242b4f926fb89cba10fff3d9b908a769788", "5410c2f239c3649928383c4478dea182f90b21f5b258081fec07e9d297ae4b1c")
| project Timestamp, ReportId, DeviceId, DeviceName, ActionType, FileName, FolderPath, SHA256, InitiatingProcessFileName
| sort by Timestamp desc
| take 1000Looks for: Task registration events for the two Armorsteal persistence task names.
Known limitations and deployment notes
- PowerQuery: This is a registry-side approximation, not the scheduled-task registration event itself.
- PowerQuery: A task created while registry telemetry is suppressed is not captured.
- PowerQuery assumes: Registering a task writes values under a Schedule TaskCache Tree key named after the task, and registry.keyPath carries that key path.
- PowerQuery assumes: The match is case-insensitive by default, which reproduces the CQL /i intent.
- KQL – Defender XDR: A task registered where the sensor does not raise a creation event is not captured.
- KQL – Defender XDR assumes: ScheduledTaskCreated is populated on the tenant and AdditionalFields carries the task name under TaskName.
// HUNT: Armorsteal scheduled task persistence (registration event) // MITRE: T1053.005, T1036.005 // CONF: high FP: low COST: low // REQUIRES: scheduled task registration telemetry // FALSE POSITIVES: an unrelated product that happens to use the same task names (none known) // VALIDATE: confirm TaskName is populated on ScheduledTaskRegistered in your tenant; Q5 covers the same behavior #event_simpleName=ScheduledTaskRegistered | TaskName=/(^|\\)SysCheck(2|X)$/i | table([@timestamp, ComputerName, aid, UserName, TaskName])
2 required fields · View requirements
Required telemetry
- Registry Value Modified
Required fields
event.type: Event typeregistry.keyPath: Registry key path
Recommended fields
src.process.name: Writing processregistry.valueName: Value name
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Registry Value Modified' registry.keyPath contains 'TaskCache' | limit 5 | columns event.type, registry.keyPath, src.process.name, registry.valueName
// HUNT: Armorsteal scheduled task persistence (registration event) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1053.005, T1036.005 // CONF: high | FP: low // FALSE POSITIVES: an unrelated product that happens to use the same task names (none known) // EQUIVALENCE: approximate-analytic - SentinelOne has no attested scheduled-task registration field here, so the same behaviour is hunted through the registry write that registering a task leaves under the Task Scheduler cache: a key path ending in the task name SysCheck2 or SysCheckX. // REQUIRES: SentinelOne registry telemetry (Registry Value Modified events) covering the Task Scheduler cache // TUNING: The source command-line hunt (Q5) covers the same behaviour; use it to corroborate. // LOOKBACK: 7d; set the console time picker or LRQ window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: This is a registry-side approximation, not the scheduled-task registration event itself. A task created while registry telemetry is suppressed is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Registry Value Modified' registry.keyPath matches '\\\\TaskCache\\\\Tree\\\\SysCheck(2|X)(\\\\|$)' | columns event.time, endpoint.name, src.process.user, src.process.name, registry.keyPath, registry.valueName | sort -event.time | limit 1000
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceEvents
Required fields
Timestamp: Event timeActionType: Action typeAdditionalFields: Task name carrier
Recommended fields
InitiatingProcessFileName: Registering process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceEvents | where Timestamp > ago(1d) | where ActionType == "ScheduledTaskCreated" | project Timestamp, ActionType, AdditionalFields | take 5
// HUNT: Armorsteal scheduled task persistence (registration event) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1053.005, T1036.005 // CONF: high | FP: low // FALSE POSITIVES: an unrelated product that happens to use the same task names (none known) // EQUIVALENCE: equivalent-with-schema-assumptions - Same predicate on task-creation events: the task name ends in SysCheck2 or SysCheckX, anchored at the start or after a backslash. The name is read from AdditionalFields. // REQUIRES: Defender for Endpoint scheduled-task telemetry (DeviceEvents ScheduledTaskCreated) // TUNING: The command-line hunt (Q5) covers the same behaviour; use it to corroborate. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A task registered where the sensor does not raise a creation event is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceEvents | where Timestamp >= ago(7d) | where ActionType == "ScheduledTaskCreated" | extend TaskName = tostring(parse_json(AdditionalFields)["TaskName"]) | where TaskName matches regex @"(?i)(^|\\)SysCheck(2|X)$" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, TaskName, InitiatingProcessFileName, InitiatingProcessCommandLine | sort by Timestamp desc | take 200
Looks for: Process-level view of the same persistence: any shell or task tool whose command line names the two tasks. Covers sensors where the registration event is not populated.
Known limitations and deployment notes
- PowerQuery: A task created through the Task Scheduler API without a command line is not captured; the registry hunt covers that.
- PowerQuery assumes: On a Process Creation event src.process.user is the user of the creating process.
- PowerQuery assumes: Image names are matched with the .exe suffix as SentinelOne reports them on Windows.
- KQL – Defender XDR: A task created through the Task Scheduler API without a command line is not captured.
- KQL – Defender XDR assumes: FileName holds the bare image name with the .exe suffix on Windows sensors.
// HUNT: Armorsteal persistence via command line // MITRE: T1053.005 // CONF: high FP: low COST: low // REQUIRES: process creation with command line // FALSE POSITIVES: administrators removing or querying the tasks during response #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^(schtasks|powershell|pwsh|cmd)(\.exe)?$/i | CommandLine=/SysCheck(2|X)\b/i | table([@timestamp, ComputerName, aid, UserName, FileName, CommandLine, ParentBaseFileName])
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typetgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creating process
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' tgt.process.name = * | limit 5 | columns event.type, tgt.process.name, tgt.process.cmdline, src.process.name
// HUNT: Armorsteal persistence via command line
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1053.005
// CONF: high | FP: low
// FALSE POSITIVES: administrators removing or querying the tasks during response
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the created process is schtasks, PowerShell, pwsh or cmd and its command line names SysCheck2 or SysCheckX at a word boundary. in:anycase and the default case-insensitive matches reproduce the case-insensitive intent.
// REQUIRES: SentinelOne process-creation telemetry with command lines
// TUNING: Expect administrators removing or querying the tasks during response; exclude the response account after review.
// LOOKBACK: 7d; set the console time picker or LRQ window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A task created through the Task Scheduler API without a command line is not captured; the registry hunt covers that.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' tgt.process.name in:anycase ('schtasks.exe', 'powershell.exe', 'pwsh.exe', 'cmd.exe') tgt.process.cmdline matches 'SysCheck(2|X)\\b'
| columns event.time, endpoint.name, src.process.user, tgt.process.name, tgt.process.cmdline, src.process.name
| sort -event.time
| limit 1000Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creating process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine, InitiatingProcessFileName | take 5
// HUNT: Armorsteal persistence via command line
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1053.005
// CONF: high | FP: low
// FALSE POSITIVES: administrators removing or querying the tasks during response
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the created process is schtasks, PowerShell, pwsh or cmd and its command line names SysCheck2 or SysCheckX at a word boundary. The creator is InitiatingProcessFileName.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents)
// TUNING: Expect administrators removing or querying the tasks during response; exclude the response account after review.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A task created through the Task Scheduler API without a command line is not captured.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where FileName in~ ("schtasks.exe", "powershell.exe", "pwsh.exe", "cmd.exe")
| where ProcessCommandLine matches regex @"(?i)SysCheck(2|X)\b"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName
| sort by Timestamp desc
| take 200Looks for: Any process image or command line under the fake system directory Armorsteal uses for its Python 3.11 runtime, Pyarmor files and modules. Strongest behavioral signal in the pack.
Known limitations and deployment notes
- PowerQuery: A process the sensor did not observe starting is not captured.
- PowerQuery assumes: tgt.process.image.path holds the full path of the created process image.
- KQL – Defender XDR: A process the sensor did not observe starting is not captured.
- KQL – Defender XDR assumes: FolderPath holds the full image path of the created process.
// HUNT: Armorsteal runtime executing from fake SystemHealth directory // MITRE: T1036.005, T1059.006, T1053.005 // CONF: high FP: low COST: low // REQUIRES: process creation with image path and command line // FALSE POSITIVES: none known; the directory is not a standard Windows path #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | (ImageFileName=/\\Windows\\SystemHealth\\Update\\/i or CommandLine=/\\Windows\\SystemHealth\\Update\\/i) | table([@timestamp, ComputerName, aid, UserName, FileName, ImageFileName, CommandLine, ParentBaseFileName, SHA256HashData])
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typetgt.process.image.path: Created process pathtgt.process.cmdline: Created command line
Recommended fields
tgt.process.name: Created process namesrc.process.name: Creating process
Optional fields
tgt.process.image.sha256: Created process hash
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' tgt.process.image.path = * | limit 5 | columns event.type, tgt.process.image.path, tgt.process.cmdline
// HUNT: Armorsteal runtime executing from fake SystemHealth directory // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1036.005, T1059.006, T1053.005 // CONF: high | FP: low // FALSE POSITIVES: none known; the directory is not a standard Windows path // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the created process image path or its command line contains the fake SystemHealth Update directory. contains is case-insensitive, which reproduces the CQL /i intent. The creator is src.process.name. // REQUIRES: SentinelOne process-creation telemetry with image path and command line // TUNING: None known; the directory is not a standard Windows path. // LOOKBACK: 7d; set the console time picker or LRQ window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A process the sensor did not observe starting is not captured. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' (tgt.process.image.path contains '\\Windows\\SystemHealth\\Update\\' or tgt.process.cmdline contains '\\Windows\\SystemHealth\\Update\\') | columns event.time, endpoint.name, src.process.user, tgt.process.name, tgt.process.image.path, tgt.process.cmdline, src.process.name, tgt.process.image.sha256 | sort -event.time | limit 1000
Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFolderPath: Created process pathProcessCommandLine: Created command line
Recommended fields
FileName: Created process nameInitiatingProcessFileName: Creating process
Optional fields
SHA1: Hash. SHA256 is often unpopulated in DeviceProcessEvents; SHA1 is the safer pivot.
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FolderPath, ProcessCommandLine | take 5
// HUNT: Armorsteal runtime executing from fake SystemHealth directory // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1036.005, T1059.006, T1053.005 // CONF: high | FP: low // FALSE POSITIVES: none known; the directory is not a standard Windows path // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the created process folder path or its command line contains the fake SystemHealth Update directory. contains is case-insensitive, which reproduces the CQL /i intent. The creator is InitiatingProcessFileName. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) // TUNING: None known; the directory is not a standard Windows path. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A process the sensor did not observe starting is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(7d) | where FolderPath contains "\\Windows\\SystemHealth\\Update\\" or ProcessCommandLine contains "\\Windows\\SystemHealth\\Update\\" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, FolderPath, ProcessCommandLine, InitiatingProcessFileName, SHA1, SHA256 | sort by Timestamp desc | take 200
Looks for: Executables, DLLs and archives written into the staging directory: the Python runtime, Pyarmor runtime and module ZIPs landing on disk.
Known limitations and deployment notes
- PowerQuery: Non-executable and non-archive writes are included, so a hit needs a glance at the file type.
- PowerQuery assumes: tgt.file.path holds the full path of the written file.
- KQL – Defender XDR: Non-executable and non-archive writes are included, so a hit needs a glance at the file type.
- KQL – Defender XDR assumes: FolderPath holds the directory or full path of the written file, so the match omits the trailing backslash and also catches a directory that merely starts with the same name.
// HUNT: Armorsteal payload staging in fake SystemHealth directory // MITRE: T1036.005, T1105 // CONF: high FP: low COST: low // REQUIRES: file write telemetry for executables and archives // FALSE POSITIVES: none known #event_simpleName=/^(PeFileWritten|NewExecutableWritten|ZipFileWritten)$/ | TargetFileName=/\\Windows\\SystemHealth\\Update\\/i | table([@timestamp, ComputerName, aid, ContextBaseFileName, TargetFileName, SHA256HashData])
2 required fields · View requirements
Required telemetry
- File Creation
- File Modification
Required fields
event.type: Event typetgt.file.path: Written file path
Recommended fields
src.process.name: Writing process
Optional fields
tgt.file.sha256: File hash
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'File Creation' tgt.file.path = * | limit 5 | columns event.type, tgt.file.path, src.process.name
// HUNT: Armorsteal payload staging in fake SystemHealth directory
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1036.005, T1105
// CONF: high | FP: low
// FALSE POSITIVES: none known
// EQUIVALENCE: approximate-analytic - Same staging directory, but the source limits itself to executable and archive write events and SentinelOne has no attested file-type event split, so this query reports every file created or modified under the directory. The writer is src.process.name.
// REQUIRES: SentinelOne file telemetry (File Creation and File Modification events)
// TUNING: None known; the directory is not a standard Windows path. Add an extension filter if non-executable writes are noisy.
// LOOKBACK: 7d; set the console time picker or LRQ window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Non-executable and non-archive writes are included, so a hit needs a glance at the file type.
// VALIDATION LEVEL: static-reviewed
event.type in ('File Creation', 'File Modification') tgt.file.path contains '\\Windows\\SystemHealth\\Update\\'
| columns event.time, event.type, endpoint.name, src.process.name, tgt.file.path, tgt.file.sha256
| sort -event.time
| limit 1000Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceFileEvents
Required fields
Timestamp: Event timeActionType: Action typeFolderPath: File folder
Recommended fields
InitiatingProcessFileName: Writing process
Optional fields
SHA1: Hash. SHA256 is often unpopulated; SHA1 is the safer pivot.
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceFileEvents | where Timestamp > ago(1d) | project Timestamp, ActionType, FolderPath, FileName | take 5
// HUNT: Armorsteal payload staging in fake SystemHealth directory
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1036.005, T1105
// CONF: high | FP: low
// FALSE POSITIVES: none known
// EQUIVALENCE: approximate-analytic - Same staging directory, but the source limits itself to executable and archive write events and Defender reports every created or modified file, so this query reports all of them under the directory. The writer is InitiatingProcessFileName.
// REQUIRES: Defender for Endpoint file telemetry (DeviceFileEvents)
// TUNING: None known; the directory is not a standard Windows path. Add an extension filter if non-executable writes are noisy.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Non-executable and non-archive writes are included, so a hit needs a glance at the file type.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceFileEvents
| where Timestamp >= ago(7d)
| where ActionType in ("FileCreated", "FileModified")
| where FolderPath contains "\\Windows\\SystemHealth\\Update"
| project Timestamp, ReportId, DeviceId, DeviceName, ActionType, FileName, FolderPath, InitiatingProcessFileName, SHA1, SHA256
| sort by Timestamp desc
| take 200Looks for: Any process whose command line contains a lure domain, C2 domain or payload host: catches the pasted PowerShell one-liner and the install.ps1 download.
Known limitations and deployment notes
- PowerQuery: The two addresses match as plain substrings, so a longer address that ends in the same digits also matches.
- PowerQuery assumes: On a Process Creation event src.process.user is the user of the creating process.
- KQL – Defender XDR: A longer address that ends in the same digits also matches.
- KQL – Defender XDR: Advanced Hunting keeps 30 days; the CQL tab covers the CQL window.
- KQL – Defender XDR assumes: The two addresses match as plain substrings.
// HUNT: Armorsteal lure one-liner (domain or host in command line) // MITRE: T1204.004, T1059.001, T1105 // CONF: high FP: low COST: low // REQUIRES: process creation with command line // FALSE POSITIVES: analysts testing the published domains #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/(choclatey\.org|massgravel\.net|workerstats\.net|datamons\.org|supertrap\.net|niceer\.net|pymons\.is|82\.153\.135\.80|209\.99\.186\.187)/i | table([@timestamp, ComputerName, aid, UserName, FileName, CommandLine, ParentBaseFileName])
2 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typetgt.process.cmdline: Created command line
Recommended fields
tgt.process.name: Created process namesrc.process.name: Creating process
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.type, tgt.process.cmdline, tgt.process.name
// HUNT: Armorsteal lure one-liner (domain or host in command line)
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1204.004, T1059.001, T1105
// CONF: high | FP: low
// FALSE POSITIVES: analysts testing the published domains
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the created process command line contains one of the seven published domains or two payload addresses. contains is case-insensitive, which reproduces the CQL /i intent.
// REQUIRES: SentinelOne process-creation telemetry with command lines
// TUNING: Analysts testing the published domains will hit; exclude their hosts after review.
// LOOKBACK: 7d; set the console time picker or LRQ window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The two addresses match as plain substrings, so a longer address that ends in the same digits also matches.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' tgt.process.cmdline contains ('choclatey.org', 'massgravel.net', 'workerstats.net', 'datamons.org', 'supertrap.net', 'niceer.net', 'pymons.is', '82.153.135.80', '209.99.186.187')
| columns event.time, endpoint.name, src.process.user, tgt.process.name, tgt.process.cmdline, src.process.name
| sort -event.time
| limit 1000Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command line
Recommended fields
FileName: Created process nameInitiatingProcessFileName: Creating process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, ProcessCommandLine, FileName | take 5
// HUNT: Armorsteal lure one-liner (domain or host in command line) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1204.004, T1059.001, T1105 // CONF: high | FP: low // FALSE POSITIVES: analysts testing the published domains // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the created process command line contains one of the seven published domains or two payload addresses. contains is case-insensitive, which reproduces the CQL /i intent. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) // TUNING: Analysts testing the published domains will hit; exclude their hosts after review. // LOOKBACK: 30d (Advanced Hunting retention; the CQL tab covers the CQL window) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: A longer address that ends in the same digits also matches. Advanced Hunting keeps 30 days; the CQL tab covers the CQL window. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(30d) | where ProcessCommandLine contains "choclatey.org" or ProcessCommandLine contains "massgravel.net" or ProcessCommandLine contains "workerstats.net" or ProcessCommandLine contains "datamons.org" or ProcessCommandLine contains "supertrap.net" or ProcessCommandLine contains "niceer.net" or ProcessCommandLine contains "pymons.is" or ProcessCommandLine contains "82.153.135.80" or ProcessCommandLine contains "209.99.186.187" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName | sort by Timestamp desc | take 200
Looks for: Behavioral approximation of the installer stage (hidden console, web download, ZIP or install.ps1). Zscaler does not publish the exact command line, so this is a hunt lead for lures that have rotated away from the known domains. FP: software deployment tools and legitimate installers use the same pattern.
Known limitations and deployment notes
- PowerQuery: An installer stage that downloads through another binary, or builds the command line at run time, is not captured.
- PowerQuery assumes: On a Process Creation event src.process.user is the user of the creating process.
- KQL – Defender XDR: An installer stage that downloads through another binary, or builds the command line at run time, is not captured.
- KQL – Defender XDR assumes: FileName holds the bare image name with the .exe suffix on Windows sensors.
// HUNT: Hidden PowerShell downloading a ZIP or install.ps1 (installer-stage approximation) // MITRE: T1059.001, T1564.003, T1105 // CONF: low FP: medium COST: low // REQUIRES: process creation with command line // FALSE POSITIVES: software deployment agents, package managers, legitimate hidden-window installers // TUNING: exclude known deployment parents and internal download hosts, for example append // TUNING: | ParentBaseFileName!=/^(ccmexec|intunemanagementextension|ninjarmmagent|ltsvc)(\.exe)?$/i #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^(powershell|pwsh)(\.exe)?$/i | CommandLine=/(-w(indowstyle)?\s+hidden|-win\s+h)/i | CommandLine=/(invoke-webrequest|invoke-restmethod|downloadfile|downloadstring|start-bitstransfer|\biwr\b|\birm\b)/i | CommandLine=/(\.zip|install\.ps1)/i | table([@timestamp, ComputerName, aid, UserName, CommandLine, ParentBaseFileName])
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typetgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creating process
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' tgt.process.name = * | limit 5 | columns event.type, tgt.process.name, tgt.process.cmdline
// HUNT: Hidden PowerShell downloading a ZIP or install.ps1 (installer-stage approximation)
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1059.001, T1564.003, T1105
// CONF: low | FP: medium
// FALSE POSITIVES: software deployment agents, package managers, legitimate hidden-window installers
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: PowerShell or pwsh with a hidden-window switch, a download cmdlet or alias, and a .zip or install.ps1 reference, each tested as its own condition on the command line. matches is case-insensitive by default, which reproduces the CQL /i intent.
// REQUIRES: SentinelOne process-creation telemetry with command lines
// TUNING: Exclude known software-deployment parents and internal download hosts, for example by adding a negated src.process.name list after baseline review.
// LOOKBACK: 7d; set the console time picker or LRQ window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: An installer stage that downloads through another binary, or builds the command line at run time, is not captured.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' tgt.process.name in:anycase ('powershell.exe', 'pwsh.exe') tgt.process.cmdline matches '(-w(indowstyle)?\\s+hidden|-win\\s+h)' tgt.process.cmdline matches '(invoke-webrequest|invoke-restmethod|downloadfile|downloadstring|start-bitstransfer|\\biwr\\b|\\birm\\b)' tgt.process.cmdline matches '(\\.zip|install\\.ps1)'
| columns event.time, endpoint.name, src.process.user, tgt.process.cmdline, src.process.name
| sort -event.time
| limit 1000Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creating process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine, InitiatingProcessFileName | take 5
// HUNT: Hidden PowerShell downloading a ZIP or install.ps1 (installer-stage approximation)
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1059.001, T1564.003, T1105
// CONF: low | FP: medium
// FALSE POSITIVES: software deployment agents, package managers, legitimate hidden-window installers
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: PowerShell or pwsh with a hidden-window switch, a download cmdlet or alias, and a .zip or install.ps1 reference, each tested as its own condition on the command line. The (?i) prefix reproduces the CQL /i intent.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents)
// TUNING: Exclude known software-deployment parents and internal download hosts, for example by adding a negated InitiatingProcessFileName list after baseline review.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: An installer stage that downloads through another binary, or builds the command line at run time, is not captured.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine matches regex @"(?i)(-w(indowstyle)?\s+hidden|-win\s+h)"
| where ProcessCommandLine matches regex @"(?i)(invoke-webrequest|invoke-restmethod|downloadfile|downloadstring|start-bitstransfer|\biwr\b|\birm\b)"
| where ProcessCommandLine contains ".zip" or ProcessCommandLine contains "install.ps1"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName
| sort by Timestamp desc
| take 200Looks for: The stealer extracts cookies over the Chrome DevTools Protocol on port 9222. Zscaler recommends reviewing browser processes started with this flag. FP: developers and QA automation launch browsers this way.
Known limitations and deployment notes
- PowerQuery: A browser launched under a renamed binary, or with a different debugging port, is not captured.
- PowerQuery assumes: Image names are matched with the .exe suffix as SentinelOne reports them on Windows.
- KQL – Defender XDR: A browser launched under a renamed binary, or with a different debugging port, is not captured.
- KQL – Defender XDR assumes: FileName holds the bare image name with the .exe suffix on Windows sensors.
// HUNT: Browser launched with --remote-debugging-port=9222 (cookie theft via CDP) // MITRE: T1539, T1555.003 // CONF: medium FP: medium COST: low // REQUIRES: process creation with command line and parent // FALSE POSITIVES: web developers, test automation, screenshot and PDF tooling // TUNING: exclude automation parents and developer groups, for example append // TUNING: | ParentBaseFileName!=/^(node|chromedriver|msedgedriver|code|pwsh)(\.exe)?$/i // TUNING: prioritise hits whose parent is a python interpreter or runs from a non-standard path #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^(chrome|msedge|brave|opera|vivaldi)(\.exe)?$/i | CommandLine=/--remote-debugging-port=9222/i | table([@timestamp, ComputerName, aid, UserName, FileName, CommandLine, ParentBaseFileName, ImageFileName])
3 required fields · View requirements
Required telemetry
- Process Creation
Required fields
event.type: Event typetgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creating processtgt.process.image.path: Created process path
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' tgt.process.name = * | limit 5 | columns event.type, tgt.process.name, tgt.process.cmdline, src.process.name
// HUNT: Browser launched with --remote-debugging-port=9222 (cookie theft via CDP)
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1539, T1555.003
// CONF: medium | FP: medium
// FALSE POSITIVES: web developers, test automation, screenshot and PDF tooling
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: a Chromium-family browser started with the remote debugging port set to 9222. in:anycase and the default case-insensitive contains reproduce the CQL /i intent.
// REQUIRES: SentinelOne process-creation telemetry with command lines
// TUNING: Exclude automation and developer parents after review, and prioritise hits whose creating process is a python interpreter or runs from a non-standard path.
// LOOKBACK: 7d; set the console time picker or LRQ window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A browser launched under a renamed binary, or with a different debugging port, is not captured.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' tgt.process.name in:anycase ('chrome.exe', 'msedge.exe', 'brave.exe', 'opera.exe', 'vivaldi.exe') tgt.process.cmdline contains '--remote-debugging-port=9222'
| columns event.time, endpoint.name, src.process.user, tgt.process.name, tgt.process.cmdline, src.process.name, tgt.process.image.path
| sort -event.time
| limit 1000Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameProcessCommandLine: Created command line
Recommended fields
InitiatingProcessFileName: Creating processFolderPath: Created process path
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, FileName, ProcessCommandLine, FolderPath, InitiatingProcessFileName | take 5
// HUNT: Browser launched with --remote-debugging-port=9222 (cookie theft via CDP)
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1539, T1555.003
// CONF: medium | FP: medium
// FALSE POSITIVES: web developers, test automation, screenshot and PDF tooling
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: a Chromium-family browser started with the remote debugging port set to 9222. in~ and the case-insensitive contains reproduce the CQL /i intent.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents)
// TUNING: Exclude automation and developer parents after review, and prioritise hits whose creating process is a python interpreter or runs from a non-standard path.
// LOOKBACK: 7d; extend to 30d for the incident window
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A browser launched under a renamed binary, or with a different debugging port, is not captured.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where FileName in~ ("chrome.exe", "msedge.exe", "brave.exe", "opera.exe", "vivaldi.exe")
| where ProcessCommandLine contains "--remote-debugging-port=9222"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, FolderPath, ProcessCommandLine, InitiatingProcessFileName
| sort by Timestamp desc
| take 200Looks for: Defender exclusion changes by command line or registry write. Zscaler advises removing unauthorized exclusions, particularly for C:\Windows. The report does not say Armorsteal adds one, so this is a posture and triage query. FP: administrators and security tooling add exclusions legitimately.
Known limitations and deployment notes
- PowerQuery: An exclusion set through Group Policy, Intune or the Windows Security UI without a matching command line or sensor-visible registry write is not captured.
- PowerQuery assumes: On a Process Creation event src.process.user is the user of the creating process.
- PowerQuery assumes: registry.keyPath carries the key path of the written value, including the Exclusions subkey.
- KQL – Defender XDR: The registry-write half of the source, values written under the Defender Exclusions key, is not covered; DeviceRegistryEvents would be needed.
- KQL – Defender XDR: An exclusion set through Group Policy, Intune or the Windows Security UI is not captured.
- KQL – Defender XDR assumes: ProcessCommandLine carries the full PowerShell command line.
// HUNT: Microsoft Defender exclusion added (posture and triage) // MITRE: T1562.001 // CONF: medium FP: medium COST: low // REQUIRES: process creation with command line, registry value writes // FALSE POSITIVES: administrators, software deployment, backup and database products adding vendor-documented exclusions // TUNING: exclude approved exclusion paths and the change-management account, and escalate any exclusion for C:\Windows or a parent of it #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|RegGenericValueUpdate)$/ | (CommandLine=/(Add-MpPreference|Set-MpPreference).*-Exclusion(Path|Process|Extension)/i or RegObjectName=/Windows Defender\\Exclusions\\(Paths|Processes|Extensions)/i) | table([@timestamp, ComputerName, aid, UserName, CommandLine, RegObjectName, RegValueName])
3 required fields · View requirements
Required telemetry
- Process Creation
- Registry Value Modified
Required fields
event.type: Event typetgt.process.cmdline: Created command lineregistry.keyPath: Registry key path
Recommended fields
registry.valueName: Registry value name
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' tgt.process.cmdline = * | limit 5 | columns event.type, tgt.process.cmdline, registry.keyPath, registry.valueName
// HUNT: Microsoft Defender exclusion added (posture and triage) // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1562.001 // CONF: medium | FP: medium // FALSE POSITIVES: administrators, software deployment, backup and database products adding vendor-documented exclusions // EQUIVALENCE: equivalent-with-schema-assumptions - Same two populations: a process whose command line adds a Defender exclusion through Add-MpPreference or Set-MpPreference, and a registry value written under the Defender Exclusions key. matches is case-insensitive by default, which reproduces the CQL /i intent. // REQUIRES: SentinelOne process-creation telemetry with command lines, and registry value telemetry // TUNING: Exclude approved exclusion paths and the change-management account, and escalate any exclusion for C:\Windows or a parent of it. // LOOKBACK: 7d; set the console time picker or LRQ window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: An exclusion set through Group Policy, Intune or the Windows Security UI without a matching command line or sensor-visible registry write is not captured. // VALIDATION LEVEL: static-reviewed (event.type = 'Process Creation' tgt.process.cmdline matches '(Add-MpPreference|Set-MpPreference).*-Exclusion(Path|Process|Extension)') or (event.type = 'Registry Value Modified' registry.keyPath matches 'Windows Defender\\\\Exclusions\\\\(Paths|Processes|Extensions)') | columns event.time, event.type, endpoint.name, src.process.user, tgt.process.cmdline, registry.keyPath, registry.valueName | sort -event.time | limit 1000
Automated validation: static-passed
2 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeProcessCommandLine: Created command line
Recommended fields
FileName: Created process nameInitiatingProcessFileName: Creating process
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, ProcessCommandLine, FileName | take 5
// HUNT: Microsoft Defender exclusion added (posture and triage) // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1562.001 // CONF: medium | FP: medium // FALSE POSITIVES: administrators, software deployment, backup and database products adding vendor-documented exclusions // EQUIVALENCE: approximate-analytic - Partial pivot on the process half only: a process whose command line adds a Defender exclusion through Add-MpPreference or Set-MpPreference. The registry-write half lives in another table and one query can read only one table. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) // TUNING: Exclude approved exclusion paths and the change-management account, and escalate any exclusion for C:\Windows or a parent of it. // LOOKBACK: 7d; extend to 30d for the incident window // CONFIGURATION: No lookup objects required. // KNOWN GAPS: The registry-write half of the source, values written under the Defender Exclusions key, is not covered; DeviceRegistryEvents would be needed. An exclusion set through Group Policy, Intune or the Windows Security UI is not captured. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(7d) | where ProcessCommandLine matches regex @"(?i)(Add-MpPreference|Set-MpPreference).*-Exclusion(Path|Process|Extension)" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName | sort by Timestamp desc | take 200
Detection Rules by Platform
CrowdStrike: Custom IOA
| IOA name | Promotes | Pattern | Exclusions | Deployment |
|---|---|---|---|---|
| Armorsteal: process from SystemHealth\Update | Q6 | Process Creation, image filename .*\\Windows\\SystemHealth\\Update\\.* (or command line match) | None needed; not a standard Windows path | Detect first. Prevent only after the same tenant shows zero legitimate hits for 14 days, and only on a path rule, never a hash |
| Armorsteal: SysCheck2 / SysCheckX task | Q4, Q5 | Process Creation, command line .*SysCheck(2|X)\b.* with image schtasks.exe, powershell.exe or pwsh.exe | Response runbooks that delete the tasks | Detect. Pair with Q4 for the registration event |
| Defender exclusion for C:\Windows | Q11 | Process Creation, command line Add-MpPreference or Set-MpPreference with -ExclusionPath and a C:\Windows value | Approved change-management account | Detect with high severity review; do not block (administrators use this legitimately) |
No IOA in this pack is set to prevent. The only indicators are single-sourced, and Q6 is a path heuristic that should be baselined first.
SentinelOne: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| Armorsteal: process from SystemHealth\Update | Q6, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
| Armorsteal: SysCheck2 / SysCheckX task | Q4, Q5, PowerQuery tab | Hunt only until the gap is closed | Q4 is only an approximation here. SentinelOne has no attested scheduled-task registration field here, so the same behaviour is hunted through the registry write that registering a task leaves under the Task Scheduler cache: a key path ending in the task name SysCheck2 or SysCheckX. |
| Defender exclusion for C:\Windows | Q11, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
Create the rule with POST /web/api/v2.1/cloud-detection/rules, queryType: "scheduled" and queryLang: "2.0", with the query in data.scheduledParams.query. Use 2.0 operator spellings: a rule written with the 1.0 token ContainsCIS was accepted and never fired on a tenant. The API does not warn, so lint the body before deploying.
Microsoft Defender: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| Armorsteal: process from SystemHealth\Update | Q6, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself |
| Armorsteal: SysCheck2 / SysCheckX task | Q4, Q5, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | The query returns Timestamp, ReportId and DeviceId, which Microsoft recommends for Defender for Endpoint tables so alerts carry the device group scope and the process tree. Remove the Timestamp filter and the final sort and take before saving the rule: the service applies the rule's lookback itself |
| Defender exclusion for C:\Windows | Q11, KQL – Defender XDR tab | Hunt only until the gap is closed | Q11 is only an approximation here. Partial pivot on the process half only: a process whose command line adds a Defender exclusion through Add-MpPreference or Set-MpPreference. The registry-write half lives in another table and one query can read only one table. |
Q1, Q2, Q3, Q7, Q8, Q9 and Q10 stay a hunt in this pack, not a rule. A rule can generate only 150 alerts per run. Continuous (NRT) frequency needs a single table and no comment lines, so delete the generated comment header from the query first.
Machine-Readable IOC Appendix
Import posture. Every row is detect: the indicators are single-sourced (Zscaler only), so none ships at prevent or critical. The Chocolatey lure hash is the attacker's own install.ps1, not the legitimate Chocolatey installer; the legitimate installer is deliberately absent and must never be blocked.
type,value,action,severity,expiration,description,tags sha256,f3846ec2a6ff0a27d9ef2b9bf7908743b81d295ef29344acfb96ff83fff994f9,detect,high,2027-04-30,Armorsteal install.ps1 (MAS lure installer),campaign:Armorsteal sha256,fb1181160e9f10ce1eb5e5dc9bc23e1498a39110ee9ea361fd150454cb01b292,detect,high,2027-04-30,Armorsteal install.ps1 (Chocolatey lure installer),campaign:Armorsteal sha256,3e3b62a9836697d978cbb620dc8af8f6eefdf8cb73fc30d16bb394d1c58f4c97,detect,high,2027-04-30,Armorsteal Update.zip / Updatef.zip (remote access module),campaign:Armorsteal sha256,237ba698d7dc8936c43e0e78e7644002375e51e8baf9b3a7d4e18683f871ee31,detect,high,2027-04-30,Armorsteal nig20084.zip (surveillance module),campaign:Armorsteal sha256,be0329d0795dd62ae587faf97e704bb36413e5c98ddd8fe575f5077db3c84e5f,detect,high,2027-04-30,Armorsteal getthem20084.zip (stealer module),campaign:Armorsteal sha256,04d2828155ed2a276cfd3c97d4f966d328391dd818e04e773212cd601e49fa74,detect,high,2027-04-30,Armorsteal nig20083.zip (older stealer module per source),campaign:Armorsteal sha256,56f05c4fb1c25c04e0dde60e2e5e41ac164a6dc53688d0f9eb52a55f82106389,detect,high,2027-04-30,Armorsteal getthem20083.zip (older stealer module),campaign:Armorsteal sha256,0ea99fbbe29af6f797e3866ed5f822c7a5d3e9cdb990a8c382a715069f156499,detect,high,2027-04-30,Armorsteal SysMon.py (remote access module),campaign:Armorsteal sha256,8fb7aaf2b9270b8b75f6844aaefd5242b4f926fb89cba10fff3d9b908a769788,detect,high,2027-04-30,Armorsteal basrq.py (surveillance module),campaign:Armorsteal sha256,5410c2f239c3649928383c4478dea182f90b21f5b258081fec07e9d297ae4b1c,detect,high,2027-04-30,Armorsteal got.py (stealer module),campaign:Armorsteal domain,choclatey.org,detect,high,2027-04-30,Armorsteal Fake Chocolatey lure (typosquat); registered 2026-09-01,campaign:Armorsteal domain,massgravel.net,detect,high,2027-04-30,Armorsteal Fake Massgrave/MAS lure (typosquat); registered 2026-08-30,campaign:Armorsteal domain,workerstats.net,detect,high,2027-04-30,Armorsteal Primary C2 (registered 2026-01-28); became unavailable early October 2026,campaign:Armorsteal domain,datamons.org,detect,high,2027-04-30,Armorsteal Standby C2; later active C2,campaign:Armorsteal domain,supertrap.net,detect,high,2027-04-30,Armorsteal Standby C2,campaign:Armorsteal domain,niceer.net,detect,high,2027-04-30,Armorsteal Standby C2,campaign:Armorsteal domain,pymons.is,detect,high,2027-04-30,Armorsteal C2 domain used from early October 2026,campaign:Armorsteal ipv4,82.153.135.80,detect,medium,2027-01-30,Armorsteal Payload host for lure sites (mid-September onward; AKILE LTD; UK),campaign:Armorsteal ipv4,209.99.186.187,detect,medium,2027-01-30,Armorsteal Payload host for fake Chocolatey site (early September),campaign:Armorsteal ipv4,186.2.175.183,detect,medium,2027-01-30,Armorsteal C2 IP (workerstats.net after leaving Cloudflare),campaign:Armorsteal ipv4,186.2.163.185,detect,medium,2027-01-30,Armorsteal C2 IP,campaign:Armorsteal ipv4,190.115.31.69,detect,medium,2027-01-30,Armorsteal C2 IP,campaign:Armorsteal
indicatorType,indicatorValue,action,title,description,expirationTime,severity,recommendedActions,rbacGroups,category,mitretechniques,GenerateAlert FileSha256,f3846ec2a6ff0a27d9ef2b9bf7908743b81d295ef29344acfb96ff83fff994f9,Audit,Armorsteal install.ps1,Armorsteal install.ps1 (MAS lure installer),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,fb1181160e9f10ce1eb5e5dc9bc23e1498a39110ee9ea361fd150454cb01b292,Audit,Armorsteal install.ps1,Armorsteal install.ps1 (Chocolatey lure installer),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,3e3b62a9836697d978cbb620dc8af8f6eefdf8cb73fc30d16bb394d1c58f4c97,Audit,Armorsteal Update.zip / Updatef.zip,Armorsteal Update.zip / Updatef.zip (remote access module),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,237ba698d7dc8936c43e0e78e7644002375e51e8baf9b3a7d4e18683f871ee31,Audit,Armorsteal nig20084.zip,Armorsteal nig20084.zip (surveillance module),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,be0329d0795dd62ae587faf97e704bb36413e5c98ddd8fe575f5077db3c84e5f,Audit,Armorsteal getthem20084.zip,Armorsteal getthem20084.zip (stealer module),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,04d2828155ed2a276cfd3c97d4f966d328391dd818e04e773212cd601e49fa74,Audit,Armorsteal nig20083.zip,Armorsteal nig20083.zip (older stealer module per source),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,56f05c4fb1c25c04e0dde60e2e5e41ac164a6dc53688d0f9eb52a55f82106389,Audit,Armorsteal getthem20083.zip,Armorsteal getthem20083.zip (older stealer module),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,0ea99fbbe29af6f797e3866ed5f822c7a5d3e9cdb990a8c382a715069f156499,Audit,Armorsteal SysMon.py,Armorsteal SysMon.py (remote access module),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,8fb7aaf2b9270b8b75f6844aaefd5242b4f926fb89cba10fff3d9b908a769788,Audit,Armorsteal basrq.py,Armorsteal basrq.py (surveillance module),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True FileSha256,5410c2f239c3649928383c4478dea182f90b21f5b258081fec07e9d297ae4b1c,Audit,Armorsteal got.py,Armorsteal got.py (stealer module),2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,choclatey.org,Audit,Armorsteal Fake Chocolatey lure,Armorsteal Fake Chocolatey lure (typosquat); registered 2026-09-01,2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,massgravel.net,Audit,Armorsteal Fake Massgrave/MAS lure,Armorsteal Fake Massgrave/MAS lure (typosquat); registered 2026-08-30,2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,workerstats.net,Audit,Armorsteal Primary C2,Armorsteal Primary C2 (registered 2026-01-28); became unavailable early October 2026,2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,datamons.org,Audit,Armorsteal Standby C2; later active C2,Armorsteal Standby C2; later active C2,2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,supertrap.net,Audit,Armorsteal Standby C2,Armorsteal Standby C2,2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,niceer.net,Audit,Armorsteal Standby C2,Armorsteal Standby C2,2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True DomainName,pymons.is,Audit,Armorsteal C2 domain used from early October 2026,Armorsteal C2 domain used from early October 2026,2027-04-30T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True IpAddress,82.153.135.80,Audit,Armorsteal Payload host for lure sites,Armorsteal Payload host for lure sites (mid-September onward; AKILE LTD; UK),2027-01-30T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,209.99.186.187,Audit,Armorsteal Payload host for fake Chocolatey site,Armorsteal Payload host for fake Chocolatey site (early September),2027-01-30T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,186.2.175.183,Audit,Armorsteal C2 IP,Armorsteal C2 IP (workerstats.net after leaving Cloudflare),2027-01-30T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,186.2.163.185,Audit,Armorsteal C2 IP,Armorsteal C2 IP,2027-01-30T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True IpAddress,190.115.31.69,Audit,Armorsteal C2 IP,Armorsteal C2 IP,2027-01-30T00:00:00.0Z,Medium,Investigate the device and pivot into the pack's queries,,,,True
Column names and allowed values are from Microsoft's documented parameter table. Check each expirationTime before importing: an indicator whose date has passed is already expired. Download the sample CSV from the Import page to confirm the column order before a bulk import. Each file takes at most 500 indicators. Learn describes the import per entity type, so if a mixed file is rejected, split it by indicatorType. An imported indicator does nothing unless the matching Advanced features are on (for example Custom network indicators and Allow or block file).
This pack does not ship a SentinelOne import block: the import format could not be verified against SentinelOne's own documentation. Export a sample from your console and the block can be added with confidence.
# Behavioral signatures (no atomic IOC needed)
Directory C:\Windows\SystemHealth\Update\
Scheduled task SysCheck2 (trigger: user logon, run level highest)
Scheduled task SysCheckX (trigger: system boot, SYSTEM, 45 s delay)
Module files SysMon.py (remote access), basrq.py (surveillance), got.py (stealer)
Archives Update.zip, Updatef.zip, nig20083.zip, nig20084.zip, getthem20083.zip, getthem20084.zip
Runtime Python 3.11 standalone, Pyarmor 9.1.7 (trial) runtime files
State files onetime_version.txt, global_version.txt (in the install directory)
Upload name keylogs.gz (multipart field keylogs)
C2 paths /receive_infof /get-onetime-code/ /get-global /downloadables/configf.json
/global_keywords /keywords/ /black-watch/ /black-watch/screenshot/
/infame/receive/zip /infame/receive/dt /ws/ /pwd /upload/st
Download gate /C2/?t=<token>451 (surveillance), /C2/?t=<token>452 (stealer); ZIPs from /p/<32 hex>.zip with X-Verify header
UA string python-requests/2.31.0 (weak, common)
Browser flag --remote-debugging-port=9222 on chrome/msedge/brave/opera/vivaldi
Network check ipinfo.io lookup from a Python image under SystemHealth
# Named malware and tooling Malware family Armorsteal (Zscaler: PS.Dropper.Armorsteal, Python.PWS.Armorsteal) Vendor MDR rules WIN-CONHOST-HEADLESS-CMD, WIN-SCHTASKS-CREATE-SHELL, WIN-PYTHON-CHROMIUM-HEADLESS Obfuscator Pyarmor 9.1.7 trial runtime Browser decryptor public chrome-decryptor tool (Chrome App-Bound Encryption Decryption project), downloaded by the stealer Lure brands fake Chocolatey package manager site, fake Microsoft Activation Scripts (Massgrave) site Real software the genuine Chocolatey installer is run by the Chocolatey lure: do NOT block or prevent it
# Posture audit (run per endpoint, read-only) Get-MpPreference | Select-Object -ExpandProperty ExclusionPath # remove unauthorized entries, especially C:\Windows Get-MpComputerStatus | Select-Object IsTamperProtected, RealTimeProtectionEnabled Get-ScheduledTask -TaskName SysCheck2,SysCheckX -ErrorAction SilentlyContinue Test-Path 'C:\Windows\SystemHealth\Update' # Policy check: PowerShell script block logging enabled? Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' -ErrorAction SilentlyContinue
Detection Validation Gates
| Gate | Check |
|---|---|
| Telemetry ready | Process creation with command line, DNS lookup, outbound connection, file writes and scheduled task registration flow from the Windows fleet (CrowdStrike: ProcessRollup2, DnsRequest, NetworkConnectIP4, PeFileWritten, ScheduledTaskRegistered. Defender: DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents, DeviceEvents) |
| Benign baseline | Run Q6, Q7, Q4, Q5 over 14 days: expect zero hits. Run Q9, Q10 and Q11 and record the legitimate parents (deployment agents, developer automation, security tooling) to build the TUNING exclusions |
| Positive tests | In an isolated lab only: create a harmless folder C:\Windows\SystemHealth\Update containing a renamed copy of cmd.exe and run it (Q6, Q7); register a benign task named SysCheck2 and delete it (Q4, Q5); resolve a published domain from a sandbox (Q1). Never execute the malware or its archives |
| Field check | Confirm TaskName is populated on ScheduledTaskRegistered in your tenant before promoting Q4 (Q5 covers the same behavior by command line); the other fields are standard process, network and DNS fields |
| Promotion | Q1, Q3, Q4, Q5, Q6, Q7, Q8 to scheduled alerts after one clean baseline week; Q2 as a lead generator; Q9, Q10, Q11 stay hunts until tuned |
Hardening: Tiered & Deployable
- Block lure and C2 domains at DNS and the web gateway (MITRE M1021 Restrict Web-Based Content). Verify: resolve each domain from a test client and confirm a block page. Authority: vendor DNS/SWG policy; no formal benchmark covers per-campaign blocks (best-practice).
- Turn on PowerShell script block and module logging (M1047 Audit; CIS Microsoft Windows 11 Enterprise Benchmark, Administrative Templates > Windows PowerShell). Verify:
Get-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging; then CQL/native check for event 4104. - Review and clean Microsoft Defender exclusions; enable Tamper Protection (M1038 Execution Prevention, M1047; Microsoft Defender for Endpoint guidance). Remove any exclusion for
C:\Windows. Verify:Get-MpPreferenceandGet-MpComputerStatus. - ASR rule: block execution of potentially obfuscated scripts (GUID 5BEB7EFE-FD9A-4556-801D-275E5FFC04CC; M1049, M1038). Pilot in audit mode first. Verify:
Get-MpPreference | Select AttackSurfaceReductionRules_Ids, AttackSurfaceReductionRules_Actions.
- Application control deny rule for
%WINDIR%\SystemHealth\*and for Python interpreters outside approved paths (M1038; Microsoft AppLocker/WDAC guidance). Default AppLocker allows everything under the Windows folder, so an explicit deny is needed. Playbook 2. - Remove local administrator rights from standard users so a pasted command cannot self-elevate (M1026 Privileged Account Management, M1018; CIS Controls v8 safeguard 5.4). Verify:
net localgroup administratorsacross a ring. - Disable browser remote debugging by policy (M1054 Software Configuration): Chrome and Edge
RemoteDebuggingAllowedset to 0. Confirm the exact policy name in your ADMX version. Playbook 3. - Audit scheduled task creation (M1047): enable Audit Other Object Access Events and forward event 4698. Playbook 1.
- Give users a sanctioned software path (an internal package repository or endpoint management catalog) so nobody needs a pasted install command from a search result (M1017 User Training, M1021). Add awareness content on verifying download domains. Best-practice, no formal benchmark.
- Move cryptocurrency and credential custody off the browser: hardware-backed wallets, no browser password storage, passkeys or hardware-key MFA for chat and exchange accounts (M1032 Multi-factor Authentication). Best-practice, no formal benchmark.
- Constrained Language Mode with WDAC for standard users to stop interactive download-and-execute one-liners (M1038). Needs a full compatibility review.
Deployable Playbooks
Playbook 1: PowerShell logging, scheduled-task auditing and ASR audit mode
Prerequisites: Local administrator or a GPO/management console; Windows 10/11 or Server 2016+ with Microsoft Defender Antivirus active.
Reboot required: No. Policies apply at next policy refresh.
Rollback: Delete the three registry values and run auditpol /set /subcategory:"Other Object Access Events" /success:disable /failure:disable; run Set-MpPreference -AttackSurfaceReductionRules_Ids 5BEB7EFE-FD9A-4556-801D-275E5FFC04CC -AttackSurfaceReductionRules_Actions Disabled.
# 1. Script block + module logging $p='HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell' New-Item "$p\ScriptBlockLogging" -Force | Out-Null Set-ItemProperty "$p\ScriptBlockLogging" EnableScriptBlockLogging 1 -Type DWord New-Item "$p\ModuleLogging" -Force | Out-Null Set-ItemProperty "$p\ModuleLogging" EnableModuleLogging 1 -Type DWord # 2. Audit scheduled task creation (event 4698) auditpol /set /subcategory:"Other Object Access Events" /success:enable /failure:enable # 3. ASR: obfuscated scripts, AUDIT mode first (switch to Enabled after review) Add-MpPreference -AttackSurfaceReductionRules_Ids 5BEB7EFE-FD9A-4556-801D-275E5FFC04CC -AttackSurfaceReductionRules_Actions AuditMode
Playbook 2: AppLocker audit-mode deny rule for the fake SystemHealth directory
Prerequisites: AppLocker (Enterprise/Education SKUs) with the Application Identity service running; an existing baseline policy. Keep the new rule in audit mode for two weeks.
Reboot required: No. The Application Identity service must be running.
Rollback: Remove the rule from the policy and re-import, or Set-AppLockerPolicy with the previously exported XML (export first with Get-AppLockerPolicy -Local -Xml).
# Export current policy for rollback Get-AppLockerPolicy -Local -Xml | Out-File "$env:TEMP\applocker-before.xml" # Rule fragment to merge into the Exe rule collection (deny, audit via EnforcementMode on the collection) <FilePathRule Id="b6f1d0aa-0000-4000-8000-00000000a501" Name="Deny Armorsteal SystemHealth directory" Description="Fake system directory used by Armorsteal" UserOrGroupSid="S-1-1-0" Action="Deny"> <Conditions><FilePathCondition Path="%WINDIR%\SystemHealth\*" /></Conditions> </FilePathRule>
Playbook 3: Disable browser remote debugging by policy
Prerequisites: Chrome and Microsoft Edge managed by policy; confirm the RemoteDebuggingAllowed policy exists in your installed ADMX version before deploying. Developers who need the port need a scoped exception.
Reboot required: No. Browsers read policy at start.
Rollback: Delete the RemoteDebuggingAllowed value under each browser policy key and restart the browser.
foreach ($k in 'HKLM:\SOFTWARE\Policies\Google\Chrome','HKLM:\SOFTWARE\Policies\Microsoft\Edge') {
New-Item $k -Force | Out-Null
Set-ItemProperty $k RemoteDebuggingAllowed 0 -Type DWord
}
# Verify
Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Google\Chrome','HKLM:\SOFTWARE\Policies\Microsoft\Edge' | Select-Object PSPath, RemoteDebuggingAllowedPlaybook 4: Remove an unauthorized Defender exclusion for C:\Windows and confirm Tamper Protection
Prerequisites: Administrator; approval from the security owner that the exclusion is not required. Record the current exclusion list first.
Reboot required: No.
Rollback: Re-add the exclusion with Add-MpPreference -ExclusionPath <path> using the list saved in step 1 (only if the owner approves).
$before = (Get-MpPreference).ExclusionPath
$before | Out-File "$env:TEMP\defender-exclusions-before.txt"
foreach ($x in $before) { if ($x -match '^(?i)C:\\Windows\\?\*?$') { Remove-MpPreference -ExclusionPath $x } }
(Get-MpPreference).ExclusionPath
(Get-MpComputerStatus).IsTamperProtected
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Network-isolate the host through EDR host containment. Block the lure, C2 and payload domains and IPs at DNS and the gateway. Do not power off before memory capture if policy allows | SOC / IR | Containment timestamp, block change ticket |
| Isolate: by platform | CrowdStrike: network-contain the host. SentinelOne: network quarantine (Disconnect from Network; confirm the label in your console version). Microsoft Defender: Isolate device (full network isolation). Keep the host powered on for memory capture on all of them. | SOC / IR | Containment timestamp, block change ticket |
| Scope | Run Q1 to Q8 fleet-wide for the last 30 days; pull proxy logs for the C2 URL paths; list every user who visited the lure domains | SOC / IR | Host list, user list |
| Credential reset (assume theft) | From a clean device: reset passwords and revoke sessions and tokens for browser-saved accounts, Discord, email, SSH keys, password-manager vault and exchange accounts. Move cryptocurrency to new wallets with new seeds. Invalidate any API keys stored on the host | IAM / affected user | Reset log, revoked session list |
| Eradicate | Preferred: reimage the host. If not: stop and delete tasks SysCheck2 and SysCheckX, kill Python processes under C:\Windows\SystemHealth\Update, delete the directory, remove unauthorized Defender exclusions, then rescan and re-run Q4 to Q7 | IR / endpoint | Before/after listings, hashes of removed files |
| Recover | Return the host to service after a clean 7-day Q1 to Q11 baseline; deploy hardening playbooks 1 to 4; brief the user on verifying install sources | Endpoint / IR | Clean query results, sign-off |
| Preserve | Keep the install directory, scheduled task XML, PowerShell history and browser history for analysis before cleanup | IR | Evidence archive with hashes |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | CrowdStrike coverage | SentinelOne | Defender XDR |
|---|---|---|---|---|---|---|
| T1608.006 / T1071.001 | Lure and C2 domain resolution | Q1 | none | Good | Good, with assumptions | Good, with assumptions |
| T1105 / T1071.001 | Payload host and C2 IP connections | Q2 | none | Partial | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1059.001 / T1059.006 | Known file hashes | Q3 | none | Partial | Partial: Q3 approximate | Partial: Q3 telemetry gap |
| T1053.005 | SysCheck2 / SysCheckX persistence | Q4, Q5 | IOA 2 | Good | Partial: Q4 approximate | Good, with assumptions |
| T1036.005 | Execution and staging in fake SystemHealth directory | Q6, Q7 | IOA 1 | Good | Partial: Q7 approximate | Partial: Q7 approximate |
| T1204.004 | Pasted one-liner naming lure hosts | Q8 | none | Good | Good, with assumptions | Good, with assumptions |
| T1564.003 / T1105 | Hidden-window PowerShell ZIP download | Q9 | none | Partial | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1539 / T1555.003 | Browser CDP launch on 9222 | Q10 | none | Partial | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1562.001 | Defender exclusions | Q11 | IOA 3 | Partial | Partial: same limit as CrowdStrike | Partial: Q11 approximate |
| T1056.001 / T1115 / T1113 / T1565.002 | Keylogging, clipboard hijack, screenshots | none | none | GAP | GAP | GAP |
| T1497 | Cloud-IP sandbox evasion check | none | none | GAP | GAP | GAP |
| T1573 / in-memory tasks | RSA-signed Python tasks executed in memory | none | none | GAP | GAP | GAP |
Known gaps: in-process surveillance and in-memory task execution are outside endpoint telemetry, and URL path indicators need proxy or gateway logs. Validation gates (section 11): telemetry ready, 14-day baseline, lab positive tests, then promotion.
Hunt Summary Ticket
TITLE: Armorsteal SEO-poisoning stealer hunt (fake Chocolatey / Massgrave installers) SEVERITY: High (credential and cryptocurrency theft; indicators single-sourced, none at prevent) SCOPE: Windows endpoints where users install software from search results; web egress; Defender exclusion posture HYPOTHESIS: A user pasted a lure PowerShell command and Armorsteal now runs from C:\Windows\SystemHealth\Update with tasks SysCheck2 / SysCheckX QUERIES: Q1-Q11 (CQL). Highest fidelity: Q4, Q5, Q6, Q7 DO FIRST: run Q6 and Q4 fleet-wide for 30 days; sweep Q1 and Q8 for lure/C2 domains; check Defender exclusions for C:\Windows FINDINGS: (fill in) host / user / first seen / tasks present / modules present GAPS: keylogging, clipboard hijack and in-memory tasks are outside endpoint telemetry; C2 URL paths need proxy or gateway logs; DNS and connection hits may be historical (lure sites down by early October 2026) ACTIONS: contain host, reset credentials and move crypto assets from a clean device, remove tasks and directory or reimage, deploy playbooks 1-4 OWNER: HuntPack VERSION: v0.1 2026-10-30
Changelog
References
| Tier | Source | Used for | Access date |
|---|---|---|---|
| 1 | Zscaler: SEO Poisoning Campaign Delivers New Armorsteal Malware | Chain, persistence, C2, infrastructure timeline, all atomic indicators, detection guidance | 2026-10-30 |
| 3 | SecurityOnline: Armorsteal malware via Python SEO poisoning | Corroboration of guidance only (no IOC values) | 2026-10-30 |
Source snapshots are saved next to this file in Armorsteal-SEOPoisoning-Hunt-sources/. MITRE ATT&CK technique and mitigation identifiers are cited by ID only.