Skip to content

Endpoint and Cross-Domain

These hunts use Defender XDR event tables ingested into Microsoft Sentinel. Run the first query broadly, inspect the named fields, and use the second query to expand the same behavior across process, file, network, logon, and persistence evidence.

First-seen public communication or remote-access software

Surface process identities that were absent from the prior 14 days but made public connections during the last day.

Run first — first-seen public communication
let baseline = 14d;
let recent = 1d;
let Known =
    DeviceNetworkEvents
    | where TimeGenerated between (ago(baseline) .. ago(recent))
    | where RemoteIPType == "Public"
    | extend Signer=iff(isempty(InitiatingProcessVersionInfoCompanyName),
        "Unsigned", InitiatingProcessVersionInfoCompanyName)
    | summarize by InitiatingProcessSHA1, InitiatingProcessFileName, Signer;
DeviceNetworkEvents
| where TimeGenerated > ago(recent)
| where RemoteIPType == "Public"
| where isnotempty(InitiatingProcessSHA1)
| extend Signer=iff(isempty(InitiatingProcessVersionInfoCompanyName),
    "Unsigned", InitiatingProcessVersionInfoCompanyName)
| join kind=leftanti Known on InitiatingProcessSHA1,
    InitiatingProcessFileName, Signer
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated),
    Connections=count(), Devices=dcount(DeviceId),
    RemoteIPs=make_set(RemoteIP, 20), RemoteUrls=make_set(RemoteUrl, 20),
    Accounts=make_set(InitiatingProcessAccountUpn, 10)
    by InitiatingProcessFileName, InitiatingProcessFolderPath,
       InitiatingProcessSHA1, Signer
| sort by Devices desc, Connections desc

Pay attention to: unsigned or unexpected signers, signer/name mismatch, user-writable paths, remote-support products on unapproved devices, one account across many devices, and uncommon public destinations. The product name is context, not a verdict.

Then pivot — expand candidate hashes
let CandidateHashes =
    DeviceNetworkEvents
    | where TimeGenerated between (ago(14d) .. ago(1d))
    | where RemoteIPType == "Public"
    | summarize Known=count() by InitiatingProcessSHA1
    | join kind=rightanti (
        DeviceNetworkEvents
        | where TimeGenerated > ago(1d)
        | where RemoteIPType == "Public" and isnotempty(InitiatingProcessSHA1)
        | distinct InitiatingProcessSHA1
      ) on InitiatingProcessSHA1
    | project SHA1=InitiatingProcessSHA1;
union isfuzzy=true
    (DeviceProcessEvents
     | where TimeGenerated > ago(1d)
     | project TimeGenerated, DeviceName, DeviceId, Event="Process",
         SHA1, FileName, FolderPath, ProcessCommandLine,
         Account=AccountUpn, Detail=InitiatingProcessCommandLine),
    (DeviceNetworkEvents
     | where TimeGenerated > ago(1d)
     | project TimeGenerated, DeviceName, DeviceId, Event="Network",
         SHA1=InitiatingProcessSHA1, FileName=InitiatingProcessFileName,
         FolderPath=InitiatingProcessFolderPath, ProcessCommandLine="",
         Account=InitiatingProcessAccountUpn,
         Detail=strcat(RemoteUrl, " ", RemoteIP, ":", RemotePort)),
    (DeviceFileEvents
     | where TimeGenerated > ago(1d)
     | project TimeGenerated, DeviceName, DeviceId, Event="File",
         SHA1=InitiatingProcessSHA1, FileName, FolderPath,
         ProcessCommandLine=InitiatingProcessCommandLine,
         Account=InitiatingProcessAccountUpn, Detail=ActionType)
| join kind=inner CandidateHashes on SHA1
| project-away SHA11
| sort by DeviceName asc, TimeGenerated asc

Needs: Sentinel; DeviceNetworkEvents, DeviceProcessEvents, and DeviceFileEvents; selected Defender XDR raw-event ingestion.

Expected noise: newly deployed software, legitimate remote support, updates, and short-lived administrative tools.

Sources: First-Time Network Connection by Unusual Process · RMM network rollup

Rare scheduled task or service with follow-on behavior

Prioritize rare task- or service-launched payloads that run outside standard locations or exhibit additional activity.

Run first — rare task or service payloads
let lookback = 14d;
DeviceProcessEvents
| where TimeGenerated > ago(lookback)
| extend LaunchType = case(
    InitiatingProcessParentFileName =~ "services.exe", "Service",
    InitiatingProcessFileName =~ "svchost.exe"
        and InitiatingProcessCommandLine has_all ("-k netsvcs", "-s Schedule"), "Scheduled task",
    "Other")
| where LaunchType != "Other"
| where FolderPath !startswith @"C:\Windows\System32\"
    or FileName in~ ("cmd.exe", "powershell.exe", "pwsh.exe", "rundll32.exe", "regsvr32.exe")
| summarize Executions=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated),
    Devices=dcount(DeviceId), Commands=make_set(ProcessCommandLine, 10),
    Parents=make_set(InitiatingProcessCommandLine, 10), Accounts=make_set(AccountUpn, 10)
    by LaunchType, FileName, FolderPath, SHA1
| where Executions < 6 or Devices < 3
| sort by Devices asc, Executions asc

Pay attention to: Task Scheduler lineage, services.exe, nonstandard payload paths or accounts, command interpreters, LOLBins, rare hashes, and payloads that immediately communicate or modify files, DLLs, or registry keys.

Then pivot — activity around rare task and service hashes
let RareHashes =
    DeviceProcessEvents
    | where TimeGenerated > ago(14d)
    | extend LaunchType = case(
        InitiatingProcessParentFileName =~ "services.exe", "Service",
        InitiatingProcessFileName =~ "svchost.exe"
            and InitiatingProcessCommandLine has_all ("-k netsvcs", "-s Schedule"), "Scheduled task",
        "Other")
    | where LaunchType != "Other" and isnotempty(SHA1)
    | summarize Executions=count(), Devices=dcount(DeviceId) by SHA1
    | where Executions < 6 or Devices < 3
    | project SHA1;
union isfuzzy=true
    (DeviceProcessEvents
     | project TimeGenerated, DeviceName, DeviceId, SHA1,
         Evidence="Process", Detail=ProcessCommandLine, Account=AccountUpn),
    (DeviceNetworkEvents
     | project TimeGenerated, DeviceName, DeviceId, SHA1=InitiatingProcessSHA1,
         Evidence="Network", Detail=strcat(RemoteUrl, " ", RemoteIP),
         Account=InitiatingProcessAccountUpn),
    (DeviceFileEvents
     | project TimeGenerated, DeviceName, DeviceId, SHA1=InitiatingProcessSHA1,
         Evidence="File", Detail=strcat(ActionType, " ", FolderPath, "\\", FileName),
         Account=InitiatingProcessAccountUpn),
    (DeviceRegistryEvents
     | project TimeGenerated, DeviceName, DeviceId, SHA1=InitiatingProcessSHA1,
         Evidence="Registry", Detail=strcat(ActionType, " ", RegistryKey),
         Account=InitiatingProcessAccountUpn),
    (DeviceImageLoadEvents
     | project TimeGenerated, DeviceName, DeviceId, SHA1=InitiatingProcessSHA1,
         Evidence="Image load", Detail=strcat(FolderPath, "\\", FileName),
         Account=InitiatingProcessAccountUpn)
| where TimeGenerated > ago(14d)
| join kind=inner RareHashes on SHA1
| project-away SHA11
| sort by DeviceName asc, TimeGenerated asc

Needs: Sentinel; Defender XDR process telemetry, plus network/file/registry/image-load tables for the full pivot.

Expected noise: software deployment, vendor agents, maintenance jobs, and one-time installers.

Sources: Rare scheduled task with activity · Rare process as a service

Remote-execution artifact chain

Find PsExec-, WMI-, or AT-style execution artifacts without assuming a particular framework produced them.

Run first — remote-execution artifacts
union isfuzzy=true
    (DeviceEvents
     | where TimeGenerated > ago(7d)
     | where ActionType in ("NamedPipeEvent", "ProcessCreatedUsingWmiQuery")
     | extend Detail=tostring(AdditionalFields)
     | where ActionType == "ProcessCreatedUsingWmiQuery" or Detail has "atsvc"
     | project TimeGenerated, DeviceName, DeviceId, ActionType,
         Account=InitiatingProcessAccountUpn,
         Process=InitiatingProcessFileName, Command=InitiatingProcessCommandLine, Detail),
    (DeviceRegistryEvents
     | where TimeGenerated > ago(7d)
     | where ActionType == "RegistryKeyCreated"
     | where RegistryKey startswith @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\"
     | project TimeGenerated, DeviceName, DeviceId, ActionType,
         Account=InitiatingProcessAccountUpn,
         Process=InitiatingProcessFileName, Command=InitiatingProcessCommandLine,
         Detail=RegistryKey),
    (DeviceFileEvents
     | where TimeGenerated > ago(7d)
     | where ActionType == "FileCreated"
     | where InitiatingProcessFileName in~ ("services.exe", "ntoskrnl.exe", "svchost.exe")
     | project TimeGenerated, DeviceName, DeviceId, ActionType,
         Account=InitiatingProcessAccountUpn,
         Process=InitiatingProcessFileName, Command=InitiatingProcessCommandLine,
         Detail=strcat(FolderPath, "\\", FileName, " sha1=", SHA1))
| sort by DeviceName asc, TimeGenerated asc

Pay attention to: NamedPipeEvent, ProcessCreatedUsingWmiQuery, atsvc, TaskCache keys, service-created binaries, services.exe, Scheduler lineage, and several artifacts occurring within one or two minutes.

Then pivot — logons and activity on affected devices
let AffectedDevices =
    union isfuzzy=true
        (DeviceEvents
         | where TimeGenerated > ago(7d)
         | where ActionType in ("NamedPipeEvent", "ProcessCreatedUsingWmiQuery")
         | where ActionType == "ProcessCreatedUsingWmiQuery" or tostring(AdditionalFields) has "atsvc"),
        (DeviceRegistryEvents
         | where TimeGenerated > ago(7d)
         | where RegistryKey startswith @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\"),
        (DeviceFileEvents
         | where TimeGenerated > ago(7d)
         | where ActionType == "FileCreated"
         | where InitiatingProcessFileName in~ ("services.exe", "ntoskrnl.exe", "svchost.exe"))
    | distinct DeviceId;
union isfuzzy=true
    (DeviceLogonEvents
     | where TimeGenerated > ago(7d)
     | project TimeGenerated, DeviceName, DeviceId, Evidence="Logon",
         Account=AccountUpn, Detail=strcat(ActionType, " ", LogonType, " from ", RemoteIP)),
    (DeviceProcessEvents
     | where TimeGenerated > ago(7d)
     | project TimeGenerated, DeviceName, DeviceId, Evidence="Process",
         Account=AccountUpn, Detail=ProcessCommandLine),
    (DeviceNetworkEvents
     | where TimeGenerated > ago(7d)
     | project TimeGenerated, DeviceName, DeviceId, Evidence="Network",
         Account=InitiatingProcessAccountUpn, Detail=strcat(RemoteUrl, " ", RemoteIP))
| join kind=inner AffectedDevices on DeviceId
| project-away DeviceId1
| sort by DeviceName asc, TimeGenerated asc

Needs: Sentinel; Defender XDR device event, registry, file, logon, process, and network tables.

Expected noise: legitimate software deployment, remote administration, endpoint management, and scheduled maintenance.

Sources: PsExec artifacts · WMI execution · AT execution

Credential access followed by lateral movement

Find an LSASS access-and-dump sequence, then inspect the affected account and device for subsequent remote logons or execution.

Run first — LSASS access followed by file creation
let Access =
    DeviceEvents
    | where TimeGenerated > ago(7d)
    | where ActionType == "OpenProcessApiCall"
    | where FileName =~ "lsass.exe"
    | project AccessTime=TimeGenerated, DeviceId, DeviceName,
        Process=InitiatingProcessFileName,
        ProcessId=InitiatingProcessId,
        ProcessCreationTime=InitiatingProcessCreationTime,
        Command=InitiatingProcessCommandLine,
        Account=InitiatingProcessAccountUpn;
let Files =
    DeviceFileEvents
    | where TimeGenerated > ago(7d)
    | where ActionType == "FileCreated"
    | project FileTime=TimeGenerated, DeviceId,
        Process=InitiatingProcessFileName,
        ProcessId=InitiatingProcessId,
        ProcessCreationTime=InitiatingProcessCreationTime,
        FileName, FolderPath, SHA1;
Access
| join kind=inner Files on DeviceId, Process, ProcessId, ProcessCreationTime
| where FileTime between (AccessTime .. AccessTime + 1m)
| project AccessTime, FileTime, DeviceName, Account, Process, Command,
    DumpPath=strcat(FolderPath, "\\", FileName), SHA1
| sort by AccessTime desc

Pay attention to: nonstandard or unsigned accessors, dump-like extensions and writable paths, unusual parents, rare hashes, service accounts, and remote logons or administration immediately after the file is created.

Then pivot — movement after the credential-access sequence
let Hits =
    DeviceEvents
    | where TimeGenerated > ago(7d)
    | where ActionType == "OpenProcessApiCall" and FileName =~ "lsass.exe"
    | project AccessTime=TimeGenerated, DeviceId,
        Account=InitiatingProcessAccountUpn,
        ProcessId=InitiatingProcessId,
        ProcessCreationTime=InitiatingProcessCreationTime
    | join kind=inner (
        DeviceFileEvents
        | where TimeGenerated > ago(7d) and ActionType == "FileCreated"
        | project FileTime=TimeGenerated, DeviceId,
            ProcessId=InitiatingProcessId,
            ProcessCreationTime=InitiatingProcessCreationTime
      ) on DeviceId, ProcessId, ProcessCreationTime
    | where FileTime between (AccessTime .. AccessTime + 1m)
    | distinct DeviceId, Account, AccessTime;
DeviceLogonEvents
| where TimeGenerated > ago(7d)
| where ActionType in ("LogonSuccess", "LogonFailed")
| join kind=inner Hits on DeviceId
| where TimeGenerated between (AccessTime .. AccessTime + 6h)
| project TimeGenerated, DeviceName, AccountUpn, LogonType, RemoteIP,
    RemoteDeviceName, InitiatingProcessFileName, HitAccount=Account, AccessTime
| sort by TimeGenerated asc

Needs: Sentinel; DeviceEvents, DeviceFileEvents, and DeviceLogonEvents. OpenProcessApiCall availability depends on Defender telemetry.

Expected noise: security software, diagnostics, crash capture, and authorized support tools that legitimately access LSASS.

Sources: LSASS credential dumping · Service accounts performing Remote PowerShell

Recovery inhibition and evidence destruction

Score multiple stable precursors on the same endpoint instead of publishing one campaign-specific command hunt per string.

Run first — clustered recovery and evidence tampering
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| extend Cmd=tolower(ProcessCommandLine)
| extend Signal = case(
    FileName =~ "vssadmin.exe" and Cmd has_all ("delete", "shadows"), "Delete shadow copies",
    FileName =~ "wmic.exe" and Cmd has_all ("shadowcopy", "delete"), "Delete shadow copies",
    FileName =~ "wbadmin.exe" and Cmd has_all ("delete", "catalog"), "Delete backup catalog",
    FileName =~ "wevtutil.exe" and Cmd has_any (" cl ", " clear-log "), "Clear event log",
    FileName =~ "fsutil.exe" and Cmd has_all ("usn", "deletejournal"), "Delete USN journal",
    Cmd has "add-mppreference" and Cmd has "exclusion", "Add Defender exclusion",
    Cmd has_any ("disablesr", "disableconfig"), "Disable recovery",
    "")
| where isnotempty(Signal)
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated),
    Signals=make_set(Signal), Commands=make_set(ProcessCommandLine, 20),
    Processes=make_set(FileName), Accounts=make_set(AccountUpn)
    by DeviceId, DeviceName, bin(TimeGenerated, 30m)
| extend SignalCount=array_length(Signals)
| where SignalCount >= 2
| sort by SignalCount desc

Pay attention to: two or more different signals, remote or service-account execution, activity on several devices, Defender exclusions preceding destruction, and commands followed by mass file modification or encryption-like extensions.

Then pivot — activity around affected devices
let Affected =
    DeviceProcessEvents
    | where TimeGenerated > ago(7d)
    | extend Cmd=tolower(ProcessCommandLine)
    | extend Signal = case(
        FileName =~ "vssadmin.exe" and Cmd has_all ("delete", "shadows"), "Shadow deletion",
        FileName =~ "wmic.exe" and Cmd has_all ("shadowcopy", "delete"), "Shadow deletion",
        FileName =~ "wbadmin.exe" and Cmd has_all ("delete", "catalog"), "Catalog deletion",
        FileName =~ "wevtutil.exe" and Cmd has_any (" cl ", " clear-log "), "Log clearing",
        FileName =~ "fsutil.exe" and Cmd has_all ("usn", "deletejournal"), "USN deletion",
        Cmd has "add-mppreference" and Cmd has "exclusion", "Defender exclusion",
        "")
    | where isnotempty(Signal)
    | summarize Signals=make_set(Signal), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated)
        by DeviceId, DeviceName, bin(TimeGenerated, 30m)
    | where array_length(Signals) >= 2
    | project DeviceId, WindowStart=TimeGenerated, FirstSeen, LastSeen, Signals;
union isfuzzy=true
    (DeviceProcessEvents
     | project TimeGenerated, DeviceId, Evidence="Process", Detail=ProcessCommandLine),
    (DeviceFileEvents
     | project TimeGenerated, DeviceId, Evidence="File",
         Detail=strcat(ActionType, " ", FolderPath, "\\", FileName)),
    (DeviceLogonEvents
     | project TimeGenerated, DeviceId, Evidence="Logon",
         Detail=strcat(ActionType, " ", AccountUpn, " from ", RemoteIP)),
    (DeviceRegistryEvents
     | project TimeGenerated, DeviceId, Evidence="Registry",
         Detail=strcat(ActionType, " ", RegistryKey))
| join kind=inner Affected on DeviceId
| where TimeGenerated between (WindowStart - 30m .. WindowStart + 2h)
| sort by DeviceId asc, TimeGenerated asc

Needs: Sentinel; Defender XDR process telemetry, with file/logon/registry tables for expansion.

Expected noise: backup maintenance, incident-response tooling, troubleshooting, and sanctioned endpoint-policy changes.

Sources: Shadow copy deletion · System log clearing

Interpreter-driven discovery burst with public communication

Find one command interpreter that launches several distinct discovery utilities across multiple discovery categories, then determine whether that exact interpreter process also communicates with a public destination. Requiring a multi-tool burst and process-instance correlation is more durable than tying the hunt to whoami.exe, net.exe, or a Microsoft-hosted destination alone.

Run first — multi-category discovery from one interpreter
let lookback = 7d;
let burst = 30m;
DeviceProcessEvents
| where TimeGenerated > ago(lookback)
| where FileName in~ ("whoami.exe", "net.exe", "net1.exe", "nltest.exe", "dsquery.exe",
    "quser.exe", "qwinsta.exe", "hostname.exe", "systeminfo.exe", "ipconfig.exe",
    "route.exe", "arp.exe", "nslookup.exe")
| where InitiatingProcessFileName in~ ("powershell.exe", "pwsh.exe", "cmd.exe",
    "wscript.exe", "cscript.exe", "mshta.exe")
| extend DiscoveryCategory = case(
    FileName in~ ("whoami.exe", "quser.exe", "qwinsta.exe"), "Identity or session",
    FileName in~ ("net.exe", "net1.exe", "nltest.exe", "dsquery.exe"), "Domain or account",
    FileName in~ ("hostname.exe", "systeminfo.exe"), "Host",
    FileName in~ ("ipconfig.exe", "route.exe", "arp.exe", "nslookup.exe"), "Network",
    "Other")
| summarize FirstDiscovery=min(TimeGenerated), LastDiscovery=max(TimeGenerated),
    Executions=count(), Tools=make_set(FileName, 20),
    Categories=make_set(DiscoveryCategory, 10),
    Commands=make_set(ProcessCommandLine, 20),
    InterpreterCommand=take_any(InitiatingProcessCommandLine),
    Account=take_any(InitiatingProcessAccountUpn)
    by DeviceId, DeviceName,
       Interpreter=InitiatingProcessFileName,
       InterpreterId=InitiatingProcessId,
       InterpreterCreationTime=InitiatingProcessCreationTime,
       WindowStart=bin(TimeGenerated, burst)
| extend ToolCount=array_length(Tools), CategoryCount=array_length(Categories)
| where ToolCount >= 3 and CategoryCount >= 2
| sort by CategoryCount desc, ToolCount desc, FirstDiscovery desc

Pay attention to: encoded or download-related interpreter arguments, discovery under service or unexpected user accounts, execution from remote sessions, uncommon combinations of tools, public destinations rare for the device or account, and follow-on credential access or remote execution.

Then pivot — public communication from the same interpreter instance
let lookback = 7d;
let burst = 30m;
let DiscoveryBursts =
    DeviceProcessEvents
    | where TimeGenerated > ago(lookback)
    | where FileName in~ ("whoami.exe", "net.exe", "net1.exe", "nltest.exe", "dsquery.exe",
        "quser.exe", "qwinsta.exe", "hostname.exe", "systeminfo.exe", "ipconfig.exe",
        "route.exe", "arp.exe", "nslookup.exe")
    | where InitiatingProcessFileName in~ ("powershell.exe", "pwsh.exe", "cmd.exe",
        "wscript.exe", "cscript.exe", "mshta.exe")
    | extend DiscoveryCategory = case(
        FileName in~ ("whoami.exe", "quser.exe", "qwinsta.exe"), "Identity or session",
        FileName in~ ("net.exe", "net1.exe", "nltest.exe", "dsquery.exe"), "Domain or account",
        FileName in~ ("hostname.exe", "systeminfo.exe"), "Host",
        FileName in~ ("ipconfig.exe", "route.exe", "arp.exe", "nslookup.exe"), "Network",
        "Other")
    | summarize FirstDiscovery=min(TimeGenerated), LastDiscovery=max(TimeGenerated),
        Tools=make_set(FileName, 20), Categories=make_set(DiscoveryCategory, 10),
        Commands=make_set(ProcessCommandLine, 20),
        InterpreterCommand=take_any(InitiatingProcessCommandLine),
        Account=take_any(InitiatingProcessAccountUpn)
        by DeviceId, DeviceName,
           Interpreter=InitiatingProcessFileName,
           InterpreterId=InitiatingProcessId,
           InterpreterCreationTime=InitiatingProcessCreationTime,
           WindowStart=bin(TimeGenerated, burst)
    | extend ToolCount=array_length(Tools), CategoryCount=array_length(Categories)
    | where ToolCount >= 3 and CategoryCount >= 2;
let PublicConnections =
    DeviceNetworkEvents
    | where TimeGenerated > ago(lookback)
    | where RemoteIPType == "Public"
    | project NetTime=TimeGenerated, DeviceId,
        InterpreterId=InitiatingProcessId,
        InterpreterCreationTime=InitiatingProcessCreationTime,
        RemoteUrl, RemoteIP, RemotePort;
DiscoveryBursts
| join kind=inner PublicConnections
    on DeviceId, InterpreterId, InterpreterCreationTime
| where NetTime between (FirstDiscovery - 5m .. LastDiscovery + 30m)
| summarize FirstPublicConnection=min(NetTime), LastPublicConnection=max(NetTime),
    RemoteUrls=make_set(RemoteUrl, 20), RemoteIPs=make_set(RemoteIP, 20),
    RemotePorts=make_set(RemotePort, 20),
    DiscoveryTools=take_any(Tools), DiscoveryCategories=take_any(Categories),
    DiscoveryCommands=take_any(Commands)
    by DeviceId, DeviceName, Account, Interpreter, InterpreterId,
       InterpreterCreationTime, InterpreterCommand, FirstDiscovery, LastDiscovery,
       ToolCount, CategoryCount
| sort by CategoryCount desc, ToolCount desc, FirstPublicConnection desc

Needs: Sentinel; DeviceProcessEvents and DeviceNetworkEvents from selected Defender XDR raw-event ingestion. RemoteIPType must be populated for public-address filtering.

Expected noise: administrative inventory scripts, login and provisioning scripts, endpoint-management products, RMM tooling, support sessions, and security assessment utilities. Tune known automation accounts, interpreter command lines, management tools, and expected destination populations rather than suppressing the discovery utilities globally.

Sources: DeviceProcessEvents schema · DeviceNetworkEvents schema · Advanced Hunting query best practices

Script or LOLBin followed by public communication

Correlate suspicious interpreter or signed-binary proxy execution with a public connection from the same process instance.

Run first — LOLBin process with public communication
let Executions =
    DeviceProcessEvents
    | where TimeGenerated > ago(7d)
    | where FileName in~ ("rundll32.exe", "regsvr32.exe", "mshta.exe", "msiexec.exe",
        "bitsadmin.exe", "certutil.exe", "curl.exe", "powershell.exe", "pwsh.exe")
    | extend Cmd=tolower(ProcessCommandLine)
    | where InitiatingProcessFileName in~ ("wscript.exe", "cscript.exe", "cmd.exe",
            "powershell.exe", "pwsh.exe", "winword.exe", "excel.exe", "outlook.exe")
        or Cmd has_any ("http://", "https://", "downloadfile", "invoke-webrequest",
            "start-bitstransfer", "/transfer", "/addfile", "javascript:", "scrobj.dll")
    | project ExecTime=TimeGenerated, DeviceId, DeviceName, ProcessId,
        ProcessCreationTime, SHA1, FileName, FolderPath, ProcessCommandLine,
        Parent=InitiatingProcessFileName, Account=AccountUpn;
let Connections =
    DeviceNetworkEvents
    | where TimeGenerated > ago(7d)
    | where RemoteIPType == "Public"
    | project NetTime=TimeGenerated, DeviceId,
        ProcessId=InitiatingProcessId,
        ProcessCreationTime=InitiatingProcessCreationTime,
        RemoteUrl, RemoteIP, RemotePort;
Executions
| join kind=inner Connections on DeviceId, ProcessId, ProcessCreationTime
| where NetTime between (ExecTime .. ExecTime + 10m)
| project ExecTime, NetTime, DeviceName, Account, Parent, FileName,
    FolderPath, ProcessCommandLine, SHA1, RemoteUrl, RemoteIP, RemotePort
| sort by ExecTime desc

Pay attention to: Office or script-host parents, encoded or download-related arguments, user-writable paths, uncommon destinations, unsigned payloads, and the same hash or destination appearing on multiple devices.

Then pivot — files and full process context
let Hits =
    DeviceProcessEvents
    | where TimeGenerated > ago(7d)
    | where FileName in~ ("rundll32.exe", "regsvr32.exe", "mshta.exe", "msiexec.exe",
        "bitsadmin.exe", "certutil.exe", "curl.exe", "powershell.exe", "pwsh.exe")
    | project DeviceId, ProcessId, ProcessCreationTime,
        HitTime=TimeGenerated, HitSHA1=SHA1
    | join kind=inner (
        DeviceNetworkEvents
        | where TimeGenerated > ago(7d) and RemoteIPType == "Public"
        | project DeviceId, ProcessId=InitiatingProcessId,
            ProcessCreationTime=InitiatingProcessCreationTime
      ) on DeviceId, ProcessId, ProcessCreationTime
    | distinct DeviceId, HitSHA1, HitTime;
union isfuzzy=true
    (DeviceProcessEvents
     | project TimeGenerated, DeviceId, Evidence="Process",
         SHA1, Detail=ProcessCommandLine),
    (DeviceFileEvents
     | project TimeGenerated, DeviceId, Evidence="File",
         SHA1=InitiatingProcessSHA1,
         Detail=strcat(ActionType, " ", FolderPath, "\\", FileName)),
    (DeviceNetworkEvents
     | project TimeGenerated, DeviceId, Evidence="Network",
         SHA1=InitiatingProcessSHA1,
         Detail=strcat(RemoteUrl, " ", RemoteIP, ":", RemotePort))
| join kind=inner Hits on DeviceId
| where TimeGenerated between (HitTime - 10m .. HitTime + 30m)
| where SHA1 == HitSHA1 or Evidence != "Process"
| sort by DeviceId asc, TimeGenerated asc

Needs: Sentinel; DeviceProcessEvents, DeviceNetworkEvents, and DeviceFileEvents from Defender XDR.

Expected noise: software installation, administration, update systems, and legitimate scripts using the same signed utilities.

Sources: Regsvr32/Rundll32 with anomalous parent · Suspicious MSHTA use · BITS activity