Skip to content

Hunt 005 — Ransomware Precursors

Objective: Detect the pre-encryption activity window of a ransomware attack — shadow copy deletion, mass file staging, backup silence, defense evasion, and large outbound transfers — before the encryption payload detonates.

Frequency: Weekly (consider running Q1, Q2, Q6 daily given the criticality)

Prerequisites:

  • SecurityEvent receiving EventID 4688 (process creation with command line), 4698, 4702, 7045, 1102
  • PowerShell Script Block Logging (EventID 4104)
  • Syslog or CommonSecurityLog receiving Palo Alto traffic logs
  • CortexXDR_CL receiving behavioral alerts (or equivalent EDR forwarding)
  • AzureActivity for detecting changes to Recovery Services Vaults and backup policies
  • Twingate_CL for outbound session data volume (if available in log schema)

The Ransomware Pre-Detonation Window

Most ransomware operators do not encrypt immediately after initial access. The median time from first foothold to encryption is 24–72 hours in modern human-operated ransomware (LockBit, BlackCat/ALPHV, Akira, Black Basta). That window is your detection opportunity.

What happens in that window, roughly in order:

  1. Initial access — phishing, VPN credential abuse, RDP brute force (already covered in hunt-002 and hunt-003)
  2. Reconnaissance — AD enumeration, network scanning, identifying backup infrastructure
  3. Credential theft — LSASS dump, Kerberoasting (covered in hunt-004)
  4. Lateral movement — spreading to additional hosts, targeting domain controllers and backup servers specifically
  5. Exfiltration staging — large outbound transfers (double extortion — they steal before they encrypt)
  6. Defense evasion — disabling AV, EDR, clearing logs, disabling backup agents
  7. Shadow copy deletion — vssadmin/wmic/PowerShell to destroy VSS snapshots
  8. Encryption — detonation across all reachable hosts simultaneously

If you catch activity in steps 3–7, you have a real chance to contain before encryption. Once encryption starts, the incident is a recovery operation, not a prevention operation.

Operational implication: The queries below should trigger same-day investigation if they return results. Do not batch these for weekly review — run Q1, Q2, and Q6 daily as a quick check, and do the full hunt weekly.


Queries

Q1 — Shadow Copy Deletion Attempts (VSS Destroy Commands)

The single most reliable ransomware pre-encryption indicator. Any process deleting shadow copies outside a known backup maintenance window is an immediate hit.

kql
SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID == 4688
| where CommandLine has_any (
    "vssadmin delete shadows",
    "vssadmin Delete Shadows",
    "wmic shadowcopy delete",
    "wmic shadowcopy where",
    "Get-WmiObject Win32_Shadowcopy",
    "gwmi Win32_ShadowCopy",
    "Remove-WmiObject",
    "bcdedit /set {default} recoveryenabled No",
    "bcdedit /set {default} bootstatuspolicy ignoreallfailures",
    "wbadmin delete catalog",
    "wbadmin DELETE BACKUP"
    )
    or (CommandLine has "powershell" and CommandLine has_any ("-EncodedCommand", "-enc", "-e ")
        and CommandLine has_any ("shadow", "vss", "backup"))
| extend Actor = SubjectAccount
| extend Host = toupper(tostring(split(Computer, ".")[0]))
| extend ProcessName = tostring(split(NewProcessName, "\\")[-1])
| project TimeGenerated, Host, Actor, ProcessName, CommandLine, ParentProcessName
| order by TimeGenerated desc

Look for: Any result. Shadow copy deletion by vssadmin or wmic should never happen outside a declared backup maintenance window, and even then it should be done by a known backup service account, not a user account. A result here is a CRITICAL incident. Also look for the bcdedit commands disabling recovery — these are the "prevent Windows recovery environment from helping you" commands ransomware uses in sequence with VSS deletion.


Q2 — Encoded PowerShell Commands Followed by Suspicious Activity

Ransomware deployment scripts frequently use -EncodedCommand to obfuscate their payload. This query finds encoded PowerShell that runs within 30 minutes of any of the other precursor behaviors.

kql
SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID == 4688
| where NewProcessName endswith "powershell.exe"
    or NewProcessName endswith "pwsh.exe"
| where CommandLine has_any ("-EncodedCommand", "-enc ", "-e ", "-ec ")
    and CommandLine !has "Get-Help"   // filter common false positive
| extend Actor = SubjectAccount
| extend Host = toupper(tostring(split(Computer, ".")[0]))
| extend DecodedPreview = base64_decode_tostring(
    extract(@"-[eE][nNcC]{0,8}\s+([A-Za-z0-9+/=]+)", 1, CommandLine))
| project TimeGenerated, Host, Actor, CommandLine, DecodedPreview, ParentProcessName
| order by TimeGenerated desc

Look for: Any encoded PowerShell where DecodedPreview contains suspicious keywords (shadow, vss, encrypt, ransom, disable, stop-service, net stop). Also look at ParentProcessName — encoded PowerShell spawned from Word, Excel, Outlook, or a browser process indicates a phishing-chain execution. Encoded PowerShell spawned from cmd.exe or wscript.exe is also suspicious. Encoded PowerShell from a system management tool like ConfigMgr or RMM can be benign but still worth decoding and reviewing.


Q3 — Mass File Rename or Deletion (Bulk Staging / Pre-Encryption Test)

Attackers sometimes test encryption on a small directory before full detonation. Ransomware that renames files adds an extension; this shows in FIM or, if endpoint has file monitoring, in Cortex. Also look for mass deletion as a staging cleanup.

kql
// If you have Cortex forwarding file events to CortexXDR_CL:
CortexXDR_CL
| where TimeGenerated > ago(7d)
| where event_type_s == "FILE" or action_file_name_s != ""
| where alert_name_s has_any ("mass", "encrypt", "rename", "ransomware",
                               "file modification", "suspicious file")
    or (action_file_name_s matches regex @"\.(locked|encrypted|enc|ransom|[a-z]{4,8})$"
        and action_file_name_s !endswith ".docx"
        and action_file_name_s !endswith ".xlsx"
        and action_file_name_s !endswith ".pdf"
        and action_file_name_s !endswith ".log")
| summarize
    Count = count(),
    Files = make_set(action_file_name_s, 20),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by endpoint_name_s, actor_process_image_name_s, bin(TimeGenerated, 10m)
| where Count >= 10
| project TimeWindow = TimeGenerated, Host = endpoint_name_s,
          Process = actor_process_image_name_s, Count, Files, FirstSeen, LastSeen
| order by Count desc

Look for: 10+ file modifications in a 10-minute window from a non-standard process (not backup software, not Word/Excel auto-save). Any files being renamed with a uniform new extension (the ransomware extension) is a detonation-in-progress indicator — immediate response, not investigation. Also look for processes with random/generated names (8-character hex strings are a Cobalt Strike artifact) doing file operations.


Q4 — Backup Agent Silence — Veeam Job Failures / Missing Heartbeat

Ransomware commonly targets backup infrastructure. This query looks for Veeam-related events disappearing from Syslog or event logs, or explicit job failure events.

kql
// Check for Veeam service events — should be regular in Syslog or SecurityEvent
let VeeamExpectedSources = dynamic(["VEEAM-BACKUP", "VeeamBackup"]);
let LastSeenVeeam = Syslog
| where TimeGenerated > ago(7d)
| where ProcessName has_any (VeeamExpectedSources)
    or HostName has "veeam"
    or SyslogMessage has "Veeam"
| summarize LastEvent = max(TimeGenerated);
let HoursSinceVeeam = datetime_diff('hour', now(), toscalar(LastSeenVeeam));
// Flag if Veeam hasn't been seen in more than 24 hours
print
    LastVeeamEvent = toscalar(LastSeenVeeam),
    HoursSinceLastEvent = HoursSinceVeeam,
    Status = iff(HoursSinceVeeam > 24, "ALERT — Veeam silence exceeds 24h", "OK")

Companion query — AzureActivity for backup policy changes:

kql
AzureActivity
| where TimeGenerated > ago(7d)
| where OperationNameValue in (
    "MICROSOFT.RECOVERYSERVICES/VAULTS/BACKUPPOLICIES/WRITE",
    "MICROSOFT.RECOVERYSERVICES/VAULTS/BACKUPPOLICIES/DELETE",
    "MICROSOFT.RECOVERYSERVICES/VAULTS/BACKUPFABRICS/PROTECTIONCONTAINERS/PROTECTEDITEMS/DELETE",
    "MICROSOFT.RECOVERYSERVICES/VAULTS/WRITE"
    )
| where ActivityStatusValue == "Success"
| project TimeGenerated, Caller, OperationNameValue, ResourceGroup,
          SubscriptionId, Properties
| order by TimeGenerated desc

Look for: On the first query — any gap over 24 hours in Veeam telemetry is suspicious. Veeam logs continuously during normal operation. On the second query — any backup policy deletion, protected item deletion, or vault modification outside a CM window is a hit. Attackers who compromise backup servers disable the software or delete policies before detonating ransomware in the production environment.


Q5 — Defense Evasion — Log Clearing (EventID 1102) and Cortex Silence

EventID 1102 means the Security audit log was cleared. Combined with Cortex XDR returning zero findings while other anomalies are present — this is the "attacker is covering tracks" pattern.

kql
// Part A: Log clearing events
SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID == 1102
| extend Actor = SubjectAccount
| extend Host = toupper(tostring(split(Computer, ".")[0]))
| project TimeGenerated, Host, Actor, Activity, Computer
| order by TimeGenerated desc
kql
// Part B: Cortex finding silence while other agents are elevated
// Run this after checking if SecurityAgent has raised any HIGH/CRITICAL incidents
// A zero-finding Cortex run while other detection is active = possible EDR bypass
CortexXDR_CL
| where TimeGenerated > ago(24h)
| summarize AlertCount = count(), LastAlert = max(TimeGenerated)
| extend HoursSinceLastAlert = datetime_diff('hour', now(), LastAlert)
| extend Status = iff(AlertCount == 0 or HoursSinceLastAlert > 8,
                      "ALERT — Cortex silence", "OK")
| project AlertCount, LastAlert, HoursSinceLastAlert, Status

Look for: Part A — any 1102 event is a CRITICAL indicator. The only legitimate reason to clear the security log is end-of-life system decommission, which would have a CM ticket. Attackers clear logs immediately before or after lateral movement to reduce forensic evidence. Part B — Cortex returning zero findings for 8+ consecutive hours while the environment has active network traffic and sign-ins is either an EDR deployment issue or an EDR bypass. Cross-reference with hunt-005 as a whole: Cortex silence + log clearing together = ransomware pre-detonation phase.


Q6 — Large Outbound Data Transfers (Double Extortion Staging)

Ransomware groups exfiltrate data before encrypting — this is the "double extortion" model. Large transfers to external IPs outside business hours are the signal.

kql
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where DeviceVendor == "Palo Alto Networks"
| where DestinationIP !startswith "10."
    and DestinationIP !startswith "172."
    and DestinationIP !startswith "192.168."
    and DestinationIP !startswith "127."
| where SentBytes > 50000000   // 50MB+ per session — tune based on your baseline
| extend SourceHost = SourceHostName
| extend DestPort = DestinationPort
| extend AppID = tostring(AdditionalExtensions)
| summarize
    TotalBytesOut = sum(SentBytes),
    SessionCount = count(),
    DestIPs = make_set(DestinationIP, 10),
    DestIPCount = dcount(DestinationIP),
    Ports = make_set(DestinationPort, 10),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by SourceIP, bin(TimeGenerated, 1h)
| where TotalBytesOut > 500000000   // 500MB+ in an hour from one source
| project TimeWindow = TimeGenerated, SourceIP, TotalBytesOut,
          TotalGB = round(TotalBytesOut / 1073741824.0, 2),
          SessionCount, DestIPCount, DestIPs, Ports, FirstSeen, LastSeen
| order by TotalBytesOut desc

Look for: Any single internal host generating 500MB+ outbound in an hour to external IPs. Known legitimate large transfers: Azure Backup (but this goes to Azure, not arbitrary IPs), Windows Update (WSUS or direct), software deployment. Flag any transfer to IPs that are not major cloud providers (AWS/Azure/Cloudflare/Akamai) in high volumes. Tools like RClone, MEGAsync, and custom Go/C# loaders appear as generic HTTPS (port 443) — the volume is the indicator when you can't inspect the App-ID.


Q7 — New Scheduled Tasks and Services Created in Rapid Succession

Ransomware establishes persistence via scheduled tasks and services to survive a reboot and to ensure the encryption binary runs across all hosts. Multiple new scheduled tasks or services in a short window from the same source is the staging indicator.

kql
SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID in (4698, 4702, 7045)
    // 4698: scheduled task created, 4702: task modified, 7045: new service
| extend Actor = SubjectAccount
| extend Host = toupper(tostring(split(Computer, ".")[0]))
| extend EventType = case(
    EventID == 4698, "ScheduledTask_Created",
    EventID == 4702, "ScheduledTask_Modified",
    EventID == 7045, "Service_Installed",
    "Unknown")
| extend TaskOrServiceName = coalesce(
    tostring(parse_json(EventData).TaskName),
    tostring(parse_json(EventData).ServiceName),
    TaskName)
| summarize
    Count = count(),
    EventTypes = make_set(EventType, 5),
    Names = make_set(TaskOrServiceName, 20),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by Actor, Host, bin(TimeGenerated, 30m)
| where Count >= 3
    and Actor !startswith "svc-"
    and Actor !startswith "SYSTEM"
    and Actor !startswith "NT AUTHORITY"
| project TimeWindow = TimeGenerated, Host, Actor, Count,
          EventTypes, Names, FirstSeen, LastSeen
| order by Count desc

Look for: A non-service, non-SYSTEM account creating 3+ scheduled tasks or services in 30 minutes. Legitimate software deployment creates tasks, but it uses SYSTEM or a deployment service account. A user account or a compromised account creating multiple tasks in rapid succession is staging behavior. Look at the Names field — random-looking names (hex strings, generic names like "Windows Update Helper", "MicrosoftUpdateTask") are red flags. Legitimate Windows tasks have specific, descriptive names.


Q8 — Application Whitelisting Bypass Indicators (Living-off-the-Land Staging)

Ransomware increasingly uses LOLBins (certutil, regsvr32, mshta, wscript, bitsadmin) to avoid triggering AV signatures. This query catches the download-and-execute pattern.

kql
SecurityEvent
| where TimeGenerated > ago(7d)
| where EventID == 4688
| where NewProcessName has_any ("certutil.exe", "regsvr32.exe", "mshta.exe",
                                 "wscript.exe", "cscript.exe", "bitsadmin.exe",
                                 "runscripthelper.exe", "schtasks.exe", "at.exe",
                                 "esentutl.exe", "expand.exe", "extrac32.exe",
                                 "findstr.exe", "ieexec.exe", "makecab.exe",
                                 "msiexec.exe", "odbcconf.exe", "replace.exe",
                                 "rpcping.exe")
| where CommandLine has_any ("http", "ftp", "\\\\", "-decode", "-urlcache",
                              "-split", "/transfer", "regsvr32 /s /n /u /i:http",
                              "scrobj.dll")
| extend Actor = SubjectAccount
| extend Host = toupper(tostring(split(Computer, ".")[0]))
| extend ProcessName = tostring(split(NewProcessName, "\\")[-1])
| project TimeGenerated, Host, Actor, ProcessName, CommandLine, ParentProcessName
| order by TimeGenerated desc

Look for: certutil.exe downloading a file from the internet (-urlcache -split -f http), mshta.exe executing a remote script, regsvr32.exe loading a remote COM scriptlet, or bitsadmin.exe transferring a file from an external URL. These are the standard LOLBin download-and-execute chains used to pull ransomware binaries after initial access. Any match here warrants immediate investigation — false positive rate is very low for the URL-fetching argument patterns.


Triage Guidance

Expected / Benign:

  • vssadmin list shadows (read-only — no "delete" keyword) run by backup software or monitoring tools
  • Encoded PowerShell from a known RMM tool (NinjaRMM, ConnectWise) — verify the base64 decodes to a known management task
  • Large outbound transfers to Azure blob storage endpoints (backup workload)
  • Scheduled task creation from SYSTEM or a known deployment account with a CM ticket covering the window
  • New service installs from Windows Update or a vetted software deployment

Confirmed Hits (no investigation phase — immediate response):

  • Any vssadmin delete shadows or wmic shadowcopy delete command — stop everything and respond
  • EventID 1102 (log cleared) with no corresponding decommission CM ticket
  • 500MB+ outbound from a workstation (not a server) to non-cloud-provider IP
  • Cortex EDR silence + log clearing occurring within the same 4-hour window
  • Multiple new scheduled tasks with random/suspicious names from a non-SYSTEM account
  • Encoded PowerShell that decodes to shadow copy deletion or ransomware-related keywords

Borderline (investigate within same business day, don't wait for next hunt cycle):

  • Encoded PowerShell from an unknown parent — decode and review before escalating
  • Veeam silence for 12–24 hours — could be a Veeam service issue or infrastructure problem; check the Veeam console before raising an incident
  • 200–500MB outbound to an unfamiliar IP — identify the IP's ASN and the source process before escalating
  • New service from SYSTEM that has an unusual name — check if it matches a Windows Update KB

Immediate Response If Precursors Found

The pre-detonation window is real and it is finite. These steps assume you have 24–72 hours before encryption if precursors are active. Act immediately.

  1. Determine the blast radius first. Before isolating anything, run Q1 (VSS deletion) and Q7 (new services) across all hosts to identify which systems are staging the attack. Isolating one host while the others continue staging is worse than a coordinated response.

  2. Isolate in one action. Use Cortex XDR group quarantine to isolate all confirmed and suspected hosts simultaneously. Piecemeal isolation tips off the attacker (if human-operated) and causes them to accelerate to encryption.

  3. Disconnect backup infrastructure. If the Veeam server or Azure Recovery Services Vault has not yet been targeted, immediately revoke all access to backup infrastructure from non-backup accounts. If the Veeam server is a physical or VM host, take it off the network now. Protecting your backups is the difference between a recovery operation and a ransom payment.

  4. Preserve the VSS snapshots that exist. On any host not yet isolated, run vssadmin list shadows to document what snapshots exist. Do not delete them — they are evidence and recovery resources.

  5. Open a CRITICAL incident in SecOps (secops.bedrockcybersecurity.org) with source_system = PROD. Set severity = CRITICAL. Note in the incident description whether you believe detonation has occurred or is imminent.

  6. Contact Arctic Wolf immediately. They have act-first authority. Brief them on what you found, which hosts are affected, and whether you've isolated. They can extend forensic coverage to endpoints outside your log visibility.

  7. Do not attempt to decrypt or recover yet. Recovery before full containment reintroduces the threat. Full isolation first, then forensics, then recovery.

  8. Follow kill-chain-incident-response.md from the Ransomware Precursor response path.

  9. If encryption has already started on any host: do not power off (destroys decryption key if it's in memory). Disconnect from the network via the switch/firewall, not via the OS (attacker may have access to the OS layer). Call Arctic Wolf and document the exact time you observed file encryption starting — this timestamp matters for insurance and forensics.

Internal use only — Cirius Group