CaptiveCrunch — Storm-2945 / Midnight Blizzard captive-portal AiTM
Executive Summary
Storm-2945, assessed by Microsoft as a sub-cluster of Midnight Blizzard (APT29 / Cozy Bear, attributed to Russia's SVR), has since early May 2026 been manipulating DNS and HTTP traffic on hospitality-sector captive-portal networks worldwide. The attacker objective is not the hotel: it is the corporate traveller's laptop and, through it, the traveller's Microsoft 365 tenant. Venue Wi-Fi is simply the position from which an adversary-in-the-middle can rewrite what a managed endpoint sees the moment it joins a network.
The delivery trick is what makes this campaign efficient. Rather than waiting for a user to browse somewhere, the actor intercepts the automated connectivity check that Windows and browsers issue on joining a network (NCSI), and answers it with a ClickFix-style page impersonating Windows Update, a browser update, DirectX or a human-verification check. The user is instructed to copy a command and run it themselves, which sidesteps both the download-mark-of-the-web path and any "don't click attachments" training. Two payloads follow: CornFlake, a Go Windows RAT that installs as a service named svchost32 with the display name "Cloud Sync Service" and re-installs its own persistence via a watchdog, and ChocoShell, an in-memory PowerShell infostealer that bypasses AMSI and UAC and harvests browser cookies, saved passwords, Wi-Fi keys and Microsoft 365 / Entra tokens. FruitStone is the operator-side web C2 panel — it never touches a victim endpoint, so no endpoint detection is written for it here.
Since 2026-07-16 a portion of the landing pages skip malware entirely and run Entra device code phishing: the user is sent to the genuine Microsoft sign-in page and told to type an actor-supplied code, authenticating the attacker's session and enabling attacker device registration plus Microsoft Graph collection. That branch leaves no endpoint artefact at all.
Highest-value defensive angle: the endpoint half of this campaign is loud and cheap to catch — a non-Microsoft binary called svchost32.exe running out of %APPDATA% and registering a service is close to a zero-false-positive signal (Q1–Q3). The identity half is the one that will actually hurt you, because it survives reimaging the laptop. Block or scope the device code flow, and treat any device-code sign-in from a traveller as an incident until proven otherwise.
Defender priority: Deploy Q1, Q2, Q3 and Q5 today — they are high-confidence and low-noise. Then, before the next travel week, block the Entra device code flow via Conditional Access (or scope it to a named app allowlist) and hunt Q13 across the last 90 days. An endpoint you reimage is recoverable; a refresh token and an attacker-registered device are not, until you revoke them.
Source Review & Web Hunter Notes
| Tier | Source | Key finding | Atomic IOCs | Carry forward |
|---|---|---|---|---|
| 1 | Microsoft Threat Intelligence — CaptiveCrunch advisory (2026-07-31) | Full campaign write-up: attribution, chain, CornFlake / ChocoShell / FruitStone internals, mitigations, hunting guidance, IOC table | YES — all 12 | Yes |
| 2 | Infosecurity Magazine | Corroborates campaign name, actor, timeline, NCSI-interception delivery, Android APK variant, device-code phase from 2026-07-16 | None printed | Narrative only |
| 2 | Security Affairs | Corroborates service name svchost32 / "Cloud Sync Service", ECDH P-256 C2, netsh wlan show profile, _.tbres token cache, cookies.sqlite | Artefact names only | Behavioural detail |
| 2 | Wiz Threat Intelligence incident tracker | Independent tracking entry; confirms Entra ID as the primary targeted technology and the hospitality captive-portal vector | None printed | Scoping only |
| 2 | Check Point Research weekly report (2026-08-03) | Independent confirmation of actor, both malware names, and the M365 / Entra token-theft objective | None printed | Corroboration only |
Decisions taken during research
- Every atomic indicator in this pack comes from one source. Microsoft is the only publisher of the hashes, domains and IPs; the four corroborating sources carry the narrative but print no indicators. The two-independent-source bar for a blocking action is therefore not met, so nothing ships at
action=prevent/severity=critical. All atomic rows aredetect/high. Promote to prevent only after a second source or an internal confirmed hit. - FruitStone gets no endpoint detection. It is the operator's browser-based C2 console. Writing endpoint logic for it would generate a coverage claim the telemetry cannot support.
- Name collision, deliberately noted. This campaign's CornFlake is a Golang Windows RAT. It is not the Interlock-associated CORNFLAKE loader covered elsewhere in the library — different family, different infrastructure, no shared indicators. Do not merge the two IOC sets or reuse the other pack's detections here.
- Prompt-injection screen: clean. All five fetched pages were checked for text attempting to direct the research process (instructions to run commands, to fetch a further URL for "the real IOCs", or claims of prior authorisation). None was present in any source. No source was disqualified.
- Perishability. Captive-portal infrastructure and C2 IPs rotate fast; Microsoft describes proxy relays with health checks and rotation built into the operator panel. Treat the six IPs as 90-day indicators and the behavioural queries as the durable coverage.
Hunt Brief & Attack Chain
Attack chain
| # | Step | Telemetry | Hunt angle |
|---|---|---|---|
| 1 | Captive-portal network manipulates DNS / HTTP for connected guests | Off-endpoint (venue infrastructure) | No endpoint telemetry — travel-correlated logon review only |
| 2 | OS/browser connectivity check (NCSI) intercepted; ClickFix page served | DnsRequest, browser process activity | Executable written or shell spawned shortly after a network-join event |
| 3 | User copies and runs the supplied command (paste-and-run) | ProcessRollup2 | Q8 — browser-parented shell with paste-and-run hallmarks |
| 4 | CornFlake dropper shows a fake progress window, copies to %APPDATA%\svchost32\svchost32.exe | ProcessRollup2, PeFileWritten | Q1, Q5 |
| 5 | Service svchost32 registered as "Cloud Sync Service" | CreateService | Q2 |
| 6 | Registry Run key + scheduled task persistence, watchdog restores removals | AsepValueUpdate, ScheduledTaskRegistered | Q3, Q4 |
| 7 | ChocoShell executes in memory; AMSI bypass; three-tier UAC bypass | ProcessRollup2, RegGenericValueUpdate | Q11 |
| 8 | Browser cookie / password theft (CDP debug port, ChromeKatz, ABE bypass) | ProcessRollup2 | Q10, Q12 |
| 9 | Wi-Fi credential harvest via netsh wlan show profile key=clear | ProcessRollup2 | Q9 |
| 10 | M365 / Entra token theft from Token Broker cache (_.tbres, WAM) | ProcessRollup2 | Q12 |
| 11 | Encrypted ECDH P-256 C2 to actor infrastructure | DnsRequest, NetworkConnectIP4 | Q6, Q7 |
| 12 | Entra device code phishing; attacker device registration; Graph collection | Entra sign-in / audit logs | Q13 + native hunts |
Hunt hypotheses (ordered by fidelity)
| # | Hypothesis | MITRE | Falcon events | Expected FP | Conf |
|---|---|---|---|---|---|
| H1 | A process named svchost32 is executing from a user-writable path — no legitimate Microsoft binary uses that name | T1036.005 | ProcessRollup2 | None expected | HIGH |
| H2 | A service is registered with the display name "Cloud Sync Service" or an image path containing svchost32 | T1543.003 | CreateService | None expected | HIGH |
| H3 | ASEP / Run-key or service-key registry writes reference svchost32 | T1547.001 | AsepValueUpdate, RegGenericValueUpdate | None expected | HIGH |
| H4 | Either published SHA256 executes or is written to disk | T1204 | ProcessRollup2, PeFileWritten | None | HIGH |
| H5 | An endpoint resolves or connects to published Storm-2945 C2 infrastructure | T1071.001 | DnsRequest, NetworkConnectIP4 | Sinkhole / TI-proxy rescans | HIGH |
| H6 | A browser process directly parents an interpreter carrying paste-and-run hallmarks (ClickFix) | T1204.004, T1059.001 | ProcessRollup2 | Developer tooling, IT self-service portals | MEDIUM |
| H7 | A scheduled task is registered whose executable lives under %APPDATA% | T1053.005 | ScheduledTaskRegistered | Updaters (Chrome, Teams, Zoom, Slack) | MEDIUM |
| H8 | Wi-Fi profile keys are dumped in cleartext | T1555 | ProcessRollup2 | Helpdesk diagnostics, MDM scripts | MEDIUM |
| H9 | A browser is relaunched with a remote debugging port (CDP cookie theft) | T1539 | ProcessRollup2 | Developers, automated UI testing | MEDIUM |
| H10 | UAC-bypass registry hijacks are written (windir env var, COM DelegateExecute, Folder shell open command) | T1548.002 | RegGenericValueUpdate | Rare; some installers touch Classes | MEDIUM |
| H11 | A process references browser credential stores or the Entra Token Broker cache by path | T1555.003, T1528 | ProcessRollup2 | Backup agents, browser migration tools | MEDIUM |
| H12 | Device code sign-ins appear for users who do not normally use that flow, or from travel geographies | T1528, T1098.005 | Entra sign-in logs | Azure CLI, kiosk/IoT enrolment | MEDIUM |
Attack-chain steps with no mappable Falcon telemetry
- Step 1 — venue-side DNS/HTTP manipulation. Happens on infrastructure you do not own or instrument. There is no sensor event for "the captive portal lied to me". Compensate with the travel-correlation native hunt in §7 and with hardening (§12) rather than pretending detection coverage exists.
- Step 2 — NCSI interception. The Falcon sensor does not surface the connectivity-check request as a distinct event, so the "file created within 2 minutes of an NCSI probe" query Microsoft published for Defender has no direct CQL equivalent. Q8 approximates the same moment from the process side.
- Android APK variant. Out of scope for Windows sensor telemetry; route to your MTD/MDM stack.
Consolidated IOC Table
Single-source caveat. Every atomic indicator below is published by Microsoft Threat Intelligence only. No second independent source reprints them. They ship as detect, never prevent, and the behavioural signals in §10 are the durable half of this pack.
| Type | Value | Conf | Action | Context | Expiry |
|---|---|---|---|---|---|
| sha256 | 918fa52ae45ed60ba7cc8bdc99c3cbe9ab92e0375ec31fc05d0d4513be11c593 | HIGH | detect | CornFlake Go RAT sample | 2027-02-04 |
| sha256 | be99857449d2856dd5a84e21c8a3d5e0e01456adb44062ddec5a6b4970d8d42c | HIGH | detect | ChocoShell PowerShell infostealer | 2027-02-04 |
| domain | ms365-device[.]com | HIGH | detect | Doppelganger / device-code phishing | 2027-02-04 |
| domain | ms365-live[.]com | HIGH | detect | Doppelganger / device-code phishing | 2027-02-04 |
| domain | m365-owa[.]com | HIGH | detect | Doppelganger / credential capture | 2027-02-04 |
| domain | owa-ms365[.]com | HIGH | detect | Doppelganger / credential capture | 2027-02-04 |
| ipv4 | 31.57.243[.]154 | HIGH | detect | Storm-2945 C2 | 2026-11-04 |
| ipv4 | 38.146.28[.]75 | HIGH | detect | Storm-2945 C2 | 2026-11-04 |
| ipv4 | 38.146.28[.]132 | HIGH | detect | Storm-2945 C2 | 2026-11-04 |
| ipv4 | 104.194.159[.]150 | HIGH | detect | Storm-2945 C2 | 2026-11-04 |
| ipv4 | 107.189.26[.]194 | HIGH | detect | Storm-2945 C2 | 2026-11-04 |
| ipv4 | 213.145.86[.]112 | HIGH | detect | Hardcoded ChocoShell C2 | 2026-11-04 |
| filename | svchost32.exe | HIGH | hunt | CornFlake payload — not a Microsoft binary name | 2026-11-04 |
| filename | sync.dat | MEDIUM | hunt | CornFlake runtime config (hot-reloadable) | 2026-11-04 |
| path | %APPDATA%\svchost32\svchost32.exe | HIGH | hunt | CornFlake install location | 2026-11-04 |
| service | svchost32 — "Cloud Sync Service" | HIGH | detect | Description: "Synchronizes files with the cloud storage provider" | 2027-02-04 |
| url-path | /t/pixel.gif?m= | MEDIUM | hunt | ChocoShell beacon (tracking-pixel mimicry) | 2026-11-04 |
| url-path | /cdn/chunks/polyfill-7e2b.min.js | MEDIUM | hunt | ChocoShell payload fetch | 2026-11-04 |
| url-path | /t/event | LOW | enrich | Exfil POST endpoint — generic, use with a domain | 2026-09-04 |
Affected Surface & Telemetry Matrix
| Surface | Required telemetry | Priority | Gap risk |
|---|---|---|---|
| Windows endpoints used off-network (laptops, travel fleet) | Falcon sensor — ProcessRollup2, PeFileWritten, CreateService, AsepValueUpdate, ScheduledTaskRegistered | CRITICAL | Low — standard sensor coverage |
| Endpoint DNS + egress | DnsRequest, NetworkConnectIP4 | HIGH | Medium — a roaming laptop on hotel Wi-Fi produces no corporate proxy/firewall log at all; the sensor is the only witness |
| Microsoft Entra ID sign-in and audit logs | Third-party ingest into NG-SIEM (azure_signinlogs, azure_auditlogs) or native Entra portal | CRITICAL | High — this is the surface that survives endpoint reimaging, and it is the one most often not ingested |
| Microsoft 365 / Exchange Online audit | Unified Audit Log — MailItemsAccessed, Graph activity | HIGH | Medium — needed to scope post-token-theft collection |
| Browsers (Chromium and Firefox families) | Process telemetry only; cookie theft is in-memory | HIGH | High — App-Bound Encryption bypass leaves little file-level trace |
| Mobile (Android) fleet | MTD / MDM app-install telemetry | MEDIUM | High — outside Falcon Windows telemetry entirely |
| Venue / captive-portal network | None — third-party infrastructure | MEDIUM | Total — no telemetry is available; mitigate by policy, not detection |
ATT&CK Mapping
| Tactic | Technique | Observed behaviour | Query / control |
|---|---|---|---|
| Initial Access | T1557 — Adversary-in-the-Middle | DNS/HTTP manipulation on captive-portal networks | Policy (§12) — no telemetry |
| Initial Access | T1566.002 — Spearphishing Link | Doppelganger domains served from the portal | Q6 |
| Execution | T1204.004 — Malicious Copy-and-Paste | ClickFix fake-update / verification prompts | Q8 |
| Execution | T1059.001 / T1059.003 — PowerShell / cmd | ChocoShell in-memory execution; CornFlake remote shell | Q8, Q9, Q12 |
| Persistence | T1543.003 — Windows Service | Service svchost32, display name "Cloud Sync Service" | Q2 |
| Persistence | T1547.001 — Registry Run Keys | Run-key entries pointing at the CornFlake payload | Q3 |
| Persistence | T1053.005 — Scheduled Task | Tasks registered with TASK_LOGON_INTERACTIVE_TOKEN | Q4 |
| Persistence | T1098.005 — Device Registration | Attacker device registered in Entra after device-code phish | Q13, §7 |
| Priv. Escalation | T1548.002 — Bypass UAC | SilentCleanup windir hijack, wsreset COM hijack, sdclt folder hijack | Q11 |
| Defense Evasion | T1562.001 — Impair Defenses (AMSI) | AMSI bypass via .NET reflection | Q8, §9 IOA-4 |
| Defense Evasion | T1036.005 — Match Legitimate Name | svchost32.exe imitating svchost.exe; "Cloud Sync Service" | Q1, Q2 |
| Credential Access | T1555.003 — Credentials from Web Browsers | ChromeKatz, App-Bound-Encryption bypass, cookies.sqlite copy | Q10, Q12 |
| Credential Access | T1539 — Steal Web Session Cookie | Chrome DevTools Protocol Network.getAllCookies | Q10 |
| Credential Access | T1555 — Credentials from Password Stores | netsh wlan show profile key=clear | Q9 |
| Credential Access | T1528 — Steal Application Access Token | WAM / Token Broker _.tbres cache theft; device code flow abuse | Q12, Q13 |
| Collection | T1056.001 / T1115 / T1113 / T1123 / T1125 | Keylogging, clipboard, screenshots, audio, webcam | Q1 (parent process), §9 IOA-1 |
| Collection | T1025 — Data from Removable Media | USB monitoring module | Coverage gap — see §15 |
| Command & Control | T1071.001 / T1573.002 | ECDH P-256 encrypted JSON over HTTPS; SNI spoofing | Q6, Q7 |
| Exfiltration | T1041 — Exfiltration Over C2 | GZip + Base64 JSON POST to /t/event | Q7 |
| Impact | T1490 — Inhibit System Recovery | VSS shadow-copy deletion during ChocoShell cleanup | §9 IOA-5 |
| Lateral / Cloud | T1550.001 — Application Access Token | Stolen refresh tokens replayed against Microsoft Graph | §7 native hunts |
Native Audit-Log Hunts (non-CQL)
The identity half of CaptiveCrunch is invisible to the endpoint sensor. These run in the Entra / M365 admin surfaces and are the checks that matter most after a travel period.
N1 — Device code sign-ins (Entra sign-in logs)
Filter Sign-in logs → Authentication requirements / Protocol for the device code flow over the last 90 days. Any result for a user who has never used a CLI or kiosk enrolment is suspicious. Cross-reference the source IP against the six published C2 addresses and against the traveller's expected geography. Microsoft's own alert names to watch for: "Anomalous OAuth device code authentication activity" and "Suspicious Azure authentication through possible device code phishing".
N2 — New Entra device registrations
Entra ID → Devices → All devices, sorted by registration date. Successful device-code phishing is followed by the actor registering their own device to make the session durable. Correlate every device registered during or shortly after a travel window with the owning user's known hardware. Also review Audit logs for Add registered device and Add device events.
N3 — MFA / passkey registration from unexpected locations
Audit logs → Activity: "User registered security info". An actor with a live session will try to add their own authentication method. Filter to registrations occurring outside your trusted named locations.
N4 — Post-token-theft mailbox and Graph collection
Unified Audit Log: MailItemsAccessed with a client app of Graph or an unfamiliar app ID, plus Add delegated permission grant and Consent to application in the Entra audit log. Midnight Blizzard's established pattern is Graph-based mail exfiltration once a session is held.
N5 — Travel correlation (the pivot that ties the two halves together)
Build a list of users who travelled in the affected window (early May 2026 onward). For each, check: any device-code sign-in, any new device registration, any interactive sign-in from a hospitality ASN, and any Falcon detection from §8 on their assigned endpoint. A hit on either side for a traveller warrants a full session revocation, not a triage note.
N6 — Conditional Access posture review
Confirm whether a policy blocking or scoping the device code flow exists at all. If it does not, this hunt has no compensating control behind it and §12 Immediate item 2 becomes the priority action.
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. An @timestamp comparison against now() is rejected by the CQL parser on line 1, which silently kills the whole query.
Looks for: any process named svchost32. There is no legitimate Microsoft binary by that name — it exists only to sit next to svchost.exe in a process list and be overlooked. Highest-fidelity query in the pack; deploy first.
// HUNT: CornFlake RAT execution (svchost32 masquerade) // MITRE: T1036.005, T1543.003 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: none expected -- svchost32 is not a Microsoft binary name // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^svchost32(\.exe)?$/i | table([@timestamp, aid, ComputerName, UserName, FileName, ImageFileName, CommandLine, ParentBaseFileName, SHA256HashData]) | sort(@timestamp, order=desc)
Looks for: service creation with CornFlake's cover identity — display name "Cloud Sync Service", or any service image path containing svchost32. The description string Microsoft published is "Synchronizes files with the cloud storage provider".
// HUNT: CornFlake service registration ("Cloud Sync Service") // MITRE: T1543.003 | CONF: high FP: low COST: low // REQUIRES: CreateService // NOTE: ServiceDisplayName / ServiceImagePath population varies by sensor version // -- (!) validate in tenant against a known-good service install first // FALSE POSITIVES: none expected for this exact display-name string // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^CreateService$/ | ServiceDisplayName=/^Cloud\s+Sync\s+Service$/i or ServiceImagePath=/svchost32/i | table([@timestamp, aid, ComputerName, UserName, ServiceName, ServiceDisplayName, ServiceImagePath]) | sort(@timestamp, order=desc)
Looks for: ASEP or generic registry writes that name the CornFlake payload. Because the malware runs a watchdog that restores persistence a defender deletes, repeat hits on the same host after remediation are expected and are themselves confirmation the RAT is still resident.
// HUNT: CornFlake Run-key / service-key persistence // MITRE: T1547.001, T1543.003 | CONF: high FP: low COST: low // REQUIRES: AsepValueUpdate, RegGenericValueUpdate // FALSE POSITIVES: none expected -- the string svchost32 is campaign-specific // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^(AsepValueUpdate|RegGenericValueUpdate)$/ | RegStringValue=/svchost32/i or RegObjectName=/\\Services\\svchost32/i | table([@timestamp, aid, ComputerName, UserName, RegObjectName, RegValueName, RegStringValue]) | sort(@timestamp, order=desc)
Looks for: the third leg of CornFlake's persistence tripod — a scheduled task whose executable lives in a user-writable roaming/local profile path. FP: consumer-grade updaters legitimately do this (Chrome, Edge, Teams, Zoom, Slack, Dropbox, OneDrive), so this is a stack-rank hunt, not an alert. Sort by rarity of the task executable across your fleet.
// HUNT: Scheduled task registered against a user-writable AppData executable // MITRE: T1053.005 | CONF: medium FP: medium COST: low // REQUIRES: ScheduledTaskRegistered // FALSE POSITIVES: Chrome/Edge/Teams/Zoom/Slack/Dropbox per-user updaters // TUNING: exclude your approved per-user updaters by task executable basename, // e.g. TaskExecutable!=/(GoogleUpdate|MicrosoftEdgeUpdate|Update\.exe|Teams\.exe|Zoom)/i // then stack the survivors by count -- CornFlake will be a rare singleton // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^ScheduledTaskRegistered$/ | TaskExecutable=/\\AppData\\(Roaming|Local)\\/i | TaskExecutable!=/(GoogleUpdate|MicrosoftEdgeUpdate|OneDrive|Teams|Zoom|Slack|Dropbox)/i | groupBy([TaskExecutable, TaskName], function=[count(as=Hosts), collect([ComputerName, UserName, TaskAuthor])], limit=500) | sort(Hosts, order=asc, limit=200)
Looks for: execution or on-disk write of either published sample. Exact-hash coverage is brittle by nature — the operator panel offers per-build symbol randomisation and XOR encoding, so assume rebuilt variants exist and rely on Q1–Q3 for durable coverage.
// HUNT: CornFlake / ChocoShell known SHA256 execution or write // MITRE: T1204 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2, PeFileWritten, NewExecutableWritten // FALSE POSITIVES: none -- exact hash match // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|PeFileWritten|NewExecutableWritten)$/ | SHA256HashData=/^(918fa52ae45ed60ba7cc8bdc99c3cbe9ab92e0375ec31fc05d0d4513be11c593|be99857449d2856dd5a84e21c8a3d5e0e01456adb44062ddec5a6b4970d8d42c)$/i | table([@timestamp, aid, ComputerName, UserName, FileName, ImageFileName, TargetFileName, CommandLine, SHA256HashData]) | sort(@timestamp, order=desc)
Looks for: DNS resolution of the four Microsoft-lookalike domains used for the phishing landing pages and device-code prompts. A roaming laptop on hotel Wi-Fi produces no corporate DNS log, so the sensor is your only witness for this step.
// HUNT: CaptiveCrunch doppelganger domain resolution // MITRE: T1566.002, T1071.001 | CONF: high FP: low COST: low // REQUIRES: DnsRequest / SuspiciousDnsRequest // FALSE POSITIVES: TI-scanner or sandbox hosts deliberately re-resolving IOCs // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^(DnsRequest|SuspiciousDnsRequest)$/ | DomainName=/^(.*\.)?(ms365-device|ms365-live|m365-owa|owa-ms365)\.com$/i | table([@timestamp, aid, ComputerName, DomainName, ContextProcessId_decimal]) | sort(@timestamp, order=desc)
Looks for: outbound connections to the six published C2 addresses. 213.145.86[.]112 is the hardcoded ChocoShell C2 and is the highest-value of the six. The initiating process name in ContextBaseFileName is the pivot — expect svchost32.exe or powershell.exe.
// HUNT: CaptiveCrunch C2 IP egress // MITRE: T1071.001, T1041 | CONF: high FP: low COST: low // REQUIRES: NetworkConnectIP4 // FALSE POSITIVES: TI-proxy or scanner infrastructure re-checking indicators // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^NetworkConnectIP4$/ | in(RemoteAddressIP4, values=["31.57.243.154", "38.146.28.75", "38.146.28.132", "104.194.159.150", "107.189.26.194", "213.145.86.112"]) | table([@timestamp, aid, ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort, Protocol_decimal]) | sort(@timestamp, order=desc)
Looks for: the moment the ClickFix lure succeeds — an interpreter spawned directly by a browser (or by explorer.exe, the Run-dialog path) carrying the hallmarks of a pasted one-liner. This is the closest CQL gets to Microsoft's "file created within 2 minutes of an NCSI probe" query. FP: developer tooling and IT self-service portals legitimately launch scripts from a browser.
// HUNT: ClickFix paste-and-run -- browser/Run-dialog parented interpreter // MITRE: T1204.004, T1059.001, T1059.003 | CONF: medium FP: medium COST: medium // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: developer workstations, IT self-service / RMM web consoles, // browser-launched installer helpers // TUNING: exclude your engineering OU or known dev hosts by ComputerName pattern, // and exclude your RMM/self-service agent by ParentBaseFileName before // promoting this to an alert. Triage newest-first; a real ClickFix hit is // followed within seconds by a write under %APPDATA% (pivot to Q1). // LOOKBACK: 30d (set in the console time picker) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | ParentBaseFileName=/^(msedge|chrome|firefox|brave|opera|iexplore|explorer)\.exe$/i | FileName=/^(powershell|pwsh|cmd|mshta|wscript|cscript|curl|certutil|conhost)\.exe$/i | CommandLine=/(-nop\b|-noprofile\b|-w\s+hidden|-windowstyle\s+hidden|-enc\b|-encodedcommand\b|iex\b|invoke-expression|invoke-webrequest|downloadstring|frombase64string|scriptblock)/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: cleartext dumping of stored Wi-Fi profile keys — a distinctive ChocoShell collection step and, for a travel-focused actor, an obvious one to want. FP: helpdesk diagnostic scripts and MDM/RMM Wi-Fi inventory jobs use the identical command.
// HUNT: Wi-Fi profile key dump (netsh wlan show profile key=clear) // MITRE: T1555 | CONF: high FP: medium COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: helpdesk diagnostics, MDM/RMM wireless inventory scripts // TUNING: exclude your RMM agent and support tooling by ParentBaseFileName // (e.g. ParentBaseFileName!=/(ninjarmm|screenconnect|intune|ccmexec)/i) // -- a genuine ChocoShell hit is parented by powershell.exe with no // management agent anywhere in the tree // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^netsh\.exe$/i | CommandLine=/wlan\s+show\s+profile/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, GrandparentBaseFileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: the App-Bound-Encryption workaround — rather than decrypting the cookie store, ChocoShell restarts the browser with a debugging port and asks it politely for the cookies via Network.getAllCookies, then restores the session with --restore-last-session so the user notices nothing. FP: developers and UI test automation do exactly this.
// HUNT: Chromium relaunch with remote debugging port (CDP cookie extraction) // MITRE: T1539, T1555.003 | CONF: medium FP: medium COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: web developers, Selenium/Playwright/Puppeteer test automation // TUNING: exclude engineering hosts and CI runners by ComputerName; a malicious // relaunch is parented by powershell.exe or svchost32.exe rather than by // a shell, an IDE, or a node.exe test harness -- filter on // ParentBaseFileName before alerting // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^(chrome|msedge|brave|opera|vivaldi)\.exe$/i | CommandLine=/--remote-debugging-(port|pipe)/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: ChocoShell's three-tier UAC bypass — the windir environment variable hijack that poisons the SilentCleanup task, the wsreset.exe COM handler hijack under HKCU\Software\Classes, and the sdclt.exe Folder shell-open-command hijack. FP: a small number of installers legitimately write under HKCU\Software\Classes; the windir and DelegateExecute values are the discriminating ones.
// HUNT: UAC bypass via HKCU windir / COM DelegateExecute / Folder shell hijack // MITRE: T1548.002, T1112 | CONF: medium FP: medium COST: low // REQUIRES: AsepValueUpdate, RegGenericValueUpdate, RegSystemConfigValueUpdate // FALSE POSITIVES: installers registering file associations under HKCU Classes // TUNING: exclude msiexec.exe and your software-deployment agent as the writing // process; a HKCU\Environment "windir" write has essentially no benign // use -- treat that value alone as high severity and the Classes writes // as the lower-confidence tier // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^(AsepValueUpdate|RegGenericValueUpdate|RegSystemConfigValueUpdate)$/ | RegValueName=/^(windir|DelegateExecute)$/i or RegObjectName=/\\Classes\\(Folder|ms-settings|mscfile|Applications\\wsreset)\\/i | table([@timestamp, aid, ComputerName, UserName, RegObjectName, RegValueName, RegStringValue]) | sort(@timestamp, order=desc)
Looks for: command lines naming browser credential stores or the Microsoft Token Broker cache. The _.tbres files are where WAM caches Microsoft 365 access and refresh tokens — the artefact that converts an endpoint compromise into a cloud compromise. FP: profile-migration utilities and endpoint backup agents enumerate the same paths.
// HUNT: browser credential store / Entra Token Broker cache targeting // MITRE: T1555.003, T1528, T1539 | CONF: medium FP: medium COST: medium // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: profile migration tools, endpoint backup agents, browser // import wizards run by the user // TUNING: exclude your backup/migration agent by FileName and ParentBaseFileName; // the malicious pattern is powershell.exe touching several of these paths // in one command line -- a single path reference alone is weak evidence // LOOKBACK: 90d (set in the console time picker) #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | CommandLine=/(tbres|TokenBroker|cookies\.sqlite|logins\.json|key4\.db|Login\s?Data|Local\s+State|Web\s+Data)/i | FileName!=/^(msedge|chrome|firefox|brave|opera|vivaldi)\.exe$/i | table([@timestamp, aid, ComputerName, UserName, FileName, ParentBaseFileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: device code authentication events in ingested Entra sign-in logs, stacked by user, source IP and app. This is the only query covering the branch of the campaign that leaves no endpoint artefact. FP: Azure CLI, device enrolment and kiosk/IoT provisioning use the same flow legitimately.
SaaS / IdP field names are parser-prefixed per tenant. Run this against a single sample row and adjust the field names before deploying it broadly — (!) validate in tenant.
// HUNT: Entra device code authentication anomaly (CaptiveCrunch device-code phish) // MITRE: T1528, T1550.001, T1098.005 | CONF: medium FP: medium COST: medium // REQUIRES: Entra sign-in logs ingested to NG-SIEM (azure_signinlogs) // NOTE: (!) validate in tenant -- IdP field names are parser-prefixed and vary // FALSE POSITIVES: Azure CLI / azd, Intune or Autopilot enrolment, kiosk and IoT // provisioning, developer sign-ins on headless hosts // TUNING: exclude your sanctioned device-code client app IDs and automation // service accounts, then review what remains user-by-user. Any hit for a // user who travelled since 2026-05-01 is an incident until disproven. // LOOKBACK: 90d (set in the console time picker) #repo=azure_signinlogs | authenticationProtocol=/deviceCode/i | groupBy([userPrincipalName, ipAddress, appDisplayName, conditionalAccessStatus], function=[count(as=SignIns), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)], limit=1000) | sort(SignIns, order=desc, limit=200)
CrowdStrike Custom IOA Recommendations
Promote only the low-FP queries to blocking IOAs. Q4, Q8, Q9, Q10, Q11, Q12 and Q13 stay hunt-only until baselined in your environment.
| IOA | Type | Pattern | Action | Exclusions |
|---|---|---|---|---|
| IOA-1 · CornFlake payload execution | Process Creation | Image filename matches svchost32\.exe and grandparent is not a Microsoft-signed installer | Block & Terminate | None required |
| IOA-2 · Cloud Sync Service registration | Process Creation | sc.exe / powershell.exe command line containing Cloud Sync Service together with a binPath under AppData | Detect — Critical | Software deployment agent as parent |
| IOA-3 · Wi-Fi key dump | Process Creation | netsh.exe with wlan show profile and key=clear | Detect — Medium | RMM / helpdesk agent in the process tree |
| IOA-4 · AMSI tampering by reflection | Process Creation | PowerShell command line referencing amsiInitFailed, AmsiUtils or System.Management.Automation.AmsiUtils | Detect — High | Security-testing OU only |
| IOA-5 · Shadow-copy deletion via WMI | Process Creation | wmic or PowerShell CIM call deleting Win32_ShadowCopy instances | Block & Terminate | Backup vendor's own maintenance binary |
| IOA-6 · Browser debug-port relaunch by a non-shell parent | Process Creation | Chromium binary with --remote-debugging-port parented by powershell.exe | Detect — Medium | Engineering hosts, CI runners |
Deployment path: Falcon console → Endpoint security → Custom IOA rule groups. Stage every rule in Monitor for one full business cycle (including a travel week) before switching to Block, then confirm the rule group is assigned to the prevention policy that actually covers your travel fleet — laptops are frequently in a looser policy than desktops, which is precisely the population this campaign targets.
Machine-Readable IOC Appendix
type,value,action,severity,expiration,description,tags # CaptiveCrunch / Storm-2945 -- Falcon IOC Management import # Source: Microsoft Threat Intelligence advisory 2026-07-31 (snapshot 01) # SINGLE-SOURCE: Microsoft is the only publisher of these indicators. The # two-independent-source bar for a blocking action is NOT met, so nothing here # ships at action=prevent / severity=critical. Promote only after a second # source or an internally confirmed hit. sha256,918fa52ae45ed60ba7cc8bdc99c3cbe9ab92e0375ec31fc05d0d4513be11c593,detect,high,2027-02-04,CornFlake Go RAT payload,campaign:CaptiveCrunch actor:Storm-2945 sha256,be99857449d2856dd5a84e21c8a3d5e0e01456adb44062ddec5a6b4970d8d42c,detect,high,2027-02-04,ChocoShell PowerShell infostealer,campaign:CaptiveCrunch actor:Storm-2945 domain,ms365-device.com,detect,high,2027-02-04,Device-code phishing doppelganger,campaign:CaptiveCrunch actor:Storm-2945 domain,ms365-live.com,detect,high,2027-02-04,Device-code phishing doppelganger,campaign:CaptiveCrunch actor:Storm-2945 domain,m365-owa.com,detect,high,2027-02-04,Credential-capture doppelganger,campaign:CaptiveCrunch actor:Storm-2945 domain,owa-ms365.com,detect,high,2027-02-04,Credential-capture doppelganger,campaign:CaptiveCrunch actor:Storm-2945 ipv4,31.57.243.154,detect,high,2026-11-04,Storm-2945 C2,campaign:CaptiveCrunch actor:Storm-2945 ipv4,38.146.28.75,detect,high,2026-11-04,Storm-2945 C2,campaign:CaptiveCrunch actor:Storm-2945 ipv4,38.146.28.132,detect,high,2026-11-04,Storm-2945 C2,campaign:CaptiveCrunch actor:Storm-2945 ipv4,104.194.159.150,detect,high,2026-11-04,Storm-2945 C2,campaign:CaptiveCrunch actor:Storm-2945 ipv4,107.189.26.194,detect,high,2026-11-04,Storm-2945 C2,campaign:CaptiveCrunch actor:Storm-2945 ipv4,213.145.86.112,detect,high,2026-11-04,ChocoShell hardcoded C2,campaign:CaptiveCrunch actor:Storm-2945
CornFlake -- persistence tripod (all three, plus a watchdog that restores them)
1. Windows service : svchost32 / display "Cloud Sync Service"
description : "Synchronizes files with the cloud storage provider"
2. Registry Run key : value data referencing svchost32
3. Scheduled task : executable under the user profile, interactive-token logon
install path : %APPDATA%\svchost32\svchost32.exe
runtime config : sync.dat (hot-reloadable, sits beside the payload)
local control API : loopback HTTP endpoints /upload, /reload, /status
C2 crypto : ECDH P-256 + SHA-256 session key, custom JSON over TLS
ChocoShell -- in-memory PowerShell, no file on disk
AMSI bypass : .NET reflection against AmsiUtils / amsiInitFailed
VM check : timing-based sandbox detection before staging
UAC bypass chain : HKCU\Environment "windir" (SilentCleanup task hijack)
-> wsreset.exe COM handler under HKCU\Software\Classes
-> sdclt.exe HKCU\Software\Classes\Folder\shell\open\command
-> visible UAC prompt as last resort
Chromium creds : App-Bound Encryption bypass via SYSTEM token impersonation
stolen from winlogon.exe / wininit.exe / services.exe
Cookie theft : browser relaunch with --remote-debugging-port, then
Chrome DevTools Protocol Network.getAllCookies, then
--restore-last-session to hide the interruption
Firefox creds : direct copy of cookies.sqlite / logins.json / key4.db
M365 tokens : _.tbres files from the WAM Token Broker cache
Wi-Fi creds : netsh wlan show profile key=clear
Anti-forensics : VSS shadow-copy deletion via WMI, UAC-bypass key removal
Delivery -- ClickFix from an intercepted connectivity check
Lure themes : winupdate, defender, directx, vcredist, sysopt, netfix,
browser, pdfview (fake progress windows)
Trigger : OS/browser NCSI connectivity probe answered by the portal
User action : copy-and-paste a command, run it manually
Android variant : APK served to mobile clients (out of Windows telemetry)
Malware names CornFlake Golang Windows RAT -- endpoint ChocoShell PowerShell stealer -- endpoint, in-memory FruitStone web C2 panel -- OPERATOR SIDE ONLY, no endpoint detection NAME COLLISION: this CornFlake is NOT the Interlock-associated CORNFLAKE loader. Different family, different infrastructure, no shared indicators. Do not merge the two IOC sets. Filesystem artefacts to check on a suspect host %APPDATA%\svchost32\ directory %APPDATA%\svchost32\svchost32.exe payload %APPDATA%\svchost32\sync.dat runtime config Registry artefacts to check SYSTEM\CurrentControlSet\Services\svchost32 HKCU\Environment -> windir HKCU\Software\Classes\Folder\shell\open\command HKCU\Software\Classes (wsreset COM handler) C2 URL paths (pair with a domain or IP before alerting -- generic on their own) /t/pixel.gif?m= ChocoShell beacon /cdn/chunks/polyfill-7e2b.min.js payload fetch /t/event exfil POST endpoint Operator infrastructure tradecraft (informs hunting, not blocking) Proxy relays with health checks and rotation Beacon profiles with SNI spoofing toward common SaaS hostnames DNS fallback channel Per-build symbol randomisation and XOR encoding -- assume hash churn
Conditional Access audit -- run before the next travel week
[ ] A policy exists that BLOCKS the device code flow for all users
[ ] If the flow is needed, it is scoped to a named app allowlist + trusted
network, not left open tenant-wide
[ ] Phishing-resistant MFA is required for every privileged role
[ ] MFA and passkey registration is restricted to trusted locations/devices
[ ] Sign-in risk policy is enabled and set to block or force reauth
[ ] Device registration requires MFA and is restricted to compliant devices
[ ] Legacy authentication is blocked
Entra hunts to run over the last 90 days
[ ] Sign-in logs: authentication protocol = device code, any user
[ ] Sign-in logs: successful sign-in from any of the 6 published C2 IPs
[ ] Audit logs: "Add registered device" / "Add device"
[ ] Audit logs: "User registered security info" outside trusted locations
[ ] Audit logs: "Consent to application" / "Add delegated permission grant"
[ ] Unified Audit Log: MailItemsAccessed via Graph by an unfamiliar app ID
Response if any of the above hits for a traveller
[ ] Revoke all refresh tokens for the user (revokeSignInSessions)
[ ] Remove attacker-registered devices
[ ] Reset credentials and re-enrol MFA in person
[ ] Review mailbox rules, forwarding, and delegate access
[ ] Isolate and reimage the endpoint -- CornFlake restores its own persistence
Detection Validation Gates
| Gate | Check | Pass criterion |
|---|---|---|
| G1 · Telemetry ready | Run each query's bare event filter alone (e.g. #event_simpleName=/^CreateService$/) over 7 days | Non-zero results on your travel fleet. A zero return means the event is not flowing and the query is decorative. |
| G2 · Entra ingest | Run #repo=azure_signinlogs alone | Rows returned, and field names match those used in Q13. If not ingested, Q13 and the whole identity half of this pack are non-functional — fix ingest before anything else. |
| G3 · Benign baseline | Run Q4, Q8, Q9, Q10, Q11, Q12 for 14 days with no exclusions | Record the FP population per query and write the exclusions into the // TUNING: line before promoting anything. |
| G4 · Positive test — service | In a lab VM, create a dummy service with display name "Cloud Sync Service" pointing at a benign binary | Q2 returns the event within the expected sensor latency. |
| G5 · Positive test — masquerade | Copy a benign signed binary to %APPDATA%\svchost32\svchost32.exe and run it in a lab VM | Q1 returns it. Confirms both the path and the basename regex behave. |
| G6 · Positive test — Wi-Fi dump | Run netsh wlan show profile on a lab host | Q9 returns it, and your RMM exclusion in the TUNING line does not suppress it. |
| G7 · Negative test | Confirm Q1 does not fire on legitimate svchost.exe | Zero results — proves the anchored basename regex is not matching a prefix. |
| G8 · Promotion | Only Q1, Q2, Q3, Q5, Q6, Q7 are eligible for scheduled-search alerting on day one | The remaining seven stay hunt-only until G3 produces a documented exclusion set. |
Hardening — Tiered
- Block the Entra device code flow with a Conditional Access policy targeting all users, all cloud apps, with the Authentication flows condition set to Device code flow, grant = Block. MITRE M1032 / M1018. Authority: Microsoft Entra Conditional Access documentation; the advisory names this as its first identity control. This is the single highest-leverage change in the pack: it neutralises the branch of the campaign that leaves no endpoint evidence.
- Require phishing-resistant MFA for all privileged roles (passkeys / FIDO2 / Windows Hello for Business). MITRE M1032. Authority: CIS Microsoft 365 Benchmark, Microsoft Security Baseline for Entra ID. A stolen refresh token cannot be replayed into a role that demands a hardware-bound credential at elevation.
- Push the ASR rule "Block executable content from email client and webmail" and "Block JavaScript or VBScript from launching downloaded executable content" to the travel fleet in Block mode. MITRE M1042. Authority: Microsoft Defender ASR rule reference, CIS Windows Benchmark. Neither stops a hand-pasted command by itself, which is why user education (item 5) is listed alongside rather than instead.
- Turn on Falcon prevention policy parity for laptops. Confirm the travel fleet is in the same (or stricter) prevention policy as desktops, with Suspicious Process Blocking and Script-Based Execution Monitoring enabled. Authority: CrowdStrike prevention policy best practice. ⚠ best-practice, no formal benchmark.
- Issue a travel advisory to staff covering the three behaviours that actually matter: never run a command a web page asks you to copy; never install anything a captive portal offers; never type a code into a Microsoft sign-in page that you did not initiate yourself. MITRE M1017.
- Restrict MFA and passkey registration to trusted locations and compliant devices via Conditional Access. MITRE M1032. Authority: Microsoft Entra Conditional Access documentation. Stops an actor holding a live session from adding their own authenticator.
- Require device registration to be MFA-gated and limited to compliant devices. MITRE M1018. Authority: Microsoft Entra device settings documentation. Directly targets the T1098.005 step.
- Enable a sign-in risk Conditional Access policy set to require reauthentication (or block) at medium risk and above. Authority: Microsoft Entra ID Protection documentation.
- Block Wi-Fi association with non-MDM-provisioned networks on the managed fleet, or at minimum require a mandatory always-on VPN or SSE tunnel before any traffic egresses on an untrusted SSID. MITRE M1037. Authority: Microsoft advisory recommendation; CIS Windows Benchmark wireless settings.
- Enforce PowerShell Constrained Language Mode + script block logging + AMSI on non-developer endpoints via AppLocker or WDAC. MITRE M1038, M1045. Authority: CIS Windows Benchmark, Microsoft PowerShell security documentation. ChocoShell is entirely in-memory PowerShell; CLM removes most of its reflection surface.
- Deploy a managed travel-router or enterprise hotspot programme for staff who regularly work from venues, establishing an encrypted tunnel back to trusted infrastructure. Authority: Microsoft advisory recommendation. ⚠ best-practice, no formal benchmark.
- Move to passwordless / passkey-first authentication tenant-wide. MITRE M1032. Authority: Microsoft passwordless deployment guidance, NIST SP 800-63B. This campaign's entire value chain terminates in reusable credentials and tokens; passkeys remove the reusable part.
- Deploy a Security Service Edge solution (e.g. Global Secure Access or equivalent) so that endpoint egress is inspected and policy-controlled regardless of which network the laptop is sitting on. MITRE M1037. Authority: Microsoft advisory recommendation, NIST SP 800-207 zero-trust architecture.
- Application control (WDAC) in enforced mode on the travel fleet, with a policy that denies execution from user-writable paths. MITRE M1038. Authority: Microsoft WDAC documentation, DISA Windows STIG. A CornFlake payload at
%APPDATA%\svchost32\svchost32.exesimply does not run. - Revise corporate-event and travel policy to evaluate whether venue-provided wireless is used at all, and to minimise what employee and organisational detail is disclosed during venue booking and conference registration. MITRE M1017. Authority: Microsoft advisory recommendation.
- Ingest Entra sign-in and audit logs into the SIEM if not already done, with retention covering at least 90 days. Authority: NIST SP 800-92, CIS Control 8. Without this, Q13 and every native hunt in §7 are unrunnable.
Deployable Playbooks
Playbook 1 — Block the Entra device code flow (Conditional Access)
Prerequisites: Entra ID P1 or higher; Conditional Access Administrator or Security Administrator role; a break-glass account excluded from all CA policies; Microsoft.Graph PowerShell module installed.
Reboot required: No.
Rollback: Set the policy state back to disabled (step 4 below), or delete the policy entirely. No client-side change is made, so rollback is immediate and total.
1. Inventory first -- find who legitimately uses the flow before you block it.
Entra portal -> Sign-in logs -> Add filter -> Authentication protocol
-> Device code. Export 90 days. Anything you find here becomes either an
exclusion or a conversation with that team.
2. Create the policy in report-only mode (Graph PowerShell):
Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess"
$params = @{
displayName = "Block device code flow"
state = "enabledForReportingButNotEnforced"
conditions = @{
users = @{
includeUsers = @("All")
excludeGroups = @("REPLACE_WITH_BREAKGLASS_GROUP_OBJECTID")
}
applications = @{ includeApplications = @("All") }
authenticationFlows = @{ transferMethods = "deviceCodeFlow" }
}
grantControls = @{ operator = "OR"; builtInControls = @("block") }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $params
3. Leave it in report-only for 7 days. Review Sign-in logs -> Report-only tab
for any impacted sign-in. Add exclusions for genuine device-code apps
(Azure CLI service accounts, kiosk enrolment) as named app exclusions --
NOT as user exclusions, which are far broader than intended.
4. Enforce, or roll back:
$id = (Get-MgIdentityConditionalAccessPolicy |
Where-Object DisplayName -eq "Block device code flow").Id
# enforce:
Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $id `
-State "enabled"
# ROLLBACK -- returns the tenant to its previous behaviour immediately:
Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $id `
-State "disabled"
5. Verify: attempt a device-code sign-in with a test account. It must fail with
a Conditional Access block, and the block must appear in Sign-in logs.
Playbook 2 — CornFlake host triage and eradication
Prerequisites: Local administrator on the target host; the host network-contained in Falcon before you begin (the watchdog re-creates persistence, and the RAT has a live remote shell); PowerShell 5.1+ running elevated.
Reboot required: Yes — after step 5, to confirm nothing re-spawns.
Rollback: Not applicable in the usual sense; these steps remove attacker-created objects only. Before deleting anything, run step 1 and preserve its output plus a copy of the payload and sync.dat as evidence. If a service or task turns out to be legitimate in your environment, re-create it from your configuration baseline. The recommended end state is reimage, not repair — the watchdog and the localhost control API mean partial removal is not reliably verifiable.
1. COLLECT BEFORE YOU DELETE (host already contained in Falcon):
$out = "$env:TEMP\cc-triage-$(Get-Date -f yyyyMMddHHmmss).txt"
Get-Service -Name "svchost32" -ErrorAction SilentlyContinue |
Format-List * | Out-File $out -Append
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\svchost32" `
-ErrorAction SilentlyContinue | Out-File $out -Append
Get-ChildItem "$env:APPDATA\svchost32" -Force -ErrorAction SilentlyContinue |
Out-File $out -Append
Get-ScheduledTask | Where-Object {
$_.Actions.Execute -like "*AppData*" } |
Select-Object TaskName, TaskPath, @{n='Exec';e={$_.Actions.Execute}} |
Out-File $out -Append
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" |
Out-File $out -Append
Get-ItemProperty "HKCU:\Environment" -Name windir -ErrorAction SilentlyContinue |
Out-File $out -Append
2. Copy $out, the payload and sync.dat off the host to evidence storage.
3. Stop and delete the service:
Stop-Service -Name "svchost32" -Force -ErrorAction SilentlyContinue
sc.exe delete "svchost32"
4. Remove the scheduled tasks identified in step 1 (review each by name first):
# Unregister-ScheduledTask -TaskName "REPLACE_WITH_TASKNAME" -Confirm:$false
5. Remove the payload directory and the UAC-bypass key:
Remove-Item "$env:APPDATA\svchost32" -Recurse -Force -ErrorAction SilentlyContinue
Remove-ItemProperty "HKCU:\Environment" -Name windir -ErrorAction SilentlyContinue
6. Reboot, then re-run step 1. If ANY artefact has returned, the watchdog is
still resident: stop repairing and reimage.
7. Regardless of outcome, treat the identity as compromised -- go to Playbook 3.
Playbook 3 — Identity containment for an affected traveller
Prerequisites: Privileged Authentication Administrator (for credential reset) and Cloud Device Administrator (to remove devices); Microsoft.Graph PowerShell module; the user reachable through an out-of-band channel you trust — not their possibly-compromised mailbox or Teams.
Reboot required: No.
Rollback: None required, and none should be attempted — every action here is a deliberate credential invalidation. The user re-authenticates and re-enrols as part of normal recovery. Removing a device that turns out to be the user's own is corrected by having them re-register it through the standard join process.
Connect-MgGraph -Scopes "User.ReadWrite.All","Device.ReadWrite.All",
"AuditLog.Read.All","Directory.AccessAsUser.All"
$upn = "REPLACE_WITH_USER_UPN"
1. Revoke every refresh token and active session:
Revoke-MgUserSignInSession -UserId $upn
2. Force a credential reset (do this via your standard process, in person or
over a verified out-of-band channel -- NOT by emailing the user).
3. List devices registered to the user and check every one against known
hardware. Anything unrecognised is the attacker's:
Get-MgUserRegisteredDevice -UserId $upn |
Select-Object Id, DisplayName, AdditionalProperties
# Remove an attacker-registered device:
# Remove-MgDevice -DeviceId "REPLACE_WITH_DEVICE_OBJECTID"
4. Re-enrol MFA from scratch. Delete existing methods and have the user
register a passkey in person.
5. Review the mailbox for actor persistence:
Get-InboxRule -Mailbox $upn | Select-Object Name, Enabled, ForwardTo,
RedirectTo, DeleteMessage
Get-Mailbox $upn | Select-Object ForwardingSmtpAddress,
DeliverToMailboxAndForward
Get-MailboxPermission -Identity $upn | Where-Object { -not $_.IsInherited }
6. Check for OAuth consent granted during the compromise window:
Get-MgUserOauth2PermissionGrant -UserId $upn |
Select-Object ClientId, Scope, ConsentType
7. Only after 1-6 are complete, release the endpoint from containment or issue
the reimaged replacement.
Playbook 4 — PowerShell Constrained Language Mode on the travel fleet
Prerequisites: AppLocker or WDAC already in audit mode on a pilot ring; an inventory of business-critical PowerShell that runs on user endpoints (CLM breaks .NET reflection, Add-Type, and COM instantiation); Intune or GPO delivery.
Reboot required: No, but users must start a new PowerShell session for the mode to apply.
Rollback: Remove the AppLocker/WDAC policy (or return it to audit mode) and delete the __PSLockdownPolicy environment variable if it was used for the pilot; the next new session returns to FullLanguage. Keep the pilot ring small precisely so this rollback stays cheap.
1. Baseline first -- enable script block logging fleet-wide and collect 14 days:
GPO: Computer Configuration -> Administrative Templates -> Windows Components
-> Windows PowerShell -> "Turn on PowerShell Script Block Logging" = Enabled
Review Event ID 4104 for reflection, Add-Type and COM use in YOUR scripts.
2. Pilot ring: apply an AppLocker policy in ENFORCE for Script rules only.
CLM is a side effect of AppLocker/WDAC enforcement -- this is the supported
route. Do not set __PSLockdownPolicy as a production mechanism; it is a
pilot/testing aid only and is trivially unset by a user.
3. Verify the mode on a pilot host (expected output: ConstrainedLanguage):
$ExecutionContext.SessionState.LanguageMode
4. Confirm the control does what it is here for -- these must now fail:
[System.Reflection.Assembly]::LoadWithPartialName("System.Management.Automation")
New-Object -ComObject WScript.Shell
5. Expand ring by ring, reviewing 4104 logs and helpdesk tickets at each stage.
6. ROLLBACK at any ring: set the AppLocker Script rule enforcement back to
"Audit only" and refresh policy (gpupdate /force). New PowerShell sessions
return to FullLanguage immediately; no reboot and no reimage needed.
Containment Runbook
| Phase | Actions | Owner | Evidence to capture |
|---|---|---|---|
| 0 · Triage (0–30 min) | Confirm the hit is real: for Q1/Q2/Q3 hits, pull the full process tree and the parent chain. Establish whether the user travelled since 2026-05-01 and to which venue type. Decide scope: one host or a travel cohort. | SOC L1 → L2 | Query output, process tree export, Falcon detection ID, user travel dates |
| 1 · Isolate (30–60 min) | Network-contain the endpoint in Falcon before touching it — CornFlake has a live remote shell and a watchdog. Do not power it off; memory is where ChocoShell lives. Suspend the user's cloud sessions in parallel (do not wait for endpoint work to finish). | SOC L2 | Containment timestamp, memory image if your process supports it |
| 2 · Identity containment (immediate, parallel) | Run Playbook 3: revoke refresh tokens, reset credentials, enumerate and remove attacker-registered devices, re-enrol MFA, review inbox rules, forwarding, delegates and OAuth grants. | Identity team | Sign-in log export, device registration list, OAuth grant list, inbox rule audit |
| 3 · Scope (1–4 h) | Run Q1–Q7 fleet-wide and Q13 over 90 days. Build the traveller cohort from HR/expense data and check every member against §7 native hunts. Check whether other users attended the same venue in the same window. | Threat hunting | Cohort list, per-user hunt results, venue/date correlation table |
| 4 · Eradicate (4–24 h) | Reimage the endpoint. Do not repair — the watchdog restores persistence and partial removal is not verifiably complete. Rotate any credential that was stored in a browser on the host, plus every corporate Wi-Fi PSK the host held. | Endpoint ops | Reimage record, credential rotation list, Wi-Fi PSK rotation record |
| 5 · Recover (24–72 h) | Return the user on a clean device with a passkey enrolled in person. Confirm no device-code sign-ins for the user after the reset. Keep enhanced monitoring on the account for 30 days. | Endpoint ops + SOC | Clean-device handover record, 30-day monitoring ticket |
| 6 · Harden & report (1–2 weeks) | Deploy §12 Immediate items if not already done — item 1 (block device code flow) is the one that closes this specific door. Brief the affected business unit on the travel advisory. Record the venue in an internal watchlist. | Security engineering | CA policy ID, ASR deployment record, advisory distribution list |
Sequencing matters here. Isolating the endpoint without revoking cloud sessions leaves the attacker holding a valid refresh token and an attacker-registered device — they keep the tenant access and simply lose one laptop. Phase 2 runs in parallel with Phase 1, not after it.
Detection Coverage Map
| Technique | Behaviour | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1557 | Captive-portal DNS/HTTP manipulation | — | — | GAP — third-party infrastructure, no telemetry. Policy control only (§12). |
| T1566.002 | Doppelganger domain served from portal | Q6 | — | GOOD |
| T1204.004 | ClickFix paste-and-run | Q8 | — | PARTIAL — approximated from the process side; NCSI probe itself is not a sensor event. |
| T1036.005 | svchost32 masquerade | Q1 | IOA-1 | GOOD |
| T1543.003 | "Cloud Sync Service" registration | Q2 | IOA-2 | GOOD |
| T1547.001 | Run-key persistence | Q3 | — | GOOD |
| T1053.005 | Scheduled task persistence | Q4 | — | PARTIAL — noisy; stack-rank hunt until baselined (G3). |
| T1204 | Known payload execution | Q5 | IOA-1 | GOOD — but brittle: per-build randomisation means hash churn is expected. |
| T1071.001 / T1041 | C2 and exfiltration | Q6, Q7 | — | PARTIAL — IP/domain coverage only; the operator panel rotates relays by design. |
| T1548.002 | UAC bypass chain | Q11 | — | PARTIAL — registry side covered; the SilentCleanup execution itself is not distinguished. |
| T1562.001 | AMSI tampering | Q8 (partial) | IOA-4 | PARTIAL — needs script block logging (Event 4104) for full fidelity. |
| T1555 / T1555.003 / T1539 | Wi-Fi, browser and cookie theft | Q9, Q10, Q12 | IOA-3, IOA-6 | GOOD |
| T1528 / T1550.001 | M365 token theft and replay | Q12, Q13 | — | PARTIAL — depends entirely on Entra log ingest (gate G2). |
| T1098.005 | Attacker device registration | — | — | GAP — no Falcon telemetry; covered by native hunt N2 only. |
| T1056.001 / T1113 / T1123 / T1125 | Keylogging, screen, audio, webcam | — | — | GAP — no discrete collection events; caught only via the parent payload (Q1). |
| T1025 | Removable-media collection | — | — | GAP — USB monitoring module produces no distinct signal in this telemetry set. |
| T1490 | VSS shadow-copy deletion | — | IOA-5 | PARTIAL — IOA only, no hunt query in this pack. |
| — | FruitStone C2 panel | — | — | N/A — operator-side only; deliberately no endpoint detection. |
| — | Android APK variant | — | — | GAP — outside Windows sensor scope; route to MTD/MDM. |
Thirteen CQL queries ship: six are alert-ready on day one (Q1, Q2, Q3, Q5, Q6, Q7) and seven are hunt-only until the benign baseline in gate G3 produces a documented exclusion set. The material gaps are all structural rather than fixable by another query: the venue network is not instrumented, attacker device registration and the collection modules produce no discrete Falcon event, and the Android variant is outside this telemetry entirely. The single most consequential dependency is gate G2 — if Entra sign-in logs are not ingested, Q13 and the whole identity half of this pack do not run, and the branch of the campaign that survives reimaging goes unwatched. Validate G1 and G2 before trusting any coverage claim on this page.
Hunt Summary Ticket
TITLE : CaptiveCrunch -- Storm-2945 / Midnight Blizzard captive-portal AiTM
SEVERITY : Critical (active nation-state, SVR-attributed, ongoing since May 2026)
SCOPE : Windows travel fleet + Microsoft 365 / Entra ID tenant
HYPOTHESIS : Corporate travellers who joined hospitality captive-portal Wi-Fi since
2026-05-01 were served ClickFix fake-update prompts, ran CornFlake
(svchost32.exe) and/or ChocoShell, and had M365 tokens stolen; a
separate branch phished Entra device codes with no endpoint artefact.
QUERIES : Q1 CornFlake svchost32 execution CONF high FP low
Q2 "Cloud Sync Service" registration CONF high FP low
Q3 Run-key / service-key persistence CONF high FP low
Q4 Scheduled task from %APPDATA% CONF med FP med
Q5 Known SHA256 execution or write CONF high FP low
Q6 Doppelganger domain resolution CONF high FP low
Q7 C2 IP egress CONF high FP low
Q8 ClickFix browser-parented interpreter CONF med FP med
Q9 netsh wlan show profile CONF high FP med
Q10 Browser remote-debugging-port relaunch CONF med FP med
Q11 UAC-bypass registry hijacks CONF med FP med
Q12 Credential store / token cache targeting CONF med FP med
Q13 Entra device code sign-in anomaly CONF med FP med
DO FIRST : 1. Run Q1, Q2, Q3, Q5 fleet-wide over 90 days -- near-zero FP.
2. Confirm Entra sign-in logs are ingested (gate G2). If not, fix
that before anything else; Q13 is the only cover for the
branch that survives reimaging.
3. Run Q13 over 90 days and cross-check every hit against the
traveller cohort.
4. Audit Conditional Access for a device-code-flow block (§10
checklist). If absent, Playbook 1 is the priority action.
FINDINGS : [ ] Q1-Q3 endpoint hits ............ hosts: ______
[ ] Q5 hash hits .................... hosts: ______
[ ] Q6/Q7 C2 contact ............... hosts: ______
[ ] Q13 device-code sign-ins ....... users: ______
[ ] New Entra device registrations . users: ______
GAPS : Venue network manipulation (no telemetry); attacker device
registration (native hunt N2 only); collection modules
(keylog/screen/audio/webcam/USB) produce no discrete events;
Android APK variant outside Windows sensor scope.
ACTIONS : Contain endpoint -> revoke tokens IN PARALLEL -> scope cohort ->
reimage (do not repair; watchdog restores persistence) ->
block device code flow -> rotate browser-stored creds and Wi-Fi PSKs.
OWNER : SOC threat hunting / Identity team (joint -- neither half works alone)
VERSION : v0.1 · 2026-08-04
Changelog
detect/high — none at prevent, because Microsoft is the sole publisher and the two-independent-source bar for a blocking action is not met. Six native Entra/M365 hunts, four deployable playbooks with rollback, and a coverage map recording four structural gaps. Name collision with the unrelated Interlock CORNFLAKE loader explicitly noted so the two IOC sets are not merged.References
| Tier | Source | Used for | Accessed |
|---|---|---|---|
| 1 | Microsoft Threat Intelligence — CaptiveCrunch: Midnight Blizzard targets travelers worldwide (2026-07-31) | Attribution, full attack chain, CornFlake / ChocoShell / FruitStone internals, all atomic indicators, mitigations, hunting guidance | 2026-08-04 |
| 2 | Infosecurity Magazine — CaptiveCrunch / Midnight Blizzard | Campaign narrative, NCSI-interception delivery, Android APK variant, device-code timeline | 2026-08-04 |
| 2 | Security Affairs — Russian hackers hijack hotel Wi-Fi to steal Microsoft 365 tokens | Service name and display name corroboration, ECDH C2, Wi-Fi and token-cache collection detail | 2026-08-04 |
| 2 | Wiz Threat Intelligence — CaptiveCrunch hospitality-network AiTM campaign | Independent incident tracking; Entra ID as the primary targeted technology | 2026-08-04 |
| 2 | Check Point Research — Threat Intelligence Report, 3rd August 2026 | Independent confirmation of actor, malware names and the M365 token-theft objective | 2026-08-04 |
| ref | MITRE ATT&CK — T1557 Adversary-in-the-Middle | Technique mapping and mitigation references | 2026-08-04 |
| ref | MITRE ATT&CK — T1528 Steal Application Access Token | Device code / OAuth abuse mapping | 2026-08-04 |
| ref | MITRE ATT&CK — G0016 APT29 (Midnight Blizzard) | Actor background and historical TTPs | 2026-08-04 |
Source snapshots for every citation above are preserved alongside this pack in CaptiveCrunch-Storm2945-Hunt-sources/, one plain-text file per source, so that every atomic indicator shipped here can be traced back to the text it came from.