HollowFrame & Matryoshka — Layered Loader and Nested Backdoors
Executive Summary
The attacker's objective was durable, deniable remote command execution inside a professional-services network, reached by walking malicious code through a chain of increasingly trusted components until the final implant ran inside a signed Microsoft process. Blackpoint Cyber's Adversary Pursuit Group documented the chain across two endpoints: a spear-phishing link, an attacker-controlled redirector, a Mega-hosted encrypted archive, a shortcut named to look like case documents, an XOR-obfuscated PowerShell downloader, a staged Python package, a fake python311.dll, the HollowFrame Go loader framework, and finally the Matryoshka Rust backdoor family in two variants.
Two properties make this pack worth running even if these exact indicators never appear in your environment. First, the actor prepared the ground before the payload arrived: PowerShell requested elevation, created %LOCALAPPDATA%\Programs\Python\Python311-Brief, then added Microsoft Defender exclusions for both that directory and the process name python.exe — all before a single executable was downloaded. Second, every executable stage ran under a name Windows expects: python.exe loading an adjacent rogue python311.dll, then a OneDrive updater.exe loading an adjacent rogue version.dll, then a Rust wtsapi32.dll proxying 41 genuine Terminal Services exports.
The highest-value defensive angle is therefore not the hashes. It is the ordering: a Defender exclusion for a user-writable directory, followed minutes later by a signed binary executing from that same directory and loading a same-directory dependency. Either event alone is common. The pair, in that order, on that path, is the intrusion. Queries Q3, Q4, Q6 and Q7 in this pack are built to be joined on host and time for exactly that reason.
The GitHub variant deserves separate attention. Matryoshka GitHub uses the GitHub Contents API against a private repository as a dead drop, with one directory per victim host holding beacon.json, cmd.json and result.json. Blocking api.github[.]com outright is not a viable control on most estates. Constraining which processes and which hosts may reach it is, and that is what the hardening section recommends.
Defender priority: hunt Defender exclusion changes first (Q3, Q4). The exclusions were added before the payloads landed, they persist after the malware is removed, and they are the one stage in this chain that leaves an obvious, durable, high-signal artifact on disk and in the registry.
Source Review & Web Hunter Notes
Two sources were fetched and saved verbatim as snapshots alongside this pack. Every atomic indicator shipped below traces to one of them. A third intended source could not be retrieved and is recorded here rather than silently dropped.
| Tier | Source | Key finding | Carry forward |
|---|---|---|---|
| 1 | Blackpoint Cyber APG — "Nested Trust: HollowFrame's Layered Loader and Matryoshka Backdoors" (Nevan Beal, Sam Decker, 2026-07-30) | Full technical chain, loader internals, both backdoor variants, complete file and network IOC tables, six defender recommendations. | Yes — sole origin of every atomic indicator in this pack. |
| 2 | The Hacker News — "HollowFrame Loader Deploys Matryoshka Backdoor in Spear-Phishing Attack on Law Firm" (Ravie Lakshmanan, 2026-07-31) | Independent restatement of the chain and both C2 endpoints. Adds one detail absent from the primary report: the GitHub account was created 2023-01-06 and its profile was updated as recently as 2026-06-07. | Partial — corroboration and account-age context only. |
| — | SC Media — "New HollowFrame loader and Matryoshka malware family discovered" | Returned HTTP 403 on two separate retrieval attempts from two different fetchers. No snapshot exists. | No — not cited, and no indicator in this pack depends on it. |
Research notes and judgement calls
- No actor attribution exists. The primary report states it is not known who is behind the activity. The Go build path
Project220Rebris explicitly assessed by the researchers as an internal build-directory name, not a malware family or an actor handle. Do not treat it as attribution. - Three of the eleven published hashes are legitimate host binaries — the bundled
python.exe,vcruntime140.dlland the OneDriveupdater.exe. They are the trusted halves of the side-load pairs, not malware. They are kept out of the block list and routed to a separate context-only block in section 10. Blocking them would break Python and OneDrive on clean hosts. - One published hash is missing: the report records "hash not recovered" for
Case Documents.lnk. That row is shipped as a labelled placeholder, never as an invented value. - Only one search hit added anything. Every other result for this campaign was a syndicated republication of the trade-press article. Nothing independent of Blackpoint's telemetry has been published, so overall confidence rests on a single primary reporter — high on internal consistency, unconfirmed by a second sensor network.
- Infrastructure perishability. Both IP addresses and the phishing redirector are single-campaign infrastructure with a short useful life. They are shipped with a three-month expiry; the hashes and the behavioural content are the durable half of this pack.
Hunt Brief & Attack Chain
Attack chain
| # | Step | Telemetry | Hunt angle |
|---|---|---|---|
| 1 | Spear-phishing email to multiple recipients; embedded link routes through an attacker-controlled redirector to a Mega-hosted encrypted archive. | Mail gateway, proxy, DNS | Gateway search for the redirector host; proxy search for cloud-storage downloads of password-protected archives. |
| 2 | Archive contains a shortcut named to look like case documents. User double-clicks it. | ZipFileWritten, ProcessRollup2 | Archive written to a download path, followed shortly by an explorer.exe-parented script interpreter. |
| 3 | Shortcut writes a Base64 blob and a companion command script into %TEMP%, then calls certutil.exe -decode to rebuild the next stage. | ProcessRollup2 | Q1 — certutil decoding content inside a user temp path. |
| 4 | Decoded script launches PowerShell, which retrieves an in-memory stage from a bare-IP HTTP server. | ProcessRollup2, NetworkConnectIP4 | Q8 — known delivery IP. Behavioural: PowerShell reaching a bare IPv4 literal over plain HTTP. |
| 5 | In-memory stage holds a 16-character key and a long hex payload; each byte is hex-decoded, XORed against the repeating key, and executed through Invoke-Expression. | Script Block Logging (4104), ScriptControlScanTelemetry | Native hunt: 4104 blocks containing a long hex literal plus Invoke-Expression. |
| 6 | Stage checks for administrative rights; if absent it writes a temp script and relaunches PowerShell with -Verb RunAs -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden. | ProcessRollup2 | Q2 — self-elevation relaunch with a hidden window and a policy bypass. |
| 7 | Creates a staging directory under %LOCALAPPDATA%\Programs\Python and adds Defender exclusions for that directory and for the process name python.exe — before any payload is downloaded. | ProcessRollup2, RegGenericValueUpdate, Defender 5007 | Q3 and Q4 — the highest-signal, longest-lived artifact in the whole chain. |
| 8 | Downloads an archive named to imitate a Python embeddable distribution but carrying a non-standard architecture suffix; validates size and ZIP signature; extracts python.exe, python311.dll, vcruntime140.dll. | ZipFileWritten, PeFileWritten | Q5 and Q7. |
| 9 | Launches python.exe with no script, no module argument and no interactive console. It imports only Py_Main from the adjacent DLL, which is a 64-bit Go DLL exporting four Python-compatible names. | ProcessRollup2 | Q6 — a Python host with nothing to interpret is a side-load, not a script run. |
| 10 | HollowFrame decrypts an interleaved region (even offsets only) with XChaCha20-Poly1305 plus zlib to recover an AMD64 executable, then runs sandbox gates on uptime, installed memory, user-profile file count and cursor movement. | None on the sensor | GAP — in-process cryptography and evasion gates produce no distinct endpoint telemetry. |
| 11 | Persists via an on-logon scheduled task with highest privileges and a 30-second delay, a permanent WMI event subscription on new logon-session instances, and a Startup-folder copy. | ScheduledTaskRegistered, AsepValueUpdate, WMI-Activity 5861 | Q11 plus the native WMI subscription hunt in section 7. |
| 12 | A second embedded container yields a native dropper, which stages a genuine OneDrive updater beside a rogue version.dll. | PeFileWritten, ProcessRollup2 | Q7 — side-load DLL names written outside the system directories. |
| 13 | Matryoshka HTTP runs inside the updater process, decodes its C2 endpoint from an XOR-encoded byte array, resolves WinHTTP at runtime and exchanges JSON tasking over plaintext HTTP. Commands run through cmd.exe /c with merged output. | NetworkConnectIP4, ProcessRollup2 | Q8, Q9, Q12. |
| 14 | Matryoshka GitHub proxies genuine Terminal Services exports while polling a private repository through the GitHub Contents API, one mailbox directory per victim host, using a spoofed OneDrive user agent. | DnsRequest, proxy logs | Q10 plus the proxy hunts in section 7. |
| 15 | Built-in reconnaissance enumerates network configuration, local token and privilege detail, domain controllers, domain computers, privileged group membership and installed software; payloads are written, launched, or executed in memory via module overloading or an allocate-write-protect-execute sequence. | ProcessRollup2, UserLogon | Post-compromise: pivot on the discovery burst and on the merged-output shell pattern. |
Hunt hypotheses (ordered by fidelity)
- H1 — Pre-staged Defender exclusion. A non-management process added a Defender path or process exclusion covering a user-writable directory, and executable content appeared under that path afterwards. Confidence: high. FP: medium (IT and line-of-business installers do this legitimately). Requires:
ProcessRollup2,RegGenericValueUpdate. - H2 — Python host with nothing to interpret.
python.exeexecuted from a user-writable path with no script path, no-mmodule and no interactive flag. Confidence: high. FP: low. Requires:ProcessRollup2. - H3 — Side-load DLL name in the wrong place. A file named
python311.dll,version.dllorwtsapi32.dllwas written outside a system or vendor-install directory. Confidence: high. FP: medium (genuine Python installs and application installers write these names). Requires:PeFileWritten. - H4 — Shortcut-driven decode chain.
certutil.exe -decoderan against content in a user temp path with a script interpreter or shell in the ancestry. Confidence: high. FP: medium. Requires:ProcessRollup2. - H5 — Self-elevating hidden PowerShell. PowerShell relaunched itself with an elevation verb, a hidden window and an execution-policy bypass. Confidence: high. FP: low. Requires:
ProcessRollup2. - H6 — Trusted host talking to the internet. A Python host or an application updater opened outbound connections to endpoints outside its vendor's own infrastructure. Confidence: medium. FP: high on developer and engineering estates. Requires:
NetworkConnectIP4. - H7 — GitHub as a dead drop. The GitHub API was resolved and contacted by a process that has no development role, at a beacon-like cadence, with an application user agent that does not match the process. Confidence: medium. FP: high without a process allowlist. Requires:
DnsRequestplus proxy logs. - H8 — Logon-triggered persistence into a user path. A scheduled task or WMI subscription was registered to run a payload stored under a user profile or temp directory at logon. Confidence: high. FP: medium. Requires:
ScheduledTaskRegistered, WMI-Activity operational log.
Consolidated IOC Table
Read the Action column before importing anything. Three of the published file hashes belong to legitimate signed binaries that the actor abused as side-load hosts. They are listed for context and retrospective search only. Setting them to prevent will break Python and OneDrive on clean endpoints.
| Type | Value | Conf | Action | Context | Expiry |
|---|---|---|---|---|---|
| sha256 | 55ed788ca7130089c4262cda8f0cb936eab9244bea68eeebe863842ba368e270 | high | detect | Initial phishing archive (avvoalert.zip) | 2027-02-01 |
| sha256 | cb6b6289698f53111bb026ad5e95f841a03cf690bda670bb551e416e8cda77d3 | high | detect | Staged payload archive imitating a Python embeddable distribution | 2027-02-01 |
| sha256 | b2123d476646459234ab3083b79d13690d3864f9f5a9aa2b1272dfd502d0d3e2 | high | detect | HollowFrame Go side-load DLL (python311.dll) | 2027-02-01 |
| sha256 | d3bf01fce1f97f86aa58e9220e6dc1dae90005e5d7552f06192393d370355ed9 | high | detect | HollowFrame Go loader, recovered from the encrypted region | 2027-02-01 |
| sha256 | f882d0a0bf6f7fc687b9be6c7991d0bce4b60e81b742f5919933d07de37d625c | high | detect | Native dropper (loader_panda.exe) staging the second side-load | 2027-02-01 |
| sha256 | f59f32c9af4fa8a5dbd4668df8893593bc0c4324816cbf9b956acedcbfb8cdb6 | high | detect | Matryoshka HTTP backdoor (version.dll) | 2027-02-01 |
| sha256 | f96ff2f3abbff7f382ace509b90e54853b4b61c402ecde27d82f1c17b414867b | high | detect | Matryoshka GitHub backdoor (wtsapi32.dll) | 2027-02-01 |
| sha256 | 14a89eda72e385f76bf15a7c4fd539c48837cf5df444a16f28c5b94f29799550 | medium | pivot | Legitimate Python interpreter used as the side-load host. Retrospective search only — do not block. | 2026-11-01 |
| sha256 | d66c3b47091ceb3f8d3cc165a43d285ae919211a0c0fcb74491ee574d8d464f8 | medium | pivot | Legitimate Visual C++ runtime bundled with the staged archive. Do not block. | 2026-11-01 |
| sha256 | 015d7b212d20681d346e690159e7f4cd9e88b51de27e84b514fce865deef3a5c | medium | pivot | Legitimate OneDrive updater used as the second side-load host. Do not block. | 2026-11-01 |
| sha256 | REPLACE_WITH_LNK_SHA256 | n/a | enrich | The malicious shortcut. The primary report records the hash as not recovered; no value is invented here. | — |
| ipv4 | 2.26.252.84 | high | detect | PowerShell payload delivery server, reached over plaintext HTTP | 2026-11-01 |
| ipv4 : port | 45.158.196.184 : 8888 | high | detect | Matryoshka HTTP C2 | 2026-11-01 |
| domain | avvoalert[.]info | high | detect | Attacker-controlled phishing redirector | 2026-11-01 |
| url host | mega[.]nz | low | pivot | Hosted the encrypted archive. A legitimate global file-sharing service — never block wholesale; use the specific file link for retrospective mail and proxy search only. | 2026-09-01 |
| domain | api.github[.]com | low | enrich | Matryoshka GitHub dead-drop channel. Legitimate on developer estates — alert on the process and host context, not the domain. | — |
| uri pattern | /repos/adioziaete/memio/contents/ | high | detect | Per-victim mailbox path on the private dead-drop repository | 2026-11-01 |
| user-agent | OneDrive/24.170.0825.0001 | medium | hunt | Spoofed agent used by the GitHub variant. Legitimate OneDrive never talks to the GitHub API — the pair is the signal. | 2026-11-01 |
| filename | loader_panda.exe | medium | hunt | Native dropper file name | 2026-11-01 |
| filepath | %LOCALAPPDATA%\Programs\Python\Python311-Brief | high | hunt | Staging directory created and Defender-excluded before payload delivery | 2026-11-01 |
| string | looks like a sandbox, exiting | medium | hunt | HollowFrame anti-analysis failure message — useful for retro-hunting recovered samples | 2026-11-01 |
| string | pongv2 | medium | enrich | Matryoshka GitHub health-check response value | 2026-11-01 |
Affected Surface & Telemetry Matrix
| Surface | Required telemetry | Priority | Gap risk |
|---|---|---|---|
| Windows endpoints (all user workstations, especially those handling external document flow) | ProcessRollup2 / SyntheticProcessRollup2 with command lines | Critical | Low — this is baseline Falcon Insight telemetry. |
| Microsoft Defender configuration state | RegGenericValueUpdate, Defender operational log event 5007 | Critical | Medium — the Defender operational log is not forwarded by default in many estates. |
| File writes into user-writable paths | PeFileWritten, NewExecutableWritten, ZipFileWritten | High | Low. |
| Outbound network from endpoints | NetworkConnectIP4, DnsRequest | High | Medium — DNS visibility is lost where endpoints use DNS-over-HTTPS to a third-party resolver. |
| Web proxy / egress | Full URI, user agent, and initiating host for outbound HTTP and HTTPS | High | High — without URI-level logging the dead-drop repository path is invisible, and the domain alone is unusable as a signal. |
| Scheduled task and WMI persistence | ScheduledTaskRegistered, ScheduledTaskModified, AsepValueUpdate, WMI-Activity operational log 5857/5861 | High | Medium — WMI subscription creation via API leaves no process-execution trace, so the operational log is the only source. |
| PowerShell execution content | Script Block Logging (4104), module logging, transcription | High | High — commonly unconfigured; without it the XOR downloader stage is opaque. |
| Mail gateway | URL rewriting and click telemetry, attachment and archive inspection verdicts | Medium | Medium — encrypted archives are frequently passed through uninspected by policy. |
| Active Directory | UserLogon, domain controller and privileged-group enumeration telemetry | Medium | Medium — the recon burst is the earliest sign the operator moved past initial access. |
| In-memory execution (module overloading, manual PE mapping, process ghosting) | — | Medium | GAP — no first-class sensor event; rely on the behavioural preconditions and on Falcon's own memory-scanning detections. |
ATT&CK Mapping
| Tactic | Technique | Observed behavior | Query / control |
|---|---|---|---|
| Initial Access | T1566.002 — Phishing: Spearphishing Link | Mail to multiple recipients with a link through an attacker-controlled redirector. | Section 7 gateway hunt; hardening H3 |
| Execution | T1204.001 / T1204.002 — User Execution: Malicious Link / Malicious File | User opens an encrypted archive and double-clicks a shortcut named as case documents. | Q1; hardening H3, H9 |
| Defense Evasion | T1027 — Obfuscated Files or Information | Password-protected archive; Base64 blob; repeating-key XOR over a hex payload. | Q1; Script Block Logging hunt |
| Defense Evasion | T1140 — Deobfuscate/Decode Files or Information | certutil.exe -decode reconstructs the next command stage from a temp file. | Q1 |
| Execution | T1059.001 — Command and Scripting Interpreter: PowerShell | Hidden, policy-bypassed PowerShell running an in-memory decoded stage via Invoke-Expression. | Q2; hardening H6 |
| Execution | T1059.003 — Windows Command Shell | Operator commands executed through cmd.exe /c with merged standard output and error. | Q12 |
| Privilege Escalation | T1548 — Abuse Elevation Control Mechanism | Script relaunches itself with an elevation verb when not already administrative. | Q2; hardening H10 |
| Defense Evasion | T1562.001 — Impair Defenses: Disable or Modify Tools | Defender path and process-name exclusions added before payload delivery. | Q3, Q4; hardening H2 |
| Command and Control | T1105 — Ingress Tool Transfer | Staged archive and follow-on payloads pulled from a bare-IP HTTP server and from the dead-drop repository. | Q5, Q8 |
| Defense Evasion | T1574.002 — Hijack Execution Flow: DLL Side-Loading | Two chained side-loads: a Python host with a rogue runtime DLL, then an application updater with a rogue version DLL. A third rogue DLL proxies 41 Terminal Services exports. | Q6, Q7; hardening H5 |
| Defense Evasion | T1036.005 — Masquerading: Match Legitimate Name or Location | Malicious DLLs carry the exact names of expected system and runtime dependencies; the staged archive imitates an official Python distribution. | Q5, Q7 |
| Defense Evasion | T1497.001 / T1497.003 — Virtualization/Sandbox Evasion: System Checks / Time Based Evasion | Gates on system uptime, installed physical memory and user-profile file count. | GAP — no endpoint telemetry |
| Defense Evasion | T1497.002 — User Activity Based Checks | Cursor-movement gate before payload execution. | GAP — no endpoint telemetry |
| Persistence | T1053.005 — Scheduled Task/Job: Scheduled Task | On-logon task with highest privileges and a 30-second delay. | Q11; hardening H8 |
| Persistence | T1546.003 — Event Triggered Execution: WMI Event Subscription | Permanent subscription triggered by new logon-session instances; prior filters, consumers and bindings of the same name are removed first. | Section 7 WMI hunt; hardening H8 |
| Persistence | T1547.001 — Boot or Logon Autostart: Registry Run Keys / Startup Folder | Startup-folder deployment offered as a lower-complexity fallback. | Section 7 autorun hunt |
| Defense Evasion | T1055 — Process Injection (process ghosting, module stomping, process-parameter poisoning) | Loader offers several execution modes, so the same framework produces different telemetry on different hosts. | Partial — Falcon behavioural detections |
| Defense Evasion | T1620 — Reflective Code Loading | Manual PE mapping, shellcode execution, and an allocate-write-protect-execute fallback for raw payloads. | Partial |
| Command and Control | T1071.001 — Application Layer Protocol: Web Protocols | Plaintext HTTP JSON tasking to a hard-coded endpoint on a non-standard port. | Q8, Q9 |
| Command and Control | T1102.002 — Web Service: Bidirectional Communication | Private code-hosting repository used as a per-host mailbox for beacons, tasks and results. | Q10; hardening H7 |
| Command and Control | T1132.001 — Data Encoding: Standard Encoding | Inline Base64 content returned by the repository contents API. | Section 7 proxy hunt |
| Discovery | T1016, T1018, T1069.002, T1082, T1518, T1033 | Network configuration, domain controllers, domain computers, privileged group membership, installed software, local token and privilege detail. | Post-compromise pivots in section 14 |
| Exfiltration | T1567 — Exfiltration Over Web Service | Result objects and uploaded files written back to the dead-drop repository. | Q10; section 7 proxy hunt |
Native Audit-Log Hunts
Run these where Falcon is absent, where the sensor was installed after the intrusion window, or to recover the stages that produce no sensor event. Each stands alone and needs no CQL.
Microsoft Defender operational log
- Event ID 5007 — configuration change. Filter for values under the exclusions key. This is the single most durable artifact in the chain: the exclusions were added before the payloads landed and survive removal of the malware.
- Event ID 5001 / 5010 / 5012 — real-time, antispyware or antivirus scanning disabled.
- Enumerate current exclusions across the fleet and compare against an approved baseline. Any path under a user profile, and any process-name exclusion for an interpreter, is a finding regardless of this campaign.
PowerShell logging
- 4104 (Script Block Logging) — search for blocks containing a long unbroken hexadecimal literal together with
Invoke-Expression, or containing-Verb RunAstogether with-WindowStyle Hidden. - 4104 — search for
Add-MpPreferenceorSet-MpPreferencewith any exclusion parameter. - 4103 (module logging) and transcription — recover the decoded stage body where script block logging was truncated.
Task Scheduler operational log
- 106 (task registered), 140 (task updated), 141 (task deleted). Export every logon-triggered task and review those whose action points into a user profile, temp or programdata path, or that run with highest privileges under a standard user account.
- Compare
C:\Windows\System32\Tasksagainst a known-good baseline. Pay attention to update-themed task names, which is the naming style the reporting calls out.
WMI-Activity operational log
- 5857 (provider started), 5860 and 5861 (permanent event consumer registration). A subscription created through the WMI API produces no process-execution telemetry, so 5861 is the only reliable source.
- Enumerate
__EventFilter,CommandLineEventConsumer,ActiveScriptEventConsumerand__FilterToConsumerBindinginstances under the subscription namespace on every host, not just suspected ones. Look for filters keyed on logon-session object creation.
Sysmon (where deployed)
- Event 7 (Image Loaded) — the cleanest possible detection for this chain: a signed executable loading an unsigned or mismatched DLL from its own directory outside Program Files. Filter on the three side-load DLL names.
- Event 11 (File Create) — archives and PE files written into download and temp paths.
- Events 19, 20, 21 — WMI filter, consumer and binding activity.
- Event 1 — carries
SignatureandSignatureStatus, which the Falcon event stream does not expose as a queryable signer field.
Web proxy / secure web gateway
- Search full request URIs for the per-victim mailbox path on the dead-drop repository, and for requests to the code-hosting contents API whose response bodies are JSON task objects.
- Search for the spoofed OneDrive user agent paired with a code-hosting destination. The genuine OneDrive client never contacts the code-hosting API, so that pairing alone is a finding.
- Search for plaintext HTTP requests to bare IPv4 literals originating from endpoints — the delivery stage used no hostname at all.
- Search for downloads of password-protected archives from consumer file-sharing services, then correlate against mail-gateway click telemetry for the same user in the same minute.
Mail gateway
- Retro-hunt for the redirector host across all recipients, not just the ones who clicked, and for any message delivering a link to a consumer file-sharing service with an archive password in the message body.
- Review policy: encrypted and password-protected archives that bypass content inspection should be quarantined or detonated, not delivered.
CrowdStrike LogScale CQL Hunt Queries
No query below carries an in-query time filter. Set the window with the console time picker. Each query records its intended lookback as a // LOOKBACK: comment. Start at 30 days for the persistence and Defender queries, 7 days for the network ones.
Looks for: the third link in the chain — the shortcut wrote a Base64 blob and a companion script into a temp path, then used a signed Microsoft utility to reconstruct the next stage. FP: certificate administrators and some software installers legitimately use certutil -decode; the discriminator is the user-writable path plus an interactive ancestor such as explorer.exe.
// HUNT: certutil decoding a staged blob inside a user-writable path // MITRE: T1140, T1204.002, T1059.003 // CONF: high | FP: medium | COST: low // REQUIRES: ProcessRollup2 with command lines (Falcon Insight) // FALSE POSITIVES: certificate tooling, log-packing scripts, some vendor installers // TUNING: exclude ParentBaseFileName values belonging to your patch and software-deployment agents, and exclude decodes whose target sits under a managed application directory // LOOKBACK: 30d - set the window with the console time picker #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^certutil\.exe$/i | CommandLine=/-decode/i | CommandLine=/\\(Temp|AppData|Downloads|Users\\Public)\\/i | table([@timestamp, ComputerName, UserName, GrandparentBaseFileName, ParentBaseFileName, FileName, CommandLine, SHA256HashData])
Looks for: the self-elevation step. The decoded stage checked for administrative rights and, when it did not have them, wrote a temp script and relaunched PowerShell with an elevation verb, no profile, a policy bypass and a hidden window. That combination is close to unheard of in benign automation, which normally runs elevated already or does not need to hide.
// HUNT: PowerShell relaunching itself elevated, hidden and policy-bypassed // MITRE: T1059.001, T1548 // CONF: high | FP: low | COST: low // REQUIRES: ProcessRollup2 with command lines // FALSE POSITIVES: a small number of self-elevating vendor install wrappers // TUNING: if a packaging tool trips this, exclude on the parent binary rather than on the flag combination - the flags are the signal // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^(powershell|powershell_ise|pwsh)\.exe$/i | CommandLine=/-Verb\s+RunAs/i | CommandLine=/-w(indowstyle)?\s+hidden/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine, SHA256HashData])
Looks for: the pivotal step. The actor excluded the staging directory and the process name python.exe before any executable arrived, which meant nothing that followed was scanned. FP: IT teams and line-of-business software genuinely add exclusions; triage on who ran it and what was excluded. A process-name exclusion for an interpreter, or a path exclusion under a user profile, is a finding on its own merits even outside this campaign.
// HUNT: Defender exclusion added for a path or a process name // MITRE: T1562.001 // CONF: high | FP: medium | COST: low // REQUIRES: ProcessRollup2 with command lines // FALSE POSITIVES: IT-run exclusion scripts, endpoint-management tooling, some application installers // TUNING: exclude your management agent as ParentBaseFileName and your build or imaging service account as UserName; never exclude on the exclusion value itself, and always triage any exclusion naming an interpreter or a user-profile path // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/(Add|Set)\-MpPreference/i | CommandLine=/Exclusion(Path|Process|Extension|IpAddress)/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine])
Looks for: the same tampering seen from the registry rather than the command line. This catches exclusions written directly, by a compiled binary, or by any path that never touches a PowerShell cmdlet — which Q3 would miss entirely. FP: legitimate management tooling writes here too; the discriminator is the writing process and the value content.
// HUNT: Defender exclusion key written in the registry // MITRE: T1562.001, T1112 // CONF: high | FP: medium | COST: low // REQUIRES: RegGenericValueUpdate / RegSystemConfigValueUpdate // FALSE POSITIVES: endpoint-management and antivirus-migration tooling writing approved exclusions // TUNING: exclude writes whose RegStringValue resolves under Program Files or your managed application root, then review everything that remains by hand - the population should be small enough to read // LOOKBACK: 30d #event_simpleName=/^(RegGenericValueUpdate|RegSystemConfigValueUpdate)$/ | RegObjectName=/\\Windows Defender\\Exclusions\\/i | table([@timestamp, ComputerName, UserName, RegObjectName, RegValueName, RegStringValue])
Looks for: the delivery archive. It was named to pass a glance as an official Python embeddable package but carried an architecture suffix that does not exist in any real Python release. This query deliberately matches the whole -embed- naming family rather than only the one observed suffix, so a re-spun archive still lands.
// HUNT: archive imitating a Python embeddable distribution written to a user path // MITRE: T1105, T1036.005 // CONF: medium | FP: low | COST: low // REQUIRES: ZipFileWritten // FALSE POSITIVES: a developer legitimately downloading an embeddable Python package // TUNING: real Python embeddable archives end in -embed-amd64, -embed-win32 or -embed-arm64; anything else is worth reading, and any of them landing on a non-developer endpoint is worth reading too // LOOKBACK: 30d #event_simpleName=/^ZipFileWritten$/ | TargetFileName=/python\-3[\d.]*\-embed/i | table([@timestamp, ComputerName, UserName, ContextBaseFileName, TargetFileName, SHA256HashData])
Looks for: the clearest single signal in the chain. The actor launched python.exe with no script path, no -m module and no interactive console, purely so that Windows would resolve and load the adjacent rogue runtime DLL. A Python interpreter with nothing to interpret is not running Python — it is a side-load host. The path filter keeps this tight to user-writable locations.
// HUNT: Python host executed with nothing to interpret (side-load tell) // MITRE: T1574.002, T1036.005 // CONF: high | FP: low | COST: low // REQUIRES: ProcessRollup2 with command lines // FALSE POSITIVES: a user opening a bare Python REPL from a portable install under their profile // TUNING: on developer estates, exclude ImageFileName paths under your sanctioned toolchain roots rather than dropping the path filter; keep the no-argument condition, which is what separates a side-load from a REPL that was actually typed into // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^pythonw?3?\d*\.exe$/i | ImageFileName=/\\(AppData|Temp|ProgramData|Downloads|Users\\Public)\\/i | CommandLine!=/\.(py|pyw|pyz)\b/i | CommandLine!=/\s\-(m|c|i|E|X)\b/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine, SHA256HashData])
Looks for: all three side-load DLLs at the moment they hit disk. Two of these names belong to Windows system libraries that should never be written outside the system directories, and the third belongs to a Python runtime that should only ever be written by a Python installer. FP: genuine Python installs, and applications that ship their own copy of a version library, both write these names legitimately — hence the system-path and installer exclusions in the tuning line.
// HUNT: side-load DLL names written outside the system directories // MITRE: T1574.002, T1036.005 // CONF: high | FP: medium | COST: medium // REQUIRES: PeFileWritten / NewExecutableWritten // FALSE POSITIVES: Python installers, application setup routines that bundle their own version library // TUNING: exclude ContextBaseFileName values of msiexec.exe, TrustedInstaller.exe and your software-deployment agent, and exclude writes landing under Program Files; what should remain is DLLs written into user-writable paths by something that is not an installer // LOOKBACK: 30d #event_simpleName=/^(PeFileWritten|NewExecutableWritten)$/ | TargetFileName=/\\(python3\d\d|version|wtsapi32|vcruntime\d+)\.dll$/i | TargetFileName!=/^[A-Za-z]:\\Windows\\/i | table([@timestamp, ComputerName, UserName, ContextBaseFileName, TargetFileName, SHA256HashData])
Looks for: direct contact with the two published endpoints — the plaintext HTTP payload-delivery server and the Matryoshka HTTP C2 on its non-standard port. Run this first as a fast yes-or-no answer for the reporting window, then move to the behavioural queries, because this infrastructure has a short useful life and its absence proves nothing.
// HUNT: known HollowFrame and Matryoshka network infrastructure // MITRE: T1071.001, T1105 // CONF: high | FP: low | COST: low // REQUIRES: NetworkConnectIP4 // FALSE POSITIVES: none expected - these are single-campaign endpoints // TUNING: none needed; a hit here is an incident, not a tuning exercise // LOOKBACK: 90d - go as far back as retention allows, the report window opens well before disclosure #event_simpleName=/^NetworkConnectIP4$/ | in(RemoteAddressIP4, values=["2.26.252.84", "45.158.196.184"]) | table([@timestamp, ComputerName, UserName, ContextBaseFileName, RemoteAddressIP4, RemotePort_decimal, Protocol_decimal])
Looks for: the durable version of Q8. Once the implant is running inside a Python host or an application updater, its network activity is attributed to that trusted process. This groups by process and destination so a small number of repeated destinations stands out from ordinary traffic. FP: high — Python legitimately reaches the internet constantly on developer and data-science endpoints, and updaters legitimately reach their own vendor. Read it as a ranked list, not as an alert.
// HUNT: trusted side-load hosts opening outbound connections // MITRE: T1071.001, T1574.002 // CONF: medium | FP: high | COST: medium // REQUIRES: NetworkConnectIP4 // FALSE POSITIVES: package installs and API calls from developer Python, and genuine updater traffic to vendor infrastructure // TUNING: run this scoped to non-developer host groups first; then exclude vendor destination ranges for the updater and your internal package mirror for Python, and read what is left - a single stable destination on a non-standard port from an updater process is the shape you want // LOOKBACK: 7d #event_simpleName=/^NetworkConnectIP4$/ | ContextBaseFileName=/^(python|pythonw|updater|loader_panda)\.exe$/i | groupBy([aid, ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort_decimal], function=count(as=Connections)) | sort(Connections, order=desc, limit=200)
Looks for: the dead-drop channel. Matryoshka GitHub polls a private repository through the contents API, so the endpoint resolution is the only piece visible on the sensor. FP: high by construction — this destination is entirely normal on engineering estates, and the query is only meaningful once you have scoped it. Treat the process list as the finding, not the domain: an updater, an interpreter with no script, or an unsigned binary resolving a code-hosting API is worth a look; a code editor doing it is not.
// HUNT: code-hosting API resolved by non-browser, non-developer processes // MITRE: T1102.002, T1567 // CONF: medium | FP: high | COST: medium // REQUIRES: DnsRequest // FALSE POSITIVES: every developer tool, package manager, CI agent and IDE extension host on the estate // TUNING: this query is unusable estate-wide - scope it to host groups with no development role first, then build the ContextBaseFileName exclusion list from what your own baseline shows, and finally pivot every survivor to proxy logs for the request URI, because the repository path and the user agent are what actually confirm it // LOOKBACK: 7d #event_simpleName=/^DnsRequest$/ | DomainName=/^api\.github\.com$/i | ContextBaseFileName!=/^(chrome|msedge|firefox|brave|iexplore|code|devenv|git|git-remote-https|gh|node|npm|yarn|dotnet|java|curl|wget|OneDrive)\.exe$/i | groupBy([aid, ComputerName, ContextBaseFileName], function=count(as=Lookups)) | sort(Lookups, order=desc, limit=200)
Looks for: the primary persistence mechanism — an on-logon task running with highest privileges after a short delay, whose action points into a user-writable directory. The reporting notes the operator favoured update-themed task names, so read the task name column rather than filtering on it. FP: some user-scoped software genuinely registers logon tasks under a profile path; the count should be small enough to review by hand.
// HUNT: logon-triggered scheduled task pointing at a user-profile payload // MITRE: T1053.005 // CONF: high | FP: medium | COST: low // REQUIRES: ScheduledTaskRegistered / ScheduledTaskModified // FALSE POSITIVES: user-scoped auto-update helpers and sync clients that install under a profile // TUNING: exclude the specific TaskName values your known user-scoped applications register, one by one, and keep a standing review of anything new; do not exclude the whole user-profile path, which is the entire point of the query // LOOKBACK: 30d #event_simpleName=/^(ScheduledTaskRegistered|ScheduledTaskModified)$/ | TaskExecutable=/\\(Users|AppData|ProgramData|Temp|Downloads)\\/i | table([@timestamp, ComputerName, UserName, TaskName, TaskAuthor, TaskExecutable])
Looks for: the operator actually typing. Both backdoor variants execute tasking through a command shell and capture merged standard output and error so the result can be returned over the C2 channel; the code-page switch appears on the GitHub variant. A Python host or an application updater is never a legitimate parent for a command shell, which is what makes this card high-confidence despite the generic child process.
// HUNT: command shell spawned by a side-load host with merged-output capture // MITRE: T1059.003, T1071.001 // CONF: high | FP: low | COST: low // REQUIRES: ProcessRollup2 with command lines // FALSE POSITIVES: build scripts that shell out from a Python parent on developer endpoints // TUNING: on developer estates exclude Python parents whose ImageFileName sits under a sanctioned toolchain root, and keep updater parents unexcluded - an application updater spawning a shell has no benign explanation // LOOKBACK: 30d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^(cmd|powershell|pwsh)\.exe$/i | ParentBaseFileName=/^(python|pythonw|updater|loader_panda)\.exe$/i | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine, SHA256HashData])
Correlation beats any single card. Run Q3 or Q4 first, take the host and the timestamp, then re-run Q5, Q6, Q7 and Q11 scoped to that host across the following two hours. The chain compresses into minutes on a live intrusion, so an exclusion followed inside the same hour by a PE write into the excluded path and a no-argument interpreter launch is a confirmed sequence, not a coincidence.
CrowdStrike Custom IOA Recommendations
Four of the twelve queries are strong enough to promote to Custom IOAs. The rest stay as scheduled hunts because their false-positive profile depends on estate composition and cannot be tuned generically.
| IOA name | Type / pattern | Exclusions | Severity | Action |
|---|---|---|---|---|
| IOA — Defender exclusion added by a non-management process | Process Creation. Image: .*\\powershell(_ise)?\.exe or .*\\pwsh\.exe. Command line: .*(Add|Set)-MpPreference.*Exclusion(Path|Process).* | Exclude the image path of your endpoint-management agent and the grandparent process of your imaging pipeline. | High | Detect first for two weeks, then Prevent once the benign population is empty. |
| IOA — Interpreter launched with no script argument from a user path | Process Creation. Image: .*\\AppData\\.*\\python\d*\.exe (repeat for the temp and programdata roots). Command line: negative match on \.py and on the module and interactive switches. | Exclude sanctioned portable-toolchain roots on developer host groups only. Do not apply the exclusion estate-wide. | Critical | Prevent on non-developer host groups; Detect on developer groups. |
| IOA — Command shell spawned by an application updater | Process Creation. Parent image: .*\\updater\.exe. Image: .*\\(cmd|powershell|pwsh)\.exe | None. An application updater spawning an interactive shell has no benign explanation in a managed estate. | Critical | Prevent. |
| IOA — Certutil decoding content in a user-writable path | Process Creation. Image: .*\\certutil\.exe. Command line: .*-decode.*\\(Temp|AppData|Downloads)\\.* | Exclude your patch-management and certificate-enrolment agents as parent image. | High | Detect, then Prevent after baseline. |
Stay as scheduled hunts
- Q9 and Q10 — false-positive rate is a function of how many developers you have. Promoting either to an IOA before scoping produces an unusable alert stream and trains analysts to close the whole rule.
- Q7 — legitimate installers write these DLL names often enough that a prevent action risks blocking a Python or application install. Run weekly, review by hand.
- Q8 — atomic infrastructure belongs in Falcon IOC Management, not in an IOA. Import the CSV in section 10 instead.
- Q11 — task persistence is better served by a scheduled search with a review queue, because the finding is usually "which task is this" rather than "block it now".
Deployment path
- Create a Custom IOA Rule Group scoped to a pilot host group of 20 to 50 endpoints spanning your real mix of roles.
- Add each rule in Detect mode with the exclusions above. Leave it for a full business cycle, including a patch window and a month-end.
- Review every detection. Convert anything benign into a named exclusion; do not broaden the pattern.
- Promote to Prevent only for the rules whose benign population reached zero, and only on the host groups where that was true.
- Record the pilot outcome in the changelog and bump this pack to v1.0.
Machine-Readable IOC Appendix
The import block below contains malicious artifacts only. The three legitimate host binaries the actor abused are in a separate context-only block and are deliberately not importable. Importing them as blocks would break Python and the OneDrive updater across the estate.
type,value,action,severity,expiration,description,tags sha256,55ed788ca7130089c4262cda8f0cb936eab9244bea68eeebe863842ba368e270,prevent,critical,2027-02-01,HollowFrame initial phishing archive,campaign:HollowFrame sha256,cb6b6289698f53111bb026ad5e95f841a03cf690bda670bb551e416e8cda77d3,prevent,critical,2027-02-01,HollowFrame staged payload archive,campaign:HollowFrame sha256,b2123d476646459234ab3083b79d13690d3864f9f5a9aa2b1272dfd502d0d3e2,prevent,critical,2027-02-01,HollowFrame Go side-load DLL,campaign:HollowFrame sha256,d3bf01fce1f97f86aa58e9220e6dc1dae90005e5d7552f06192393d370355ed9,prevent,critical,2027-02-01,HollowFrame Go loader payload,campaign:HollowFrame sha256,f882d0a0bf6f7fc687b9be6c7991d0bce4b60e81b742f5919933d07de37d625c,prevent,critical,2027-02-01,HollowFrame native dropper,campaign:HollowFrame sha256,f59f32c9af4fa8a5dbd4668df8893593bc0c4324816cbf9b956acedcbfb8cdb6,prevent,critical,2027-02-01,Matryoshka HTTP backdoor DLL,campaign:Matryoshka sha256,f96ff2f3abbff7f382ace509b90e54853b4b61c402ecde27d82f1c17b414867b,prevent,critical,2027-02-01,Matryoshka GitHub backdoor DLL,campaign:Matryoshka sha256,REPLACE_WITH_LNK_SHA256,detect,high,2027-02-01,Malicious shortcut - hash not recovered by the reporting vendor,campaign:HollowFrame ipv4,2.26.252.84,detect,high,2026-11-01,HollowFrame PowerShell payload delivery server,campaign:HollowFrame ipv4,45.158.196.184,detect,high,2026-11-01,Matryoshka HTTP C2 on port 8888,campaign:Matryoshka domain,avvoalert.info,detect,high,2026-11-01,Attacker-controlled phishing redirector,campaign:HollowFrame
# These three hashes are LEGITIMATE SIGNED BINARIES abused as side-load hosts. # Use them for retrospective search and for scoping. Never set them to prevent. # python.exe 14a89eda72e385f76bf15a7c4fd539c48837cf5df444a16f28c5b94f29799550 # vcruntime140.dll d66c3b47091ceb3f8d3cc165a43d285ae919211a0c0fcb74491ee574d8d464f8 # updater.exe 015d7b212d20681d346e690159e7f4cd9e88b51de27e84b514fce865deef3a5c # # Pivot instead on WHERE they ran from and WHAT loaded beside them: # - any of these executing from a user-writable path # - a same-directory DLL whose name matches a system or runtime library # - the interpreter running with no script, module or interactive argument
B1 Defender exclusion added for a user-writable DIRECTORY and for a PROCESS NAME,
minutes apart, by the same process, before any executable is written there.
B2 Interpreter host executed with no script path, no module switch and no
interactive flag, from a user-writable directory.
B3 A DLL bearing the name of a system or runtime library written into the same
directory as a signed executable, outside Program Files and outside Windows.
B4 Signed utility used to decode content in a user temp path, with an interactive
shell or the desktop shell in the ancestry.
B5 PowerShell relaunching itself with an elevation verb plus a hidden window plus
an execution-policy bypass.
B6 Archive named to imitate an official runtime distribution but carrying an
architecture suffix that no real release uses.
B7 On-logon scheduled task, highest privileges, short start delay, action path
inside a user profile or temp directory.
B8 Permanent WMI event subscription whose filter keys on logon-session object
creation, where prior same-named filters and consumers were deleted first.
B9 Application updater or interpreter process opening a command shell.
B10 Beacon-cadence requests to a code-hosting contents API from a process with no
development role, with an application user agent that does not match the
process making the request.
B11 Plaintext HTTP to a bare IPv4 literal with no hostname, from an endpoint,
fetching an archive.
B12 Repeated GET-then-PUT pairs against the same small set of JSON objects under
one repository path, one path per host.
HollowFrame Go modular loader and persistence framework Matryoshka HTTP Rust backdoor, plaintext HTTP JSON tasking Matryoshka GitHub Rust backdoor, private repository dead drop loader_panda.exe native dropper staging the second side-load Project220Rebr Go build directory name - NOT a family or actor name looks like a sandbox, exiting anti-analysis failure message pongv2 health-check response value /repos/adioziaete/memio/contents/ per-victim mailbox path OneDrive/24.170.0825.0001 spoofed user agent beacon.json / cmd.json / result.json per-host tasking objects <computer>_<username> per-victim directory naming scheme %LOCALAPPDATA%\Programs\Python\Python311-Brief staging directory
# Dump every current Defender exclusion and the tamper-protection state.
# Anything under a user profile, or any process-name exclusion for an
# interpreter, is a finding regardless of this campaign.
$p = Get-MpPreference
[PSCustomObject]@{
Host = $env:COMPUTERNAME
ExclusionPath = ($p.ExclusionPath -join '; ')
ExclusionProcess= ($p.ExclusionProcess-join '; ')
ExclusionExt = ($p.ExclusionExtension -join '; ')
TamperProtection= (Get-MpComputerStatus).IsTamperProtected
RealTimeDisabled= $p.DisableRealtimeMonitoring
ASRRules = ($p.AttackSurfaceReductionRules_Ids -join '; ')
ASRActions = ($p.AttackSurfaceReductionRules_Actions -join '; ')
}
# Logon-triggered tasks whose action points into a user-writable path.
Get-ScheduledTask | Where-Object {
$_.Triggers.CimClass.CimClassName -contains 'MSFT_TaskLogonTrigger'
} | ForEach-Object {
[PSCustomObject]@{
TaskName = $_.TaskName
RunLevel = $_.Principal.RunLevel
Action = ($_.Actions.Execute -join '; ')
}
} | Where-Object { $_.Action -match 'AppData|\\Temp\\|ProgramData|\\Users\\' }
# Permanent WMI event subscriptions - the mechanism that leaves no process trace.
Get-WmiObject -Namespace root\subscription -Class __EventFilter
Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer
Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding
Detection Validation Gates
Do not treat a query as deployed until it has cleared all four gates. A query that returns nothing because the telemetry is absent looks identical to a query that returns nothing because the estate is clean.
| Gate | What to prove | How | Fail action |
|---|---|---|---|
| 1 · Telemetry ready | Each required event type is actually arriving from a representative sample of hosts. | For each of ProcessRollup2, RegGenericValueUpdate, PeFileWritten, ZipFileWritten, NetworkConnectIP4, DnsRequest, ScheduledTaskRegistered, run a bare event-name query grouped by host and confirm the host count matches your sensor inventory. | Investigate sensor policy and event-collection settings before trusting any empty result. Record the shortfall as a coverage gap. |
| 2 · Benign baseline | You know what each query returns on a clean estate, in normal working hours and during a patch window. | Run every query over 30 days with no exclusions. Record the hit count and the distinct process, user and host counts. Q9 and Q10 in particular must be baselined before anyone is asked to triage them. | If Q3, Q6 or Q12 returns more than a handful of results per week, tune before deploying — those three are meant to be quiet. |
| 3 · Positive test | Each query fires on a controlled, benign reproduction of the behavior it targets. | On an isolated lab host: add and immediately remove a Defender path exclusion (Q3, Q4); launch a portable interpreter with no arguments from a profile directory (Q6); copy a harmless renamed DLL beside a signed binary in a temp folder (Q7); register and delete a logon task pointing at a profile path (Q11); spawn a shell from a renamed parent (Q12). Confirm each returns the expected row. | A query that does not fire on its own reproduction has a field, path or regex problem. Fix it before it is trusted in production. |
| 4 · Promotion | The rule survives a full business cycle without generating noise the SOC learns to ignore. | Pilot the four IOA candidates in Detect mode on 20 to 50 mixed-role hosts for at least one month, covering a patch cycle and a month-end. | Any rule whose benign population is not empty at the end of the pilot stays a scheduled hunt. Do not promote on a partial baseline. |
Order matters for the reproduction test. Run the Q3 exclusion test and the Q7 DLL-drop test on the same lab host within the same hour, then confirm your correlation logic surfaces the pair. That pairing — not either event alone — is what this pack is really detecting.
Hardening — Tiered
Every control below is anchored to a MITRE mitigation (the why) and to a platform benchmark or vendor guidance (the what). The chain has ten links; you do not need to break all of them. Breaking the execution link (user-writable execution) or the tamper link (Defender exclusions) ends the intrusion on its own.
H1 · Import the malicious hashes and network indicators. Load the section 10 CSV into Falcon IOC Management. Do not import the three trusted host binaries. MITRE M1040 Behavior Prevention on Endpoint. Verify: confirm the indicator count in IOC Management matches the CSV row count minus the placeholder row.
H2 · Enable Defender Tamper Protection everywhere and alert on every exclusion change. Tamper Protection blocks exclusion changes made outside the management channel, which is exactly the step this actor performed first. Pair it with a standing alert on Defender operational event 5007 and on writes to the exclusions registry key. MITRE M1024 Restrict Registry Permissions, M1018 User Account Management. CIS Microsoft Windows Benchmark, Microsoft Security Baseline for Windows. Verify: (Get-MpComputerStatus).IsTamperProtected returns True on a sample of hosts, and a test exclusion added locally is rejected.
H3 · Quarantine or detonate password-protected archives at the mail gateway, and strip shortcut files from archives. An encrypted archive that content inspection cannot open should not be delivered on the assumption that it is benign. A shortcut inside a delivered archive has no legitimate business use in almost every estate. MITRE M1021 Restrict Web-Based Content, M1049 Antivirus/Antimalware. Verify: send yourself a password-protected archive containing a harmless shortcut and confirm it is held.
H4 · Turn on the relevant ASR rules in Block mode. Block executable content from email client and webmail; block execution of potentially obfuscated scripts; block process creations originating from PSExec and WMI commands; block credential stealing from the LSASS subsystem. These four cover the delivery, the obfuscated PowerShell stage, the WMI persistence path and the most likely follow-on tooling. MITRE M1038 Execution Prevention, M1042 Disable or Remove Feature or Program. Verify: read back AttackSurfaceReductionRules_Actions from Get-MpPreference and confirm each is 1 (block).
H5 · Block execution from user-writable paths with WDAC or AppLocker. This single control ends the chain at step 8: the staged interpreter, the loader, the dropper and both backdoors all execute from a user profile directory. Deploy in audit mode first, harvest the legitimate population, then enforce. MITRE M1038 Execution Prevention. CIS Microsoft Windows Benchmark; Microsoft WDAC deployment guidance. Verify: attempt to run a benign executable copied to %LOCALAPPDATA% and confirm it is blocked, with the block logged.
H6 · Constrain and instrument PowerShell. Enable Script Block Logging, module logging and transcription estate-wide; remove PowerShell 2.0; enforce Constrained Language Mode for standard users through WDAC. The XOR downloader stage is invisible without script block logging and largely non-functional under constrained language. MITRE M1042, M1047 Audit. Microsoft Security Baseline. Verify: run a benign script and confirm 4104 events land in your log pipeline; confirm $ExecutionContext.SessionState.LanguageMode returns ConstrainedLanguage for a standard user.
H7 · Constrain code-hosting API access from non-development endpoints. Use the proxy, application control, or identity-aware policy to limit which hosts and which processes may reach the code-hosting API, rather than blocking the service outright. This is the reporting vendor's own recommendation and it is the only practical answer to a dead drop hosted on infrastructure your developers legitimately need. Require full-URI proxy logging on the paths you do allow. MITRE M1037 Filter Network Traffic, M1057 Data Loss Prevention. Verify: from a non-developer host, confirm the API is unreachable; from a developer host, confirm the request URI appears in proxy logs.
H8 · Baseline and monitor scheduled tasks, WMI subscriptions and autoruns. Collect the full task, subscription and Startup-folder inventory from every endpoint, store it, and diff it weekly. HollowFrame offered all three mechanisms and would pick whichever the environment allowed. MITRE M1047 Audit, M1028 Operating System Configuration. Verify: register a benign logon task and confirm it appears in the next diff.
H9 · Enforce Mark-of-the-Web propagation through archive handling. Configure your standard archive tool to propagate the zone identifier to extracted files, and restrict third-party archivers that strip it. Without propagation, a shortcut extracted from a downloaded archive carries no web mark and receives no warning. MITRE M1021 Restrict Web-Based Content, M1038. Flagged: vendor configuration guidance rather than a formal benchmark control. Verify: extract a file from a downloaded archive and confirm the zone identifier stream is present.
H10 · Remove standing local administrator rights. The chain explicitly tested for elevation and relaunched to request it. Without a route to administrator, the actor loses the machine-wide exclusion, the highest-privileges task and the WMI subscription. MITRE M1026 Privileged Account Management, M1018. CIS Microsoft Windows Benchmark. Verify: confirm the local Administrators group on a sample of endpoints contains only the approved management principals.
H11 · Force endpoint egress through an inspecting proxy and deny direct outbound HTTP. Both the delivery stage and the HTTP backdoor used plaintext HTTP straight to an IP address with no hostname and no proxy. Denying direct outbound HTTP from endpoints removes both. MITRE M1037 Filter Network Traffic. Verify: from an endpoint, attempt a plain HTTP request to an external IP literal and confirm it fails and is logged.
H12 · Move Falcon to prevention on the behaviours this pack proves out. Once the section 11 pilot completes, promote the qualifying Custom IOAs to Prevent and raise the sensor policy to block the in-memory execution patterns the loader relies on. MITRE M1040 Behavior Prevention on Endpoint. Verify: re-run the section 11 positive tests and confirm the behaviour is now blocked rather than merely detected.
Deployable Playbooks
Playbook 1 — Enforce Tamper Protection and audit Defender exclusions (H2)
Prerequisites: Microsoft Defender Antivirus in active mode; Intune or Configuration Manager for the Tamper Protection setting (it cannot be enabled by local registry edit on a managed device); local administrator for the audit step. Reboot required: no. Rollback: set the Tamper Protection policy back to Not Configured in the management console; the exclusion audit is read-only and needs no rollback. Note that rolling back Tamper Protection re-exposes exactly the tamper path this campaign used, so record a reason if you do it.
1. In Intune: Endpoint security > Antivirus > create a Windows Security Experience profile, set "TamperProtection (Device)" to "On", assign to all devices. (Tamper Protection is enforced by the cloud-managed policy; a local registry write is not a supported enablement path and will be reverted.) 2. Confirm enforcement on a sample of hosts: (Get-MpComputerStatus).IsTamperProtected # expect: True 3. Inventory every existing exclusion across the fleet: $p = Get-MpPreference $p.ExclusionPath $p.ExclusionProcess $p.ExclusionExtension 4. Flag for removal any exclusion that: - resolves under a user profile, %TEMP%, %APPDATA% or %LOCALAPPDATA% - names an interpreter process (python.exe, powershell.exe, wscript.exe, ...) - has no owner, ticket or documented business justification 5. Remove a flagged exclusion through your management channel, not locally: Remove-MpPreference -ExclusionPath 'C:\Path\To\Remove' Remove-MpPreference -ExclusionProcess 'python.exe' 6. Forward Defender operational event 5007 to the SIEM and alert on every occurrence whose new value falls under the exclusions key.
Playbook 2 — Attack Surface Reduction rules in Block mode (H4)
Prerequisites: Defender Antivirus active with real-time protection on; Windows 10 1709 or later; a pilot ring of 20 to 50 mixed-role endpoints. Reboot required: no. Rollback: re-run the same commands with -AttackSurfaceReductionRules_Actions Disabled for the specific rule ID, or set the rule to AuditMode to keep visibility while removing enforcement. Roll back per rule, never all four at once, so you keep the coverage you have already proven.
1. Put the four rules in AUDIT first and leave them for one business cycle:
Add-MpPreference -AttackSurfaceReductionRules_Ids `
BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550, ` # exec content from mail/webmail
5BEB7EFE-FD9A-4556-801D-275E5FFC04CC, ` # potentially obfuscated scripts
D1E49AAC-8F56-4280-B9BA-993A6D77406C, ` # process creation from PSExec/WMI
9E6C4E1F-7D60-472F-BA1A-A39EF669E4B2 ` # credential theft from LSASS
-AttackSurfaceReductionRules_Actions AuditMode, AuditMode, AuditMode, AuditMode
2. Review Defender event 1122 (audit) for each rule. Build the exclusion list from
what you actually see, not from what you expect to see.
3. Add only the exclusions the audit justified:
Add-MpPreference -AttackSurfaceReductionOnlyExclusions 'C:\Approved\Path'
4. Promote to BLOCK, one rule at a time, a week apart:
Set-MpPreference -AttackSurfaceReductionRules_Ids BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550 `
-AttackSurfaceReductionRules_Actions Enabled
5. Verify the enforced state:
$p = Get-MpPreference
$p.AttackSurfaceReductionRules_Ids
$p.AttackSurfaceReductionRules_Actions # expect: 1 = Block, 2 = Audit
Playbook 3 — PowerShell Script Block Logging and transcription (H6)
Prerequisites: Group Policy or Intune for the policy path; a log destination sized for the volume — script block logging is verbose; a write-protected file share for transcripts. Reboot required: no; new PowerShell sessions pick the policy up immediately. Rollback: set both policies back to Not Configured in the GPO, or delete the two registry keys below and run gpupdate /force. Transcripts already written are not removed by rollback — retire them under your normal evidence-retention policy.
1. GPO path:
Computer Configuration > Administrative Templates > Windows Components >
Windows PowerShell
- "Turn on PowerShell Script Block Logging" = Enabled
- "Turn on PowerShell Transcription" = Enabled
Output directory = \\logserver\ps-transcripts$ (append-only to clients)
- "Turn on Module Logging" = Enabled, module names = *
2. Equivalent registry values if you are deploying outside GPO:
HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
EnableScriptBlockLogging (DWORD) = 1
HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription
EnableTranscripting (DWORD) = 1
OutputDirectory (SZ) = \\logserver\ps-transcripts$
3. Remove the legacy engine, which ignores these policies entirely:
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
4. Verify: run a benign script and confirm event 4104 appears in
Microsoft-Windows-PowerShell/Operational and reaches the SIEM.
5. Standing hunts to build on top of it:
- 4104 containing a long unbroken hex literal together with Invoke-Expression
- 4104 containing -Verb RunAs together with -WindowStyle Hidden
- 4104 containing Add-MpPreference or Set-MpPreference with any Exclusion parameter
Playbook 4 — AppLocker audit-then-block for user-writable paths (H5)
Prerequisites: Application Identity service set to Automatic and running on targets; a GPO scoped to a pilot ring; an inventory owner who can adjudicate the audit results. Use WDAC instead of AppLocker if you already run it — the intent is identical. Reboot required: no, but the Application Identity service must be restarted or the host recycled for a first-time enablement. Rollback: unlink the GPO and run gpupdate /force; rules stop being enforced immediately. Keep the audit-mode GPO linked after rolling back enforcement so you do not lose visibility while you re-scope.
1. Ensure the enforcement service will run: Set-Service -Name AppIDSvc -StartupType Automatic Start-Service -Name AppIDSvc 2. Create the GPO at: Computer Configuration > Windows Settings > Security Settings > Application Control Policies > AppLocker Create default rules for Executable and DLL collections, then set BOTH collections to "Audit only". 3. Add a deny rule for user-writable execution (Executable + DLL collections): Path condition: %OSDRIVE%\Users\*\AppData\* Path condition: %OSDRIVE%\Users\*\Downloads\* Path condition: %OSDRIVE%\ProgramData\* Path condition: %OSDRIVE%\Windows\Temp\* (The DLL collection is the one that matters here - the side-loaded payloads are DLLs, and an executable-only policy would have allowed all three.) 4. Leave in audit for one full business cycle. Harvest results: Get-AppLockerFileInformation -EventLog -EventType Audited -Statistics 5. Convert every legitimate hit into a publisher rule - signed by vendor - rather than a path exception. Path exceptions are what the actor was exploiting. 6. Flip the collections to "Enforce rules", one collection at a time, a week apart. 7. Verify: copy a benign signed executable to %LOCALAPPDATA% and run it. Expect a block, and expect event 8004 (exe) or 8007 (dll) in Microsoft-Windows-AppLocker/EXE and DLL.
Playbook 5 — Mark-of-the-Web propagation and shortcut handling (H3, H9)
Prerequisites: control of the standard archive tool deployed to endpoints; mail-gateway policy edit rights. Reboot required: no. Rollback: set the archive tool's zone-propagation value back to its prior setting and re-run the deployment; revert the gateway rule to its previous action. Both are single-value changes with no dependent state.
1. Standard archive tool - propagate the zone identifier to extracted files. For 7-Zip, deploy the "Propagate Zone.Id stream" option set to "All files" (HKCU\Software\7-Zip\Options : WriteZoneIdExtract (DWORD) = 1) via GPP or Intune. 2. Block or restrict archive tools that cannot propagate the zone identifier, using the same AppLocker or WDAC policy built in Playbook 4. 3. Mail gateway - hold, do not deliver, any message whose attachment is a password-protected archive that content inspection could not open. Route to detonation if your platform supports it, otherwise to a review queue. 4. Mail gateway - strip or quarantine .lnk files found inside delivered archives. Confirm with the business first; the legitimate rate is close to zero in most estates but is not always exactly zero. 5. Verify: send yourself a password-protected archive containing a harmless shortcut, confirm it is held, then extract a file from an ordinary downloaded archive and confirm the zone identifier stream is present: Get-Item .\extracted-file.txt -Stream Zone.Identifier
Containment Runbook
| Phase | Actions | Owner | Evidence to capture |
|---|---|---|---|
| 0 · Triage 15 min | Confirm the hit is genuine before isolating. Pull the full process tree around the trigger. Check whether a Defender exclusion exists for the path involved and when it was created. Determine whether the host is one of several — this actor phished multiple recipients. | SOC L2 | Process tree export; Get-MpPreference output; the triggering query and its time window. |
| 1 · Isolate Immediate | Network-contain the host in Falcon. Do not power it off — both backdoors hold state in memory and the loader decrypts its payload in process. Preserve the running state for collection. Suspend, do not delete, the user's sessions. | SOC L2 / IR lead | Containment timestamp; sensor state before containment. |
| 2 · Collect 1 hr | Memory image first, then triage collection: %TEMP%, %LOCALAPPDATA%\Programs, the Startup folder, C:\Windows\System32\Tasks, the WMI subscription namespace, Defender and PowerShell operational logs, and the browser download history. Hash every DLL sitting beside a signed executable outside the system directories. | IR / forensics | Memory image; triage package; hash list with paths. |
| 3 · Scope 2–4 hrs | Re-run Q3, Q4, Q6, Q7, Q8, Q11 and Q12 estate-wide over the maximum retention window. Query the mail gateway for every recipient of the lure, not just the ones who clicked. Query the proxy for the repository path and the spoofed user agent. Identify every account that logged onto the affected host since the first Defender exclusion. | Threat hunter | Affected-host list; recipient list; account list with logon times. |
| 4 · Eradicate 4–8 hrs | Remove all three persistence mechanisms, in this order: the scheduled task, the WMI filter, consumer and binding, then the Startup-folder entry. Remove the staged directory and every side-loaded DLL. Remove the Defender exclusions — they outlive the malware and are the most commonly missed artifact. Block the hashes and network indicators. Re-image if any elevated session was observed, rather than cleaning. | IR / endpoint ops | Before-and-after persistence inventory; exclusion removal confirmation. |
| 5 · Credentials Same day | The backdoors executed with the rights of whoever launched the side-load host, and the GitHub variant enumerated domain controllers, domain computers and privileged group membership. Reset the interactive user's password and any credential cached on or entered into the host during the window. Review privileged group membership against a known-good baseline. Rotate any service account whose credential could have been used from that host. | Identity / AD team | Reset log; group-membership diff. |
| 6 · Recover 1–3 days | Return the host from a known-good image. Confirm Tamper Protection is on, exclusions are clean, ASR rules are enforced, and the AppLocker or WDAC policy applies. Re-run the section 11 positive tests on the rebuilt host before returning it to the user. | Endpoint ops | Rebuild record; post-rebuild verification output. |
| 7 · Watch 30 days | Keep Q3, Q4, Q6, Q11 and Q12 on a daily schedule scoped to the affected business unit. Keep the network indicators on detect. Re-run the estate-wide scope query weekly, because a second dormant foothold on another endpoint is the expected failure mode for a chain with three persistence options. | SOC | Scheduled search definitions; weekly review notes. |
The most commonly missed eradication step is the Defender exclusion. It was created before the payload and it survives every payload removal. A host that is cleaned but keeps a path and process-name exclusion is a host that is pre-prepared for the next attempt.
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1566.002 | Spear-phishing link through an attacker-controlled redirector | — | — | Partial gateway and proxy hunts only (section 7) |
| T1204.002 / T1027 | Encrypted archive holding a masquerading shortcut | — | — | Partial archive write is visible; the shortcut itself is not distinguishable on the sensor |
| T1140 | Signed utility decoding a staged blob in a temp path | Q1 | Yes | Good |
| T1059.001 / T1548 | Self-elevating hidden PowerShell with a policy bypass | Q2 | — | Good |
| T1562.001 | Defender path and process-name exclusions added pre-payload | Q3, Q4 | Yes | Good — two independent angles, process and registry |
| T1105 | Staged archive pulled over plaintext HTTP from a bare IP | Q5, Q8 | — | Good |
| T1574.002 | Chained DLL side-loads through a Python host and an application updater | Q6, Q7 | Yes | Good on execution and on file write. The load event itself is only visible via Sysmon event 7 |
| T1036.005 | Malicious DLLs named as expected system and runtime dependencies | Q5, Q7 | — | Good |
| T1497.001 / .002 / .003 | Sandbox gates on uptime, installed memory, profile file count, cursor movement | — | — | GAP — entirely in-process; no endpoint telemetry exists for this |
| T1053.005 | On-logon task, highest privileges, short delay, user-path action | Q11 | — | Good |
| T1546.003 | Permanent WMI subscription on logon-session creation | — | — | GAP for CQL — created through the WMI API, so no process event fires. Covered only by the WMI-Activity 5861 hunt in section 7 |
| T1547.001 | Startup-folder fallback persistence | — | — | Partial — AsepValueUpdate and the autorun inventory in section 7 |
| T1055 / T1620 | Process ghosting, module stomping, parameter poisoning, manual PE mapping, allocate-write-protect-execute | — | — | GAP for custom content — rely on the sensor's own memory and injection detections. The loader deliberately varies mode per host, so no single pattern holds |
| T1071.001 | Plaintext HTTP JSON tasking to a hard-coded endpoint on a non-standard port | Q8, Q9 | — | Partial — atomic coverage is solid, behavioural coverage is noisy by nature |
| T1102.002 / T1567 | Private repository used as a per-host dead drop for tasking and results | Q10 | — | Partial — the sensor sees only the name resolution. Confirmation needs full-URI proxy logs |
| T1059.003 | Operator commands through a shell with merged output capture | Q12 | Yes | Good |
| T1016 / T1018 / T1069.002 / T1082 / T1518 / T1033 | Post-access discovery burst against the domain | — | — | Partial — generic discovery detections apply; nothing campaign-specific was published |
Known gaps, stated plainly
- The anti-analysis gates and the in-memory execution modes cannot be hunted from telemetry. They happen inside one process with no observable side effect. This pack does not pretend otherwise, and no query below claims to cover them.
- WMI subscription persistence has no CQL angle here because the loader creates it through the WMI API rather than through
wmic.exeor a cmdlet. The WMI-Activity operational log is the only source, so if you are not collecting it you have no coverage of one of the three persistence mechanisms. - The dead-drop channel is only half-visible on the endpoint. Without full-URI proxy logging you can see that a process resolved a code-hosting API and nothing more, which is not enough to call it.
- Single-source intelligence. Every atomic indicator here comes from one vendor's telemetry on one intrusion at two endpoints. Treat absence of hits as uninformative rather than reassuring, and lean on the behavioural half of the pack.
All four validation gates in section 11 must pass before any of this coverage is real. In particular, gate 1 — telemetry ready — is what separates a genuine "no hits" from a silently broken query, and gate 2 must be completed for Q9 and Q10 before either is handed to an analyst.
Hunt Summary Ticket
TITLE: HollowFrame loader / Matryoshka backdoor family - proactive hunt
SEVERITY: High
SCOPE: All Windows endpoints. Prioritise users who handle inbound external
documents, and any host group without application allowlisting.
HYPOTHESIS: An operator delivered a shortcut inside an encrypted archive, used
PowerShell to request elevation and pre-authorise a staging directory
in Defender, then ran a modular Go loader and a Rust backdoor under
the names of trusted binaries via two chained DLL side-loads, keeping
C2 on plaintext HTTP and on a private code-hosting repository.
QUERIES: Q1 certutil decoding a staged blob in a user-writable path
Q2 PowerShell relaunching itself elevated, hidden, policy-bypassed
Q3 Defender exclusion added for a path or a process name
Q4 Defender exclusion key written in the registry
Q5 Archive imitating a Python embeddable distribution
Q6 Python host executed with nothing to interpret
Q7 Side-load DLL names written outside the system directories
Q8 Known campaign network infrastructure
Q9 Trusted side-load hosts opening outbound connections
Q10 Code-hosting API resolved by non-developer processes
Q11 Logon-triggered task pointing at a user-profile payload
Q12 Command shell spawned by a side-load host
DO FIRST: 1. Run Q8 across full retention. A hit is an incident, not a hunt.
2. Run Q3 and Q4 across 30 days. Review EVERY result by hand.
3. For each Q3/Q4 host, re-run Q5, Q6, Q7 and Q11 scoped to that host
across the following two hours. That sequence is the intrusion.
4. Run Q12. An updater process parenting a shell needs no further
corroboration before you isolate.
FINDINGS: [ ] Q8 hits: ____ hosts: ____
[ ] Q3 hits: ____ unexplained after review: ____
[ ] Q4 hits: ____ unexplained after review: ____
[ ] Q6 hits: ____ [ ] Q7 hits: ____ [ ] Q11 hits: ____
[ ] Q12 hits: ____
[ ] Estate-wide Defender exclusion audit complete: ____ / ____ hosts
[ ] WMI subscription inventory complete: ____ / ____ hosts
GAPS: - Sandbox-evasion gates and in-memory execution: no telemetry exists.
- WMI subscription persistence: needs WMI-Activity 5861 collection.
- Dead-drop channel: needs full-URI proxy logging to confirm.
- Single-vendor intelligence; absence of hits proves little.
ACTIONS: [ ] Import section 10 CSV (malicious rows only - NOT the three
trusted host binaries)
[ ] Enable Defender Tamper Protection fleet-wide (H2)
[ ] Four ASR rules to Audit, then Block (H4)
[ ] Mail gateway: hold password-protected archives (H3)
[ ] AppLocker/WDAC audit mode incl. the DLL collection (H5)
[ ] PowerShell script block logging + transcription (H6)
[ ] Scope code-hosting API access to development hosts (H7)
[ ] Pilot the four Custom IOA candidates in Detect mode (section 9)
OWNER: ____________________ DUE: ____________
VERSION: v0.1 - 2026-08-01 - HuntPack
Changelog
References
| Tier | Source | Used for | Accessed |
|---|---|---|---|
| 1 · Primary | Blackpoint Cyber APG — Nested Trust: HollowFrame's Layered Loader and Matryoshka Backdoors (Nevan Beal, Sam Decker, published 2026-07-30) | Entire attack chain, loader and backdoor internals, both persistence sets, complete file and network indicator tables, and the six defender recommendations that H2, H5, H7, H8 and the section 7 proxy hunts are built on. | 2026-08-01 |
| 2 · Corroboration | The Hacker News — HollowFrame Loader Deploys Matryoshka Backdoor in Spear-Phishing Attack on Law Firm (Ravie Lakshmanan, published 2026-07-31) | Independent restatement of the chain, both C2 endpoints, and the dead-drop repository structure. Sole source for the account-creation and profile-update dates noted in section 2. | 2026-08-01 |
| Framework | MITRE ATT&CK — Enterprise matrix and mitigations | Technique identifiers in section 6 and the M-number anchors on every hardening control in section 12. | 2026-08-01 |
| Vendor | CrowdStrike Falcon LogScale event and field documentation | Event names and field names used by all twelve queries in section 8. | 2026-08-01 |
| Baseline | CIS Microsoft Windows Benchmarks; Microsoft Security Baselines; Microsoft ASR, WDAC, AppLocker and Defender configuration documentation | Platform citations for the tiered hardening controls and the five deployable playbooks. | 2026-08-01 |
| Unavailable | SC Media — New HollowFrame loader and Matryoshka malware family discovered | Intended as a third corroborating source. Returned HTTP 403 on two attempts from two different fetchers, so no snapshot exists and it is not cited. No indicator in this pack depends on it. | 2026-08-01 (failed) |
Source snapshots for both cited sources are saved verbatim alongside this file in HollowFrame-Matryoshka-Hunt-sources/. Every atomic indicator shipped in section 10 traces to one of them.