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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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