NetamLoader
Executive Summary
NetamLoader is a layered .NET loader that Kaspersky GReAT disclosed on 2026-10-29 (Securelist and a press release, presented at Security Analyst Summit 2026). It was found on a single host running a customized Windows 10 build used only by government organizations in an Asian country. Kaspersky has not attributed it to any known actor and did not see how the package arrived, but the location and contents point to a possible software supply chain attack.
The chain starts with a CAB archive in the Windows update download area. Its seven files unpack into C:\Windows\SoftwareDistribution\Download\Install. When a legitimate, signed PatchMyPC-ScriptRunner.exe starts, a planted PatchMyPC-ScriptRunner.exe.config makes the .NET runtime load a Microsoft-signed assembly, which reads fabric.config, which in turn makes Resources.resx deserialize nested payloads. Layers of encoding, compression and encryption lead to an AMSI patch, a cleanup stage that deletes the dropped files, CLR usage logs, Prefetch files and selected update-log lines, and finally a plugin that runs inside dllhost.exe, started through a remote COM object so that it leaves the original process tree. After a random 20 to 40 minute pause it polls an HTTPS C2 on openaigpt4v.com, which sits on Cloudflare's CDN.
Defensive angle. The files are legitimate or inert, so hashes alone will not carry this hunt. The durable signals are where and how things run: the Patch My PC wrapper and its companion DLLs under SoftwareDistribution\Download\Install, a dllhost.exe that makes outbound connections, DNS for the C2 domain, and gaps in locally kept logs. Kaspersky says Patch My PC and its distribution systems were not shown to be compromised, so none of its files or hashes ship as a block here.
Defender priority: run Q1, Q2 and Q6 over 30 to 90 days now, then check that update and CLR logs are forwarded off the endpoint. Everything in this pack ships at detect. The domain and file hashes come from one vendor report, and the stock Patch My PC and Windows binaries must never be blocked.
Source Review & Web Hunter Notes
| Tier | Source | Key finding | Carry forward | Snapshot |
|---|---|---|---|---|
| 1 | Kaspersky GReAT, Securelist: Unknown APT uses NetamLoader to attack a Win10 OS customized for governments in Asia (2026-10-29) | Full chain, config values, C2 domain and address, hashes, paths, detection names, victim count, attribution statement | yes | 01-kaspersky-securelist.txt |
| 1 | Kaspersky press release, GReAT uncovers attack targeting government-only Windows system in Asia (2026-10-29) | Patch My PC and its distribution not shown to be compromised; possible supply chain; delays; Kaspersky mitigation advice. No IOCs | yes | 02-kaspersky-press-release.txt |
| 2 | The Hacker News, NetamLoader Malware Targets Government Windows 10 Systems in Asia via .NET Injection (2026-11-01) | Secondary summary of the Kaspersky report; lists PatchMyPC-Enum.dll and PatchMyPC-Models.dll. Its guess about the country is its own and is not carried forward | partial | 03-thn.txt |
| 1 | Patch My PC, What Is Patch My PC ScriptRunner? (vendor knowledge base, undated) | What the legitimate wrapper does, that it is signed, and its normal staging folders (CCMCache, IMECache); advises ASR exclusions by binary name | partial | 04-patchmypc-scriptrunner-kb.txt |
| 2 | MITRE ATT&CK T1574.014 AppDomainManager | Technique definition; M1022 mitigation | partial | 05-mitre-t1574-014.txt |
| 2 | MITRE ATT&CK T1685 Disable or Modify Tools (T1562.001 now redirects here in v20) | AMSI patching context; M1047, M1038, M1022, M1024, M1054, M1018 | partial | 06-mitre-t1685.txt |
| 2 | MITRE ATT&CK T1070 Indicator Removal | Log and Prefetch deletion; M1029, M1022, M1041 | partial | 07-mitre-t1070.txt |
| 2 | MITRE ATT&CK T1559.001 Component Object Model | COM abuse; M1048, M1026 (AppID registry permissions) | partial | 08-mitre-t1559-001.txt |
| 2 | MITRE ATT&CK T1497.003 Time Based Checks | Sandbox delay technique | partial | 09-mitre-t1497-003.txt |
| 2 | MITRE ATT&CK T1620 Reflective Code Loading | In-memory loading technique | partial | 10-mitre-t1620.txt |
Decisions and judgment calls
- One technical origin. The Hacker News and the press release restate the Securelist report, so they are not independent confirmation. Nothing in this pack is therefore eligible for prevent or critical severity.
- Cloudflare is hosting, not fronting. Securelist says the C2 address belongs to Cloudflare's CDN and the domain is registered with Cloudflare. It does not describe domain fronting, so this pack does not claim it.
- Country not named by Kaspersky. Kaspersky says only "an Asian country". The Hacker News speculates about which one. That is not carried into this pack.
- Delivery and persistence unknown. Kaspersky did not observe how the CAB arrived and describes no persistence mechanism. The plugin ends itself after roughly 28 hours (config value wi of 100801 seconds), so a restart would need a fresh delivery.
- CAB name length. Securelist prints the CAB name as 40 hex characters. The Hacker News prints 39. The Securelist value is used.
- Hash reuse. The report lists MD5 values only. Seven of the eleven belong to in-memory payloads that normally never touch disk, so only the four on-disk items go into the import CSV.
- Fetched content. No fetched page contained text directed at the analyst or the pipeline.
Hunt Brief & Attack Chain
Hypotheses
| # | Hypothesis | MITRE | Telemetry | Confidence | Queries |
|---|---|---|---|---|---|
| H1 | The Patch My PC wrapper ran from the Windows update download folder, which is not where the product stages its files. | T1574.014, T1195 | Process creation with command line and image path (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | HIGH | Q1 |
| H2 | Wrapper and companion DLLs were extracted into SoftwareDistribution\Download\Install. | T1574.014, T1195 | Executable or DLL written with target path (CrowdStrike: PeFileWritten, NewExecutableWritten. SentinelOne: File Creation. Defender: DeviceFileEvents) | HIGH | Q2 |
| H3 | Something else executes from the update download folder. | T1574.014, T1195 | Process creation with image path (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | MEDIUM | Q3 |
| H4 | A dllhost.exe was started for the remote COM object or the DllSurrogate AppID the report names. | T1559.001 | Process creation with command line (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | MEDIUM | Q4 |
| H5 | dllhost.exe polled an external address over HTTPS. | T1071.001, T1497.003 | Outbound network connection with initiating process (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents) | MEDIUM | Q5 |
| H6 | A host resolved the C2 domain. | T1071.001 | DNS lookup with requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents, DNS action type) | HIGH | Q6 |
| H7 | A DllSurrogate value was written under a COM AppID key. | T1559.001, T1112 | Registry value set (CrowdStrike: RegGenericValueUpdate. SentinelOne: Registry Value Modified. Defender: DeviceRegistryEvents) | LOW | Q7 |
Attack chain (as reported by Kaspersky)
| Step | Behavior | Telemetry | Hunt angle |
|---|---|---|---|
| 1 | Delivery of a CAB into SoftwareDistribution\Download. Not observed by Kaspersky; possible supply chain | Outside endpoint telemetry for the delivery itself. Update-channel and software-distribution logs | Native N1, N4; hardening tier 3 |
| 2 | Seven files extracted to Download\Install; PatchMyPC-ScriptRunner.exe started with -updatepackage -applicationid= | Process creation and executable write (see H1, H2) | Q1, Q2, Q3 |
| 3 | Runtime reads PatchMyPC-ScriptRunner.exe.config, loads Microsoft.ApplicationServer.Caching.DataCacheFactoryConfiguration from Microsoft.HostIntegration.Connectors, which reads fabric.config, which names Resources.resx | Inside the process. Config and resx are not PE files, so no executable-write event | Gap for the file writes: native N1, N2. Process side: Q1 |
| 4 | Resources.resx deserializes a TypeConfuseDelegate chain into XamlReader.Parse; XAML-1 decrypts XAML-2 using a key derived from the host command line | In memory | Gap: in-process only |
| 5 | XAML-2-1 patches AMSI (CAmsiAntimalware::Scan pointer redirected to a patched Console.Clear stub) | In memory | Gap: in-process only |
| 6 | .NET 1 Loader deletes dropped files, CLR usage log, Prefetch entries, CmitUpdateAgent log lines, and registry exception-state keys | File delete and log tamper. No verified delete event used here | Gap in queries; native N3 |
| 7 | Remote COM object created over RPC; XAML-3 loads in dllhost.exe; COMPlus_ETWEnabled set to 0 first | Process creation of dllhost.exe; DllSurrogate registry value | Q4, Q7 |
| 8 | .NET 2 Plugin waits 20 to 40 minutes, then polls the C2 over HTTPS; 5 to 6 minute loop on success, 22 to 28 hours on failure; exits after about 28 hours | Outbound connection and DNS from dllhost.exe | Q5, Q6, native N4 |
Consolidated IOC Table
| Type | Value | Confidence | Action | Context |
|---|---|---|---|---|
| domain | openaigpt4v.com | high | detect | C2 domain in the plugin configuration (URL path /model_v4/ plus an instance GUID). Cloudflare-hosted. Securelist, repeated by THN. Single origin. |
| ipv4 | 104.18.14.219 | low | enrich only | Address Securelist lists for the C2 domain. It sits in Cloudflare's CDN range and is shared with other sites, so it is not imported and not blocked. |
| md5 | f2458ed133f8d0ca19e22c10f577e98e | medium | detect | CAB archive saved as e4537a1762fcafd2fd96bfc825bad34f9db9a24d under SoftwareDistribution\Download. Single source. |
| md5 | e1dc6f1ed193594f2284be22ae08dfa4 | medium | detect | Resources.resx, the only file in the CAB that carries malicious content |
| md5 | 67e3617f8709d34f806f8e02badba8a5 | medium | detect | fabric.config, crafted to name Resources.resx. Not a stock file |
| md5 | 514ac0d73b8a1b73488ef5e2e55a75c3 | medium | detect | PatchMyPC-ScriptRunner.exe.config with appDomainManager settings. Not the vendor's config |
| md5 | 514117d536e347560d049cfe0c609632 9000820d8153c24cd60d53001d3a97b6 b3c4a635179680edafb5d006cf0cffc6 4f28622c5e60c01319bf48cf742fe1a5 c86d53392a87dd3f3e62d62c9b92a9bd ab81564bcb8ca7e710c66832de747556 cfb8b281bdf5d9390e64b10c312fb2c1 | medium | hunt | In-memory payloads (XAML-1, XAML-2, XAML-2-1, XAML-2-2, XAML-3, .NET 1 Loader, .NET 2 Plugin, in that order). Not expected on disk; kept for memory or sandbox scanning. Not imported |
| filename | PatchMyPC-ScriptRunner.exe.config | high | hunt | Planted config beside the wrapper. Look for an appDomainManagerAssembly element in it |
| filename | fabric.config Resources.resx | high | hunt | Dropped beside the wrapper; deleted by the loader |
| filename | PatchMyPC-ScriptRunner.exe | low | hunt (legitimate) | Stock signed product file. Context only: the folder it runs from is the signal. Never prevent |
| filename | Microsoft.HostIntegration.Connectors.dll | low | hunt (legitimate) | Microsoft-signed assembly abused as the AppDomainManager host. Never prevent |
| path | C:\Windows\SoftwareDistribution\Download\Install | high | hunt | Extraction folder for the CAB contents |
| path | %LOCALAPPDATA%\Microsoft\CLR_v4.0\UsageLogs\PatchMyPC-ScriptRunner.exe.log %WINDIR%\Prefetch\PATCHMYPC-SCRIPTRUNNER.EXE-*.pf | medium | hunt | Deleted by the loader. Absence after a recorded execution is the signal |
| path | %WINDIR%\Logs\CmitUpdateAgent\20????\20?????? | medium | hunt | Lines containing GetExtendedUpdateInfoHandler are filtered out. Path belongs to the customized build |
| registry | HKLM\SOFTWARE\Classes\AppID\{ff259bcd-2f0a-44de-b34a-0092fec1d8a2} DllSurrogate | low | hunt | AppID derived from the C2 URL, so specific to this sample. Other nine exception-state GUIDs exist per C2 |
| clsid | ab8902b4-09ca-4bb6-b78d-a8f59079a8d5 | medium | hunt | Remote COM object created over RPC to start dllhost.exe |
| url path | /model_v4/94aafd58-c00c-402a-aae6-858b4486a1a4 | medium | hunt | Request path shape with instance GUID. Search proxy logs for /model_v4/ on the C2 host. Query string carries b= and c= parameters |
| string | GetExtendedUpdateInfoHandler | medium | hunt | Substring the loader strips from update logs. Missing lines in logs that normally have them |
Nothing here ships at prevent. Every indicator traces to one vendor report, the hashes are of inert or in-memory content, and the two PatchMyPC and Microsoft filenames are legitimate signed files. Kaspersky found a single victim, so expect low coverage and perishable infrastructure.
Affected Surface & Telemetry Matrix
| Surface | Telemetry (plain terms, then platforms) | Priority | Gap risk |
|---|---|---|---|
| Windows 10 endpoints, especially customized or government builds with a non-standard update agent | Process creation with command line, path and parent (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents) | CRITICAL | Low where the sensor runs. Sensor support on a customized OS build must be confirmed |
| Update download folder writes | Executable or DLL written with target path (CrowdStrike: PeFileWritten, NewExecutableWritten. SentinelOne: File Creation. Defender: DeviceFileEvents) | HIGH | Medium: .config and .resx files are not PE files and have no verified write event used here |
| COM surrogate (dllhost.exe) network behavior | Outbound network connection with initiating process (CrowdStrike: NetworkConnectIP4. SentinelOne: IP Connect. Defender: DeviceNetworkEvents) | HIGH | Medium: shared CDN addresses make the destination weak on its own |
| DNS | DNS lookup with requesting process (CrowdStrike: DnsRequest. SentinelOne: DNS Resolved. Defender: DeviceEvents, DNS action type) | HIGH | Medium: encrypted DNS on the endpoint blinds it; resolver logs are the second source |
| COM AppID registry | Registry value set (CrowdStrike: RegGenericValueUpdate. SentinelOne: Registry Value Modified. Defender: DeviceRegistryEvents) | MEDIUM | High: sensor coverage of Classes\AppID is unconfirmed |
| In-process activity (AMSI patch, XAML loading, ETW disable) | No endpoint event for these | HIGH | High: outside endpoint telemetry; covered only by behavior around them |
| Local logs the loader edits (CLR usage logs, Prefetch, update agent logs) | File and log forwarded off the endpoint (SIEM or log pipeline) | HIGH | High unless logs are forwarded before the loader runs |
| Network egress | Proxy, firewall and resolver logs for the C2 host and a /model_v4/ URL path | MEDIUM | Depends on TLS inspection and log retention |
ATT&CK Mapping
| Tactic | Technique | Observed behavior | Query or control |
|---|---|---|---|
| Initial Access | T1195 Supply Chain Compromise (possible, per Kaspersky) | CAB placed in the Windows update download folder; Patch My PC components used; delivery not observed | Q1, Q2, Q3; hardening tier 3. Gap for delivery |
| Defense Evasion | T1574.014 Hijack Execution Flow: AppDomainManager | Planted PatchMyPC-ScriptRunner.exe.config names a Microsoft-signed manager assembly | Q1, Q2, Q3; M1022; N1 |
| Execution | T1559.001 Component Object Model | Remote COM object created over RPC to start dllhost.exe and load XAML-3 | Q4, Q7; M1048, M1026 |
| Defense Evasion | T1620 Reflective Code Loading | Payloads decoded and loaded as XAML and .NET assemblies in memory | Gap: in-process only |
| Defense Evasion | T1562.001 (now T1685) Disable or Modify Tools | AMSI Scan pointer redirected; COMPlus_ETWEnabled set to 0 | Gap: in-process only; M1038, M1047 |
| Defense Evasion | T1070 Indicator Removal | Deletes dropped files, CLR usage logs, Prefetch files and update-log lines | N3; M1029 |
| Defense Evasion | T1497.003 Time Based Checks | Random 20 to 40 minute pause before first C2 contact | Q5 (long-window view); sandbox runs must exceed 40 minutes |
| Command and Control | T1071.001 Web Protocols | HTTPS polling of openaigpt4v.com from dllhost.exe | Q5, Q6, N4 |
Native Audit-Log Hunts
| ID | Source | Hunt | Why |
|---|---|---|---|
| N1 | Endpoint file inventory (PowerShell, read-only) | List everything under C:\Windows\SoftwareDistribution\Download with extension .exe, .dll, .config or .resx; hash with MD5 and compare to section 10. Search for *.exe.config files that contain appDomainManagerAssembly. Command in playbook P1. | Closes the gap on non-PE files. The loader deletes its own files, so a clean result after a hit window proves little. |
| N2 | Sysmon (where deployed) | Event ID 1 where Image ends in PatchMyPC-ScriptRunner.exe and the path contains SoftwareDistribution; Event ID 11 for the three non-PE file names in Download\Install; Event ID 12 and 13 for DllSurrogate under Classes\AppID; Event ID 22 for the C2 domain. | Mirrors Q1, Q2, Q7 and Q6 and covers file writes the sensor query cannot see. |
| N3 | Windows logs and file system | (a) For any recorded ScriptRunner execution, check whether the matching Prefetch file and the CLR usage log exist; their absence fits the cleanup stage. (b) On the customized build, compare CmitUpdateAgent logs with the central copy for missing lines containing GetExtendedUpdateInfoHandler. (c) Look for ETW or AMSI telemetry from the host that stops after a ScriptRunner start. | The only direct evidence of anti-forensics. Needs off-endpoint copies to compare against (hardening tier 1). |
| N4 | Proxy, firewall, resolver | Requests to openaigpt4v.com and any URL path beginning /model_v4/ with a GUID segment. With TLS inspection, look for query parameters b and c. Also look for a host that connects every 5 to 6 minutes after a 20 to 40 minute delay, or once every 22 to 28 hours. | Network view independent of the endpoint sensor. The C2 address is shared, so key on the host and path, not the address. |
| N5 | Update channel and software distribution logs | Review how any package reached SoftwareDistribution\Download around the first sighting: which service wrote it, which management console or share served it, and its signature. | Delivery is unknown. Kaspersky recommends auditing update channels and verifying package origin. |
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: Process creation of the stock Patch My PC wrapper from a path under SoftwareDistribution\Download. Kaspersky saw the loader chain start only when the wrapper ran from Download\Install.
Known limitations and deployment notes
- PowerQuery: A process the agent did not observe starting is not captured.
- PowerQuery assumes: tgt.process.image.sha256 is populated on Process Creation events on this tenant.
- PowerQuery assumes: tgt.process.image.path holds the full Windows path of the created 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: PatchMyPC-ScriptRunner.exe launched from the Windows update download folder // MITRE: T1574.014, T1195 // CONF: high FP: low COST: low // REQUIRES: process creation with image path and command line (ProcessRollup2) // FALSE POSITIVES: none expected. Patch My PC documents CCMCache and IMECache as its staging folders, not SoftwareDistribution. // SOURCE: Securelist 2026-10-29 (observed command line used -updatepackage from the Install subfolder) // NOTE: the binary is a legitimate signed product file. Detect only, never prevent. // LOOKBACK: 30d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^PatchMyPC-ScriptRunner\.exe$/i | ImageFileName=/\\SoftwareDistribution\\Download\\/i | table([@timestamp, ComputerName, aid, UserName, FileName, ImageFileName, CommandLine, ParentBaseFileName, SHA256HashData])
3 required fields · View requirements
Required telemetry
- SentinelOne Process Creation events
Required fields
event.type: Event typetgt.process.name: Created process nametgt.process.image.path: Created process path
Recommended fields
tgt.process.cmdline: Created command line
Optional fields
tgt.process.image.sha256: Hash. Population varies by tenant.
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' and tgt.process.name = * | limit 5 | columns event.type, tgt.process.name, tgt.process.image.path
// HUNT: PatchMyPC-ScriptRunner.exe launched from the Windows update download folder
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1574.014, T1195
// CONF: high | FP: low
// FALSE POSITIVES: none expected. Patch My PC documents CCMCache and IMECache as its staging folders, not SoftwareDistribution.
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: the Patch My PC script runner image name running from the Windows update download folder. On a Process Creation event the created process is tgt.process.*, so name and path are read there. The name test is case-insensitive like the CQL /i.
// REQUIRES: SentinelOne Process Creation events with the created process image path and command line
// TUNING: None expected. The binary is legitimate, so this stays a detect-only hunt, never a block.
// LOOKBACK: 30d
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A process the agent did not observe starting is not captured.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' and tgt.process.name in:anycase ('PatchMyPC-ScriptRunner.exe') and tgt.process.image.path matches '\\\\SoftwareDistribution\\\\Download\\\\'
| columns event.time, endpoint.name, agent.uuid, src.process.user, tgt.process.name, tgt.process.image.path, tgt.process.cmdline, src.process.name, tgt.process.image.sha256
| limit 200Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameFolderPath: Created process path
Recommended fields
ProcessCommandLine: Created command line
Optional fields
SHA256: 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, FileName, FolderPath | take 5
// HUNT: PatchMyPC-ScriptRunner.exe launched from the Windows update download folder // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1574.014, T1195 // CONF: high | FP: low // FALSE POSITIVES: none expected. Patch My PC documents CCMCache and IMECache as its staging folders, not SoftwareDistribution. // EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate on DeviceProcessEvents: the runner image name from the update download folder. =~ and contains are case-insensitive, which reproduces the CQL /i intent. The parent is InitiatingProcessFileName. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) // TUNING: None expected. The binary is legitimate, so this stays a detect-only hunt. // LOOKBACK: 30d (Advanced Hunting retention) // 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(30d) | where FileName =~ "PatchMyPC-ScriptRunner.exe" | where FolderPath contains "\\SoftwareDistribution\\Download\\" | project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, FolderPath, ProcessCommandLine, InitiatingProcessFileName, SHA256 | sort by Timestamp desc | take 200
Looks for: PE writes of ScriptRunner and its companion DLLs into SoftwareDistribution\Download\Install. The three non-PE files (.config, .resx) have no verified Falcon write event, so this hunts the PE half of the CAB extraction. See native hunt N1 for the rest.
Known limitations and deployment notes
- PowerQuery: A write the agent did not report as a file event is not captured, and the CQL's PE-specific event type has no separate equivalent.
- PowerQuery assumes: File Creation and File Modification stand in for the CQL's PE-write events; the .exe or .dll name keeps the population to executables.
- PowerQuery assumes: tgt.file.path holds the full target path including the file name.
- KQL – Defender XDR: The CQL's PE-specific write event has no separate equivalent; the executable name is the filter.
- KQL – Defender XDR assumes: FileCreated and FileModified stand in for the CQL's PE-write events.
- KQL – Defender XDR assumes: FolderPath holds the folder of the written file.
// HUNT: Patch My PC and Microsoft connector components extracted into SoftwareDistribution\Download\Install // MITRE: T1574.014, T1195 // CONF: high FP: low COST: low // REQUIRES: PE file write events with target path (PeFileWritten, NewExecutableWritten) // FALSE POSITIVES: none expected for these names in this folder. // SOURCE: Securelist 2026-10-29 (files extracted from the CAB to Download\Install); THN 2026-11-01 (PatchMyPC-Enum.dll, PatchMyPC-Models.dll) // NOTE: names are legitimate product files. The folder is the signal. Detect only. // LOOKBACK: 30d #event_simpleName=/PeFileWritten|NewExecutableWritten/ | TargetFileName=/\\SoftwareDistribution\\Download\\Install\\/i | FileName=/^(PatchMyPC-ScriptRunner|PatchMyPC-Enum|PatchMyPC-Models|Microsoft\.HostIntegration\.Connectors)\.(exe|dll)$/i | table([@timestamp, ComputerName, aid, FileName, TargetFileName, ContextBaseFileName, SHA256HashData])
2 required fields · View requirements
Required telemetry
- SentinelOne File Creation and File Modification events
Required fields
event.type: Event typetgt.file.path: Target file path
Recommended fields
src.process.name: Writing process
Optional fields
tgt.file.sha256: Hash. Population varies by tenant.
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'File Creation' and tgt.file.path = * | limit 5 | columns event.type, tgt.file.path
// HUNT: Patch My PC and Microsoft connector components extracted into SoftwareDistribution\Download\Install
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1574.014, T1195
// CONF: high | FP: low
// FALSE POSITIVES: none expected for these names in this folder.
// EQUIVALENCE: equivalent-with-schema-assumptions - Same file-write predicate: the four loader component names written under the update download Install folder. The CQL's PE-write event is approximated by File Creation and File Modification on a .exe or .dll name. On a File event the acting process is src.process.*.
// REQUIRES: SentinelOne File Creation and File Modification events with the target path
// TUNING: None expected for these names in this folder. The names are legitimate product files, so the folder is the signal. Detect only.
// LOOKBACK: 30d
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: A write the agent did not report as a file event is not captured, and the CQL's PE-specific event type has no separate equivalent.
// VALIDATION LEVEL: static-reviewed
event.type in ('File Creation', 'File Modification') and tgt.file.path matches '\\\\SoftwareDistribution\\\\Download\\\\Install\\\\(.*\\\\)?(PatchMyPC-ScriptRunner|PatchMyPC-Enum|PatchMyPC-Models|Microsoft\\.HostIntegration\\.Connectors)\\.(exe|dll)$'
| columns event.time, endpoint.name, agent.uuid, tgt.file.path, src.process.name, tgt.file.sha256
| limit 200Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceFileEvents
Required fields
Timestamp: Event timeActionType: ActionFileName: File nameFolderPath: File folder
Recommended fields
InitiatingProcessFileName: Writing process
Optional fields
SHA256: Hash. Population varies by tenant.
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceFileEvents | where Timestamp > ago(1d) | project Timestamp, ActionType, FileName, FolderPath | take 5
// HUNT: Patch My PC and Microsoft connector components extracted into SoftwareDistribution\Download\Install
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1574.014, T1195
// CONF: high | FP: low
// FALSE POSITIVES: none expected for these names in this folder.
// EQUIVALENCE: equivalent-with-schema-assumptions - Same file-write predicate on DeviceFileEvents: the four component names in the update download Install folder. FileCreated and FileModified stand in for the CQL's PE-write events, and the executable name keeps the population to PE files.
// REQUIRES: Defender for Endpoint file telemetry (DeviceFileEvents)
// TUNING: None expected for these names in this folder. Detect only.
// LOOKBACK: 30d (Advanced Hunting retention)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The CQL's PE-specific write event has no separate equivalent; the executable name is the filter.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceFileEvents
| where Timestamp >= ago(30d)
| where ActionType in ("FileCreated", "FileModified")
| where FolderPath contains "\\SoftwareDistribution\\Download\\Install\\"
| where FileName matches regex @"(?i)^(PatchMyPC-ScriptRunner|PatchMyPC-Enum|PatchMyPC-Models|Microsoft\.HostIntegration\.Connectors)\.(exe|dll)$"
| project Timestamp, ReportId, DeviceId, DeviceName, FileName, FolderPath, InitiatingProcessFileName, SHA256
| sort by Timestamp desc
| take 200Looks for: Generic supply-chain angle from the Kaspersky advice to allow third-party management tools only from approved locations: every image that executes from SoftwareDistribution\Download, counted, rarest first. FP: Some servicing and driver packages briefly execute from Download. Baseline 30 days and exclude verified, vendor-signed installers by FileName.
Known limitations and deployment notes
- PowerQuery: Servicing stack and driver installers that run from the Download folder are expected noise until baselined.
- PowerQuery assumes: estimate_distinct is an approximate host count, which is enough for ranking.
- PowerQuery assumes: tgt.process.image.path holds the full Windows path of the created image.
- KQL – Defender XDR: Servicing stack and driver installers that run from the Download folder are expected noise until baselined.
- KQL – Defender XDR assumes: FolderPath holds the full image path of the created process.
// HUNT: executables running from SoftwareDistribution\Download, rarest first // MITRE: T1574.014, T1195 // CONF: medium FP: medium COST: medium // REQUIRES: process creation with image path (ProcessRollup2) // FALSE POSITIVES: servicing stack and driver or firmware installers that unpack and run from the Download folder // TUNING: baseline 30 days, then exclude verified signed installers, for example add: | FileName!=/^(approved-installer-1|approved-installer-2)\.exe$/i // SOURCE: Kaspersky press release 2026-10-29 (monitor update folders for unexpected executables) // LOOKBACK: 30d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ImageFileName=/\\SoftwareDistribution\\Download\\/i | groupBy([FileName, ImageFileName], function=[count(aid, distinct=true, as=Hosts), count(as=Runs), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)]) | sort(Runs, order=asc, limit=200)
5 required fields · View requirements
Required telemetry
- SentinelOne Process Creation events
Required fields
event.type: Event typetgt.process.name: Created process nametgt.process.image.path: Created process pathagent.uuid: Host identifierevent.time: Event time
Readiness test
event.type = 'Process Creation' and tgt.process.image.path = * | limit 5 | columns event.type, event.time, agent.uuid, tgt.process.name, tgt.process.image.path
// HUNT: executables running from SoftwareDistribution\Download, rarest first // SCHEMA PROFILE: sentinelone-endpoint-ocsf // MITRE: T1574.014, T1195 // CONF: medium | FP: medium // FALSE POSITIVES: servicing stack and driver or firmware installers that unpack and run from the Download folder // EQUIVALENCE: equivalent-with-schema-assumptions - Same rarity view: every process created from the update download folder, grouped by name and path with host and run counts, rarest first. The created process is tgt.process.* on a Process Creation event. // REQUIRES: SentinelOne Process Creation events with the created process image path // TUNING: Baseline 30 days, then exclude verified signed installers by tgt.process.name. // LOOKBACK: 30d // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Servicing stack and driver installers that run from the Download folder are expected noise until baselined. // VALIDATION LEVEL: static-reviewed event.type = 'Process Creation' and tgt.process.image.path matches '\\\\SoftwareDistribution\\\\Download\\\\' | group hosts = estimate_distinct(agent.uuid), runs = count(), first_seen = min(event.time), last_seen = max(event.time) by tgt.process.name, tgt.process.image.path | sort +runs | limit 200
Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceProcessEvents
Required fields
Timestamp: Event timeFileName: Created process nameFolderPath: Created process pathDeviceId: Host identifier
Readiness test
DeviceProcessEvents | where Timestamp > ago(1d) | project Timestamp, DeviceId, FileName, FolderPath | take 5
// HUNT: executables running from SoftwareDistribution\Download, rarest first // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1574.014, T1195 // CONF: medium | FP: medium // FALSE POSITIVES: servicing stack and driver or firmware installers that unpack and run from the Download folder // EQUIVALENCE: equivalent-with-schema-assumptions - Same rarity view on DeviceProcessEvents: processes created from the update download folder, grouped by name and path with host and run counts, rarest first. // REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents) // TUNING: Baseline 30 days, then exclude verified signed installers by FileName. // LOOKBACK: 30d (Advanced Hunting retention) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Servicing stack and driver installers that run from the Download folder are expected noise until baselined. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0 DeviceProcessEvents | where Timestamp >= ago(30d) | where FolderPath contains "\\SoftwareDistribution\\Download\\" | summarize Hosts = dcount(DeviceId), Runs = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by FileName, FolderPath | sort by Runs asc | take 200
Looks for: dllhost.exe whose command line carries the remote COM object CLSID or the AppID GUID that Securelist names. Kaspersky does not print the dllhost command line, so the format is inferred from how COM surrogates are launched. The AppID GUID is derived from the C2 URL, so it matches this sample only.
Known limitations and deployment notes
- PowerQuery: The command line format is inferred, not published, so a zero result does not clear a host.
- PowerQuery assumes: The command line carries the GUID as Processid:{GUID}, the format the CQL tab also assumes.
- PowerQuery assumes: tgt.process.image.sha256 is populated on Process Creation events on this tenant.
- KQL – Defender XDR: The command line format is inferred, not published, so a zero result does not clear a host.
- KQL – Defender XDR assumes: The command line carries the GUID as Processid:{GUID}, the format the CQL tab also assumes.
// HUNT: dllhost.exe launched for the NetamLoader remote COM object or its DllSurrogate AppID // MITRE: T1559.001, T1574.014 // CONF: medium FP: low COST: low // REQUIRES: process creation with command line (ProcessRollup2) // FALSE POSITIVES: none expected for these two GUIDs. A miss does not clear a host: the command line format is inferred, not published. // SOURCE: Securelist 2026-10-29 (remote COM object CLSID used to start dllhost.exe; DllSurrogate AppID written by the .NET 2 Plugin) // NOTE: dllhost.exe is a Windows binary. Detect only, never prevent. // LOOKBACK: 30d #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | FileName=/^dllhost\.exe$/i | CommandLine=/Processid:\{(ab8902b4-09ca-4bb6-b78d-a8f59079a8d5|ff259bcd-2f0a-44de-b34a-0092fec1d8a2)\}/i | table([@timestamp, ComputerName, aid, UserName, FileName, CommandLine, ParentBaseFileName, SHA256HashData])
3 required fields · View requirements
Required telemetry
- SentinelOne Process Creation events
Required fields
event.type: Event typetgt.process.name: Created process nametgt.process.cmdline: Created command line
Recommended fields
src.process.name: Creating process
Optional fields
tgt.process.image.sha256: Hash. Population varies by tenant.
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Process Creation' and tgt.process.name = * | limit 5 | columns event.type, tgt.process.name, tgt.process.cmdline
// HUNT: dllhost.exe launched for the NetamLoader remote COM object or its DllSurrogate AppID
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1559.001, T1574.014
// CONF: medium | FP: low
// FALSE POSITIVES: none expected for these two GUIDs. A miss does not clear a host: the command line format is inferred, not published.
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate: dllhost.exe whose command line carries either of the two GUIDs from the report. contains is case-insensitive like the CQL /i. The created process is tgt.process.*.
// REQUIRES: SentinelOne Process Creation events with the created process command line
// TUNING: None expected for these two GUIDs. A miss does not clear a host: the command line format is inferred, not published.
// LOOKBACK: 30d
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The command line format is inferred, not published, so a zero result does not clear a host.
// VALIDATION LEVEL: static-reviewed
event.type = 'Process Creation' and tgt.process.name in:anycase ('dllhost.exe') and (tgt.process.cmdline contains 'Processid:{ab8902b4-09ca-4bb6-b78d-a8f59079a8d5}' or tgt.process.cmdline contains 'Processid:{ff259bcd-2f0a-44de-b34a-0092fec1d8a2}')
| columns event.time, endpoint.name, agent.uuid, src.process.user, tgt.process.name, tgt.process.cmdline, src.process.name, tgt.process.image.sha256
| limit 200Automated 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: Initiating process
Optional fields
SHA256: 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, FileName, ProcessCommandLine | take 5
// HUNT: dllhost.exe launched for the NetamLoader remote COM object or its DllSurrogate AppID
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1559.001, T1574.014
// CONF: medium | FP: low
// FALSE POSITIVES: none expected for these two GUIDs. A miss does not clear a host: the command line format is inferred, not published.
// EQUIVALENCE: equivalent-with-schema-assumptions - Same process-creation predicate on DeviceProcessEvents: dllhost.exe whose command line carries either GUID. =~ and contains are case-insensitive, which reproduces the CQL /i intent. The parent is InitiatingProcessFileName.
// REQUIRES: Defender for Endpoint process-creation telemetry (DeviceProcessEvents)
// TUNING: None expected for these two GUIDs. A miss does not clear a host: the command line format is inferred, not published.
// LOOKBACK: 30d (Advanced Hunting retention)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: The command line format is inferred, not published, so a zero result does not clear a host.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceProcessEvents
| where Timestamp >= ago(30d)
| where FileName =~ "dllhost.exe"
| where ProcessCommandLine contains "Processid:{ab8902b4-09ca-4bb6-b78d-a8f59079a8d5}" or ProcessCommandLine contains "Processid:{ff259bcd-2f0a-44de-b34a-0092fec1d8a2}"
| project Timestamp, ReportId, DeviceId, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, SHA256
| sort by Timestamp desc
| take 200Looks for: The implant polls its C2 from inside dllhost.exe: 5 to 6 minutes between requests while the server answers, 22 to 28 hours when it does not. A COM surrogate with external HTTPS is rare, so low-count destinations are the rows to read. FP: Some COM surrogates reach Microsoft or CDN endpoints. Baseline 30 days and exclude known-good destinations; keep the rare ones.
Known limitations and deployment notes
- PowerQuery: COM surrogate hosts for Microsoft services and security components also connect outward until baselined.
- PowerQuery assumes: src.process.name is the connecting process on IP Connect events, per the field model.
- PowerQuery assumes: IPv6 destinations are not excluded by the IPv4 range tests and may appear.
- KQL – Defender XDR: COM surrogate hosts for Microsoft services and security components also connect outward until baselined.
- KQL – Defender XDR assumes: RemoteIPType is populated, and Public excludes private, loopback and link-local addresses.
- KQL – Defender XDR assumes: IPv6 public destinations are included, unlike the IPv4-only CQL.
// HUNT: dllhost.exe outbound connections to non-private addresses, rare destinations first // MITRE: T1071.001, T1497.003, T1559.001 // CONF: medium FP: medium COST: medium // REQUIRES: outbound network connection events with initiating process (NetworkConnectIP4) // FALSE POSITIVES: COM surrogate hosts for Microsoft services, thumbnail or preview handlers that fetch content, endpoint security components // TUNING: baseline 30 days, then exclude known-good destinations, for example add: | !cidr(RemoteAddressIP4, subnet=["203.0.113.0/24"]) with your own approved ranges // SOURCE: Securelist 2026-10-29 (.NET 2 Plugin polls over HTTPS from dllhost.exe; initial 20 to 40 minute delay; 5 to 6 minute poll; 22 to 28 hour retry) // NOTE: the C2 sits behind a shared CDN address, so do not treat the destination address alone as an indicator. // LOOKBACK: 7d #event_simpleName=NetworkConnectIP4 | ContextBaseFileName=/^dllhost\.exe$/i | !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16"]) | groupBy([aid, ComputerName, RemoteAddressIP4, RemotePort], function=[count(as=Conns), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)]) | sort(Conns, order=asc, limit=200)
6 required fields · View requirements
Required telemetry
- SentinelOne IP Connect events
Required fields
event.type: Event typesrc.process.name: Connecting processdst.ip.address: Destination addressdst.port.number: Destination portagent.uuid: Host identifierevent.time: Event time
Recommended fields
endpoint.name: Hostname
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'IP Connect' and dst.ip.address = * | limit 5 | columns event.type, event.time, agent.uuid, src.process.name, dst.ip.address, dst.port.number
// HUNT: dllhost.exe outbound connections to non-private addresses, rare destinations first
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1071.001, T1497.003, T1559.001
// CONF: medium | FP: medium
// FALSE POSITIVES: COM surrogate hosts for Microsoft services, thumbnail or preview handlers that fetch content, endpoint security components
// EQUIVALENCE: equivalent-with-schema-assumptions - Same analytic: dllhost.exe outbound connections to non-private addresses, grouped by host and destination, rarest first. On an IP Connect event the acting process is src.process.*. The CQL's five excluded ranges are reproduced by the RFC 1918 test plus loopback and link-local subnet tests.
// REQUIRES: SentinelOne IP Connect events with the acting process name
// TUNING: Baseline 30 days on the CQL tab, then exclude known-good destinations with a net_ipsubnet test on dst.ip.address using your own approved ranges. The C2 sits behind a shared CDN address, so the destination alone is not an indicator.
// LOOKBACK: 7d
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: COM surrogate hosts for Microsoft services and security components also connect outward until baselined.
// VALIDATION LEVEL: static-reviewed
event.type = 'IP Connect' and src.process.name in:anycase ('dllhost.exe') and dst.ip.address = * and !(net_rfc1918(dst.ip.address)) and !(net_ipsubnet(dst.ip.address, '127.0.0.0/8')) and !(net_ipsubnet(dst.ip.address, '169.254.0.0/16'))
| group conns = count(), first_seen = min(event.time), last_seen = max(event.time) by agent.uuid, endpoint.name, dst.ip.address, dst.port.number
| sort +conns
| limit 200Automated validation: static-passed
5 required fields · View requirements
Required telemetry
- DeviceNetworkEvents
Required fields
Timestamp: Event timeInitiatingProcessFileName: Connecting processRemoteIPType: Destination address typeRemoteIP: Destination addressRemotePort: Destination port
Recommended fields
DeviceName: Hostname
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceNetworkEvents | where Timestamp > ago(1d) | project Timestamp, InitiatingProcessFileName, RemoteIPType, RemoteIP, RemotePort | take 5
// HUNT: dllhost.exe outbound connections to non-private addresses, rare destinations first // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1071.001, T1497.003, T1559.001 // CONF: medium | FP: medium // FALSE POSITIVES: COM surrogate hosts for Microsoft services, thumbnail or preview handlers that fetch content, endpoint security components // EQUIVALENCE: equivalent-with-schema-assumptions - Same analytic on DeviceNetworkEvents: dllhost.exe outbound connections to public addresses, grouped by host and destination, rarest first. RemoteIPType equal to Public stands in for the CQL's five excluded ranges. // REQUIRES: Defender for Endpoint network telemetry (DeviceNetworkEvents) // TUNING: Baseline 30 days on the CQL tab, then exclude known-good destinations with an ipv4_is_in_range test using your own approved ranges. The C2 sits behind a shared CDN address, so the destination alone is not an indicator. // LOOKBACK: 7d // CONFIGURATION: No lookup objects required. // KNOWN GAPS: COM surrogate hosts for Microsoft services and security components also connect outward until baselined. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0 DeviceNetworkEvents | where Timestamp >= ago(7d) | where InitiatingProcessFileName =~ "dllhost.exe" | where RemoteIPType == "Public" | summarize Conns = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceId, DeviceName, RemoteIP, RemotePort | sort by Conns asc | take 200
Looks for: Any host resolving openaigpt4v.com or a subdomain, with the requesting process. Single-source infrastructure, so this ships as detect and hunt, not block.
Known limitations and deployment notes
- PowerQuery: Lookups by a resolver the agent does not observe are not captured.
- PowerQuery assumes: event.dns.request is the populated name field; some schema versions carry dns.request instead, so run the readiness test first.
- PowerQuery assumes: The contains test on the dotted subdomain form can also match a name that merely embeds it after a dot, which is the intended subdomain case.
- KQL – Defender XDR: Advanced Hunting keeps 30 days, so the CQL tab's 90d window is not reproduced here.
- KQL – Defender XDR assumes: DnsQueryResponse events carry the name under DnsQueryString or QueryName in AdditionalFields; the readiness test prints the field so you can see which key your tenant populates.
// HUNT: DNS lookups for openaigpt4v.com // MITRE: T1071.001 // CONF: high FP: low COST: low // REQUIRES: DNS request events with process context (DnsRequest) // FALSE POSITIVES: researchers or analysts resolving the domain from a lab or from a threat-intel enrichment tool // SOURCE: Securelist 2026-10-29 (C2 domain, first seen 2023-09-27 per Kaspersky); THN 2026-11-01 repeats it // LOOKBACK: 90d #event_simpleName=DnsRequest | DomainName=/(^|\.)openaigpt4v\.com$/i | groupBy([aid, ComputerName, ContextBaseFileName, DomainName], function=[count(as=Requests), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)]) | sort(FirstSeen, order=desc, limit=200)
4 required fields · View requirements
Required telemetry
- SentinelOne DNS Resolved events
Required fields
event.type: Event typeevent.dns.request: Requested nameagent.uuid: Host identifierevent.time: Event time
Recommended fields
src.process.name: Requesting process
Fallbacks
event.dns.request→dns.request: Same value under the older field name on some schema versions.
Readiness test
event.type = 'DNS Resolved' and event.dns.request = * | limit 5 | columns event.type, event.time, agent.uuid, src.process.name, event.dns.request
// HUNT: DNS lookups for openaigpt4v.com
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1071.001
// CONF: high | FP: low
// FALSE POSITIVES: researchers or analysts resolving the domain from a lab or from a threat-intel enrichment tool
// EQUIVALENCE: equivalent-with-schema-assumptions - Same DNS predicate: lookups for the C2 domain or any subdomain of it, grouped by host, process and name. On a DNS event the acting process is src.process.*. The name is read from event.dns.request, the field the independent backend maps.
// REQUIRES: SentinelOne DNS Resolved events with the requested name
// TUNING: Exclude analyst or threat-intel enrichment hosts that resolve the domain on purpose.
// LOOKBACK: 90d
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Lookups by a resolver the agent does not observe are not captured.
// VALIDATION LEVEL: static-reviewed
event.type = 'DNS Resolved' and event.dns.request = * and (event.dns.request in:anycase ('openaigpt4v.com') or event.dns.request contains '.openaigpt4v.com')
| group requests = count(), first_seen = min(event.time), last_seen = max(event.time) by agent.uuid, endpoint.name, src.process.name, event.dns.request
| sort -first_seen
| limit 200Automated validation: static-passed
3 required fields · View requirements
Required telemetry
- DeviceEvents
Required fields
Timestamp: Event timeActionType: Action typeAdditionalFields: DNS details
Recommended fields
InitiatingProcessFileName: Requesting process
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: DNS lookups for openaigpt4v.com // SCHEMA PROFILE: microsoft-defender-xdr // MITRE: T1071.001 // CONF: high | FP: low // FALSE POSITIVES: researchers or analysts resolving the domain from a lab or from a threat-intel enrichment tool // EQUIVALENCE: equivalent-with-schema-assumptions - Same DNS predicate: lookups for the C2 domain or any subdomain. There is no endpoint DNS table, so the name is read from the DnsQueryResponse action on DeviceEvents by bracket access into AdditionalFields. // REQUIRES: Defender for Endpoint DNS telemetry (DeviceEvents, DnsQueryResponse) // TUNING: Exclude analyst or threat-intel enrichment hosts that resolve the domain on purpose. // LOOKBACK: 30d (Advanced Hunting retention; the CQL tab covers 90d) // CONFIGURATION: No lookup objects required. // KNOWN GAPS: Advanced Hunting keeps 30 days, so the CQL tab's 90d window is not reproduced here. // VALIDATION LEVEL: schema-aligned // AUTOMATED VALIDATION: static-passed; static=passed-with-warnings; fixtures=0/0 DeviceEvents | where Timestamp >= ago(30d) | where ActionType == "DnsQueryResponse" | extend QueryName = tostring(coalesce(parse_json(AdditionalFields)["DnsQueryString"], parse_json(AdditionalFields)["QueryName"])) | where QueryName =~ "openaigpt4v.com" or QueryName endswith ".openaigpt4v.com" | summarize Requests = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceId, DeviceName, InitiatingProcessFileName, QueryName | sort by FirstSeen desc | take 200
Looks for: The .NET 2 Plugin creates an empty DllSurrogate value under HKLM\SOFTWARE\Classes\AppID\{GUID}. Sensor coverage of writes to this key is unconfirmed, so treat a zero result as untested, not clean. FP: Installers and COM-based products register DllSurrogate AppIDs. Baseline and exclude the writing images you recognise.
Known limitations and deployment notes
- PowerQuery: Coverage of the AppID key by the agent is unconfirmed, so this hunt can be silent while the write happens.
- PowerQuery assumes: Sensor coverage of the Classes\AppID key is unconfirmed; a zero result may mean the write is not recorded.
- PowerQuery assumes: Registry Value Modified covers value creation as well as change, and registry.keyPath holds the key path without the value name.
- KQL – Defender XDR: Coverage of the AppID key by the sensor is unconfirmed, so this hunt can be silent while the write happens.
- KQL – Defender XDR assumes: Sensor coverage of the Classes\AppID key is unconfirmed; a zero result may mean the write is not recorded.
- KQL – Defender XDR assumes: RegistryKey holds the key path without the value name.
// HUNT: DllSurrogate value created under a COM AppID registry key // MITRE: T1559.001, T1112 // CONF: low FP: medium COST: low // REQUIRES: registry value write events (RegGenericValueUpdate). Sensor coverage of Classes\AppID is unconfirmed: validate in tenant that a test write to this key is recorded before trusting a zero result. // FALSE POSITIVES: software installers and COM server registrations that legitimately use a surrogate host // TUNING: baseline 30 days, then exclude known installer images, for example add: | ContextBaseFileName!=/^(msiexec|setup)\.exe$/i // SOURCE: Securelist 2026-10-29 (DllSurrogate set to an empty string under LocalMachine\SOFTWARE\Classes\appid\{GUID}; the GUID derives from the C2 URL) // LOOKBACK: 30d #event_simpleName=/RegGenericValueUpdate|AsepValueUpdate/ | RegObjectName=/\\Classes\\AppID\\\{[0-9a-f-]{36}\}$/i | RegValueName=/^DllSurrogate$/i | table([@timestamp, ComputerName, aid, UserName, RegObjectName, RegValueName, RegStringValue, ContextBaseFileName, ImageFileName])
3 required fields · View requirements
Required telemetry
- SentinelOne Registry Value Modified events
Required fields
event.type: Event typeregistry.keyPath: Registry key pathregistry.valueName: Registry value name
Recommended fields
src.process.name: Writing process
Optional fields
registry.value: Value data
Fallbacks
endpoint.name→agent.uuid: Changes the displayed host identifier only.
Readiness test
event.type = 'Registry Value Modified' and registry.keyPath = * | limit 5 | columns event.type, registry.keyPath, registry.valueName
// HUNT: DllSurrogate value created under a COM AppID registry key
// SCHEMA PROFILE: sentinelone-endpoint-ocsf
// MITRE: T1559.001, T1112
// CONF: low | FP: medium
// FALSE POSITIVES: software installers and COM server registrations that legitimately use a surrogate host
// EQUIVALENCE: equivalent-with-schema-assumptions - Same registry predicate: a DllSurrogate value written under a GUID-named AppID key. On a registry event the acting process is src.process.*. A non-alphanumeric class stands for the path separator so the pattern needs no backslash escaping, and the GUID shape is kept.
// REQUIRES: SentinelOne Registry Value Modified events with key path and value name
// TUNING: Baseline 30 days, then exclude known installer images by src.process.name. Validate in tenant that a test write to this key is recorded before trusting a zero result.
// LOOKBACK: 30d
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Coverage of the AppID key by the agent is unconfirmed, so this hunt can be silent while the write happens.
// VALIDATION LEVEL: static-reviewed
event.type = 'Registry Value Modified' and registry.valueName in:anycase ('DllSurrogate') and registry.keyPath matches 'Classes[^a-z0-9]AppID[^a-z0-9][{][0-9a-f-]{36}[}]$'
| columns event.time, endpoint.name, agent.uuid, src.process.user, registry.keyPath, registry.valueName, registry.value, src.process.name, src.process.image.path
| limit 200Automated validation: static-passed
4 required fields · View requirements
Required telemetry
- DeviceRegistryEvents
Required fields
Timestamp: Event timeActionType: ActionRegistryKey: Registry keyRegistryValueName: Registry value name
Recommended fields
InitiatingProcessFileName: Writing process
Optional fields
RegistryValueData: Value data
Fallbacks
DeviceName→DeviceId: Changes the displayed host identifier only.
Readiness test
DeviceRegistryEvents | where Timestamp > ago(1d) | project Timestamp, ActionType, RegistryKey, RegistryValueName | take 5
// HUNT: DllSurrogate value created under a COM AppID registry key
// SCHEMA PROFILE: microsoft-defender-xdr
// MITRE: T1559.001, T1112
// CONF: low | FP: medium
// FALSE POSITIVES: software installers and COM server registrations that legitimately use a surrogate host
// EQUIVALENCE: equivalent-with-schema-assumptions - Same registry predicate on DeviceRegistryEvents: a DllSurrogate value set under a GUID-named AppID key. A non-alphanumeric class stands for the path separator so the pattern needs only one backslash escape, for the braces.
// REQUIRES: Defender for Endpoint registry telemetry (DeviceRegistryEvents)
// TUNING: Baseline 30 days, then exclude known installer images by InitiatingProcessFileName. Validate in tenant that a test write to this key is recorded before trusting a zero result.
// LOOKBACK: 30d (Advanced Hunting retention)
// CONFIGURATION: No lookup objects required.
// KNOWN GAPS: Coverage of the AppID key by the sensor is unconfirmed, so this hunt can be silent while the write happens.
// VALIDATION LEVEL: schema-aligned
// AUTOMATED VALIDATION: static-passed; static=passed; fixtures=0/0
DeviceRegistryEvents
| where Timestamp >= ago(30d)
| where ActionType == "RegistryValueSet"
| where RegistryValueName =~ "DllSurrogate"
| where RegistryKey matches regex @"(?i)Classes[^a-z0-9]AppID[^a-z0-9]\{[0-9a-f-]{36}\}$"
| project Timestamp, ReportId, DeviceId, DeviceName, RegistryKey, RegistryValueName, RegistryValueData, InitiatingProcessFileName, InitiatingProcessFolderPath
| sort by Timestamp desc
| take 200Detection Rules by Platform
CrowdStrike: Custom IOA
| IOA name | Pattern | Exclusions | Deployment | Promotes |
|---|---|---|---|---|
| NetamLoader wrapper in update folder | Process Creation: image file name PatchMyPC-ScriptRunner.exe and image path matches .*\\SoftwareDistribution\\Download\\.* | None expected | Detect only. Legitimate signed binary; the path is the signal. No prevent. | Promote Q1 |
| Executable from update download folder | Process Creation: image path matches .*\\SoftwareDistribution\\Download\\.* | Verified, signed servicing installers per host group | Detect only, low severity until baselined. | Promote Q3 |
| dllhost.exe COM activation with report GUIDs | Process Creation: image dllhost.exe and command line contains ab8902b4-09ca-4bb6-b78d-a8f59079a8d5 or ff259bcd-2f0a-44de-b34a-0092fec1d8a2 | None expected | Detect, medium severity. Windows binary; never prevent. | Promote Q4 |
| C2 domain lookup | Domain Name: openaigpt4v.com and subdomains | Threat-intel enrichment tools and analyst lab hosts | Detect, high severity. Single-source infrastructure, so no prevent. | Promote Q6 |
No IOA in this pack is set to prevent. The indicators are single-sourced, two of the filenames are legitimate signed files, and dllhost.exe is a core Windows binary. Revisit prevent for the domain after a second independent vendor reports on NetamLoader.
SentinelOne: custom detection rules
| Rule | Source query | Run as | Notes |
|---|---|---|---|
| NetamLoader wrapper in update folder | Q1, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
| Executable from update download folder | Q3, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
| dllhost.exe COM activation with report GUIDs | Q4, PowerQuery tab | Scheduled custom detection rule | Bind entities so the alert names the host instead of "Unknown Device" |
| C2 domain lookup | Q6, 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 |
|---|---|---|---|
| NetamLoader wrapper in update folder | Q1, 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 |
| Executable from update download folder | Q3, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | Make the final result include Timestamp, ReportId and DeviceId: Microsoft recommends them 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 |
| dllhost.exe COM activation with report GUIDs | Q4, 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 |
| C2 domain lookup | Q6, KQL – Defender XDR tab | Custom detection rule, alert only, every hour | Make the final result include Timestamp, ReportId and DeviceId: Microsoft recommends them 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 |
Q2, Q5 and Q7 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
Grouped IOC Quick-Copy
One-click blocks. The CSV is short and every row is detect: one vendor report, inert on-disk files, and a single C2 domain. Behavior is the durable detection.
type,value,action,severity,expiration,description,tags domain,openaigpt4v.com,detect,high,2027-05-02,NetamLoader C2 per Securelist 2026-10-29 (single source; Cloudflare hosted),campaign:NetamLoader md5,f2458ed133f8d0ca19e22c10f577e98e,detect,medium,2027-05-02,NetamLoader CAB archive per Securelist (single source),campaign:NetamLoader md5,e1dc6f1ed193594f2284be22ae08dfa4,detect,medium,2027-05-02,NetamLoader Resources.resx payload carrier per Securelist (single source),campaign:NetamLoader md5,67e3617f8709d34f806f8e02badba8a5,detect,medium,2027-05-02,NetamLoader fabric.config per Securelist (single source),campaign:NetamLoader md5,514ac0d73b8a1b73488ef5e2e55a75c3,detect,medium,2027-05-02,NetamLoader planted PatchMyPC-ScriptRunner.exe.config per Securelist (single source),campaign:NetamLoader # Not imported: 104.18.14.219 is a shared Cloudflare address. In-memory payload hashes are listed in the Memory-Resident block. # Not imported: PatchMyPC-ScriptRunner.exe and Microsoft.HostIntegration.Connectors.dll are legitimate signed files. No hash or filename block.
indicatorType,indicatorValue,action,title,description,expirationTime,severity,recommendedActions,rbacGroups,category,mitretechniques,GenerateAlert DomainName,openaigpt4v.com,Audit,NetamLoader C2 per Securelist 2026-10-29,NetamLoader C2 per Securelist 2026-10-29 (single source; Cloudflare hosted),2027-05-02T00:00:00.0Z,High,Investigate the device and pivot into the pack's queries,,,,True
Column names and allowed values are from Microsoft's documented parameter table. Left out because this block maps only SHA-1, SHA-256, IPv4, domain and URL indicators: md5 4. 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 (hunt, not import) 1. PatchMyPC-ScriptRunner.exe image path under \SoftwareDistribution\Download\ (Q1) 2. Product DLLs and wrapper written into \SoftwareDistribution\Download\Install\ (Q2) 3. Any executable running from \SoftwareDistribution\Download\ (Q3) 4. dllhost.exe started with the report's CLSID or AppID GUID (Q4) 5. dllhost.exe making outbound HTTPS connections, 5 to 6 minute cadence (Q5) 6. DNS for openaigpt4v.com (Q6) 7. DllSurrogate value created under Classes\AppID (Q7) 8. Prefetch and CLR usage log for the wrapper missing after a recorded execution (N3) 9. 20 to 40 minute silence before first C2 contact; 22 to 28 hour retry when unanswered
Named tooling and detections Kaspersky detection names: HEUR:Trojan.MSIL.NetamLoader.gen, Trojan.MSIL.Zapchast.*, Trojan.Script.Agent.* Framework name used by Kaspersky: NetamLoader Stages named in the report: XAML-1, XAML-2, XAML-2-1, XAML-2-2, XAML-3, .NET 1 Loader, .NET 2 Plugin Legitimate files abused (do not block): PatchMyPC-ScriptRunner.exe, PatchMyPC-Enum.dll, PatchMyPC-Models.dll, Microsoft.HostIntegration.Connectors.dll Planted or crafted files: PatchMyPC-ScriptRunner.exe.config, fabric.config, Resources.resx
Memory-resident payload hashes (MD5, reference only, not for import) 514117d536e347560d049cfe0c609632 XAML-1 9000820d8153c24cd60d53001d3a97b6 XAML-2 b3c4a635179680edafb5d006cf0cffc6 XAML-2-1 4f28622c5e60c01319bf48cf742fe1a5 XAML-2-2 c86d53392a87dd3f3e62d62c9b92a9bd XAML-3 ab81564bcb8ca7e710c66832de747556 .NET 1 Loader cfb8b281bdf5d9390e64b10c312fb2c1 .NET 2 Plugin
Patch / ASR / log audit checklist [ ] Which hosts have PatchMyPC-ScriptRunner.exe excluded from ASR or AV by process name? Scope exclusions to CCMCache / IMECache paths (playbook P3) [ ] Any executable, .config or .resx under C:\Windows\SoftwareDistribution\Download? (playbook P1) [ ] Are Windows Update, CLR usage and Prefetch-related logs forwarded off the endpoint? [ ] Is there an alert on a gap in local update-agent logs? [ ] Which AppID keys carry a DllSurrogate value, and are they all expected? (playbook P4) [ ] Resolver and proxy logs searched for openaigpt4v.com and /model_v4/ for 90 days
Detection Validation Gates
| Gate | Check |
|---|---|
| Telemetry ready | Confirm process creation with command line and path (CrowdStrike: ProcessRollup2. SentinelOne: Process Creation. Defender: DeviceProcessEvents), executable writes (CrowdStrike: PeFileWritten, NewExecutableWritten), outbound connections with process (CrowdStrike: NetworkConnectIP4) and DNS lookup with the requesting process (CrowdStrike: DnsRequest) are present fleet-wide, including on any customized Windows 10 build. Confirm a test write under Classes\AppID is recorded before trusting Q7. |
| Benign baseline | Run Q3, Q5 and Q7 over 30 days and baseline. Q1, Q2, Q4 and Q6 should return zero rows in a normal estate. If Q1 returns rows, confirm whether your Patch My PC deployment is running from an unusual location before escalating. |
| Positive tests | Do not run malware. Copy any harmless signed executable into a test folder named like the update download path (a lab path, not the live one) and rename it PatchMyPC-ScriptRunner.exe to exercise the rule logic, or resolve openaigpt4v.com from a lab host for Q6. Q4 needs a lab dllhost.exe launched with the GUID argument and is optional. |
| Promotion | IOAs ship detect only (section 9). Promote the C2 domain to prevent only after a second independent source reports it. Never prevent on PatchMyPC-ScriptRunner.exe, Microsoft.HostIntegration.Connectors.dll or dllhost.exe. |
Hardening, Tiered & Deployable
NetamLoader is delivered malware with no CVE, so there is no patch. Hardening targets what it needs: a place to run signed binaries with attacker-controlled config files, local logs it can edit, and a COM surrogate that can reach the internet. Controls are anchored to MITRE mitigations from the saved ATT&CK pages and to the Kaspersky and Patch My PC statements in the snapshots.
- Audit the update download folder. Run the read-only inventory in P1 on a sample of hosts. Kaspersky advises monitoring this folder for unexpected executables and configuration files. M1047 Audit (T1685). Vendor advisory: Kaspersky press release.
- Forward logs off the endpoint and alert on gaps. Send Windows event logs, update-agent logs and EDR telemetry to a central store, and alert when a host's local log goes missing lines. NetamLoader deletes local copies. M1029 Remote Data Storage (T1070). Vendor advisory: Kaspersky. ⚠ best-practice, no formal benchmark for the update-agent log.
- Scope any Patch My PC exclusion by path, not by name. The vendor article recommends excluding PatchMyPC-ScriptRunner.exe from ASR rules. That is safe only for its CCMCache and IMECache staging folders. A by-name exclusion would also cover the copy abused here. Check with P3. M1047 Audit (T1685). Judgment call: the vendor article does not say to scope by path.
- Turn on Q1, Q2 and Q6 as scheduled detections at detect severity, after the 30-day look-back is clean.
- Application control in audit mode for the update download folder. Audit execution from SoftwareDistribution\Download with AppLocker, then enforce on a ring once the audit log is clean (P2). M1038 Execution Prevention (T1685, T1574.014 context). Edition and policy-mode limits apply: ⚠ verify AppLocker support on a customized Windows 10 build.
- Baseline COM surrogate registrations. List every AppID that carries a DllSurrogate value and review changes (P4). M1026 Privileged Account Management and M1048 Application Isolation and Sandboxing (T1559.001: AppID registry permissions under HKLM\SOFTWARE\Classes\AppID).
- Egress control for dllhost.exe. Where policy allows, block or alert on outbound internet access from dllhost.exe at the host firewall or proxy. ⚠ best-practice, no formal benchmark.
- Verify the origin of every update package. Inventory the tools allowed to place packages in the update folder, require signature verification of each, and run third-party management tools only from approved locations. Vendor advisory: Kaspersky press release. Supports T1195.
- Write-protect .NET application directories. Install .NET software in locations that standard and service accounts cannot write to, including the folders applications run from. M1022 Restrict File and Directory Permissions (T1574.014). Note the loader needs administrator rights, so this reduces but does not remove risk.
- Managed-code application control. Prefer WDAC policies that control which .NET assemblies and configs a signed host may load, over name-based allow lists. M1038. ⚠ best-practice, no formal benchmark.
- Network anomaly monitoring for infrequent encrypted connections to unfamiliar hosts behind content delivery networks, including 22 to 28 hour retry cadences. Vendor advisory: Kaspersky press release (KATA is Kaspersky's product; any NDR with long-window analytics fits).
Deployable Playbooks
Every step lists Prerequisites, Reboot required and Rollback. Replace placeholders such as the evidence share before running.
P1 Inventory the update download folder (read-only)
Prerequisites: local administrator on a pilot host (read-only commands; file hashing may take a moment). Reboot required: no. Rollback: none required, nothing is changed. # 1) Everything staged under the update download folder (report only) $root = 'C:\Windows\SoftwareDistribution\Download' Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue -Include *.exe,*.dll,*.config,*.resx | Select-Object FullName, Length, LastWriteTime # 2) MD5 of those files, to compare with the list in section 10 Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue -Include *.exe,*.dll,*.config,*.resx | Get-FileHash -Algorithm MD5 | Select-Object Hash, Path # 3) Config files that name an AppDomainManager (report only) Get-ChildItem -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue -Filter *.config | Select-String -Pattern 'appDomainManagerAssembly|appDomainManagerType' | Select-Object Path, Line
P2 AppLocker: audit execution from the update download folder
Prerequisites: an edition that supports AppLocker; Application Identity service set to automatic; a pilot ring only. This sets the Exe collection to AuditOnly, so nothing is blocked. If the host already has an AppLocker policy, do not run step 2: merge by hand after reviewing the backup.
Reboot required: no.
Rollback: Set-AppLockerPolicy -XmlPolicy "$env:ProgramData\applocker-backup.xml" (restores the policy saved in step 1). If there was no policy before, clear it with: Set-AppLockerPolicy -XmlPolicy "$env:ProgramData\applocker-empty.xml" after writing an empty <AppLockerPolicy Version="1" /> file there.
# 1) Back up the effective policy first
Get-AppLockerPolicy -Effective -Xml | Out-File -Encoding utf8 "$env:ProgramData\applocker-backup.xml"
Set-Service -Name AppIDSvc -StartupType Automatic; Start-Service AppIDSvc
# 2) Save the policy below as C:\Temp\netam-audit.xml, then apply it
Set-AppLockerPolicy -XmlPolicy 'C:\Temp\netam-audit.xml'
# 3) Review audit events (ID 8003 = would have been blocked) for 2 to 4 weeks
Get-WinEvent -LogName 'Microsoft-Windows-AppLocker/EXE and DLL' -FilterXPath "*[System[(EventID=8003)]]" -MaxEvents 200 |
Select-Object TimeCreated, Message
# Only after the audit log is clean on the pilot ring, change EnforcementMode to Enabled.
# ---- netam-audit.xml ----
<AppLockerPolicy Version="1">
<RuleCollection Type="Exe" EnforcementMode="AuditOnly">
<FilePathRule Id="921cc481-6e17-4653-8f75-050b80acca20" Name="(Default) All files located in the Program Files folder" Description="" UserOrGroupSid="S-1-1-0" Action="Allow">
<Conditions><FilePathCondition Path="%PROGRAMFILES%\*" /></Conditions>
</FilePathRule>
<FilePathRule Id="a61c8b2c-a319-4cd0-9690-d2177cad7b51" Name="(Default) All files located in the Windows folder" Description="" UserOrGroupSid="S-1-1-0" Action="Allow">
<Conditions><FilePathCondition Path="%WINDIR%\*" /></Conditions>
</FilePathRule>
<FilePathRule Id="fd686d83-a829-4351-8ff4-27c7de5755d2" Name="(Default) All files" Description="" UserOrGroupSid="S-1-5-32-544" Action="Allow">
<Conditions><FilePathCondition Path="*" /></Conditions>
</FilePathRule>
<FilePathRule Id="5b0b6d11-3c49-4d0c-9f43-6c2d1a7d0a11" Name="HuntPack: deny execution from the Windows Update download folder" Description="Audit only" UserOrGroupSid="S-1-1-0" Action="Deny">
<Conditions><FilePathCondition Path="%WINDIR%\SoftwareDistribution\Download\*" /></Conditions>
</FilePathRule>
</RuleCollection>
</AppLockerPolicy>
P3 Review Patch My PC exclusions and scope them by path
Prerequisites: Microsoft Defender Antivirus is the active AV; local administrator; read-only commands.
Reboot required: no.
Rollback: none required for steps 1 and 2. For step 3, re-add the previous exclusion with the command printed by step 1.
# 1) Current exclusions that mention the Patch My PC wrapper (report only)
$p = Get-MpPreference
$p.ExclusionProcess | Where-Object { $_ -match 'PatchMyPC' }
$p.ExclusionPath | Where-Object { $_ -match 'PatchMyPC|CCMCache|IMECache' }
$p.AttackSurfaceReductionOnlyExclusions | Where-Object { $_ -match 'PatchMyPC|CCMCache|IMECache' }
# 2) Decide: a by-name exclusion for PatchMyPC-ScriptRunner.exe also covers a copy run from
# C:\Windows\SoftwareDistribution\Download\Install. Prefer path-scoped exclusions.
# 3) Example replacement (edit paths to match your Configuration Manager or Intune cache)
# Remove-MpPreference -ExclusionProcess 'PatchMyPC-ScriptRunner.exe'
# Add-MpPreference -AttackSurfaceReductionOnlyExclusions 'C:\Windows\ccmcache\*'
P4 List COM AppIDs that use a DllSurrogate (read-only)
Prerequisites: local administrator; read-only.
Reboot required: no.
Rollback: none required, nothing is changed.
# AppID keys that carry a DllSurrogate value. Baseline the list, then review changes.
Get-ChildItem 'HKLM:\SOFTWARE\Classes\AppID' -ErrorAction SilentlyContinue |
ForEach-Object {
$v = Get-ItemProperty -LiteralPath $_.PSPath -Name DllSurrogate -ErrorAction SilentlyContinue
if ($null -ne $v) { [pscustomobject]@{ AppID = $_.PSChildName; DllSurrogate = $v.DllSurrogate } }
} | Sort-Object AppID
# Report only: compare the GUID in section 4 with the output.
P5 Preserve evidence before reimaging (copy-only)
Prerequisites: local administrator on the suspect host; a write-protected evidence share; do this BEFORE reimaging. The plugin stops about 28 hours after it starts, so act promptly.
Reboot required: no. Do not reboot before capture.
Rollback: none required, copy-only.
# 1) Identify dllhost.exe instances with the GUIDs from section 4 (report only)
Get-CimInstance Win32_Process -Filter "Name='dllhost.exe'" |
Where-Object { $_.CommandLine -match 'ab8902b4-09ca-4bb6-b78d-a8f59079a8d5|ff259bcd-2f0a-44de-b34a-0092fec1d8a2' } |
Select-Object ProcessId, CreationDate, CommandLine
# 2) Copy evidence to the share (replace the placeholder)
$dst = '\\EVIDENCE-SHARE\case-01\HOST-01'
Copy-Item 'C:\Windows\Logs\CmitUpdateAgent' "$dst\CmitUpdateAgent" -Recurse -ErrorAction SilentlyContinue
Copy-Item 'C:\Windows\SoftwareDistribution\Download' "$dst\Download" -Recurse -ErrorAction SilentlyContinue
Copy-Item "$env:WINDIR\Prefetch\*.pf" "$dst\Prefetch" -ErrorAction SilentlyContinue
# 3) Capture process memory of the dllhost.exe found in step 1 with your EDR or forensic tooling.
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Network-contain the host in your EDR. Do not power off or reboot; the plugin lives in memory. Do not block the Cloudflare address at the network layer, it is shared. | SOC L2 | Containment timestamp; Q1 to Q7 rows |
| 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 L2 | Containment timestamp; Q1 to Q7 rows |
| Preserve | Capture memory of the dllhost.exe from Q4 or Q5, then copy CmitUpdateAgent logs, the SoftwareDistribution\Download folder and Prefetch (P5). The loader deletes many of these, so absent files are findings too. | IR | Hashes of copied evidence; memory image |
| Scope | Run Q1 to Q7 fleet-wide for 90 days. Pull resolver and proxy logs for openaigpt4v.com and /model_v4/. Check update-channel logs for how the CAB arrived (N5). | IR + SOC | Host list with first and last sighting |
| Eradicate | Reimage after evidence is preserved. The loader required administrator rights, so treat credentials on the host as exposed: rotate local admin and service account secrets used there. Remove any unexpected package from the update download folder on peers. | IR + identity team | Reimage record; rotation log |
| Recover | Rejoin only a rebuilt host. Re-verify update-channel tooling and its allowed locations. Restore path-scoped exclusions (P3). | IT | Change record |
| Hunt | Keep Q6 and Q5 scheduled at detect for 90 days. A missed sandbox is likely: the plugin pauses 20 to 40 minutes before its first request and 22 to 28 hours between failed polls. | SOC | Hunt log |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | CrowdStrike coverage | SentinelOne | Defender XDR |
|---|---|---|---|---|---|---|
| T1195 | Delivery into the update download folder | Q2, Q3 | IOA 2 | Partial delivery path unknown; executable half only | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1574.014 | Wrapper run beside a planted .config | Q1, Q2 | IOA 1 | Good process and PE halves; config and resx file writes are a gap (N1, N2) | Good, with assumptions | Good, with assumptions |
| T1559.001 | dllhost.exe started for a remote COM object | Q4, Q7 | IOA 3 | Partial command line format inferred; AppID key coverage unconfirmed | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1071.001 | HTTPS polling from dllhost.exe; C2 domain lookup | Q5, Q6 | IOA 4 | Good for the known domain; domain-agnostic view is Q5 | Good, with assumptions | Good, with assumptions |
| T1497.003 | 20 to 40 minute pause; 22 to 28 hour retry | Q5 | none | Partial 7-day Q5 window can miss the long retry; use 30 days | Partial: same limit as CrowdStrike | Partial: same limit as CrowdStrike |
| T1070 | Deletion of files, CLR logs, Prefetch, log lines | none | none | GAP no deletion query; native N3 and off-endpoint log forwarding | GAP | GAP |
| T1620 / T1685 | In-memory XAML loading; AMSI and ETW tampering | none | none | GAP in-process; outside endpoint telemetry | GAP | GAP |
Validation gates: see section 11. Two known blind spots are declared above: file deletion and in-process tampering. A sandbox that stops after a few minutes will see no network activity.
Hunt Summary Ticket
TITLE: Hunt - NetamLoader (.NET AppDomainManager loader in the Windows update folder)
SEVERITY: High (stealthy in-memory loader on government-grade Windows builds; one victim reported)
SCOPE: Windows 10 endpoints, especially customized builds and any host where Patch My PC runs
HYPOTHESIS: A CAB placed in SoftwareDistribution\Download unpacks a signed Patch My PC wrapper beside a
planted config. The runtime loads a Microsoft-signed assembly that reads fabric.config and
Resources.resx, patches AMSI, deletes its traces and runs a plugin in dllhost.exe that
polls openaigpt4v.com after a 20 to 40 minute pause.
QUERIES RUN: Q1 wrapper in update folder | Q2 PE writes in Download\Install | Q3 any exe from Download |
Q4 dllhost COM GUIDs | Q5 dllhost external connections | Q6 C2 DNS | Q7 DllSurrogate write + Native N1-N5
DO FIRST: Q1, Q2, Q6 for 30 to 90 days. Any hit: capture dllhost.exe memory, preserve logs, then isolate.
FINDINGS: <pending analyst execution>
GAPS: Delivery path unknown; config and resx writes, file and log deletion and all in-process
activity (AMSI, XAML loading) are outside endpoint telemetry; no persistence described.
ACTIONS: Audit the update folder (P1); forward logs off-host; scope Patch My PC exclusions by path (P3);
AppLocker audit (P2); never prevent on PatchMyPC-ScriptRunner.exe, Microsoft connector DLL or dllhost.exe.
OWNER: HuntPack
VERSION: v0.1 - 2026-11-02
Changelog
References
| Tier | Source | Used for | Accessed |
|---|---|---|---|
| 1 | Kaspersky Securelist: Unknown APT uses NetamLoader to attack a Win10 OS customized for governments in Asia (2026-10-29) | All technical facts, indicators, victim and attribution statements | 2026-11-02 |
| 1 | Kaspersky press release (2026-10-29) | Supply chain framing, Patch My PC statement, delays, recommendations | 2026-11-02 |
| 2 | The Hacker News (2026-11-01) | Secondary summary; two extra companion DLL names | 2026-11-02 |
| 1 | Patch My PC: What Is Patch My PC ScriptRunner? | Legitimate behavior, signing, staging folders, exclusion advice | 2026-11-02 |
| 2 | MITRE ATT&CK T1574.014 AppDomainManager | Technique and M1022 | 2026-11-02 |
| 2 | MITRE ATT&CK T1685 Disable or Modify Tools | AMSI tampering context and mitigations | 2026-11-02 |
| 2 | MITRE ATT&CK T1070 Indicator Removal | Anti-forensics and M1029 | 2026-11-02 |
| 2 | MITRE ATT&CK T1559.001 Component Object Model | COM abuse and AppID mitigations | 2026-11-02 |
| 2 | MITRE ATT&CK T1497.003 Time Based Checks | Sandbox delay | 2026-11-02 |
| 2 | MITRE ATT&CK T1620 Reflective Code Loading | In-memory loading | 2026-11-02 |
HuntPack v0.1 · NetamLoader · Generated 2026-11-02 · Defensive use only, no malware code. Single technical origin: validate field names in your tenant, confirm sensor support on any customized Windows 10 build and treat a zero from Q7 as untested.