Respond: The First Hour
# S3 / Wasabi / B2: is Object Lock on, and in which mode?
aws s3api get-object-lock-configuration --bucket <backup-bucket>
# restic: newest snapshots (read-only)
restic -r <repo> snapshots --latest 3
Get-GPO -All | Sort-Object ModificationTime -Descending |
Select-Object -First 10 DisplayName, ModificationTime
Get-ScheduledTask | Where-Object { $_.Date -and [datetime]$_.Date -gt (Get-Date).AddDays(-3) } |
Select-Object TaskName, TaskPath, Date, Author
Hour 1–24: Evict, Scope, Preserve
krbtgt twice, with the second reset after replication and the maximum ticket lifetime (10 h by default). Why: one reset leaves forged Kerberos tickets valid; two back-to-back breaks legitimate authentication across the domain.- Entra Connect sync accounts. The AD DS connector account (
MSOL_<hex>) has replication rights, so it is a DCSync credential; the Entra connector account (Sync_<server>_<hex>) holds Directory Synchronization Accounts. Reset the AD account in AD and update it in Synchronization Service Manager; reset the cloud one withAdd-ADSyncAADServiceAccounton the Connect server. - AD CS. A certificate issued through a misconfigured template (ESC1) authenticates as that user until it expires, and a stolen CA key signs new ones forever (a "golden certificate"). List and revoke everything issued in the dwell window; if the CA key may have been exported (CA server reached, or 4876/4877 backup events), the CA is reissued, not reset.
- DSRM. The Directory Services Restore Mode password on every DC; with
DsrmAdminLogonBehaviorset to 2 it is a working domain-controller logon over the network. - AdminSDHolder and SIDHistory. An extra ACE on AdminSDHolder is re-applied to every protected group hourly; a
SIDHistoryvalue equal to a privileged SID is domain admin on a plain user. Both persist through any reset. - gMSA. The password rotates on the KDS schedule, but anyone in
PrincipalsAllowedToRetrieveManagedPasswordcan read the current one; remove what the attacker added. If a DC was fully compromised, the KDS root key is theirs too (GoldenGMSA) and the domain is rebuilt, not cleaned. - Entra tokens and PRTs.
Revoke-MgUserSignInSessioninvalidates refresh tokens and browser sessions; access tokens already issued stay valid until they expire (about 1 h; CAE-capable clients enforce the revoke within minutes). The Primary Refresh Token on an attacker-held device dies when that device object is disabled (Update-MgDevice -AccountEnabled:$false), so disable the device records of every host the stage 2–5 hunts returned. Disable the account before revoking; revoking first leaves the still-valid password usable for a new sign-in.
# AD CS: everything issued in the dwell window, then revoke by serial (reason 1 = key compromise)
certutil -view -restrict "NotBefore>=<dd/mm/yyyy>,Disposition=20" -out "RequestID,RequesterName,CommonName,SerialNumber,CertificateTemplate"
certutil -revoke <SerialNumber> 1
# DSRM behaviour and reset, on every DC
reg query "HKLM\System\CurrentControlSet\Control\Lsa" /v DsrmAdminLogonBehavior
ntdsutil "set dsrm password" "reset password on server null" q q
# AdminSDHolder ACL and SIDHistory
(Get-Acl "AD:\CN=AdminSDHolder,CN=System,$((Get-ADDomain).DistinguishedName)").Access | Where-Object { $_.IdentityReference -notmatch 'Domain Admins|Enterprise Admins|Administrators|SYSTEM|Enterprise Key Admins|Key Admins|Pre-Windows 2000' }
Get-ADObject -LDAPFilter "(sIDHistory=*)" -Properties sIDHistory | Select-Object Name, sIDHistory
# gMSA readers
Get-ADServiceAccount -Filter * -Properties PrincipalsAllowedToRetrieveManagedPassword | Select-Object Name, PrincipalsAllowedToRetrieveManagedPassword
# Entra: disable, revoke, disable the attacker's device records
Update-MgUser -UserId <upn> -AccountEnabled:$false
Revoke-MgUserSignInSession -UserId <upn>
Get-MgDevice -Filter "displayName eq '<host>'" | ForEach-Object { Update-MgDevice -DeviceId $_.Id -AccountEnabled:$false }
Get-ChildItem C:\Users\*\AppData\Roaming\rclone\rclone.conf -ErrorAction SilentlyContinue
- Windows Security, System and Sysmon logs on DCs, file servers and the hosts in scope: default 20 MB sizes overwrite in hours on a busy DC.
wevtutil epleach channel, or Live ResponseCollect investigation package/ RTReventlog export. - Defender advanced hunting: 30 days. Sentinel: the workspace retention (90 days interactive by default). Export the stage queries' results as CSV while they still return rows.
- Entra sign-in and audit logs: 30 days on P1/P2, 7 days free, unless streamed to Log Analytics.
Get-MgAuditLogSignIn -AllandGet-MgAuditLogDirectoryAudit -All. - Unified Audit Log: 180 days on Audit Standard, 1 year on Audit Premium.
Search-UnifiedAuditLog -StartDate <dwell start> -EndDate (Get-Date) -SessionCommand ReturnLargeSet, paged until empty;-ResultSize 5000alone truncates. - ESXi and vCenter: ESXi logs sit on a ramdisk and are gone at reboot;
vm-supporton each host, and the VCSA support bundle. Veeam: the Veeam Backup event log andC:\ProgramData\Veeam\Backup. - Firewall, VPN and proxy logs for the dwell window, with the bytes-out per host, and DHCP lease logs so that addresses in every other log resolve to hosts.
wevtutil epl Security C:\IR\%COMPUTERNAME%-Security.evtx
wevtutil epl Microsoft-Windows-Sysmon/Operational C:\IR\%COMPUTERNAME%-Sysmon.evtx
Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-30) -EndDate (Get-Date) -SessionId "IR-<n>" -SessionCommand ReturnLargeSet -ResultSize 5000 | Export-Csv ual-page.csv -Append
Get-MgAuditLogSignIn -All -Filter "createdDateTime ge <yyyy-mm-dd>T00:00:00Z" | Export-Csv signins.csv
Get-ChildItem C:\IR | Get-FileHash -Algorithm SHA256 | Export-Csv C:\IR\manifest.csv
putfile/run, RTR put/run, a RemoteOps script, Velociraptor Windows.System.PowerShell, the Elastic response console upload/execute) — the exact commands per tool are in each tab's Collect step under Hunt & Act.
- Magnet RESPONSE 1.7.2
d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d— Windows volatile data: RAM, pagefile, running processes and triage files, in one package. Run it elevated from removable media or push it with the EDR; the/unattended /captureram /capturepagefileswitches avoid the prompt. Velociraptor users can take RAM natively withWindows.Memory.Acquisition. - Get-PersistenceSnapshot.ps1
a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df— Windows persistence, read-only: run keys, services, scheduled tasks, WMI subscriptions, startup folders and more, with file hashes and signatures.-Zipfor chain of custody; run it on a clean host of the same build and pass that snapshot to-CompareToto see only what the attacker added. - get-persistence-snapshot.sh
8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67— the Linux counterpart for servers and x86 NAS: cron, systemd units and timers, SSH keys, PAM,ld.so.preload, SUID files, with package-manager verification of every referenced binary.--zipand--compare-toas above. ESXi has no bash; the Linux / ESXi tab lists the equivalent checks by hand.
Day 1–7: Notify, Decide, Recover
- The entry point is named and closed (stage 1 hunt answered; edge device patched or replaced; the VPN account disabled), and every account seen in stages 1, 4 and 5 is in the Kill Authentication list.
- Kill Authentication is complete, including the second
krbtgtreset and the resets listed above (Entra Connect, AD CS, DSRM, AdminSDHolder, SIDHistory, gMSA, device objects). - Every host the stage 2 and 5 hunts returned is rebuilt from a known-good image or still isolated; none has been cleaned in place.
- The hypervisors in scope are reinstalled, SSH and the shell are off, lockdown is on, and the backup console credentials are new (stage 6 hunts, re-run, return nothing after the eviction time).
- Verification hunt: the stage 2 (RMM, tunnels, beacon indicators), stage 4 (LSASS, DCSync, Kerberoasting), stage 5 (fan-out, remote execution), stage 6 (ESXi audit, backup console) and stage 8 (shadow copies, security services, GPO) hunts, re-run over the window from the eviction time to now, all return nothing new. Run them in every tab you have and in Velociraptor across the rebuilt hosts; a hit on any of them reopens scoping and resets the window.
- Persistence snapshots from the rebuilt hosts (
Get-PersistenceSnapshot.ps1 -CompareTo,get-persistence-snapshot.sh --compare-to) diff clean against the gold image. - The restored data was taken from a point before the dwell start, or scanned for the attacker's tools and persistence if it had to come from inside the window.
After Day 7: Feed the Slow Loops
- Entry route → vulnerability management and posture: the edge device, credential or exposure that let them in, and every other place it exists.
- What detection missed → the detection backlog: each stage they reached without an alert (staging, credential dumping, backup tampering) becomes a tested detection with an owner.
- Visibility and process gaps → structural improvement: missing logs, slow decisions, backups that were reachable, handovers that stalled.
- Indicators become intelligence only after an analyst checks them and gives each one a date, a source, an expiry and a confidence level. Attacker IPs and hashes rotate; an unexpired block list becomes noise.
- Re-run the tabletop against what actually happened, and update this playbook where the response was slow.
How Far Has It Got?
Ransomware attack chain (Scenarios 1, 2 and 5)
| # | Stage | ATT&CK | Question | Where to look |
|---|---|---|---|---|
| 1 | Initial access | T1133 T1190 | How did they get in, and when was the earliest malicious activity? | VPN, firewall and edge-device auth logs; edge-device versions against CISA KEV; EDR first-seen times |
| 2 | Foothold | T1219 T1055 | Do they have C2 beacons or remote-access tools on hosts, and on how many? | EDR alerts; Velociraptor hunt for RMM and beacon artifacts; named pipes; new services and scheduled tasks |
| 3 | Discovery | T1087.002 T1482 | Have they mapped Active Directory and the network? | BloodHound/SharpHound, AdFind or network-scanner execution; LDAP query spikes |
| 4 | Privilege | T1003.001 T1003.006 | Do they hold domain admin or other Tier 0 credentials? | Admin logons (4624/4672) from unusual hosts; LSASS access; NTDS.dit copies; DCSync (4662 with replication GUIDs) on domain controllers; Kerberoasting (4769 RC4) |
| 5 | Lateral movement | T1021.002 T1569.002 | How many hosts have they reached? | Logon type 3/10 from foothold hosts; RDP, SMB, WMI and WinRM; service installs (7045), remote tasks (4698), admin-share writes (5145) |
| 6 | Recovery planes | T1021.004 T1490 | Have they touched hypervisors, vCenter or the backup console? | vCenter and ESXi auth logs; esx.audit.ssh.enabled in vobd.log; Veeam event ids 10050/23090/28200/40201; backup console logons |
| 7 | Exfiltration | T1567.002 T1560.001 | Has data left, how much, and from where? | Firewall/proxy egress volume per host; rclone, MEGAcmd, WinSCP; 7-Zip/WinRAR archives on file servers; cloud-storage destinations |
| 8 | Impact preparation | T1490 T1562.001 | Are they disabling defences or recovery? | Security agents stopped; shadow copies deleted (vssadmin, wmic); backup jobs or retention changed; new or changed GPOs |
| 9 | Encryption | T1486 | Has encryption started, and how widely? | Ransom notes; mass renames; canary tokens; EDR mass-file-modification alerts |
| 10 | Extortion | T1657 | Have they made contact or listed us on a leak site? | Ransom note contact details; emails or calls to staff; leak-site monitoring |
Data theft without encryption (Scenario 3)
| # | Stage | ATT&CK | Question | Where to look |
|---|---|---|---|---|
| 1 | First contact | T1566.004 | How did the attacker first reach us: call or email, which users, when? | Phone-system logs; helpdesk tickets; user interviews; email gateway |
| 2 | Other targets | T1566.004 | Were other staff contacted with the same pretext? | Phone-system logs; email gateway search; ask staff |
| 3 | Remote access | T1219 | Was a remote-access tool installed or a session granted, on which hosts? | EDR software inventory; AnyDesk, Zoho Assist, ScreenConnect installs; browser download history |
| 4 | Session activity | T1083 | How long did the access last, and what did they do? | The tool's own session logs; EDR process tree |
| 5 | Data accessed | T1039 T1530 | Which shares, mailboxes and applications were read? | File-server audit; SharePoint/OneDrive and Unified Audit Log; document-management audit |
| 6 | Exfiltration | T1567.002 | How much left, and to where? | Egress volume from the host; cloud-storage uploads; rclone |
| 7 | Persistence | T1078 T1219 | Do they still have access: other tools, accounts, tokens? | Sign-ins after the session; a second remote-access tool; new accounts |
| 8 | Demand | T1657 | Has a demand arrived, with samples and a deadline? | The message itself; samples validated against our systems |
| 9 | Publication | T1657 | Are we listed on a leak site, or is data already published? | Leak-site monitoring; threat-intel feeds |
| 10 | Third parties | T1657 | Has the extortionist contacted our clients or partners? | Client and partner reports; account managers |
Hypervisor / NAS (Scenario 4)
| # | Stage | ATT&CK | Question | Where to look |
|---|---|---|---|---|
| 1 | Network entry | T1133 | How did they reach our network? | VPN and firewall logs; EDR on Windows hosts |
| 2 | Management plane | T1021.004 | Did they reach ESXi, vCenter or NAS management interfaces, and from which host? | Firewall logs into the management VLAN; vCenter logins; ESXi auth.log "Accepted" lines by source |
| 3 | Credentials | T1078 | Which account did they use: vCenter SSO, root, the AD "ESX Admins" group? | ESXi auth.log and hostd.log; vCenter events; AD group changes (4728/4732/4756) |
| 4 | Shell access | T1059.004 | Was SSH or the ESXi Shell enabled, when, and by whom? | vobd.log esx.audit.ssh.enabled / esx.audit.shell.enabled; hostd.log; shell.log (interactive commands only) |
| 5 | Defence changes | T1562.001 | Was lockdown disabled, VIB acceptance lowered or execInstalledOnly turned off? | esxcli software acceptance get; esxcli system settings kernel list -o execInstalledOnly; esx.audit.lockdownmode.disabled in vobd.log |
| 6 | Recovery | T1490 | Were snapshots deleted, or the backup appliance reached? | hostd.log RemoveSnapshot tasks; vCenter tasks; backup console audit log |
| 7 | Exfiltration | T1048 | Did data leave from VMs or NAS before encryption? | Egress from the management network and NAS; NAS access logs; rclone configs on the NAS |
| 8 | VMs stopped | T1489 | Which VMs were powered off? | vCenter events; esxcli vm process list; vim-cmd vmsvc/getallvms |
| 9 | Encryption | T1486 | Which datastores and NAS volumes are encrypted, on how many hosts? | Listing of /vmfs/volumes; ransom notes; NAS share contents |
| 10 | Extortion | T1657 | Have they made contact or listed us on a leak site? | Ransom note; emails to staff; leak-site monitoring |
Hunt & Act by Platform
SecurityEvent, Syslog, Event and CommonSecurityLog are Sentinel-only. Advanced hunting keeps 30 days; anything older is in the Sentinel workspace or nowhere.Hunt
Stage 1 · Initial accessInbound RDP, SMB and WinRM from public addresses; VPN logons by country
DeviceNetworkEvents sees the endpoint side only. VPN and firewall authentication arrives in Sentinel as CommonSecurityLog (CEF) or a vendor table; the second query assumes CEF. Check edge-device versions against CISA KEV by hand.
DeviceNetworkEvents
| where Timestamp > ago(30d) and ActionType == "InboundConnectionAccepted"
| where LocalPort in (3389, 445, 5985, 5986)
| where not(ipv4_is_private(RemoteIP)) and RemoteIP != "127.0.0.1"
| summarize FirstSeen=min(Timestamp), Connections=count() by DeviceName, RemoteIP, LocalPort
| order by FirstSeen asc
// Sentinel: VPN logons per user, how many source countries (CEF-based appliances)
CommonSecurityLog
| where TimeGenerated > ago(30d) and DeviceProduct has_any ("VPN", "Firewall", "FortiGate", "PAN-OS", "ASA")
| where Activity has_any ("vpn", "tunnel", "login", "logon") and DeviceAction !has "fail"
| extend Country = tostring(geo_info_from_ip_address(SourceIP).country)
| summarize Countries=make_set(Country), Sources=dcount(SourceIP), First=min(TimeGenerated) by DestinationUserName
| where Sources > 1 | order by First asc
Stage 2 · FootholdRemote-access and tunnelling tools, first seen per tool
DeviceProcessEvents
| where Timestamp > ago(14d)
| where FileName has_any ("anydesk","screenconnect","atera","splashtop","teamviewer","rustdesk","meshagent","client32","simplehelp","syncro","level.exe","tacticalrmm",
"ngrok","chisel","plink","ligolo","cloudflared","gost","frpc","rsocx")
or ProcessCommandLine has_any ("--reverse", "-R 0.0.0.0", "tunnel run", "client -connect")
| summarize FirstSeen=min(Timestamp), Hosts=dcount(DeviceName), Example=any(ProcessCommandLine) by FileName
| order by FirstSeen asc
Stage 2 · FootholdBeacon indicators — known named pipes, rundll32 with no arguments, remote thread injection
rundll32.exe parent; a process creating a thread in another process is the loader. These date the foothold when the RMM hunt is empty.DeviceEvents
| where Timestamp > ago(14d) and ActionType == "NamedPipeEvent"
| extend PipeName = tostring(parse_json(AdditionalFields).PipeName)
| where PipeName matches regex @"(?i)\\(msagent_[a-f0-9]{2}|postex_[a-f0-9]{4}|MSSE-\d+-server|status_\d+|mojo\.5688\.8052\.|wkssvc_|ntsvcs_|DserNamePipe|SearchTextHarvester|mypipe-[fh])"
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, PipeName
// rundll32 with no arguments, or a remote thread from an unsigned/odd parent
DeviceProcessEvents
| where Timestamp > ago(14d) and FileName =~ "rundll32.exe"
| where ProcessCommandLine matches regex @"(?i)rundll32(\.exe)?""?\s*$"
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine
DeviceEvents
| where Timestamp > ago(14d) and ActionType == "CreateRemoteThreadApiCall"
| where InitiatingProcessFolderPath !startswith @"c:\windows\system32" and InitiatingProcessFolderPath !startswith @"c:\program files"
| summarize Count=count(), Targets=make_set(FileName, 10) by DeviceName, InitiatingProcessFileName, InitiatingProcessFolderPath
Stage 3 · DiscoveryAD and network reconnaissance tools; LDAP enumeration from workstations
DeviceProcessEvents
| where Timestamp > ago(14d)
| where FileName in~ ("adfind.exe","sharphound.exe","netscan.exe","nltest.exe","dsquery.exe")
or FileName startswith "advanced_ip_scanner"
or ProcessCommandLine has_any ("trustdmp","domain_trusts","-collectionmethod","objectcategory=computer","samaccounttype=805306368","/domain_trusts","Get-DomainComputer","Get-NetSession")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine
// Workstations issuing broad LDAP searches (MDE LdapSearch telemetry)
DeviceEvents
| where Timestamp > ago(14d) and ActionType == "LdapSearch"
| extend Filter = tostring(parse_json(AdditionalFields).SearchFilter)
| where Filter has_any ("objectClass=trustedDomain", "samAccountType=805306368", "objectCategory=computer", "adminCount=1", "servicePrincipalName=*")
| summarize Searches=count(), Filters=make_set(Filter, 5) by DeviceName, InitiatingProcessFileName, InitiatingProcessAccountName
| where InitiatingProcessFileName !in~ ("lsass.exe","svchost.exe","Microsoft.Tri.Sensor.exe") | order by Searches desc
Stage 4 · PrivilegeLSASS credential dumping — handle access and known tools
OpenProcessApiCall event sees the handle itself.DeviceEvents
| where Timestamp > ago(14d) and ActionType == "OpenProcessApiCall" and FileName =~ "lsass.exe"
| where InitiatingProcessFolderPath !startswith @"c:\windows\system32" and InitiatingProcessFolderPath !startswith @"c:\program files"
| summarize Count=count(), First=min(Timestamp) by DeviceName, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessSHA256
| order by First asc
// Known tools and techniques by command line
DeviceProcessEvents
| where Timestamp > ago(14d)
| where (ProcessCommandLine has "comsvcs" and ProcessCommandLine has "MiniDump")
or (FileName startswith "procdump" and ProcessCommandLine has "lsass")
or ProcessCommandLine has_any ("sekurlsa", "nanodump", "lsassy", "pypykatz", "dumpert", "handlekatz", "mimikatz")
or (FileName =~ "taskmgr.exe" and ProcessCommandLine has "lsass")
or (FileName =~ "rundll32.exe" and ProcessCommandLine has "lsass")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine
Stage 4 · PrivilegeNTDS.dit theft from a domain controller
ntds.dit plus the SYSTEM hive is every password hash in the domain. It is the reason the krbtgt reset has to happen twice and why every service account is rotated.DeviceProcessEvents
| where Timestamp > ago(30d)
| where (FileName =~ "ntdsutil.exe" and ProcessCommandLine has_any ("ifm", "create full"))
or (FileName =~ "vssadmin.exe" and ProcessCommandLine has_all ("create", "shadow"))
or (FileName in~ ("esentutl.exe","copy.exe","xcopy.exe","robocopy.exe") and ProcessCommandLine has "ntds.dit")
or (FileName =~ "diskshadow.exe")
or ProcessCommandLine has_all ("reg", "save", "hklm\\system")
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine
DeviceFileEvents
| where Timestamp > ago(30d) and FileName =~ "ntds.dit" and FolderPath !startswith @"c:\windows\ntds"
| project Timestamp, DeviceName, FolderPath, InitiatingProcessFileName, InitiatingProcessAccountName
Stage 4 · PrivilegeDCSync and Kerberoasting
IdentityDirectoryEvents; without MDI, SecurityEvent 4662 and 4769 in Sentinel are the source.// Defender for Identity: replication from anything that is not a DC or Entra Connect
IdentityDirectoryEvents
| where Timestamp > ago(30d) and Application == "Active Directory" and ActionType == "Directory Services replication"
| where DeviceName !in~ ("<dc01>", "<dc02>", "<entra-connect>")
| project Timestamp, AccountName, DeviceName, DestinationDeviceName
// Sentinel, Security log on DCs: 4662 with the replication GUIDs from a non-machine account
SecurityEvent
| where TimeGenerated > ago(30d) and EventID == 4662 and ObjectServer == "DS"
| where Properties has_any ("1131f6aa-9c07-11d1-f79f-00c04fc2dcd2", "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2", "89e95b76-444d-4c62-991a-0facbeda640c")
| where SubjectUserName !endswith "$" and SubjectUserName !startswith "MSOL_"
| project TimeGenerated, Computer, SubjectUserName, SubjectDomainName
// Kerberoasting: RC4 service tickets for many SPNs from one source
SecurityEvent
| where TimeGenerated > ago(14d) and EventID == 4769 and TicketEncryptionType == "0x17" and Status == "0x0"
| where ServiceName !endswith "$" and ServiceName != "krbtgt"
| summarize Services=dcount(ServiceName), Names=make_set(ServiceName, 20) by TargetUserName, IpAddress, bin(TimeGenerated, 1h)
| where Services > 5 | order by Services desc
Stage 5 · Lateral movementOne account or source reaching many hosts
DeviceLogonEvents
| where Timestamp > ago(7d) and ActionType == "LogonSuccess"
| where LogonType in ("Network","RemoteInteractive") and isnotempty(RemoteIP)
| summarize Targets=dcount(DeviceName), First=min(Timestamp), Last=max(Timestamp) by AccountName, RemoteIP
| where Targets > 10 | order by Targets desc
Stage 5 · Lateral movementRemote execution — services, tasks, WMI, WinRM and Impacket
wmiexec/smbexec leave a fixed cmd.exe /Q /c … \\127.0.0.1\ADMIN$ shape. Each hit is a target host for the isolation list.DeviceEvents
| where Timestamp > ago(7d) and ActionType in ("ServiceInstalled", "ScheduledTaskCreated")
| extend F = parse_json(AdditionalFields)
| extend Name = coalesce(tostring(F.ServiceName), tostring(F.TaskName)), Path = coalesce(tostring(F.ServiceFilePath), tostring(F.TaskContent))
| where InitiatingProcessFileName in~ ("services.exe", "svchost.exe")
| summarize Hosts=dcount(DeviceName), Example=any(Path) by ActionType, Name
| where Hosts > 3 or Name matches regex @"^[A-Za-z]{8}$|^[0-9a-f]{8,}$|PSEXESVC|csexec|paexec|RemCom"
| order by Hosts desc
// WMI, WinRM and Impacket signatures
DeviceProcessEvents
| where Timestamp > ago(7d)
| where (InitiatingProcessFileName in~ ("wmiprvse.exe", "wsmprovhost.exe") and FileName in~ ("cmd.exe", "powershell.exe", "pwsh.exe", "rundll32.exe", "mshta.exe"))
or (FileName =~ "cmd.exe" and ProcessCommandLine has "/Q /c" and ProcessCommandLine has_any ("ADMIN$", "\\127.0.0.1\\", "2>&1"))
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, ProcessCommandLine
Stage 6 · Recovery planesESXi and vCenter audit events from syslog
Syslog
| where TimeGenerated > ago(14d)
| where ProcessName in~ ("vobd", "hostd", "Hostd", "vpxd", "sso", "vmware-sso")
| where SyslogMessage has_any ("esx.audit.ssh.enabled", "esx.audit.shell.enabled", "esx.audit.lockdownmode.disabled",
"esx.audit.account.loginfailures", "esx.audit.account.locked", "User root@", "logged in as", "esx.audit.dcui.enabled")
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| order by TimeGenerated asc
// AD "ESX Admins" group membership changes (CVE-2024-37085 path)
SecurityEvent
| where TimeGenerated > ago(30d) and EventID in (4728, 4732, 4756)
| where TargetUserName =~ "ESX Admins"
| project TimeGenerated, Computer, SubjectUserName, MemberName, TargetUserName
Stage 6 · Recovery planesBackup console logons, backup deletion and retention changes
Veeam Backup & Replication writes to the Windows event log Veeam Backup (collect it with AMA into Event) and, from 12.1, to syslog with the same ids as instanceId. Ids below are from the Veeam Backup & Replication Event Reference.
// Veeam audit events: 10050 restore point deleted, 23050 job settings updated, 23090 job deleted,
// 28200 repository deleted, 30200 scale-out repository deleted, 31400 user/group deleted,
// 40201 MFA disabled, 40204 MFA for user disabled, 42401 four-eyes authorisation disabled, 41800 attempt to delete backup failed
Event
| where TimeGenerated > ago(30d) and EventLog == "Veeam Backup"
| where EventID in (10050, 23050, 23090, 28200, 30200, 31400, 40201, 40204, 42401, 41800)
| project TimeGenerated, Computer, EventID, RenderedDescription
Syslog
| where TimeGenerated > ago(30d) and SyslogMessage has "enterpriseId=\"31023\""
| where SyslogMessage matches regex @"instanceId=(10050|23050|23090|28200|30200|31400|40201|40204|42401|41800)\b"
| project TimeGenerated, Computer, SyslogMessage
// Interactive and RDP logons to the backup servers, and backup-admin logons anywhere else
SecurityEvent
| where TimeGenerated > ago(30d) and EventID == 4624 and LogonType in (2, 10)
| where Computer in~ ("<vbr01>", "<vbr-proxy01>") or TargetUserName in~ ("<svc-veeam>", "<backup-admin>")
| summarize Logons=count(), First=min(TimeGenerated) by Computer, TargetUserName, IpAddress, LogonType
| order by First asc
Stage 7 · ExfiltrationTransfer tools, cloud-storage destinations and archive staging
DeviceNetworkEvents has no byte counts. Egress volume per host comes from the firewall or proxy: in Sentinel, CommonSecurityLog | summarize sum(SentBytes) by SourceIP, DestinationIP over the dwell window.
DeviceProcessEvents
| where Timestamp > ago(14d)
| where FileName in~ ("rclone.exe","megasync.exe","megacmd.exe","winscp.exe","filezilla.exe","restic.exe","curl.exe")
or ProcessCommandLine has_any ("rclone", "mega-put", "--transfers", "b2 upload", "ftp://")
or (FileName in~ ("7z.exe","7za.exe","rar.exe","winrar.exe") and ProcessCommandLine has " a " and ProcessCommandLine has_any ("-p", "-hp", "-mhe", "-r", "-v"))
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine
DeviceNetworkEvents
| where Timestamp > ago(14d)
| where RemoteUrl has_any ("mega.nz","mega.io","mega.co.nz","dropbox.com","dropboxapi.com","pcloud.com","backblazeb2.com","backblaze.com","gofile.io","temp.sh","file.io","ufile.io","filetransfer.io","anonfiles.com","transfer.sh","put.io","storj.io","wasabisys.com")
| summarize Connections=count(), First=min(Timestamp) by DeviceName, InitiatingProcessFileName, RemoteUrl
| order by Connections desc
// Archives created in bulk on file servers
DeviceFileEvents
| where Timestamp > ago(14d) and ActionType == "FileCreated" and (FileName endswith ".7z" or FileName endswith ".rar" or FileName endswith ".zip" or FileName endswith ".001")
| summarize Archives=count(), Size=sum(FileSize) by DeviceName, InitiatingProcessFileName, InitiatingProcessAccountName, bin(Timestamp, 1h)
| where Archives > 20 or Size > 5000000000
Stage 8 · Impact preparationShadow-copy, boot-recovery and backup-catalog deletion
DeviceProcessEvents
| where Timestamp > ago(3d)
| where (FileName =~ "vssadmin.exe" and ProcessCommandLine has_all ("delete","shadows"))
or (FileName =~ "vssadmin.exe" and ProcessCommandLine has_all ("resize","shadowstorage"))
or (FileName =~ "wmic.exe" and ProcessCommandLine has_all ("shadowcopy","delete"))
or (FileName =~ "bcdedit.exe" and ProcessCommandLine has_any ("recoveryenabled", "bootstatuspolicy"))
or (FileName =~ "wbadmin.exe" and ProcessCommandLine has_all ("delete","catalog"))
or (FileName =~ "powershell.exe" and ProcessCommandLine has "Win32_ShadowCopy" and ProcessCommandLine has "Delete")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine
Stage 8 · Impact preparationSecurity tooling stopped and GPO changes
DeviceProcessEvents
| where Timestamp > ago(3d)
| where ProcessCommandLine has_any ("Set-MpPreference", "DisableRealtimeMonitoring", "Add-MpPreference -ExclusionPath")
or (FileName in~ ("net.exe","net1.exe","sc.exe") and ProcessCommandLine has_any ("stop", "config", "delete") and ProcessCommandLine has_any ("WinDefend", "Sense", "MsMpSvc", "SepMasterService", "CSFalconService", "SentinelAgent", "veeam", "sql", "vss", "backup"))
or (FileName =~ "taskkill.exe" and ProcessCommandLine has_any ("sql", "veeam", "backup", "/im"))
| project Timestamp, DeviceName, AccountName, ProcessCommandLine
DeviceRegistryEvents
| where Timestamp > ago(3d) and RegistryKey has @"Windows Defender" and RegistryValueName in~ ("DisableAntiSpyware", "DisableRealtimeMonitoring", "TamperProtection")
| project Timestamp, DeviceName, InitiatingProcessAccountName, RegistryKey, RegistryValueName, RegistryValueData
// Sentinel: GPO objects created or modified (needs Directory Service Changes auditing on DCs)
SecurityEvent
| where TimeGenerated > ago(7d) and EventID in (5136, 5137) and ObjectClass == "groupPolicyContainer"
| project TimeGenerated, Computer, SubjectUserName, ObjectDN, AttributeLDAPDisplayName, AttributeValue
Stage 9 · EncryptionMass file changes by one process; ransom notes
DeviceFileEvents
| where Timestamp > ago(1d) and ActionType in ("FileRenamed","FileModified")
| summarize Files=count(), Extensions=dcount(FileName) by DeviceName, InitiatingProcessFileName, InitiatingProcessSHA256, bin(Timestamp, 5m)
| where Files > 500 | order by Files desc
DeviceFileEvents
| where Timestamp > ago(1d) and ActionType == "FileCreated"
| where FileName matches regex @"(?i)^(readme|restore|recover|decrypt|how_to|how-to|!!!|_readme|unlock).*\.(txt|html?|hta)$"
| summarize Hosts=dcount(DeviceName), Notes=count(), Example=any(FolderPath) by FileName, InitiatingProcessFileName
Stage 10 · ExtortionRansom contact by mail and the note's contact details
Leak-site listing is not visible in any Microsoft table; threat intel or the IR firm monitors the group's site. The note itself: getfile in Live Response, then grep the .onion URL, Tox id and case id.
EmailEvents
| where Timestamp > ago(7d) and EmailDirection == "Inbound"
| where SenderFromDomain in~ ("onionmail.org","proton.me","protonmail.com","tutanota.com","tuta.io","cock.li","mail2tor.com","skiff.com")
or Subject has_any ("your network", "your files", "encrypted", "decrypt", "negotiat", "data leak", "stolen", "ransom", "your company")
| project Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, DeliveryAction, NetworkMessageId
EmailUrlInfo
| where Timestamp > ago(7d) and (Url has ".onion" or Url has "tox.chat")
| join kind=inner (EmailEvents | project NetworkMessageId, SenderFromAddress, RecipientEmailAddress, Subject) on NetworkMessageId
Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+15–30 · IsolateIsolate every host the attacker could have reached
Console: device page › Isolate device (Full). Live Response keeps working on an isolated device.
POST https://api.security.microsoft.com/api/machines/{id}/isolate
{"Comment": "IR case #<n>", "IsolationType": "Full"}
T+15–30 · IsolateContain the accounts the attacker holds, and let attack disruption run
Incident page › the user entity › Contain user (needs Defender for Identity and the MDE sensor at version 10.8470 or later; AD accounts are disabled on the DC by the MDI sensor and in Entra if synced). Attack disruption: Settings › Microsoft Defender XDR › Identity automated response; leave it on. Under Hunt & Act, every account the stage 4–5 hunts returned is contained here, then reset in Kill Authentication.
T+15–30 · IsolateBlock the payload tenant-wide
IpAddress or DomainName with action Block (needs network protection in block mode). Set an expiry: attacker infrastructure rotates.POST https://api.security.microsoft.com/api/indicators
{"indicatorValue": "<sha256>", "indicatorType": "FileSha256",
"action": "AlertAndBlock", "title": "IR case #<n>",
"description": "Ransomware payload", "expirationTime": "<yyyy-mm-dd>T00:00:00Z"}
T+15–30 · CollectCollect before you stop anything
d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d; persistence from Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df. Both are pushed through the Live Response library; putfile needs the file uploaded to the library first (Settings › Endpoints › Response actions). Allow unsigned script execution in Advanced features for the duration of the case, then turn it off.Console: device page › Collect investigation package. Then in Live Response (library files uploaded first):
processes
connections
persistence
scheduledtasks
services
getfile "C:\path\to\ransom-note.txt"
putfile MagnetRESPONSEv172_Self_Extracting_Archive.exe
run MagnetRESPONSEv172_Self_Extracting_Archive.exe -parameters "/accepteula /unattended /output:C:\IR\magnet /capturesystemfiles /captureram /capturepagefile"
run Get-PersistenceSnapshot.ps1 -parameters "-Zip -OutputPath C:\IR\persistence"
getfile "C:\IR\persistence\<host>_<timestamp>.zip"
T+15–30 · StopStop the running payload
remediate process <pid>
Day 1–7 · ScopedRelease hosts one at a time, after the eradication gate
Console: device page › Release from isolation.
POST https://api.security.microsoft.com/api/machines/{id}/unisolate
{"Comment": "IR case #<n>: rebuilt, gate met"}
Hunt
Stage 1 · Initial accessInbound RDP, SMB and WinRM from public addresses; first external logon per account
The sensor sees inbound connections and logons, not the VPN appliance. VPN authentication is in your firewall or SIEM; if Next-Gen SIEM has the connector, search its vendor fields for the same user and date.
#event_simpleName=NetworkReceiveAcceptIP4
| LocalPort=3389 or LocalPort=445 or LocalPort=5985 or LocalPort=5986
| !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"])
| groupBy([ComputerName, RemoteAddressIP4, LocalPort], function=[min(@timestamp, as=FirstSeen), count(as=Connections)])
| sort(FirstSeen, order=asc)
#event_simpleName=UserLogon LogonType=10
| !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"])
| groupBy([UserName, RemoteAddressIP4], function=[min(@timestamp, as=FirstSeen), count(ComputerName, distinct=true, as=Hosts)])
Stage 2 · FootholdRemote-access and tunnelling tools, first seen per tool
#event_simpleName=ProcessRollup2
| ImageFileName=/\\(anydesk|screenconnect|atera|splashtop|teamviewer|rustdesk|meshagent|client32|simplehelp|syncro|tacticalrmm|ngrok|chisel|plink|ligolo[^\\]*|cloudflared|gost|frpc)[^\\]*\.exe$/i
or CommandLine=/--reverse|-R 0\.0\.0\.0|tunnel run|client -connect/i
| groupBy([FileName], function=[min(@timestamp, as=FirstSeen), count(ComputerName, distinct=true, as=Hosts), collect([CommandLine], limit=3)])
| sort(FirstSeen, order=asc)
Stage 2 · FootholdBeacon indicators — hollow rundll32, LOLBins with network, Falcon's own injection detections
rundll32.exe with no arguments or inject into a signed process. The sensor has no named-pipe event; injection is a Falcon detection (tactic Defense Evasion, technique Process Injection), so check the detections list for the same window as this query.#event_simpleName=ProcessRollup2
| ImageFileName=/\\rundll32\.exe$/i CommandLine=/rundll32(\.exe)?"?\s*$/i
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, CommandLine])
// signed LOLBins making outbound connections to non-RFC1918 addresses
#event_simpleName=NetworkConnectIP4
| !cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"])
| join({#event_simpleName=ProcessRollup2 ImageFileName=/\\(rundll32|regsvr32|mshta|msbuild|installutil|wscript|cscript|dllhost|werfault|notepad)\.exe$/i}, field=ContextProcessId, key=TargetProcessId, include=[ImageFileName, CommandLine, ParentBaseFileName])
| groupBy([ComputerName, ImageFileName, RemoteAddressIP4, RemotePort], function=count())
Stage 3 · DiscoveryAD and network reconnaissance tools
#event_simpleName=ProcessRollup2
| ImageFileName=/\\(adfind|sharphound|netscan|nltest|dsquery|advanced_ip_scanner[^\\]*)\.exe$/i
or CommandLine=/trustdmp|domain_trusts|-collectionmethod|objectcategory=computer|samaccounttype=805306368|Get-DomainComputer|Get-NetSession|\/dclist/i
| table([@timestamp, ComputerName, UserName, ImageFileName, CommandLine])
Stage 4 · PrivilegeCredential dumping — LSASS tools, NTDS.dit, Kerberoasting
#event_simpleName=ProcessRollup2
| CommandLine=/comsvcs.*MiniDump|procdump.*lsass|sekurlsa|nanodump|lsassy|pypykatz|dumpert|handlekatz|mimikatz|taskmgr.*lsass/i
or (ImageFileName=/\\ntdsutil\.exe$/i CommandLine=/ifm|create full/i)
or (ImageFileName=/\\vssadmin\.exe$/i CommandLine=/create shadow/i)
or CommandLine=/ntds\.dit|reg(\.exe)? save hklm\\system/i
or CommandLine=/kerberoast|GetUserSPNs|Invoke-Kerberoast|asktgs/i
| table([@timestamp, ComputerName, UserName, ImageFileName, CommandLine])
Stage 5 · Lateral movementOne account or source reaching many hosts
#event_simpleName=UserLogon
| LogonType=3 or LogonType=10
| groupBy([UserName, RemoteAddressIP4], function=[count(ComputerName, distinct=true, as=Targets), min(@timestamp, as=First), max(@timestamp, as=Last)])
| Targets > 10
| sort(Targets, order=desc)
Stage 5 · Lateral movementRemote execution — services and tasks created remotely, WMI, WinRM and Impacket
ServiceStarted and ScheduledTaskRegistered carry ClientComputerName and RemoteAddressIP4 when the request came over the network, which is exactly the PsExec-clone and remote-task case. The second query catches WMI, WinRM and Impacket, which create no service.#event_simpleName=/^(ServiceStarted|ScheduledTaskRegistered)$/
| RemoteAddressIP4=* RemoteAddressIP4!="127.0.0.1"
| ServiceDisplayName=* or TaskName=*
| groupBy([#event_simpleName, ServiceDisplayName, TaskName, RemoteAddressIP4, ClientComputerName], function=[count(aid, distinct=true, as=Hosts), collect([ImageFileName, CommandLine, TaskExecCommand], limit=3)])
| sort(Hosts, order=desc)
#event_simpleName=ProcessRollup2
| (ParentBaseFileName=/^(wmiprvse|wsmprovhost)\.exe$/i FileName=/^(cmd|powershell|pwsh|rundll32|mshta)\.exe$/i)
or (FileName=/^cmd\.exe$/i CommandLine=/\/Q \/c .*(ADMIN\$|\\\\127\.0\.0\.1\\|2>&1)/i)
| table([@timestamp, ComputerName, UserName, ParentBaseFileName, CommandLine])
Stage 6 · Recovery planesWho touched the backup servers; the Veeam console by user
#event_simpleName=UserLogon
| ComputerName=/^(vbr01|vbr-proxy01)$/i
| LogonType=2 or LogonType=10 or LogonType=3
| groupBy([ComputerName, UserName, RemoteAddressIP4, LogonType], function=[count(as=Logons), min(@timestamp, as=First)])
| sort(First, order=asc)
#event_simpleName=ProcessRollup2
| FileName=/^(Veeam\.Backup\.Shell|Veeam\.Backup\.Manager|VeeamBackupShell|vmware-cmd|govc|vmrun)\.exe$/i
or ImageFileName=/\\Veeam\.Backup\.PowerShell|\\VMware\.PowerCLI/i
| groupBy([ComputerName, UserName, FileName], function=[count(as=Runs), min(@timestamp, as=First), collect([CommandLine], limit=3)])
Stage 7 · ExfiltrationTransfer tools, cloud-storage destinations and archive staging
#event_simpleName=ProcessRollup2
| ImageFileName=/\\(rclone|megasync|megacmd|winscp|filezilla|restic|curl)\.exe$/i
or CommandLine=/rclone|mega-put|--transfers|b2 upload|ftp:\/\//i
or (ImageFileName=/\\(7z|7za|rar|winrar)\.exe$/i CommandLine=/ a .*(-p|-hp|-mhe|-r|-v\d)/i)
| table([@timestamp, ComputerName, UserName, FileName, CommandLine])
#event_simpleName=DnsRequest
| DomainName=/mega\.(nz|io|co\.nz)$|dropbox(api)?\.com$|pcloud\.com$|backblazeb2\.com$|backblaze\.com$|gofile\.io$|temp\.sh$|file\.io$|ufile\.io$|filetransfer\.io$|transfer\.sh$|put\.io$|storj\.io$|wasabisys\.com$/i
| groupBy([ComputerName, DomainName], function=[count(as=Lookups), min(@timestamp, as=First)])
| sort(Lookups, order=desc)
Stage 8 · Impact preparationShadow-copy, boot-recovery and backup-catalog deletion; security services stopped
#event_simpleName=ProcessRollup2
| CommandLine=/vssadmin.*delete.*shadows|vssadmin.*resize.*shadowstorage|shadowcopy.*delete|bcdedit.*(recoveryenabled|bootstatuspolicy)|wbadmin.*delete.*catalog|Win32_ShadowCopy.*Delete/i
or CommandLine=/Set-MpPreference.*Disable|DisableRealtimeMonitoring|(net|sc)(\.exe)? (stop|config|delete) .*(WinDefend|Sense|MsMpSvc|CSFalconService|SentinelAgent|veeam|sql|vss|backup)/i
or (ImageFileName=/\\taskkill\.exe$/i CommandLine=/sql|veeam|backup/i)
| table([@timestamp, ComputerName, UserName, CommandLine])
| sort(@timestamp, order=asc)
Stage 9 · EncryptionRansomware detections and ransom-note creation
Endpoint security > Detections: filter Tactic = Impact, Technique = Data Encrypted for Impact; open the process tree.
// Notes written across many hosts
#event_simpleName=/^(NewScriptWritten|NewExecutableWritten)$/ or #event_simpleName=ProcessRollup2
| FileName=/^(readme|restore|recover|decrypt|how_to|how-to|!!!|_readme|unlock)[^\\]*\.(txt|html?|hta)$/i
| groupBy([FileName, ContextBaseFileName], function=[count(aid, distinct=true, as=Hosts), min(@timestamp, as=First)])
EmailEvents, Defender tab); the leak-site listing comes from threat intel or the IR firm. Pull the note itself with RTR get and read the .onion URL, Tox id and case id from it.Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+15–30 · IsolateContain every host in scope in one call
Console: Host management › select the hosts › Network contain.
POST /devices/entities/devices-actions/v2?action_name=contain
{"ids": ["<AID>", "<AID>", "<AID>"]}
T+15–30 · IsolateBlock the payload hash fleet-wide
prevent stops the payload on hosts you have not found yet. Domain and IP IOCs can only detect, not block — cut C2 at the firewall or proxy. Set an expiry.POST /iocs/entities/indicators/v1
{"indicators": [{"type": "sha256", "value": "<sha256>",
"action": "prevent", "platforms": ["windows"],
"applied_globally": true, "severity": "high",
"expiration": "<yyyy-mm-dd>T00:00:00Z",
"description": "IR case #<n>: ransomware payload"}]}
T+15–30 · CollectCollect before you stop anything
d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d (RTR memdump dumps one process, not RAM). Persistence: Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df. Upload both under Host setup and management › Response scripts and files first; put and runscript -CloudFile need the Active Responder or RTR Administrator role.In RTR (Active Responder for get, put, memdump and eventlog; RTR Administrator for run):
ps
netstat
reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Run
runscript -Raw=```Get-ScheduledTask | Where-Object State -ne 'Disabled' | Select TaskName,TaskPath```
get C:\path\to\ransom-note.txt
mkdir C:\IR
put MagnetRESPONSEv172_Self_Extracting_Archive.exe
run "C:\MagnetRESPONSEv172_Self_Extracting_Archive.exe" -CommandLine="/accepteula /unattended /output:C:\IR\magnet /capturesystemfiles /captureram /capturepagefile"
runscript -CloudFile="Get-PersistenceSnapshot.ps1" -CommandLine="-Zip -OutputPath C:\IR\persistence"
get C:\IR\persistence\<host>_<timestamp>.zip
eventlog export Security
T+15–30 · StopStop the running payload
kill <pid>
Day 1–7 · ScopedLift containment one host at a time, after the eradication gate
POST /devices/entities/devices-actions/v2?action_name=lift_containment
{"ids": ["<AID>"]}
Hunt
Stage 1 · Initial accessRDP and network logons from public addresses; first external logon per account
event.type = 'Login' AND event.login.loginIsSuccessful = true
AND event.login.type in ('REMOTE_INTERACTIVE','NETWORK')
AND NOT (src.endpoint.ip.address matches '^(10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.|192\\.168\\.|127\\.)')
| group First = min(event.time), Hosts = estimate_distinct(endpoint.name), Logons = count() by event.login.userName, src.endpoint.ip.address
| sort - First
Stage 2 · FootholdRemote-access and tunnelling tools, first seen per tool
event.type = 'Process Creation' AND (tgt.process.name contains ('anydesk','screenconnect','atera','splashtop','teamviewer','rustdesk','meshagent','client32','simplehelp','syncro','tacticalrmm','ngrok','chisel','plink','ligolo','cloudflared','gost','frpc')
OR tgt.process.cmdline contains ('--reverse','-R 0.0.0.0','tunnel run','client -connect'))
| group First = min(event.time), Events = count(), Hosts = estimate_distinct(endpoint.name) by tgt.process.name
| sort - First
Stage 2 · FootholdBeacon indicators — hollow rundll32 and SentinelOne's injection indicators
rundll32.exe with no arguments or inject into a signed process. The agent's behavioural indicators flag the injection even when the loader is new; the story line gives the parent.event.type = 'Process Creation' AND tgt.process.name = 'rundll32.exe' AND tgt.process.cmdline matches 'rundll32(\\.exe)?"?\\s*$'
| columns event.time, endpoint.name, src.process.name, src.process.cmdline
indicator.category in ('Injection','Evasion') AND src.process.image.path contains ('\\Users\\','\\ProgramData\\','\\Temp\\')
| group Hits = count(), Hosts = estimate_distinct(endpoint.name) by src.process.name, src.process.image.path, indicator.name
Stage 3 · DiscoveryAD and network reconnaissance tools
event.type = 'Process Creation' AND (tgt.process.name contains ('adfind','sharphound','netscan','nltest','dsquery','advanced_ip_scanner')
OR tgt.process.cmdline contains ('trustdmp','domain_trusts','-collectionmethod','objectcategory=computer','samaccounttype=805306368','Get-DomainComputer','Get-NetSession'))
| columns event.time, endpoint.name, src.process.user, tgt.process.name, tgt.process.cmdline
Stage 4 · PrivilegeCredential dumping — LSASS handles, known tools, NTDS.dit, Kerberoasting
event.type = 'Open Remote Process Handle' AND tgt.process.name = 'lsass.exe'
AND NOT src.process.image.path contains ('\\Windows\\System32\\','\\Program Files')
| group Opens = count(), First = min(event.time) by endpoint.name, src.process.name, src.process.image.path, src.process.image.sha256
| sort - Opens
event.type = 'Process Creation' AND (
(tgt.process.cmdline contains 'comsvcs' AND tgt.process.cmdline contains 'MiniDump')
OR (tgt.process.name contains 'procdump' AND tgt.process.cmdline contains 'lsass')
OR tgt.process.cmdline contains ('sekurlsa','nanodump','lsassy','pypykatz','dumpert','handlekatz','mimikatz')
OR (tgt.process.name = 'ntdsutil.exe' AND tgt.process.cmdline contains ('ifm','create full'))
OR (tgt.process.name = 'vssadmin.exe' AND tgt.process.cmdline contains 'create shadow')
OR tgt.process.cmdline contains ('ntds.dit','kerberoast','GetUserSPNs','Invoke-Kerberoast'))
| columns event.time, endpoint.name, src.process.user, tgt.process.name, tgt.process.cmdline
Stage 5 · Lateral movementOne account or source reaching many hosts
event.type = 'Login' AND event.login.loginIsSuccessful = true AND event.login.type in ('NETWORK','REMOTE_INTERACTIVE')
| group Targets = estimate_distinct(endpoint.name), First = min(event.time), Last = max(event.time) by event.login.userName, src.endpoint.ip.address
| filter Targets > 10
| sort - Targets
Stage 5 · Lateral movementRemote execution — services, tasks, WMI, WinRM and Impacket
wmiexec/smbexec leave a fixed cmd.exe /Q /c … \\127.0.0.1\ADMIN$ shape. Each hit is a target host for the isolation list.event.type = 'Process Creation' AND (
(src.process.name in ('wmiprvse.exe','wsmprovhost.exe') AND tgt.process.name in ('cmd.exe','powershell.exe','pwsh.exe','rundll32.exe','mshta.exe'))
OR (tgt.process.name = 'cmd.exe' AND tgt.process.cmdline contains '/Q /c' AND tgt.process.cmdline contains ('ADMIN$','\\127.0.0.1\\','2>&1'))
OR (src.process.name = 'services.exe' AND tgt.process.name in ('psexesvc.exe','paexec.exe','csexec.exe','remcomsvc.exe'))
OR (src.process.name = 'services.exe' AND tgt.process.image.path matches '\\\\Windows\\\\[A-Za-z]{8}\\.exe$'))
| group Hosts = estimate_distinct(endpoint.name), First = min(event.time) by src.process.name, tgt.process.name, tgt.process.cmdline
| sort - Hosts
Stage 6 · Recovery planesWho touched the backup servers; the Veeam console by user
event.type = 'Login' AND event.login.loginIsSuccessful = true AND endpoint.name in ('vbr01','vbr-proxy01')
| group Logons = count(), First = min(event.time) by endpoint.name, event.login.userName, src.endpoint.ip.address, event.login.type
| sort - First
event.type = 'Process Creation' AND (tgt.process.name in ('Veeam.Backup.Shell.exe','Veeam.Backup.Manager.exe','govc.exe','vmrun.exe')
OR tgt.process.cmdline contains ('Veeam.Backup.PowerShell','VMware.PowerCLI','Connect-VIServer'))
| group Runs = count(), First = min(event.time) by endpoint.name, src.process.user, tgt.process.name
Stage 7 · ExfiltrationTransfer tools, cloud-storage destinations and archive staging
(event.type = 'Process Creation' AND (tgt.process.name contains ('rclone','megasync','megacmd','winscp','filezilla','restic')
OR tgt.process.cmdline contains ('rclone','mega-put','--transfers','b2 upload','ftp://')
OR (tgt.process.name in ('7z.exe','7za.exe','rar.exe','winrar.exe') AND tgt.process.cmdline contains ' a ' AND tgt.process.cmdline contains ('-p','-hp','-mhe','-r','-v'))))
OR dns.request contains ('mega.nz','mega.io','mega.co.nz','dropbox.com','dropboxapi.com','pcloud.com','backblazeb2.com','backblaze.com','gofile.io','temp.sh','file.io','ufile.io','filetransfer.io','transfer.sh','put.io','storj.io','wasabisys.com')
| columns event.time, endpoint.name, src.process.user, src.process.name, tgt.process.cmdline, dns.request
Stage 8 · Impact preparationShadow-copy, boot-recovery and backup-catalog deletion; security services stopped
event.type = 'Process Creation' AND (
(tgt.process.name = 'vssadmin.exe' AND tgt.process.cmdline contains ('delete','resize'))
OR (tgt.process.cmdline contains 'shadowcopy' AND tgt.process.cmdline contains 'delete')
OR (tgt.process.name = 'bcdedit.exe' AND tgt.process.cmdline contains ('recoveryenabled','bootstatuspolicy'))
OR (tgt.process.name = 'wbadmin.exe' AND tgt.process.cmdline contains 'catalog')
OR tgt.process.cmdline contains ('Set-MpPreference','DisableRealtimeMonitoring')
OR (tgt.process.name in ('net.exe','net1.exe','sc.exe') AND tgt.process.cmdline contains ('stop','config','delete') AND tgt.process.cmdline contains ('WinDefend','Sense','SentinelAgent','veeam','sql','vss','backup'))
OR (tgt.process.name = 'taskkill.exe' AND tgt.process.cmdline contains ('sql','veeam','backup')))
| columns event.time, endpoint.name, src.process.user, tgt.process.cmdline
Stage 9 · EncryptionRansomware indicators and ransom-note creation
indicator.category contains 'Ransomware'
| group Hits = count(), First = min(event.time) by endpoint.name, src.process.name, src.process.image.sha256
| sort - Hits
event.type = 'File Creation' AND tgt.file.name matches '^(?i)(readme|restore|recover|decrypt|how_to|how-to|!!!|_readme|unlock).*\\.(txt|html?|hta)$'
| group Hosts = estimate_distinct(endpoint.name), Notes = count() by tgt.file.name, src.process.name
EmailEvents, Defender tab); the leak-site listing comes from threat intel or the IR firm. Pull the note with Fetch Files and read the .onion URL, Tox id and case id from it.Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+15–30 · IsolateDisconnect every agent in scope with one filter
Console: Endpoints › select the endpoints › Actions › Disconnect from network.
POST /web/api/v2.1/agents/actions/disconnect
{"filter": {"groupIds": ["<groupId>"]}}
T+15–30 · IsolateBlocklist the payload hash for the whole tenant
black_hash; any other value creates an exclusion instead.POST /web/api/v2.1/restrictions
{"filter": {"tenant": true},
"data": {"type": "black_hash", "sha256Value": "<sha256>",
"osType": "windows", "description": "IR case #<n>"}}
T+15–30 · CollectCollect before you stop anything
a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df and get-persistence-snapshot.sh 8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67 to the RemoteOps Script Library and run them against the affected group with the output set to Fetch. Magnet RESPONSE 1.7.2 d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d is a binary: stage it on an internal share and have a RemoteOps PowerShell script copy it down and run it, as below. Verify the SHA256 on the host before running.Fetch Files: ransom note, payload, event logs (Security, System, Sysmon). RemoteOps script (PowerShell) across the affected group, or Remote Shell on one host:
New-Item -ItemType Directory -Force C:\IR | Out-Null
Copy-Item \\<evidence-share>\ir\MagnetRESPONSEv172_Self_Extracting_Archive.exe C:\IR\
if ((Get-FileHash C:\IR\MagnetRESPONSEv172_Self_Extracting_Archive.exe).Hash -ne 'D315C63D1AD4B89E03C7688B29169E977C2ABC4559C23B9F6B2282F3C91A6C7D') { throw 'hash mismatch' }
Start-Process C:\IR\MagnetRESPONSEv172_Self_Extracting_Archive.exe -ArgumentList '/accepteula /unattended /output:C:\IR\magnet /capturesystemfiles /captureram /capturepagefile' -Wait
.\Get-PersistenceSnapshot.ps1 -Zip -OutputPath C:\IR\persistence
Get-Process | Select-Object Id, ProcessName, Path, StartTime
Get-NetTCPConnection -State Established | Select-Object LocalPort, RemoteAddress, RemotePort, OwningProcess
Get-ScheduledTask | Where-Object State -ne 'Disabled' | Select-Object TaskName, TaskPath
T+15–30 · StopStop the payload; roll back only what you need
Console: Threat › Mitigation › Kill, Quarantine (then Rollback if needed).
Day 1–7 · ScopedReconnect one endpoint at a time, after the eradication gate
POST /web/api/v2.1/agents/actions/connect
{"filter": {"ids": ["<agentId>"]}}
Hunt
Stage 1 · Initial accessFirst external RDP logon per account; SSH logins on Linux
Hunt: Windows.EventLogs.RDPAuth
SourceIPRegex = ^(?!10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.)
DateAfter = <start of the window>
Hunt: Linux.Syslog.SSHLogin (accepted logins by user and source, from auth.log / secure)
Stage 2 · FootholdRemote-access and tunnelling tools, first seen per tool; Cobalt Strike configs in memory
Hunt: Windows.System.Pslist
ProcessRegex = (?i)anydesk|screenconnect|atera|splashtop|teamviewer|rustdesk|meshagent|client32|simplehelp|syncro|tacticalrmm|ngrok|chisel|plink|ligolo|cloudflared|gost|frpc
Hunt: Windows.Forensics.Prefetch
binaryRegex = (?i)anydesk|screenconnect|atera|splashtop|teamviewer|rustdesk|meshagent|client32|simplehelp|syncro|tacticalrmm|ngrok|chisel|plink|ligolo|cloudflared|gost|frpc
Hunt: Windows.Carving.CobaltStrike
ProcessRegex = (?i)rundll32|regsvr32|mshta|dllhost|werfault|notepad|svchost
Hunt: Windows.EventLogs.EvtxHunter (Sysmon 17/18 named pipes, if Sysmon is deployed)
IocRegex = (?i)\\(msagent_[a-f0-9]{2}|postex_[a-f0-9]{4}|MSSE-\d+-server|status_\d+|mojo\.5688\.8052\.|DserNamePipe|SearchTextHarvester)
Stage 3 · DiscoveryAD and network reconnaissance tools
Hunt: Windows.Forensics.Prefetch
binaryRegex = (?i)adfind|sharphound|netscan|advanced_ip_scanner|nltest|dsquery
Hunt: Windows.EventLogs.EvtxHunter
IocRegex = (?i)trustdmp|domain_trusts|collectionmethod|objectcategory=computer|samaccounttype=805306368|Get-DomainComputer|Get-NetSession
Hunt: Windows.Forensics.SRUM (network bytes per application: a scanner shows thousands of small connections)
Stage 4 · PrivilegeCredential dumping — LSASS, NTDS.dit, DCSync and Kerberoasting
Hunt (DCs): Windows.EventLogs.EvtxHunter
IocRegex = 1131f6a[ad]-9c07-11d1-f79f-00c04fc2dcd2|89e95b76-444d-4c62-991a-0facbeda640c
IdRegex = ^4662$
WhitelistRegex = (?i)MSOL_|\$$
Hunt (DCs): Windows.EventLogs.Kerberoasting
Hunt: Windows.EventLogs.EvtxHunter
IocRegex = (?i)comsvcs.+MiniDump|procdump.+lsass|sekurlsa|nanodump|lsassy|pypykatz|dumpert|handlekatz|mimikatz|ntdsutil.+(ifm|create full)|ntds\.dit|reg(\.exe)? save hklm\\system
Hunt: Windows.Search.FileFinder
Glob = C:/{Users,Windows/Temp,ProgramData,Temp}/**/{lsass*.dmp,*.dmp,ntds.dit,SYSTEM,SAM}
Calculate_Hash = Y
Stage 5 · Lateral movementOne account or source reaching many hosts; remote services, tasks and Impacket
%COMSPEC% or random-name image, which is most PsExec clones; the task artifact pairs 4698 with the TaskScheduler log.Hunt: Windows.EventLogs.RDPAuth
DateAfter = <start of the window> (group the results by source IP and user; > 10 targets per source is the lead)
Hunt: Windows.EventLogs.ServiceCreationComspec
Hunt: Windows.Detection.PsexecService
Hunt: Windows.EventLogs.ScheduledTasks
DateAfter = <start of the window>
TaskActionRegex = (?i)cmd|powershell|rundll32|mshta|wscript|\.bat|\.vbs|\.ps1
Hunt: Windows.EventLogs.EvtxHunter
IocRegex = (?i)\/Q \/c .*(ADMIN\$|127\.0\.0\.1|2>&1)|wsmprovhost|wmiprvse.+(cmd|powershell)
Stage 6 · Recovery planesBackup server logons and the Veeam audit log
Hunt (backup servers): Windows.EventLogs.RDPAuth
DateAfter = <start of the window>
Hunt (backup servers): Windows.EventLogs.EvtxHunter
EvtxGlob = C:/Windows/System32/winevt/Logs/Veeam Backup.evtx
IdRegex = ^(10050|23050|23090|28200|30200|31400|40201|40204|42401|41800)$
Hunt (DCs): Windows.EventLogs.EvtxHunter
IdRegex = ^(4728|4732|4756)$
IocRegex = (?i)ESX Admins
Stage 7 · ExfiltrationTransfer tools, rclone configs and archive staging
Hunt: Windows.Forensics.Prefetch
binaryRegex = (?i)rclone|megasync|megacmd|winscp|filezilla|restic|7z|7za|rar|winrar
Hunt: Windows.Search.FileFinder
Glob = C:/Users/*/AppData/Roaming/rclone/rclone.conf
Upload_File = Y
Hunt: Windows.Forensics.Usn
FileNameRegex = (?i)\.(7z|rar|zip|001)$
DateAfter = <start of the window>
Hunt: Windows.Network.NetstatEnriched (live connections, while the host is up)
Stage 8 · Impact preparationShadow-copy, boot-recovery and backup-catalog deletion; security services stopped; GPO changes
Hunt: Windows.EventLogs.EvtxHunter
IocRegex = (?i)vssadmin.+(delete.+shadows|resize.+shadowstorage)|shadowcopy.+delete|recoveryenabled|bootstatuspolicy|wbadmin.+delete.+catalog|Set-MpPreference.+Disable|DisableRealtimeMonitoring|(net|sc)(\.exe)? (stop|config|delete) .*(WinDefend|Sense|veeam|sql|vss|backup)
Hunt (DCs): Windows.EventLogs.EvtxHunter
IdRegex = ^(5136|5137)$
IocRegex = groupPolicyContainer
Hunt: Windows.System.CriticalServices (Defender, Sense, Falcon, SentinelAgent, VSS stopped or disabled)
Stage 9 · EncryptionMass file changes by one process; ransom notes
Hunt: Windows.Forensics.Usn
FileNameRegex = (?i)^(readme|restore|recover|decrypt|how_to|how-to|!!!|_readme|unlock).*\.(txt|html?|hta)$
DateAfter = <start of the window>
Hunt: Windows.Search.FileFinder
Glob = C:/**/{readme,RESTORE,RECOVER,DECRYPT,how_to,HOW_TO}*.{txt,html,hta}
Upload_File = Y
Hunt: Windows.System.Pslist (then Windows.Memory.ProcessDump with PidRegex for the writer)
Stage 10 · ExtortionCollect the note and extract the contact channel
Hunt: Windows.Search.FileFinder
Glob = C:/**/{readme,RESTORE,RECOVER,DECRYPT,how_to,HOW_TO}*.{txt,html,hta}
Upload_File = Y
Calculate_Hash = Y
Then in a notebook:
SELECT * FROM source(artifact="Windows.Search.FileFinder")
WHERE Upload AND read_file(filename=Upload.Path, accessor="fs") =~ "(?i)\.onion|tox|session id|@(proton|onionmail|tutanota)"
Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+15–30 · IsolateQuarantine every client in scope with one hunt
Label the in-scope clients, then Hunt Manager › New Hunt › Include Condition: Match by label › artifact:
Windows.Remediation.Quarantine
Linux.Remediation.Quarantine
T+15–30 · IsolateBlock the payload and C2 outside Velociraptor
T+15–30 · CollectCollect before you stop anything
Windows.Memory.Acquisition (WinPmem) is the native route; Magnet RESPONSE 1.7.2 d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d adds the pagefile and triage files when you want one package for the IR firm. Persistence: Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df and get-persistence-snapshot.sh 8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67. The cleanest way to push a file is a custom artifact with a tools: entry (the server serves the binary and checks its hash); the quick way is Windows.System.PowerShell / Linux.Sys.BashShell with the URL of your evidence share, as below. Windows.Memory.ProcessDump takes ProcessRegex or PidRegex.Windows.System.Pslist
Windows.Network.NetstatEnriched
Windows.System.TaskScheduler
Windows.System.Services
Windows.Sys.StartupItems
Windows.Sysinternals.Autoruns
Windows.Memory.Acquisition
Windows.Memory.ProcessDump
Windows.EventLogs.EvtxHunter
Windows.System.PowerShell
Command = Invoke-WebRequest https://<evidence-server>/ir/MagnetRESPONSEv172_Self_Extracting_Archive.exe -OutFile C:\IR\magnet.exe; if ((Get-FileHash C:\IR\magnet.exe).Hash -ne 'D315C63D1AD4B89E03C7688B29169E977C2ABC4559C23B9F6B2282F3C91A6C7D') { throw 'hash' }; Start-Process C:\IR\magnet.exe -ArgumentList '/accepteula /unattended /output:C:\IR\magnet /capturesystemfiles /captureram /capturepagefile' -Wait
Windows.System.PowerShell
Command = Invoke-WebRequest https://<evidence-server>/ir/Get-PersistenceSnapshot.ps1 -OutFile C:\IR\gps.ps1; C:\IR\gps.ps1 -Zip -OutputPath C:\IR\persistence
StdoutUpload = Y
Linux.Sys.BashShell
Command = curl -fsSLo /tmp/gps.sh https://<evidence-server>/ir/get-persistence-snapshot.sh && echo "8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67 /tmp/gps.sh" | sha256sum -c && bash /tmp/gps.sh --zip -o /tmp/ir-persistence
Linux.Search.FileFinder
SearchFilesGlob = /tmp/ir-persistence*.tar.gz
Upload_File = Y
T+15–30 · StopStop the running payload
Windows.System.PowerShell
Command = Stop-Process -Id <pid> -Force
Linux.Sys.BashShell
Command = kill -STOP <pid> (freeze rather than kill while memory is still being collected)
Day 1–7 · ScopedLift the quarantine one client at a time, after the eradication gate
Windows.Remediation.Quarantine
RemovePolicy = true
Linux.Remediation.Quarantine
RemovePolicy = true
Hunt
Stage 1 · Initial accessVPN and RDP logons from new countries; first external logon per account
| tstats earliest(_time) as first latest(_time) as last count from datamodel=Authentication
where Authentication.action=success earliest=-30d
by Authentication.user Authentication.src Authentication.app
| iplocation Authentication.src
| stats dc(Country) as countries values(Country) as country_list min(first) as first by Authentication.user
| where countries > 1 | sort first
// RDP straight from the internet
index=windows source="XmlWinEventLog:Security" EventCode=4624 LogonType=10 earliest=-30d
NOT IpAddress IN ("10.*","172.16.*","172.17.*","172.18.*","172.19.*","172.2*","172.30.*","172.31.*","192.168.*","127.*","-")
| stats earliest(_time) as first dc(host) as hosts by TargetUserName IpAddress | convert ctime(first)
Stage 2 · FootholdRemote-access and tunnelling tools, first seen per tool
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 earliest=-14d
(Image="*anydesk*" OR Image="*screenconnect*" OR Image="*atera*" OR Image="*splashtop*" OR Image="*teamviewer*"
OR Image="*rustdesk*" OR Image="*meshagent*" OR Image="*client32*" OR Image="*simplehelp*" OR Image="*syncro*" OR Image="*tacticalrmm*"
OR Image="*ngrok*" OR Image="*chisel*" OR Image="*plink*" OR Image="*ligolo*" OR Image="*cloudflared*" OR Image="*gost*" OR Image="*frpc*"
OR CommandLine="*--reverse*" OR CommandLine="*-R 0.0.0.0*" OR CommandLine="*tunnel run*" OR CommandLine="*client -connect*")
| stats min(_time) as first_seen dc(host) as hosts values(CommandLine) as cmd by Image
| convert ctime(first_seen) | sort first_seen
Stage 2 · FootholdBeacon indicators — named pipes (Sysmon 17/18), hollow rundll32, remote threads (Sysmon 8)
rundll32.exe parent; Sysmon 8 records the loader creating a thread in another process. These date the foothold when the RMM hunt is empty.index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" earliest=-14d
((EventCode IN (17,18) PipeName IN ("\\msagent_*","\\postex_*","\\MSSE-*-server","\\status_*","\\mojo.5688.8052.*","\\DserNamePipe*","\\SearchTextHarvester*","\\mypipe-f*","\\mypipe-h*"))
OR (EventCode=1 Image="*\\rundll32.exe" (CommandLine="*rundll32.exe" OR CommandLine="*rundll32.exe\"" OR CommandLine="rundll32"))
OR (EventCode=8 NOT SourceImage="C:\\Program Files*" NOT SourceImage="C:\\Windows\\System32\\*"))
| table _time host EventCode Image SourceImage TargetImage PipeName ParentImage CommandLine
Stage 3 · DiscoveryAD and network reconnaissance tools; port-scan fan-out
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 earliest=-14d
(Image="*\\adfind.exe" OR Image="*\\sharphound.exe" OR Image="*\\netscan.exe" OR Image="*advanced_ip_scanner*" OR Image="*\\nltest.exe" OR Image="*\\dsquery.exe"
OR CommandLine="*trustdmp*" OR CommandLine="*domain_trusts*" OR CommandLine="*-collectionmethod*" OR CommandLine="*objectcategory=computer*" OR CommandLine="*samaccounttype=805306368*")
| table _time host User Image CommandLine
// one process, many destinations
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=3 Initiated=true earliest=-14d
| bin _time span=10m
| stats dc(DestinationIp) as hosts dc(DestinationPort) as ports by _time host Image
| where hosts > 50 OR ports > 20 | sort - hosts
Stage 4 · PrivilegeLSASS handle access, known tools and NTDS.dit theft
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" earliest=-14d
((EventCode=1 (CommandLine="*comsvcs*MiniDump*" OR CommandLine="*sekurlsa*" OR CommandLine="*nanodump*" OR CommandLine="*lsassy*" OR CommandLine="*pypykatz*" OR CommandLine="*dumpert*" OR CommandLine="*mimikatz*"
OR (Image="*\\procdump*" CommandLine="*lsass*") OR (Image="*\\taskmgr.exe" CommandLine="*lsass*")
OR (Image="*\\ntdsutil.exe" (CommandLine="*ifm*" OR CommandLine="*create full*")) OR (Image="*\\vssadmin.exe" CommandLine="*create shadow*")
OR CommandLine="*ntds.dit*" OR CommandLine="*reg* save*hklm\\system*"))
OR (EventCode=10 TargetImage="*\\lsass.exe" GrantedAccess IN ("0x1010","0x1410","0x1438","0x143a","0x1fffff") NOT SourceImage="C:\\Program Files*" NOT SourceImage="C:\\Windows\\System32\\*")
OR (EventCode=11 TargetFilename="*ntds.dit" NOT TargetFilename="C:\\Windows\\NTDS\\*"))
| table _time host User EventCode SourceImage TargetImage GrantedAccess CommandLine TargetFilename
Stage 4 · PrivilegeDCSync and Kerberoasting
index=windows source="XmlWinEventLog:Security" EventCode=4662 ObjectServer=DS earliest=-30d
(Properties="*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*" OR Properties="*1131f6ad-9c07-11d1-f79f-00c04fc2dcd2*" OR Properties="*89e95b76-444d-4c62-991a-0facbeda640c*")
NOT SubjectUserName="*$" NOT SubjectUserName="MSOL_*"
| table _time host SubjectUserName SubjectDomainName
index=windows source="XmlWinEventLog:Security" EventCode=4769 TicketEncryptionType=0x17 Status=0x0 earliest=-14d
NOT ServiceName="*$" NOT ServiceName="krbtgt"
| bin _time span=1h
| stats dc(ServiceName) as services values(ServiceName) as names by _time TargetUserName IpAddress
| where services > 5 | sort - services
Stage 5 · Lateral movementOne account or source reaching many hosts; admin-share writes
ADMIN$ or C$ with write access shows the copy itself (needs File Share object-access auditing on the targets).index=windows source="XmlWinEventLog:Security" EventCode=4624 LogonType IN (3,10) earliest=-7d
| stats dc(host) as targets earliest(_time) as first latest(_time) as last by TargetUserName IpAddress
| where targets > 10 | convert ctime(first) ctime(last) | sort - targets
index=windows source="XmlWinEventLog:Security" EventCode=5145 ShareName IN ("\\\\*\\ADMIN$","\\\\*\\C$") AccessMask IN ("0x2","0x6","0x12019f") earliest=-7d
| stats dc(host) as targets values(RelativeTargetName) as files by SubjectUserName IpAddress
| where targets > 3 | sort - targets
Stage 5 · Lateral movementRemote execution — service installs (7045), remote tasks (4698), WMI, WinRM and Impacket
wmiexec/smbexec leave a fixed cmd.exe /Q /c … \\127.0.0.1\ADMIN$ shape. Each hit is a target host for the isolation list.index=windows source="XmlWinEventLog:System" EventCode=7045 earliest=-7d
| rex field=ServiceName "^(?<svc>[A-Za-z]{8}|[0-9a-f]{8,})$"
| stats dc(host) as hosts values(ImagePath) as paths by ServiceName svc
| where hosts > 3 OR isnotnull(svc) OR ServiceName IN ("PSEXESVC","csexecsvc","PAExec*","RemComSvc") OR match(paths, "(?i)%COMSPEC%|cmd\.exe|powershell|ADMIN\$")
| sort - hosts
index=windows source="XmlWinEventLog:Security" EventCode=4698 earliest=-7d
| stats dc(host) as hosts values(TaskName) as tasks by SubjectUserName
| where hosts > 3
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 earliest=-7d
((ParentImage IN ("*\\wmiprvse.exe","*\\wsmprovhost.exe") Image IN ("*\\cmd.exe","*\\powershell.exe","*\\pwsh.exe","*\\rundll32.exe","*\\mshta.exe"))
OR (Image="*\\cmd.exe" CommandLine="*/Q /c*" (CommandLine="*ADMIN$*" OR CommandLine="*\\127.0.0.1\\*" OR CommandLine="*2>&1*")))
| table _time host User ParentImage CommandLine
Stage 6 · Recovery planesESXi and vCenter audit events; backup console logons, deletions and retention changes
ESXi sourcetypes are vmware:esxlog:* from the Splunk Add-on for VMware ESXi Logs. Veeam ids are from the Veeam Backup & Replication Event Reference: 10050 restore point deleted, 23050 job settings updated, 23090 job deleted, 28200 repository deleted, 30200 scale-out repository deleted, 31400 user/group deleted, 40201 MFA disabled, 40204 MFA for user disabled, 42401 four-eyes authorisation disabled, 41800 attempt to delete backup failed.
sourcetype=vmware:esxlog:* earliest=-14d
("esx.audit.ssh.enabled" OR "esx.audit.shell.enabled" OR "esx.audit.lockdownmode.disabled" OR "esx.audit.account.loginfailures" OR "esx.audit.account.locked" OR "esx.audit.dcui.enabled" OR "User root@" OR "logged in as")
| table _time host sourcetype _raw | sort _time
index=windows source="WinEventLog:Veeam Backup" EventCode IN (10050,23050,23090,28200,30200,31400,40201,40204,42401,41800) earliest=-30d
| table _time host EventCode Message
index=windows source="XmlWinEventLog:Security" EventCode=4624 LogonType IN (2,10) earliest=-30d (host IN ("vbr01","vbr-proxy01") OR TargetUserName IN ("svc-veeam","backup-admin"))
| stats count earliest(_time) as first by host TargetUserName IpAddress LogonType | convert ctime(first) | sort first
index=windows source="XmlWinEventLog:Security" EventCode IN (4728,4732,4756) TargetUserName="ESX Admins" earliest=-30d
| table _time host SubjectUserName MemberName
Stage 7 · ExfiltrationTransfer tools, destinations, archive staging and bytes out per host
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" earliest=-14d
((EventCode=1 (Image IN ("*\\rclone.exe","*\\megasync.exe","*\\megacmd.exe","*\\winscp.exe","*\\filezilla.exe","*\\restic.exe") OR CommandLine IN ("*rclone*","*mega-put*","*--transfers*","*b2 upload*")
OR (Image IN ("*\\7z.exe","*\\7za.exe","*\\rar.exe","*\\winrar.exe") CommandLine="* a *" (CommandLine="* -p*" OR CommandLine="* -hp*" OR CommandLine="*-mhe*" OR CommandLine="* -v*"))))
OR (EventCode=22 QueryName IN ("*mega.nz","*mega.io","*mega.co.nz","*dropbox.com","*dropboxapi.com","*pcloud.com","*backblazeb2.com","*backblaze.com","*gofile.io","*temp.sh","*file.io","*ufile.io","*filetransfer.io","*transfer.sh","*put.io","*storj.io","*wasabisys.com")))
| table _time host User Image CommandLine QueryName
// bytes out per internal source over the dwell window (firewall / Zeek conn via CIM)
| tstats sum(All_Traffic.bytes_out) as bytes_out dc(All_Traffic.dest) as dests from datamodel=Network_Traffic
where All_Traffic.src="10.0.0.0/8" NOT All_Traffic.dest="10.0.0.0/8" earliest=-14d
by All_Traffic.src
| eval gb_out=round(bytes_out/1073741824,1) | sort - bytes_out | head 20
Stage 8 · Impact preparationShadow-copy, boot-recovery and backup-catalog deletion; security services stopped; GPO changes
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 earliest=-3d
((Image="*\\vssadmin.exe" (CommandLine="*delete*shadows*" OR CommandLine="*resize*shadowstorage*")) OR (Image="*\\wmic.exe" CommandLine="*shadowcopy*delete*")
OR (Image="*\\bcdedit.exe" (CommandLine="*recoveryenabled*" OR CommandLine="*bootstatuspolicy*")) OR (Image="*\\wbadmin.exe" CommandLine="*delete*catalog*")
OR CommandLine="*Set-MpPreference*Disable*" OR CommandLine="*DisableRealtimeMonitoring*"
OR (Image IN ("*\\net.exe","*\\net1.exe","*\\sc.exe") (CommandLine="*stop*" OR CommandLine="*config*" OR CommandLine="*delete*") (CommandLine="*WinDefend*" OR CommandLine="*Sense*" OR CommandLine="*CSFalconService*" OR CommandLine="*SentinelAgent*" OR CommandLine="*veeam*" OR CommandLine="*sql*" OR CommandLine="*vss*" OR CommandLine="*backup*"))
OR (Image="*\\taskkill.exe" (CommandLine="*sql*" OR CommandLine="*veeam*" OR CommandLine="*backup*")))
| table _time host User CommandLine
index=windows source="XmlWinEventLog:Security" EventCode IN (5136,5137) ObjectClass=groupPolicyContainer earliest=-7d
| table _time host SubjectUserName ObjectDN AttributeLDAPDisplayName AttributeValue
Stage 9 · EncryptionMass file changes by one process; ransom notes
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=11 earliest=-1d
| bin _time span=5m
| stats count as files dc(TargetFilename) as names by _time host Image Hashes
| where files > 500 | sort - files
index=windows source="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=11 earliest=-1d
TargetFilename IN ("*\\readme*.txt","*\\readme*.html","*\\readme*.hta","*\\RESTORE*.txt","*\\RECOVER*.txt","*\\DECRYPT*.txt","*\\DECRYPT*.html","*\\how_to*.txt","*\\how_to*.html","*\\!!!*.txt","*\\_readme.txt","*\\unlock*.txt")
| stats dc(host) as hosts count as notes values(Image) as writer by TargetFilename
Stage 10 · ExtortionRansom contact by mail
The sourcetype below is the Splunk Add-on for Microsoft Office 365 message trace; substitute your mail gateway's sourcetype and fields (CIM Email model: src_user, subject, recipient).
sourcetype="ms:o365:reporting:messagetrace" earliest=-7d
(SenderAddress IN ("*@onionmail.org","*@proton.me","*@protonmail.com","*@tutanota.com","*@tuta.io","*@cock.li","*@mail2tor.com","*@skiff.com")
OR Subject IN ("*your network*","*your files*","*encrypted*","*decrypt*","*negotiat*","*data leak*","*stolen*","*ransom*","*your company*"))
| table _time SenderAddress RecipientAddress Subject Status
Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+15–30 · IsolateAct in the EDR, against everything the searches returned
With Splunk SOAR, a playbook can run the EDR's contain action against every host in the search results at once.
T+15–30 · CollectCollect through the EDR, and export the searches that scoped the case
d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d, Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df and get-persistence-snapshot.sh 8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67. What Splunk owns is the timeline: export every search above as raw events before the index rolls (hot/warm retention is yours to check with | rest /services/data/indexes), and freeze the buckets for the dwell window.| rest /services/data/indexes | table title frozenTimePeriodInSecs maxTotalDataSizeMB
index=windows earliest=<dwell start> latest=now host IN (<affected hosts>) | outputcsv ir_case_<n>_windows.csv
Hunt
Stage 1 · Initial accessSuccessful authentication from public addresses, by user and country
authentication category, so one query covers RDP (4624 type 10) and the VPN.FROM logs-*
| WHERE event.category == "authentication" AND event.outcome == "success" AND source.ip IS NOT NULL
| WHERE NOT CIDR_MATCH(source.ip, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8")
| STATS first = MIN(@timestamp), logons = COUNT(*), countries = COUNT_DISTINCT(source.geo.country_iso_code), hosts = COUNT_DISTINCT(host.name) BY user.name, source.ip, event.dataset
| SORT first ASC
// Wazuh: data.win.system.eventID:4624 AND data.win.eventdata.logonType:10 AND NOT data.win.eventdata.ipAddress:(10.* OR 192.168.* OR 172.1[6-9].* OR 172.2*.* OR 172.3[01].*)
Stage 2 · FootholdRemote-access and tunnelling tools, first seen per tool
FROM logs-*
| WHERE event.category == "process" AND event.type == "start"
| WHERE process.name RLIKE "(?i)(anydesk|screenconnect|atera|splashtop|teamviewer|rustdesk|meshagent|client32|simplehelp|syncro|tacticalrmm|ngrok|chisel|plink|ligolo|cloudflared|gost|frpc).*"
OR process.command_line RLIKE ".*(--reverse|-R 0\\.0\\.0\\.0|tunnel run|client -connect).*"
| STATS first = MIN(@timestamp), hosts = COUNT_DISTINCT(host.name), example = VALUES(process.command_line) BY process.name
| SORT first ASC
Stage 2 · FootholdBeacon indicators — named pipes (Sysmon 17/18), hollow rundll32, remote threads
rundll32.exe parent; Sysmon 8 records the loader creating a thread in another process. These date the foothold when the RMM hunt is empty.any where
(event.code in ("17", "18") and winlog.event_data.PipeName : ("\\msagent_*", "\\postex_*", "\\MSSE-*-server", "\\status_*", "\\mojo.5688.8052.*", "\\DserNamePipe*", "\\SearchTextHarvester*", "\\mypipe-f*", "\\mypipe-h*")) or
(event.category == "process" and event.type == "start" and process.name : "rundll32.exe" and process.args_count <= 1) or
(event.code == "8" and not winlog.event_data.SourceImage : ("C:\\Program Files*", "C:\\Windows\\System32\\*"))
Stage 3 · DiscoveryAD and network reconnaissance tools; connection fan-out
process where event.type == "start" and
(process.name : ("adfind.exe", "sharphound.exe", "netscan.exe", "advanced_ip_scanner*", "nltest.exe", "dsquery.exe") or
process.command_line : ("*trustdmp*", "*domain_trusts*", "*-collectionmethod*", "*objectcategory=computer*", "*samaccounttype=805306368*", "*Get-DomainComputer*", "*Get-NetSession*"))
// one process, many destinations (ES|QL)
FROM logs-endpoint.events.network-*
| WHERE event.action == "connection_attempted"
| STATS dests = COUNT_DISTINCT(destination.ip), ports = COUNT_DISTINCT(destination.port) BY host.name, process.name, bucket = DATE_TRUNC(10 minutes, @timestamp)
| WHERE dests > 50 OR ports > 20
| SORT dests DESC
Stage 4 · PrivilegeLSASS handle access, known tools and NTDS.dit theft
any where
(event.code == "10" and winlog.event_data.TargetImage : "*\\lsass.exe" and
winlog.event_data.GrantedAccess in ("0x1010", "0x1410", "0x1438", "0x143a", "0x1fffff") and
not winlog.event_data.SourceImage : ("C:\\Program Files*", "C:\\Windows\\System32\\*")) or
(event.category == "process" and event.type == "start" and
(process.command_line : ("*comsvcs*MiniDump*", "*sekurlsa*", "*nanodump*", "*lsassy*", "*pypykatz*", "*dumpert*", "*mimikatz*", "*ntds.dit*", "*reg* save*hklm\\system*") or
(process.name : "procdump*" and process.command_line : "*lsass*") or
(process.name : "ntdsutil.exe" and process.command_line : ("*ifm*", "*create full*")) or
(process.name : "vssadmin.exe" and process.command_line : "*create*shadow*"))) or
(event.category == "file" and file.name : "ntds.dit" and not file.path : "C:\\Windows\\NTDS\\*")
Stage 4 · PrivilegeDCSync and Kerberoasting
FROM logs-*
| WHERE event.code == "4662" AND winlog.event_data.ObjectServer == "DS"
| WHERE winlog.event_data.Properties LIKE "*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*" OR winlog.event_data.Properties LIKE "*1131f6ad-9c07-11d1-f79f-00c04fc2dcd2*" OR winlog.event_data.Properties LIKE "*89e95b76-444d-4c62-991a-0facbeda640c*"
| WHERE NOT winlog.event_data.SubjectUserName LIKE "*$" AND NOT winlog.event_data.SubjectUserName LIKE "MSOL_*"
| KEEP @timestamp, host.name, winlog.event_data.SubjectUserName, winlog.event_data.SubjectDomainName
FROM logs-*
| WHERE event.code == "4769" AND winlog.event_data.TicketEncryptionType == "0x17" AND winlog.event_data.Status == "0x0"
| WHERE NOT winlog.event_data.ServiceName LIKE "*$" AND winlog.event_data.ServiceName != "krbtgt"
| STATS services = COUNT_DISTINCT(winlog.event_data.ServiceName), names = VALUES(winlog.event_data.ServiceName) BY winlog.event_data.TargetUserName, winlog.event_data.IpAddress, bucket = DATE_TRUNC(1 hour, @timestamp)
| WHERE services > 5
| SORT services DESC
Stage 5 · Lateral movementOne account or source reaching many hosts; admin-share writes
ADMIN$ or C$ with write access shows the copy itself (needs File Share object-access auditing on the targets).FROM logs-*
| WHERE event.code == "4624" AND winlog.event_data.LogonType IN ("3", "10")
| STATS targets = COUNT_DISTINCT(host.name), first = MIN(@timestamp), last = MAX(@timestamp) BY user.name, source.ip
| WHERE targets > 10
| SORT targets DESC
FROM logs-*
| WHERE event.code == "5145" AND winlog.event_data.ShareName IN ("\\\\*\\ADMIN$", "\\\\*\\C$") AND winlog.event_data.AccessMask IN ("0x2", "0x6", "0x12019f")
| STATS targets = COUNT_DISTINCT(host.name), files = VALUES(winlog.event_data.RelativeTargetName) BY user.name, source.ip
| WHERE targets > 3
Stage 5 · Lateral movementRemote execution — service installs (7045), remote tasks (4698), WMI, WinRM and Impacket
wmiexec/smbexec leave a fixed cmd.exe /Q /c … \\127.0.0.1\ADMIN$ shape. Each hit is a target host for the isolation list.any where
(event.code == "7045" and (winlog.event_data.ServiceName regex~ "[a-z]{8}|[0-9a-f]{8,}|PSEXESVC|csexec.*|PAExec.*|RemComSvc" or winlog.event_data.ImagePath : ("*COMSPEC*", "*cmd.exe*", "*powershell*", "*ADMIN$*"))) or
(event.code == "4698") or
(event.category == "process" and event.type == "start" and
((process.parent.name : ("wmiprvse.exe", "wsmprovhost.exe") and process.name : ("cmd.exe", "powershell.exe", "pwsh.exe", "rundll32.exe", "mshta.exe")) or
(process.name : "cmd.exe" and process.command_line : "*/Q /c*" and process.command_line : ("*ADMIN$*", "*\\127.0.0.1\\*", "*2>&1*"))))
// Wazuh: data.win.system.eventID:7045 AND NOT data.win.eventdata.imagePath:"C:\\Program Files*"
Stage 6 · Recovery planesESXi and vCenter audit events; backup console logons, deletions and retention changes
ESXi arrives through the Filebeat/Elastic Agent syslog input as message with log.syslog.appname. Veeam ids are from the Veeam Backup & Replication Event Reference: 10050 restore point deleted, 23050 job settings updated, 23090 job deleted, 28200 repository deleted, 30200 scale-out repository deleted, 31400 user/group deleted, 40201 MFA disabled, 40204 MFA for user disabled, 42401 four-eyes authorisation disabled, 41800 attempt to delete backup failed; collect the Veeam Backup channel with the Windows integration (custom channel).
FROM logs-*
| WHERE message LIKE "*esx.audit.ssh.enabled*" OR message LIKE "*esx.audit.shell.enabled*" OR message LIKE "*esx.audit.lockdownmode.disabled*"
OR message LIKE "*esx.audit.account.loginfailures*" OR message LIKE "*esx.audit.account.locked*" OR message LIKE "*esx.audit.dcui.enabled*" OR message LIKE "*User root@*"
| KEEP @timestamp, host.name, log.syslog.appname, message
| SORT @timestamp ASC
FROM logs-*
| WHERE winlog.channel == "Veeam Backup" AND event.code IN ("10050", "23050", "23090", "28200", "30200", "31400", "40201", "40204", "42401", "41800")
| KEEP @timestamp, host.name, event.code, message
FROM logs-*
| WHERE event.code == "4624" AND winlog.event_data.LogonType IN ("2", "10") AND (host.name IN ("vbr01", "vbr-proxy01") OR user.name IN ("svc-veeam", "backup-admin"))
| STATS logons = COUNT(*), first = MIN(@timestamp) BY host.name, user.name, source.ip, winlog.event_data.LogonType
| SORT first ASC
FROM logs-*
| WHERE event.code IN ("4728", "4732", "4756") AND winlog.event_data.TargetUserName == "ESX Admins"
| KEEP @timestamp, host.name, winlog.event_data.SubjectUserName, winlog.event_data.MemberName
Stage 7 · ExfiltrationTransfer tools, destinations, archive staging and bytes out per host
conn log, is the number the breach notification needs.any where
(event.category == "process" and (process.name : ("rclone.exe", "megasync.exe", "megacmd.exe", "winscp.exe", "filezilla.exe", "restic.exe") or process.command_line : ("*rclone*", "*mega-put*", "*--transfers*", "*b2 upload*") or
(process.name : ("7z.exe", "7za.exe", "rar.exe", "winrar.exe") and process.command_line : "* a *" and process.command_line : ("* -p*", "* -hp*", "*-mhe*", "* -v*")))) or
(event.category == "network" and dns.question.name : ("*mega.nz", "*mega.io", "*mega.co.nz", "*dropbox.com", "*dropboxapi.com", "*pcloud.com", "*backblazeb2.com", "*backblaze.com", "*gofile.io", "*temp.sh", "*file.io", "*ufile.io", "*filetransfer.io", "*transfer.sh", "*put.io", "*storj.io", "*wasabisys.com"))
// bytes out per internal source over the dwell window (firewall or Zeek conn, ECS source.bytes)
FROM logs-*
| WHERE event.category == "network" AND source.bytes IS NOT NULL AND CIDR_MATCH(source.ip, "10.0.0.0/8") AND NOT CIDR_MATCH(destination.ip, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")
| STATS bytes_out = SUM(source.bytes), dests = COUNT_DISTINCT(destination.ip) BY source.ip
| EVAL gb_out = ROUND(bytes_out / 1073741824.0, 1)
| SORT bytes_out DESC
| LIMIT 20
Stage 8 · Impact preparationShadow-copy, boot-recovery and backup-catalog deletion; security services stopped; GPO changes
any where
(event.category == "process" and event.type == "start" and
((process.name : "vssadmin.exe" and process.command_line : ("*delete*shadows*", "*resize*shadowstorage*")) or
(process.name : "wmic.exe" and process.command_line : "*shadowcopy*delete*") or
(process.name : "bcdedit.exe" and process.command_line : ("*recoveryenabled*", "*bootstatuspolicy*")) or
(process.name : "wbadmin.exe" and process.command_line : "*delete*catalog*") or
process.command_line : ("*Set-MpPreference*Disable*", "*DisableRealtimeMonitoring*") or
(process.name : ("net.exe", "net1.exe", "sc.exe") and process.command_line : ("*stop*", "*config*", "*delete*") and process.command_line : ("*WinDefend*", "*Sense*", "*CSFalconService*", "*SentinelAgent*", "*veeam*", "*sql*", "*vss*", "*backup*")) or
(process.name : "taskkill.exe" and process.command_line : ("*sql*", "*veeam*", "*backup*")))) or
(event.code in ("5136", "5137") and winlog.event_data.ObjectClass == "groupPolicyContainer")
// Wazuh: data.win.eventdata.commandLine:*shadows* and data.win.system.eventID:1
Stage 9 · EncryptionMass file changes by one process; ransom notes
FROM logs-endpoint.events.file-*
| WHERE event.action IN ("rename", "modification")
| STATS files = COUNT(*) BY host.name, process.name, process.hash.sha256, bucket = DATE_TRUNC(5 minutes, @timestamp)
| WHERE files > 500
| SORT files DESC
FROM logs-endpoint.events.file-*
| WHERE event.action == "creation" AND file.name RLIKE "(?i)(readme|restore|recover|decrypt|how_to|how-to|!!!|_readme|unlock).*\\.(txt|html?|hta)"
| STATS hosts = COUNT_DISTINCT(host.name), notes = COUNT(*), writer = VALUES(process.name) BY file.name
Stage 10 · ExtortionRansom contact by mail
Needs a mail-gateway integration that maps to the ECS email.* fieldset (Mimecast, Proofpoint TAP and the Microsoft Defender XDR integration do); the Microsoft 365 audit integration alone does not carry inbound subjects.
FROM logs-*
| WHERE email.from.address IS NOT NULL
| WHERE email.from.address RLIKE ".*@(onionmail\\.org|proton\\.me|protonmail\\.com|tutanota\\.com|tuta\\.io|cock\\.li|mail2tor\\.com|skiff\\.com)"
OR email.subject RLIKE "(?i).*(your network|your files|encrypted|decrypt|negotiat|data leak|stolen|ransom|your company).*"
| KEEP @timestamp, email.from.address, email.to.address, email.subject, event.action
Act
Broad first, surgical once the scope is known. Collect before you stop anything; compromised hosts are rebuilt, not cleaned.
T+15–30 · IsolateIsolate every endpoint in scope in one call
Console (Elastic Defend): host › Take action › Isolate host. Wazuh ships no isolation script: an active-response script run through PUT /active-response has to be written and tested before you need it.
POST /api/endpoint/action/isolate
{"endpoint_ids": ["<endpoint_id>", "<endpoint_id>"], "comment": "IR case #<n>"}
T+15–30 · IsolateBlocklist the payload hash
Console: Artifacts › Blocklist tab (the Blocklist page before 9.4) › Add blocklist entry › hash, Assignment: Global.
T+15–30 · CollectCollect before you stop anything
d315c63d1ad4b89e03c7688b29169e977c2abc4559c23b9f6b2282f3c91a6c7d. Persistence: Get-PersistenceSnapshot.ps1 a9534865f9e8e7b1f5077b4e1cfd8c29e7e32f6e9e49a4918ce4f8fb713e64df on Windows, get-persistence-snapshot.sh 8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67 on Linux. upload pushes a file from your workstation to the host; execute runs it with cmd.exe (Windows) or bash (Linux); get-file brings the output back. execute and upload need the matching response-action privileges on the Kibana role.In the Elastic response console:
processes
get-file --path "C:\path\to\ransom-note.txt"
upload --file MagnetRESPONSEv172_Self_Extracting_Archive.exe --overwrite
execute --command "C:\Program Files\Elastic\Endpoint\state\<upload-path>\MagnetRESPONSEv172_Self_Extracting_Archive.exe /accepteula /unattended /output:C:\IR\magnet /capturesystemfiles /captureram /capturepagefile" --timeout 30m
upload --file Get-PersistenceSnapshot.ps1
execute --command "powershell -ExecutionPolicy Bypass -File <upload-path>\Get-PersistenceSnapshot.ps1 -Zip -OutputPath C:\IR\persistence"
get-file --path "C:\IR\persistence\<host>_<timestamp>.zip"
# Linux
upload --file get-persistence-snapshot.sh
execute --command "bash <upload-path>/get-persistence-snapshot.sh --zip -o /tmp/ir-persistence"
get-file --path "/tmp/ir-persistence.tar.gz"
T+15–30 · StopStop the running payload
kill-process --pid <pid>
Day 1–7 · ScopedRelease one endpoint at a time, after the eradication gate
POST /api/endpoint/action/unisolate
{"endpoint_ids": ["<endpoint_id>"], "comment": "IR case #<n>: rebuilt, gate met"}
Linux.* artifacts where a client runs (Linux and x86 NAS, not ESXi). ESXi logs live under /var/run/log on a ramdisk unless scratch is persistent or syslog is forwarded: copy them first, never reboot. Commands run as ssh host cmd do not appear in shell.log; only interactive shells do.Hunt
Stage 1 · Initial accessSSH and web-UI logins by source, first seen per account
auth.log and web/API sessions in hostd.log; vCenter records SSO logins in its audit log.# ESXi
grep -hE "Accepted (password|publickey)|session opened" /var/run/log/auth.log* | awk '{print $1, $0}' | sort | head -50
grep -hE "User [^ ]+@[0-9.]+ logged in|Rejected password" /var/run/log/hostd.log* | sort | uniq -c | sort -rn | head -30
# vCenter (VCSA)
grep -hE "UserLoginSessionEvent|logged in" /var/log/vmware/vpxd/vpxd.log* | head -50
ls -la /var/log/audit/sso-events/ && grep -hiE "login|authentication" /var/log/audit/sso-events/audit_events.log | tail -50
# Linux / NAS
journalctl _COMM=sshd --since "-30d" | grep -E "Accepted|Failed password" | awk '{print $NF, $(NF-3), $(NF-5)}' | sort | uniq -c | sort -rn
last -F -i | head -50
Velociraptor: Linux.Syslog.SSHLogin ; Linux.Sys.LastUserLogin
Stage 2 · FootholdPersistence and new binaries on the host
/etc/rc.local.d/local.sh, an unsigned VIB or a binary dropped in /tmp or a datastore. The snapshot script records every persistence location with hashes and package verification so you can diff against a clean host.# Linux / NAS: full persistence snapshot (read-only), then diff against a clean host of the same build
sudo ./get-persistence-snapshot.sh --zip -o /tmp/ir-persistence
sudo ./get-persistence-snapshot.sh --compare-to /path/to/clean/persistence.tsv -o /tmp/ir-diff
# ESXi: busybox has no bash; check the same locations by hand
cat /etc/rc.local.d/local.sh
esxcli software vib list | grep -viE "VMware|Dell|HPE|Lenovo|Cisco|Broadcom"
esxcli software acceptance get
find /tmp /var/tmp /scratch /vmfs/volumes -type f \( -perm -u+x -o -name "*.sh" -o -name "*.py" \) -mtime -14 ! -path "*/.sdd.sf/*" 2>/dev/null
cat /etc/ssh/keys-root/authorized_keys
Velociraptor: Linux.Sys.Crontab ; Linux.Sys.Services ; Linux.Ssh.AuthorizedKeys ; Linux.Sys.SUID ; Linux.Search.FileFinder (SearchFilesGlob=/tmp/**, MoreRecentThan=<window>)
Stage 3 · DiscoveryEnumeration commands in shell history and shell.log
esxcli vm process list, vim-cmd vmsvc/getallvms, df -h /vmfs/volumes) and on Linux maps the network. ESXi's shell.log keeps every interactive command with its world id; bash history survives unless the operator cleared it.# ESXi
grep -hE "vmsvc/getallvms|vm process list|esxcli storage|df |ls /vmfs|hostsvc/|esxcli system account|esxcli network" /var/run/log/shell.log* | head -50
grep -h "" /.ash_history 2>/dev/null
# Linux / NAS
for h in /root /home/*; do echo "== $h"; cat $h/.bash_history 2>/dev/null | grep -nE "nmap|masscan|arp -a|ip neigh|smbclient|rpcclient|ldapsearch|showmount|crontab|find / -perm" ; done
Velociraptor: Linux.Sys.BashHistory (SearchRegex=(?i)nmap|masscan|ldapsearch|smbclient|showmount)
Stage 4 · PrivilegeNew or changed root-equivalent accounts; sudo and credential files
Admin-role local account; via AD it is the ESX Admins group (CVE-2024-37085). On Linux it is a new UID-0 user, a sudoers entry or a readable /etc/shadow. Finding the account used tells you which credential rotation ends their access.# ESXi
esxcli system account list
esxcli system permission list
grep -hE "esx.audit.account|AddUser|SetPassword|UpdateUser" /var/run/log/hostd.log* /var/run/log/vobd.log* | head -30
esxcli system settings advanced list -o /Config/HostAgent/plugins/hostsvc/esxAdminsGroup
# Linux / NAS
awk -F: '$3==0 {print}' /etc/passwd
awk -F: '$2=="" {print $1" has no password"}' /etc/shadow
find /etc/sudoers /etc/sudoers.d -newermt "-14 days" -type f -exec ls -la {} \;
journalctl _COMM=sudo --since "-14d" | grep -vE "pam_unix|session (opened|closed)" | tail -50
Velociraptor: Linux.Sys.Users ; Linux.Sys.SUID
Stage 5 · Lateral movementLogins to this host from other internal hosts; outbound SSH from it
# ESXi: sessions by source address and world id
grep -hE "Accepted|session opened" /var/run/log/auth.log* | grep -oE "from [0-9.]+" | sort | uniq -c | sort -rn
grep -hE "scp|sftp-server|wget|curl" /var/run/log/shell.log* | head -30
esxcli network ip connection list | grep -E ":22 |:443 " | grep -v "0.0.0.0"
# Linux / NAS
journalctl _COMM=sshd --since "-14d" | grep Accepted | grep -oE "from [0-9.]+" | sort | uniq -c | sort -rn
ss -tnp state established '( dport = :22 or dport = :445 )'
grep -hE "ssh |scp |sftp " /root/.bash_history /home/*/.bash_history 2>/dev/null
Velociraptor: Linux.Network.NetstatEnriched ; Linux.Ssh.KnownHosts (hosts this box has connected to)
Stage 6 · Recovery planesSSH and shell enablement, lockdown changes, snapshot deletion and the backup appliance
vobd.log records every SSH, shell and lockdown change as an audit VOB; hostd.log and vCenter tasks record snapshot removal and VM reconfiguration; a Linux-based backup appliance records console logins in its own audit log and auth.log.# ESXi: audit VOBs (the authoritative record of SSH/shell/lockdown changes)
grep -hE "esx.audit.(ssh|shell|dcui).(enabled|disabled)|esx.audit.lockdownmode|esx.audit.account" /var/run/log/vobd.log*
grep -hE "enable_ssh|enable_esx_shell|lockdown_mode_exit|TSM-SSH|TSM\b" /var/run/log/shell.log* /var/run/log/hostd.log* | head -30
vim-cmd vimsvc/auth/lockdown_is_enabled
esxcli system settings advanced list -o /UserVars/ESXiShellTimeOut
# snapshots and VM state changes
grep -hE "RemoveSnapshot|RemoveAllSnapshots|snapshot.remove|VmPoweredOffEvent|vmsvc/power.off|ReconfigVM" /var/run/log/hostd.log* /var/log/vmware/vpxd/vpxd.log* 2>/dev/null | head -50
for v in $(vim-cmd vmsvc/getallvms | awk 'NR>1{print $1}'); do echo "== VM $v"; vim-cmd vmsvc/snapshot.get $v | head -5; done
# Veeam hardened repository / Linux backup appliance
grep -hE "Accepted|sudo:" /var/log/auth.log* /var/log/secure* | tail -50
ls -la --time-style=full-iso /backups 2>/dev/null | head; find /backups -type f -mmin -1440 -name "*.vbk" -o -mmin -1440 -name "*.vib" 2>/dev/null | head
Velociraptor (Linux appliance): Linux.Sys.LogGrep (TargetGlob=/var/log/auth.log*, GrepRegex=Accepted|sudo)
Stage 7 · ExfiltrationTransfer tools and bulk reads from datastores and NAS shares
# Linux / NAS
find / -name rclone.conf -o -name ".megarc" -o -name "mega-cmd*" 2>/dev/null | grep -v "^/proc"
ps -eo pid,user,lstart,cmd | grep -E "rclone|rsync|scp|sftp|curl -T|mega-put|restic" | grep -v grep
grep -hE "rclone|rsync .*@|scp .*@|curl -T|mega-put" /root/.bash_history /home/*/.bash_history 2>/dev/null
cat /proc/net/dev | awk 'NR>2 {printf "%-10s rx %.1f GB tx %.1f GB\n", $1, $2/1073741824, $10/1073741824}'
# ESXi
grep -hE "scp|sftp|wget|curl|nc " /var/run/log/shell.log* | head -30
esxcli network ip connection list | grep -vE ":(22|443|902|8000|8182) " | grep -E "ESTABLISHED"
Velociraptor: Linux.Search.FileFinder (SearchFilesGlob=/**/rclone.conf, Upload_File=Y) ; Linux.Sys.Pslist
Stage 8 · Impact preparationAcceptance level lowered, execInstalledOnly off, syslog cut, VMs killed
execInstalledOnly first, then kills the VMs so the disks are not locked. A syslog change hides what follows. Each of these is minutes before encryption.esxcli software acceptance get
esxcli system settings kernel list -o execInstalledOnly
esxcli system syslog config get
grep -hE "acceptance set|execInstalledOnly|syslog config set|vm process kill|power.off|kill -9" /var/run/log/shell.log* /var/run/log/hostd.log* | head -30
esxcli vm process list | grep -c "World ID" ; vim-cmd vmsvc/getallvms | wc -l
# Linux / NAS: snapshot and backup tooling disabled
systemctl list-units --type=service --state=inactive,failed | grep -iE "backup|snap|veeam|restic|borg|rsnapshot"
journalctl --since "-3d" | grep -iE "stopped .*(backup|snapshot)|Removed snapshot|synosnapshot|btrfs subvolume delete|zfs destroy" | tail -30
Stage 9 · EncryptionEncrypted VMDKs and shares, ransom notes, the encryptor process
.vmdk, .vmx and .vmsn files; it is still running when you find it, from /tmp or a datastore. The binary and the note identify the variant and give the extortion channel.# ESXi
find /vmfs/volumes -maxdepth 3 -type f \( -iname "*readme*" -o -iname "*how_to*" -o -iname "*recover*" -o -iname "*decrypt*" -o -iname "*.txt" \) -mmin -1440 2>/dev/null | head
find /vmfs/volumes -maxdepth 3 -type f -newer /var/run/log/vobd.log ! -name "*.vmdk" ! -name "*.log" 2>/dev/null | head -50
ls /vmfs/volumes/*/*/ | grep -vE "\.(vmdk|vmx|vmxf|vmsd|nvram|log|vswp|vmsn|vmem)$" | sort | uniq -c | sort -rn | head
ps -c | grep -vE "vmx|hostd|vpxa|rhttpproxy|sshd|busybox|sfcb|vobd|dcui|nfsgssd|net-|vsan|clomd|epd|iofilter|lwsmd|vmware-|vmkeventd|vmsyslogd|ntpd|smartd|slpd|vmtoolsd" | head
esxcli storage filesystem list
# Linux / NAS
find / -xdev -type f \( -iname "*readme*" -o -iname "*how_to*" -o -iname "*decrypt*" \) -mmin -1440 2>/dev/null | head
ps -eo pid,user,pcpu,lstart,cmd --sort=-pcpu | head -15
lsof -p <pid> | head ; cp /proc/<pid>/exe /tmp/ir/encryptor.bin && sha256sum /tmp/ir/encryptor.bin
Velociraptor: Linux.Search.FileFinder (SearchFilesGlob=/vmfs/volumes/**/*.{txt,html}, MoreRecentThan=<window>) ; Linux.Triage.ProcessMemory (processPid=<pid>)
Stage 10 · ExtortionRead the contact channel from the note on the datastore
f=$(find /vmfs/volumes -maxdepth 3 -type f \( -iname "*readme*" -o -iname "*how_to*" -o -iname "*recover*" \) -mmin -1440 2>/dev/null | head -1)
sha256sum "$f"; cp "$f" /tmp/ir/ ; grep -oiE "[a-z2-7]{16,56}\.onion[^ ]*|tox[: ]+[0-9A-F]{76}|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+|id[: ]+[A-Za-z0-9-]{8,}" "$f"
Act
These hosts have no EDR. Copy the logs first, snapshot before you touch anything, then close the shell and rotate every credential that reaches the management plane. Compromised hosts are rebuilt, not cleaned.
T+15–30 · CollectCopy the logs and take a memory-inclusive snapshot before anything else
vm-support bundles every log and configuration file with a manifest, and a VM snapshot with memory preserves the running state of each guest while the host stays up. On Linux and NAS, get-persistence-snapshot.sh 8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67 records every persistence location with hashes and package verification; AVML or LiME takes the memory. There is no RAM acquisition for the ESXi kernel itself; the per-VM snapshot is the memory evidence.Everything below is read-only or additive. Write to a datastore or USB that the encryptor has not touched, and record the SHA256 of each output in the case.
# ESXi
vm-support -w /vmfs/volumes/<clean-datastore>/ir # full log and config bundle, with manifest
tar czf /vmfs/volumes/<clean-datastore>/ir/logs-$(hostname)-$(date -u +%Y%m%dT%H%MZ).tgz /var/run/log /etc/rc.local.d /etc/ssh
for v in $(vim-cmd vmsvc/getallvms | awk 'NR>1{print $1}'); do vim-cmd vmsvc/snapshot.create $v "IR-<case>" "pre-response, with memory" 1 0; done
sha256sum /vmfs/volumes/<clean-datastore>/ir/*
# Linux / NAS
mkdir -p /tmp/ir && curl -fsSLo /tmp/ir/gps.sh https://<evidence-server>/ir/get-persistence-snapshot.sh && echo "8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67 /tmp/ir/gps.sh" | sha256sum -c && sudo bash /tmp/ir/gps.sh --zip -o /tmp/ir/persistence
sudo ./avml /tmp/ir/memory-$(hostname).lime && sha256sum /tmp/ir/memory-*.lime
sudo tar czf /tmp/ir/logs-$(hostname).tgz /var/log /etc/cron* /etc/systemd /etc/ssh /root/.ssh /home/*/.ssh
Velociraptor: Linux.Sys.BashShell (Command=<the lines above>) ; Linux.Search.FileFinder (SearchFilesGlob=/tmp/ir/**, Upload_File=Y)
T+15–30 · StopFreeze the encryptor, keep the binary
kill -STOP halts the process without destroying its memory, which may still hold the key; copy the binary out of /proc first because many encryptors delete themselves from disk. Kill it only after memory is captured.# ESXi (busybox): find the writer, freeze it, keep the binary
ps -c | grep -vE "vmx|hostd|vpxa" | head
kill -STOP <pid>
cp /proc/<pid>/exe /vmfs/volumes/<clean-datastore>/ir/encryptor.bin 2>/dev/null; sha256sum /vmfs/volumes/<clean-datastore>/ir/encryptor.bin
# Linux / NAS
kill -STOP <pid>; cp /proc/<pid>/exe /tmp/ir/encryptor.bin; sha256sum /tmp/ir/encryptor.bin
T+15–30 · IsolateClose the shell, re-enter lockdown, cut the management path
shell.log and you want the attacker's entries intact first. The NAS admin interface is isolated at the firewall or switch, not on the appliance.vim-cmd hostsvc/disable_ssh
vim-cmd hostsvc/disable_esx_shell
vim-cmd hostsvc/disable_remote_tsm
vim-cmd vimsvc/auth/lockdown_mode_enter
esxcli network firewall ruleset set --ruleset-id sshServer --enabled false
esxcli system settings advanced set -o /Config/HostAgent/plugins/hostsvc/esxAdminsGroup -s "IR-no-such-group"
# Linux / NAS: Velociraptor Linux.Remediation.Quarantine on labelled clients; otherwise the switch port
Hour 1–24 · Kill authRotate root, vCenter SSO and every account that reaches the management plane
vdcadmintool on the VCSA, remove the AD group mapping, and change the NAS admin and every service account (backup proxy, monitoring) that can log in to these hosts. Then verify the new credentials work before the old sessions are killed, from a known-good workstation.# every ESXi host
esxcli system account set -i root -p '<new>' -c '<new>'
esxcli system account list
# VCSA: reset the SSO administrator
/usr/lib/vmware-vmdir/bin/vdcadmintool # option 3: Reset account password, [email protected]
# Linux / NAS
passwd root; for u in $(awk -F: '$3>=1000 && $1!="nobody"{print $1}' /etc/passwd); do passwd -e $u; done
find / -xdev -name authorized_keys -newermt "-<dwell> days" 2>/dev/null
Day 1–7 · ScopedRebuild the host, restore VMs from a clean point, then re-enable management one host at a time
esxcli software acceptance set --level=PartnerSupported
esxcli system settings kernel set -s execInstalledOnly -v TRUE
esxcli system syslog config set --loghost='tcp://<siem>:514' && esxcli system syslog reload
vim-cmd vimsvc/auth/lockdown_is_enabled
By Scenario: What Changes
1. Encryption in Progress
Variation: Speed beats precision. Run Kill Internet and isolate segments now and accept the business outage — every minute is more encrypted data. Scope after the spread stops. Template: ransomware-resilience.
2. Discovered Before Encryption
Variation: Don't tip them off one host at a time. Killing a single beacon tells the operator they are found while their other footholds stay live, and many then encrypt early. Contain at least as wide as they could be, in one move: cut estate or segment egress, revoke all sessions and disable the accounts seen, then map every foothold, account and persistence mechanism behind that cut — minutes to hours, not days. Go surgical only once the scope is known. Exception: anything destructive in progress — encryption starting, backup deletion, payload staging (GPO changes, PsExec copies to many hosts) — means contain immediately, at the widest level, scoped or not. Template: ransomware-pre-encryption.
3. Data Theft Without Encryption
Variation: Backups do not help: this is a confidentiality incident. First validate the claim — do the samples come from us, and from which system? Then scope what left and whose data it is; the notification clocks started when you became aware. Cut the remote-access tool's path and every other foothold in one pass: block its egress, revoke the user's sessions (a reset alone leaves tokens valid), reset the credentials, and rebuild the hosts it ran on rather than uninstalling it. Nobody talks to the extortionist except through counsel or an insurer-approved negotiator. Template: ransomware-data-extortion.
4. Hypervisor / NAS Encryption
.vmdk files renamed or unreadable, SSH enabled on ESXi outside a change, lockdown mode disabled, VIB acceptance level changed, NAS shares encrypted.Variation: No EDR runs here, so the host logs are the evidence — collect them before anything else, and do not reboot: without persistent scratch or remote syslog, ESXi logs are gone after a restart. vm-support bundles every log with a manifest; a VM snapshot with memory preserves each guest's running state. Note that commands run as ssh host cmd never reach shell.log, so an empty shell log does not mean nothing ran — auth.log and the VOB audit records in vobd.log are the authoritative trail. Work out how they got in (vCenter credentials, the AD "ESX Admins" group — CVE-2024-37085), then freeze the encryptor, rotate root and vCenter SSO, disable SSH and the shell and re-enter lockdown. The full command set, stage by stage, is the Linux / ESXi tab under Hunt & Act; on Linux servers and x86 NAS run get-persistence-snapshot.sh 8fe386a7d6ebd50225b04ad83581ee3e5d504a10a4263253b5a11b8a61eeca67 and the Velociraptor Linux.Sys.Crontab, Linux.Sys.Services, Linux.Ssh.AuthorizedKeys, Linux.Syslog.SSHLogin and Linux.Search.FileFinder artifacts; ESXi has no client, so it is shell only. Template: ransomware-hypervisor.
# copy before anything else
vm-support -w /vmfs/volumes/<clean-datastore>/ir
tar czf /vmfs/volumes/<clean-datastore>/ir/logs.tgz /var/run/log /etc/rc.local.d /etc/ssh
# then triage
esxcli system account list
esxcli software acceptance get
esxcli software vib list
esxcli system settings kernel list -o execInstalledOnly
grep -hE "esx.audit.(ssh|shell).enabled|lockdownmode" /var/run/log/vobd.log*
# then close the door
vim-cmd hostsvc/disable_ssh ; vim-cmd hostsvc/disable_esx_shell ; vim-cmd vimsvc/auth/lockdown_mode_enter
5. Backups Destroyed or Tampered
Variation: Treat it as the pre-encryption signal it usually is: encryption tends to follow. Sever backup admin from production and change its credentials now, then run Scenario 2's scoping. If no clean copy survives, tell the board the same day — it changes the recovery estimate and the ransom question. Handled with the ransomware-pre-encryption template, or the ransomware-resilience template if encryption has already started.
6. Cloud / SaaS Data Held Hostage
Variation: Containment is identity, not network: revoke the compromised user's or app's sessions and keys (Identity Breach Response for Microsoft 365, Cloud Incident Response for AWS, Azure and GCP storage). Recovery comes from versioning: OneDrive and SharePoint restore to an earlier point in time (up to 30 days), S3 from object versions or Object Lock. Handled with the identity-breach-response template plus a Recovery task for the restore.
aws iam update-access-key --user-name <user> --access-key-id <key> --status Inactive
aws s3api list-object-versions --bucket <bucket> --prefix <path> --max-items 20
Extortion: Negotiation and the Leak Site
# copies only, never the originals
robocopy \\fs01\share\encrypted D:\decrypt-test /E /COPY:DAT /R:1 /W:1
Get-FileHash D:\decrypt-test\sample\* | Export-Csv before.csv
# run the decryptor on D:\decrypt-test only, then
Get-FileHash D:\decrypt-test\sample\* | Export-Csv after.csv
Compare-Object (Import-Csv known-good.csv) (Import-Csv after.csv) -Property Hash