diff --git a/Campaigns/APT Baby Shark.txt b/Campaigns/APT Baby Shark.txt index 65f01eda..48a22aae 100644 --- a/Campaigns/APT Baby Shark.txt +++ b/Campaigns/APT Baby Shark.txt @@ -1,8 +1,8 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_babyshark.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where ProcessCommandLine =~ @"reg query ""HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default""" or ProcessCommandLine startswith "powershell.exe mshta.exe http" or ProcessCommandLine =~ "cmd.exe /c taskkill /im cmd.exe" -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/APT29 thinktanks.txt b/Campaigns/APT29 thinktanks.txt index 80d00515..c2a09400 100644 --- a/Campaigns/APT29 thinktanks.txt +++ b/Campaigns/APT29 thinktanks.txt @@ -1,6 +1,6 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_apt29_thinktanks.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where ProcessCommandLine has "-noni -ep bypass $" -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/Abusing settingcontent-ms.txt b/Campaigns/Abusing settingcontent-ms.txt index 8a650489..a5ba6406 100644 --- a/Campaigns/Abusing settingcontent-ms.txt +++ b/Campaigns/Abusing settingcontent-ms.txt @@ -1,9 +1,9 @@ // Sample query that search for .settingcontent-ms that has been downloaded from the web // through Microsoft Edge, Internet Explorer, Google Chrome, Mozilla Firefox, Microsoft Outlook // For questions @MiladMSFT on Twitter or milad.aslaner@microsoft.com -FileCreationEvents +DeviceFileEvents | where InitiatingProcessFileName in~ ("browser_broker.exe", "chrome.exe", "iexplore.exe", "firefox.exe", "outlook.exe") | where FileName endswith ".settingcontent-ms" // The FileOrigin* columns are available only on Edge and Chrome and from Windows 10 version 1703 // https://techcommunity.microsoft.com/t5/Threat-Intelligence/Hunting-tip-of-the-month-Browser-downloads/td-p/220454 -| project EventTime, ComputerName, FileName, FolderPath, FileOriginUrl, FileOriginReferrerUrl, FileOriginIP +| project Timestamp, DeviceName, FileName, FolderPath, FileOriginUrl, FileOriginReferrerUrl, FileOriginIP diff --git a/Campaigns/Bear Activity GTR 2019.txt b/Campaigns/Bear Activity GTR 2019.txt index 893ccca1..a46d1ae7 100644 --- a/Campaigns/Bear Activity GTR 2019.txt +++ b/Campaigns/Bear Activity GTR 2019.txt @@ -1,7 +1,7 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_bear_activity_gtr19.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where (FileName =~ "xcopy.exe" and ProcessCommandLine has @" /S /E /C /Q /H \") or (FileName =~ "adexplorer.exe" and ProcessCommandLine has @" -snapshot """" c:\users\") -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/Cloud Hopper.txt b/Campaigns/Cloud Hopper.txt index fc32099c..d5e920d9 100644 --- a/Campaigns/Cloud Hopper.txt +++ b/Campaigns/Cloud Hopper.txt @@ -1,6 +1,6 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_cloudhopper.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where FileName =~ @"cscript.exe" and ProcessCommandLine has ".vbs /shell " -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/DofoilNameCoinServerTraffic.txt b/Campaigns/DofoilNameCoinServerTraffic.txt index f523d611..04db1ac7 100644 --- a/Campaigns/DofoilNameCoinServerTraffic.txt +++ b/Campaigns/DofoilNameCoinServerTraffic.txt @@ -1,10 +1,10 @@ // This is a query to retrieve last 30 days network connections to known Dofoil NameCoin servers // The full article is available here: https://cloudblogs.microsoft.com/microsoftsecure/2018/04/04/hunting-down-dofoil-with-windows-defender-atp/ -NetworkCommunicationEvents +DeviceNetworkEvents | where RemoteIP in ( "139.59.208.246","130.255.73.90","31.3.135.232","52.174.55.168","185.121.177.177","185.121.177.53", "62.113.203.55","144.76.133.38","169.239.202.202","5.135.183.146","142.0.68.13","103.253.12.18", "62.112.8.85","69.164.196.21","107.150.40.234","162.211.64.20","217.12.210.54","89.18.27.34", "193.183.98.154","51.255.167.0","91.121.155.13","87.98.175.85","185.97.7.7") -| project ComputerName, InitiatingProcessCreationTime, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteIP, RemotePort +| project DeviceName, InitiatingProcessCreationTime, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteIP, RemotePort diff --git a/Campaigns/Dragon Fly.txt b/Campaigns/Dragon Fly.txt index 61be954a..2f5e9c9a 100644 --- a/Campaigns/Dragon Fly.txt +++ b/Campaigns/Dragon Fly.txt @@ -1,6 +1,6 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_dragonfly.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where FileName =~ "crackmapexec.exe" -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/Elise backdoor.txt b/Campaigns/Elise backdoor.txt index 21cb6fdf..968f49e8 100644 --- a/Campaigns/Elise backdoor.txt +++ b/Campaigns/Elise backdoor.txt @@ -1,7 +1,7 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_elise.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where (FolderPath =~ @"C:\Windows\SysWOW64\cmd.exe" and ProcessCommandLine has @"\Windows\Caches\NavShExt.dll") or (ProcessCommandLine endswith @"\AppData\Roaming\MICROS~1\Windows\Caches\NavShExt.dll,Setting") -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/Equation Group C2 Communication.txt b/Campaigns/Equation Group C2 Communication.txt index 2a2a95b0..40716629 100644 --- a/Campaigns/Equation Group C2 Communication.txt +++ b/Campaigns/Equation Group C2 Communication.txt @@ -1,7 +1,7 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_equationgroup_c2.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where (FolderPath endswith @"\rundll32.exe" and ProcessCommandLine endswith ",dll_u") or ProcessCommandLine has " -export dll_u " -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/Hurricane Panda activity.txt b/Campaigns/Hurricane Panda activity.txt index ee062777..efd4b8e9 100644 --- a/Campaigns/Hurricane Panda activity.txt +++ b/Campaigns/Hurricane Panda activity.txt @@ -1,7 +1,7 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_hurricane_panda.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where ProcessCommandLine endswith " localgroup administrators admin /add" or ProcessCommandLine has @"\Win64.exe" -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/Judgement Panda exfil activity.txt b/Campaigns/Judgement Panda exfil activity.txt index 2d31c13a..487e29cf 100644 --- a/Campaigns/Judgement Panda exfil activity.txt +++ b/Campaigns/Judgement Panda exfil activity.txt @@ -1,7 +1,7 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_judgement_panda_gtr19.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where ProcessCommandLine has @"\ldifde.exe -f -n " or ProcessCommandLine has @"\7za.exe a 1.7z " or ProcessCommandLine endswith @" eprod.ldf" @@ -11,4 +11,4 @@ ProcessCreationEvents or ProcessCommandLine has @"copy .\1.7z \" or ProcessCommandLine has @"copy \client\c$\aaaa\" or FolderPath == @"C:\Users\Public\7za.exe" -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/MacOceanLotusBackdoor.txt b/Campaigns/MacOceanLotusBackdoor.txt index 898a2e78..9e878f05 100644 --- a/Campaigns/MacOceanLotusBackdoor.txt +++ b/Campaigns/MacOceanLotusBackdoor.txt @@ -3,8 +3,8 @@ // https://blog.trendmicro.com/trendlabs-security-intelligence/new-macos-backdoor-linked-to-oceanlotus-found/ // // OS platforms: Macintosh -ProcessCreationEvents -| where EventTime > ago(14d) +DeviceProcessEvents +| where Timestamp > ago(14d) | where FileName in~ ("screenassistantd","spellagentd") -| top 100 by EventTime +| top 100 by Timestamp diff --git a/Campaigns/MacOceanLotusDropper.txt b/Campaigns/MacOceanLotusDropper.txt index 7021121e..0df349d0 100644 --- a/Campaigns/MacOceanLotusDropper.txt +++ b/Campaigns/MacOceanLotusDropper.txt @@ -2,9 +2,9 @@ // References: // https://blog.trendmicro.com/trendlabs-security-intelligence/new-macos-backdoor-linked-to-oceanlotus-found/ // OS Platforms: Macintosh -ProcessCreationEvents -| where EventTime > ago(14d) +DeviceProcessEvents +| where Timestamp > ago(14d) | where ProcessCommandLine contains "theme0" -| project EventTime, MachineId , ComputerName, AccountName , AccountSid , InitiatingProcessCommandLine , ProcessCommandLine -| top 100 by EventTime +| project Timestamp, DeviceId , DeviceName, AccountName , AccountSid , InitiatingProcessCommandLine , ProcessCommandLine +| top 100 by Timestamp diff --git a/Campaigns/OceanLotus registry activity.txt b/Campaigns/OceanLotus registry activity.txt index 678e3980..72f81971 100644 --- a/Campaigns/OceanLotus registry activity.txt +++ b/Campaigns/OceanLotus registry activity.txt @@ -1,7 +1,7 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_oceanlotus_registry.yml // Questions via Twitter: @janvonkirchheim -RegistryEvents -| where EventTime > ago(7d) +DeviceRegistryEvents +| where Timestamp > ago(7d) | where ActionType == "RegistryValueSet" | where RegistryKey endswith @"\SOFTWARE\Classes\CLSID\{E08A0F4B-1F65-4D4D-9A09-BD4625B9C5A1}\Model" or RegistryKey endswith @"\SOFTWARE\App\AppXbf13d4ea2945444d8b13e2121cb6b663\Application" diff --git a/Campaigns/apt sofacy zebrocy.txt b/Campaigns/apt sofacy zebrocy.txt index 34425632..72747923 100644 --- a/Campaigns/apt sofacy zebrocy.txt +++ b/Campaigns/apt sofacy zebrocy.txt @@ -1,6 +1,6 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_sofacy_zebrocy.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where ProcessCommandLine endswith "cmd.exe /c SYSTEMINFO & TASKLIST" -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Campaigns/apt tropictrooper.txt b/Campaigns/apt tropictrooper.txt index 5f74eef7..68625a74 100644 --- a/Campaigns/apt tropictrooper.txt +++ b/Campaigns/apt tropictrooper.txt @@ -1,6 +1,6 @@ // Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/apt/apt_tropictrooper.yml // Questions via Twitter: @janvonkirchheim -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where ProcessCommandLine contains "abCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCc" -| top 100 by EventTime desc +| top 100 by Timestamp desc diff --git a/Command and Control/Tor.txt b/Command and Control/Tor.txt index 7274c262..14d92664 100644 --- a/Command and Control/Tor.txt +++ b/Command and Control/Tor.txt @@ -1,13 +1,13 @@ // This query looks for Tor client, or for a common Tor plugin called Meek. -// We query for active Tor connections, but could have alternatively looked for active Tor runs (ProcessCreateEvents) or Tor downloads (FileCreationEvents) +// We query for active Tor connections, but could have alternatively looked for active Tor runs (ProcessCreateEvents) or Tor downloads (DeviceFileEvents) // To read more about this technique, see: // Tor: https://attack.mitre.org/wiki/Software/S0183#Techniques_Used // Meek plugin: https://attack.mitre.org/wiki/Software/S0175 // Multi-hop proxy technique: https://attack.mitre.org/wiki/Technique/T1188 // Tags: #Tor, #MultiHopProxy, #CnC -NetworkCommunicationEvents -| where EventTime < ago(3d) and InitiatingProcessFileName in~ ("tor.exe", "meek-client.exe") +DeviceNetworkEvents +| where Timestamp < ago(3d) and InitiatingProcessFileName in~ ("tor.exe", "meek-client.exe") // Returns MD5 hashes of files used by Tor, to enable you to block them. // We count how prevalent each file is (by machines) and show examples for some of them (up to 5 machine names per hash). -| summarize MachineCount=dcount(ComputerName), MachineNames=makeset(ComputerName, 5) by InitiatingProcessMD5 +| summarize MachineCount=dcount(DeviceName), MachineNames=makeset(DeviceName, 5) by InitiatingProcessMD5 | order by MachineCount desc diff --git a/Delivery/Doc attachment with link to download.txt b/Delivery/Doc attachment with link to download.txt index 74b84c7c..53e0c762 100644 --- a/Delivery/Doc attachment with link to download.txt +++ b/Delivery/Doc attachment with link to download.txt @@ -5,8 +5,8 @@ // Implementation comment #1: Matching events by time // Matching the 3 different events (saving attachment, clicking on link, downloading file) is done purely by time difference - so could sometimes link together unrelated events. // Doing a more exact lookup would create a much more complex query due to -// Implementation comment #2: Deduping FileCreationEvents -// Oftentimes there are multiple FileCreationEvents for a single file - e.g. if the file keeps being appended into before being closed. +// Implementation comment #2: Deduping DeviceFileEvents +// Oftentimes there are multiple DeviceFileEvents for a single file - e.g. if the file keeps being appended into before being closed. // So, we query only for the last reported file state to ignore intermediate file states. // Explaining the underlying data: // BrowserLaunchedToOpenUrl event: @@ -14,33 +14,33 @@ // For this event, RemoteUrl contains the opened URL. let minTimeRange = ago(7d); let wordLinks = - MiscEvents + DeviceEvents // Filter on click on links from WinWord - | where EventTime > minTimeRange and ActionType == "BrowserLaunchedToOpenUrl" and isnotempty(RemoteUrl) and InitiatingProcessFileName =~ "winword.exe" - | project ClickTime=EventTime, MachineId, ComputerName, ClickUrl=RemoteUrl; + | where Timestamp > minTimeRange and ActionType == "BrowserLaunchedToOpenUrl" and isnotempty(RemoteUrl) and InitiatingProcessFileName =~ "winword.exe" + | project ClickTime=Timestamp, DeviceId, DeviceName, ClickUrl=RemoteUrl; let docAttachments = - FileCreationEvents - | where EventTime > minTimeRange + DeviceFileEvents + | where Timestamp > minTimeRange // Query for common document file extensions and (FileName endswith ".docx" or FileName endswith ".docm" or FileName endswith ".doc") // Query for files saved from email clients such as the Office Outlook app or the Windows Mail app and InitiatingProcessFileName in~ ("outlook.exe", "hxoutlook.exe") - | summarize AttachmentSaveTime=min(EventTime) by AttachmentName=FileName, MachineId; + | summarize AttachmentSaveTime=min(Timestamp) by AttachmentName=FileName, DeviceId; let browserDownloads = - FileCreationEvents - | where EventTime > minTimeRange + DeviceFileEvents + | where Timestamp > minTimeRange // Query for files created by common browsers and InitiatingProcessFileName in~ ("browser_broker.exe", "chrome.exe", "iexplore.exe", "firefox.exe") // Exclude JS files that are used for loading sites (but still query for JS files that are known to be downloaded) and not (FileName endswith ".js" and isempty(FileOriginUrl)) // Further filter to exclude file extensions that are less indicative of an attack (when there were already previously a doc attachment that included a link) | where FileName !endswith ".partial" and FileName !endswith ".docx" - | summarize (EventTime, SHA1) = argmax(EventTime, SHA1) by FileName, MachineId, FileOriginUrl; + | summarize (Timestamp, SHA1) = argmax(Timestamp, SHA1) by FileName, DeviceId, FileOriginUrl; // Perf tip: start the joins from the smallest table (put it on the left-most side of the joins) wordLinks -| join kind= inner (docAttachments) on MachineId | where ClickTime - AttachmentSaveTime between (0min..3min) -| join kind= inner (browserDownloads) on MachineId | where EventTime - ClickTime between (0min..3min) +| join kind= inner (docAttachments) on DeviceId | where ClickTime - AttachmentSaveTime between (0min..3min) +| join kind= inner (browserDownloads) on DeviceId | where Timestamp - ClickTime between (0min..3min) // Aggregating multiple "attachments" together - because oftentimes the same file is stored multiple times under different names | summarize Attachments=makeset(AttachmentName), AttachmentSaveTime=min(AttachmentSaveTime), ClickTime=min(ClickTime) by // Downloaded file details - bin(EventTime, 1tick), FileName, FileOriginUrl, ClickUrl, SHA1, ComputerName, MachineId + bin(Timestamp, 1tick), FileName, FileOriginUrl, ClickUrl, SHA1, DeviceName, DeviceId diff --git a/Delivery/Dropbox downloads linked from other site.txt b/Delivery/Dropbox downloads linked from other site.txt index c0cc3813..289ccb0c 100644 --- a/Delivery/Dropbox downloads linked from other site.txt +++ b/Delivery/Dropbox downloads linked from other site.txt @@ -3,9 +3,9 @@ // Read more about download URL data and about this attack vector in this blog post: // https://techcommunity.microsoft.com/t5/Threat-Intelligence/Hunting-tip-of-the-month-Browser-downloads/td-p/220454 // Tags: #DownloadUrl, #Referer, #Dropbox -FileCreationEvents +DeviceFileEvents | where - EventTime > ago(7d) + Timestamp > ago(7d) and FileOriginUrl startswith "https://dl.dropboxusercontent.com/" and isnotempty(FileOriginReferrerUrl) and FileOriginReferrerUrl !startswith "https://www.dropbox.com/" diff --git a/Delivery/Email link + download + SmartScreen warning.txt b/Delivery/Email link + download + SmartScreen warning.txt index 48111dd7..ffe9b5e6 100644 --- a/Delivery/Email link + download + SmartScreen warning.txt +++ b/Delivery/Email link + download + SmartScreen warning.txt @@ -4,29 +4,29 @@ // Tags: #EmailLink, #BrowserDownload, #SmartScreen let smartscreenAppWarnings = // Query for SmartScreen warnings of unknown executed applications - MiscEvents + DeviceEvents | where ActionType == "SmartScreenAppWarning" - | project WarnTime=EventTime, ComputerName, WarnedFileName=FileName, WarnedSHA1=SHA1, ActivityId=extractjson("$.ActivityId", AdditionalFields, typeof(string)) + | project WarnTime=Timestamp, DeviceName, WarnedFileName=FileName, WarnedSHA1=SHA1, ActivityId=extractjson("$.ActivityId", AdditionalFields, typeof(string)) // Select only warnings that the user has decided to ignore and has executed the app. | join kind=leftsemi ( - MiscEvents + DeviceEvents | where ActionType == "SmartScreenUserOverride" - | project ComputerName, ActivityId=extractjson("$.ActivityId", AdditionalFields, typeof(string))) - on ComputerName, ActivityId + | project DeviceName, ActivityId=extractjson("$.ActivityId", AdditionalFields, typeof(string))) + on DeviceName, ActivityId | project-away ActivityId; // Query for links opened from outlook, that are close in time to a SmartScreen warning let emailLinksNearSmartScreenWarnings = - MiscEvents + DeviceEvents | where ActionType == "BrowserLaunchedToOpenUrl" and isnotempty(RemoteUrl) and InitiatingProcessFileName =~ "outlook.exe" | extend WasOutlookSafeLink=(tostring(parse_url(RemoteUrl).Host) endswith "safelinks.protection.outlook.com") - | project ComputerName, MailLinkTime=EventTime, + | project DeviceName, MailLinkTime=Timestamp, MailLink=iff(WasOutlookSafeLink, url_decode(tostring(parse_url(RemoteUrl)["Query Parameters"]["url"])), RemoteUrl) - | join kind=inner smartscreenAppWarnings on ComputerName | where (WarnTime-MailLinkTime) between (0min..4min); + | join kind=inner smartscreenAppWarnings on DeviceName | where (WarnTime-MailLinkTime) between (0min..4min); // Add the browser download event to tie in all the dots -FileCreationEvents +DeviceFileEvents | where isnotempty(FileOriginUrl) and InitiatingProcessFileName in~ ("chrome.exe", "browser_broker.exe") -| project FileName, FileOriginUrl, FileOriginReferrerUrl, ComputerName, EventTime, SHA1 -| join kind=inner emailLinksNearSmartScreenWarnings on ComputerName -| where (EventTime-MailLinkTime) between (0min..3min) and (WarnTime-EventTime) between (0min..1min) -| project FileName, MailLink, FileOriginUrl, FileOriginReferrerUrl, WarnedFileName, ComputerName, SHA1, WarnedSHA1, EventTime +| project FileName, FileOriginUrl, FileOriginReferrerUrl, DeviceName, Timestamp, SHA1 +| join kind=inner emailLinksNearSmartScreenWarnings on DeviceName +| where (Timestamp-MailLinkTime) between (0min..3min) and (WarnTime-Timestamp) between (0min..1min) +| project FileName, MailLink, FileOriginUrl, FileOriginReferrerUrl, WarnedFileName, DeviceName, SHA1, WarnedSHA1, Timestamp | distinct * diff --git a/Delivery/Open email link.txt b/Delivery/Open email link.txt index 1fad0917..e8313678 100644 --- a/Delivery/Open email link.txt +++ b/Delivery/Open email link.txt @@ -9,28 +9,28 @@ // For this event, RemoteUrl contains the opened URL. let minTimeRange = ago(7d); let outlookLinks = - MiscEvents + DeviceEvents // Filter on click on links from outlook - | where EventTime > minTimeRange and ActionType == "BrowserLaunchedToOpenUrl" and isnotempty(RemoteUrl) + | where Timestamp > minTimeRange and ActionType == "BrowserLaunchedToOpenUrl" and isnotempty(RemoteUrl) | where // outlook.exe is the Office Outlook app InitiatingProcessFileName =~ "outlook.exe" // RuntimeBroker.exe opens links for all apps from the Windows store, including the Windows Mail app (HxOutlook.exe). // However, it will also include some links opened from other apps. or InitiatingProcessFileName =~ "runtimebroker.exe" - | project EventTime, MachineId, ComputerName, RemoteUrl, InitiatingProcessFileName, ParsedUrl=parse_url(RemoteUrl) + | project Timestamp, DeviceId, DeviceName, RemoteUrl, InitiatingProcessFileName, ParsedUrl=parse_url(RemoteUrl) // When applicable, parse the link sent via email from the clicked O365 ATP SafeLink | extend WasOutlookSafeLink=(tostring(ParsedUrl.Host) endswith "safelinks.protection.outlook.com") - | project EventTime, MachineId, ComputerName, WasOutlookSafeLink, InitiatingProcessFileName, + | project Timestamp, DeviceId, DeviceName, WasOutlookSafeLink, InitiatingProcessFileName, OpenedLink=iff(WasOutlookSafeLink, url_decode(tostring(ParsedUrl["Query Parameters"]["url"])), RemoteUrl); let alerts = - AlertEvents - | summarize (FirstDetectedActivity, Title)=argmin(EventTime, Title) by AlertId, MachineId + DeviceAlertEvents + | summarize (FirstDetectedActivity, Title)=argmin(Timestamp, Title) by AlertId, DeviceId // Filter alerts that include events from before the queried time period | where FirstDetectedActivity > minTimeRange; // Join the two together - looking for alerts that are right after an abnormal network logon -alerts | join kind=inner (outlookLinks) on MachineId | where FirstDetectedActivity - EventTime between (0min..3min) +alerts | join kind=inner (outlookLinks) on DeviceId | where FirstDetectedActivity - Timestamp between (0min..3min) // If there are multiple alerts close to a single click-on-link, aggregate them together to a single row -// Note: bin(EventTime, 1tick) is used because when summarizing by a datetime field, the default "bin" used is 1-hour. -| summarize FirstDetectedActivity=min(FirstDetectedActivity), AlertTitles=makeset(Title) by OpenedLink, InitiatingProcessFileName, EventTime=bin(EventTime, 1tick), ComputerName, MachineId, WasOutlookSafeLink +// Note: bin(Timestamp, 1tick) is used because when summarizing by a datetime field, the default "bin" used is 1-hour. +| summarize FirstDetectedActivity=min(FirstDetectedActivity), AlertTitles=makeset(Title) by OpenedLink, InitiatingProcessFileName, Timestamp=bin(Timestamp, 1tick), DeviceName, DeviceId, WasOutlookSafeLink diff --git a/Delivery/Pivot from detections to related downloads.txt b/Delivery/Pivot from detections to related downloads.txt index 9cbb57f5..f42b9a9f 100644 --- a/Delivery/Pivot from detections to related downloads.txt +++ b/Delivery/Pivot from detections to related downloads.txt @@ -2,9 +2,9 @@ // To learn more about the download URL info that is available and see other sample queries, // check out this blog post: https://techcommunity.microsoft.com/t5/Threat-Intelligence/Hunting-tip-of-the-month-Browser-downloads/td-p/220454 let detectedDownloads = - MiscEvents + DeviceEvents | where ActionType == "AntivirusDetection" and isnotempty(FileOriginUrl) - | project EventTime, FileOriginUrl, FileName, MachineId, + | project Timestamp, FileOriginUrl, FileName, DeviceId, ThreatName=tostring(parse_json(AdditionalFields).ThreatName) // Filter out less severe threat categories on which we do not want to pivot | where ThreatName !startswith "PUA" @@ -19,21 +19,21 @@ let detectedDownloadsSummary = ThreatNames=makeset(ThreatName, 4) by Host=tostring(parse_url(FileOriginUrl).Host); // Query for downloads from sites from which other downloads were detected by Windows Defender Antivirus -FileCreationEvents +DeviceFileEvents | where isnotempty(FileOriginUrl) -| project FileName, FileOriginUrl, MachineId, EventTime, +| project FileName, FileOriginUrl, DeviceId, Timestamp, Host=tostring(parse_url(FileOriginUrl).Host) // Filter downloads from hosts serving detected files | join kind=inner(detectedDownloadsSummary) on Host // Filter out download file create events that were also detected. // This is needed because sometimes both of these events will be reported, // and sometimes only the AntivirusDetection event - depending on timing. -| join kind=leftanti(detectedDownloads) on MachineId, FileOriginUrl +| join kind=leftanti(detectedDownloads) on DeviceId, FileOriginUrl // Summarize a single row per host - with the machines count // and an example event for a missed download (select the last event) -| summarize MachineCount=dcount(MachineId), arg_max(EventTime, *) by Host +| summarize MachineCount=dcount(DeviceId), arg_max(Timestamp, *) by Host // Filter out common hosts, as they probably ones that also serve benign files | where MachineCount < 20 -| project Host, MachineCount, MachineId, FileName, DetectedFiles, - FileOriginUrl, DetectedUrl, ThreatNames, EventTime, SHA1 +| project Host, MachineCount, DeviceId, FileName, DetectedFiles, + FileOriginUrl, DetectedUrl, ThreatNames, Timestamp, SHA1 | order by MachineCount desc diff --git a/Discovery/Discover hosts doing possible network scans.txt b/Discovery/Discover hosts doing possible network scans.txt index c8ce15df..1874b647 100644 --- a/Discovery/Discover hosts doing possible network scans.txt +++ b/Discovery/Discover hosts doing possible network scans.txt @@ -1,9 +1,9 @@ -// Looking for high volume queries against a given RemoteIP, per ComputerName, RemotePort and Process -// Please change the EventTime window according your preference/objective, as also the subnet ranges that you want to analyze against +// Looking for high volume queries against a given RemoteIP, per DeviceName, RemotePort and Process +// Please change the Timestamp window according your preference/objective, as also the subnet ranges that you want to analyze against let remotePortCountThreshold = 10; // Please change the min value, for a host reaching out to remote ports on a remote IP, that you consider to be threshold for a suspicious behavior -NetworkCommunicationEvents -| where EventTime > ago(1d) and RemoteIP startswith "172.16" or RemoteIP startswith "192.168" +DeviceNetworkEvents +| where Timestamp > ago(1d) and RemoteIP startswith "172.16" or RemoteIP startswith "192.168" | summarize - by ComputerName, RemoteIP, RemotePort, InitiatingProcessFileName -| summarize RemotePortCount=dcount(RemotePort) by ComputerName, RemoteIP, InitiatingProcessFileName + by DeviceName, RemoteIP, RemotePort, InitiatingProcessFileName +| summarize RemotePortCount=dcount(RemotePort) by DeviceName, RemoteIP, InitiatingProcessFileName | where RemotePortCount > remotePortCountThreshold diff --git a/Discovery/Enumeration of users & groups for lateral movement.txt b/Discovery/Enumeration of users & groups for lateral movement.txt index c1e61d01..74882eef 100644 --- a/Discovery/Enumeration of users & groups for lateral movement.txt +++ b/Discovery/Enumeration of users & groups for lateral movement.txt @@ -1,8 +1,8 @@ // The query finds attempts to list users or groups using Net commands -ProcessCreationEvents -| where EventTime > ago(14d) +DeviceProcessEvents +| where Timestamp > ago(14d) | where FileName == 'net.exe' and AccountName != "" and ProcessCommandLine !contains '\\' and ProcessCommandLine !contains '/add' | where (ProcessCommandLine contains ' user ' or ProcessCommandLine contains ' group ') and (ProcessCommandLine endswith ' /do' or ProcessCommandLine endswith ' /domain') | extend Target = extract("(?i)[user|group] (\"*[a-zA-Z0-9-_ ]+\"*)", 1, ProcessCommandLine) | filter Target != '' -| project AccountName, Target, ProcessCommandLine, ComputerName, EventTime +| project AccountName, Target, ProcessCommandLine, DeviceName, Timestamp | sort by AccountName, Target \ No newline at end of file diff --git a/Discovery/SMB shares discovery.txt b/Discovery/SMB shares discovery.txt index 9d8c8cfe..1574a8f1 100644 --- a/Discovery/SMB shares discovery.txt +++ b/Discovery/SMB shares discovery.txt @@ -1,14 +1,14 @@ // Query for processes that accessed more than 10 IP addresses over port 445 (SMB) - possibly scanning for network shares. // To read more about Network Share Discovery, see: https://attack.mitre.org/wiki/Technique/T1135 // Tags: #SMB, #NetworkScanning, #UniqueProcessId -NetworkCommunicationEvents -| where RemotePort == 445 and EventTime > ago(7d) +DeviceNetworkEvents +| where RemotePort == 445 and Timestamp > ago(7d) // Exclude Kernel processes, as they are too noisy in this query and InitiatingProcessId !in (0, 4) -| summarize RemoteIPCount=dcount(RemoteIP) by ComputerName, InitiatingProcessFileName, InitiatingProcessId, InitiatingProcessCreationTime +| summarize RemoteIPCount=dcount(RemoteIP) by DeviceName, InitiatingProcessFileName, InitiatingProcessId, InitiatingProcessCreationTime | where RemoteIPCount > 10 // Implementation comment: // Process IDs are recycled and reused, so are not a unique identifier for a process. -// For this reason we use a combination of ProcessId and ProcessCreationTime together with the ComputerName or MachineId. +// For this reason we use a combination of ProcessId and ProcessCreationTime together with the DeviceName or DeviceId. // Read more here: https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-atp/advanced-hunting-best-practices-windows-defender-advanced-threat-protection diff --git a/Discovery/URL Detection.txt b/Discovery/URL Detection.txt index b35cc6c8..5c9259a0 100644 --- a/Discovery/URL Detection.txt +++ b/Discovery/URL Detection.txt @@ -2,8 +2,8 @@ // Please note that in line #7 it filters RemoteUrl using has operator, which looks for a "whole term" and runs faster. // Example: RemoteUrl has "microsoft" matches "www.microsoft.com" but not "microsoftonline.com" let partialRemoteUrlToDetect = "microsoft.com"; // Change this to a URL you'd like to find machines connecting to -NetworkCommunicationEvents -| where EventTime > ago(7d) +DeviceNetworkEvents +| where Timestamp > ago(7d) and RemoteUrl has partialRemoteUrlToDetect // Can be changed to "contains" operator as explained above -| project EventTime, ComputerName, MachineId, ReportId -| top 100 by EventTime desc +| project Timestamp, DeviceName, DeviceId, ReportId +| top 100 by Timestamp desc diff --git a/Execution/Base64encodePEFile.txt b/Execution/Base64encodePEFile.txt index 0c3d2cdc..c4bd5fa8 100644 --- a/Execution/Base64encodePEFile.txt +++ b/Execution/Base64encodePEFile.txt @@ -1,6 +1,6 @@ // Finding base64 encoded PE files header seen in the command line parameters // Tags: #fileLess #powershell -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where ProcessCommandLine contains "TVqQAAMAAAAEAAA" -| top 1000 by EventTime +| top 1000 by Timestamp diff --git a/Execution/ExecuteBase64DecodedPayload.txt b/Execution/ExecuteBase64DecodedPayload.txt index 0f7f8ee2..af6be181 100644 --- a/Execution/ExecuteBase64DecodedPayload.txt +++ b/Execution/ExecuteBase64DecodedPayload.txt @@ -3,13 +3,13 @@ // The first and second ProcessCommandLine component is looking for Python decoding base64 // The third ProcesssCommandLine component is looking for the Bash/sh commandline base64 decoding tool // The fourth one is looking for Ruby decoding base64 -ProcessCreationEvents -| where EventTime > ago(14d) +DeviceProcessEvents +| where Timestamp > ago(14d) | where ProcessCommandLine contains ".decode('base64')" or ProcessCommandLine contains ".b64decode(" or ProcessCommandLine contains "base64 --decode" or ProcessCommandLine contains ".decode64(" -| project EventTime , ComputerName , FileName , FolderPath , ProcessCommandLine , InitiatingProcessCommandLine -| top 100 by EventTime +| project Timestamp , DeviceName , FileName , FolderPath , ProcessCommandLine , InitiatingProcessCommandLine +| top 100 by Timestamp diff --git a/Execution/Malware_In_recyclebin.txt b/Execution/Malware_In_recyclebin.txt index fc3f8f51..9b8f23c1 100644 --- a/Execution/Malware_In_recyclebin.txt +++ b/Execution/Malware_In_recyclebin.txt @@ -1,8 +1,8 @@ // Finding attackers hiding malware in the recycle bin. // Read more here: https://azure.microsoft.com/en-us/blog/how-azure-security-center-helps-reveal-a-cyberattack/ // Tags: #execution #SuspiciousPath -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where FileName in~('cmd.exe','ftp.exe','schtasks.exe','powershell.exe','rundll32.exe','regsvr32.exe','msiexec.exe') | where ProcessCommandLine contains ":\\recycler" -| project EventTime, ComputerName, ProcessCommandLine, InitiatingProcessFileName +| project Timestamp, DeviceName, ProcessCommandLine, InitiatingProcessFileName diff --git a/Execution/PowerShell downloads.txt b/Execution/PowerShell downloads.txt index 7515f6cd..39a30afc 100644 --- a/Execution/PowerShell downloads.txt +++ b/Execution/PowerShell downloads.txt @@ -1,6 +1,6 @@ // Finds PowerShell execution events that could involve a download. -ProcessCreationEvents -| where EventTime > ago(7d) +DeviceProcessEvents +| where Timestamp > ago(7d) | where FileName in~ ("powershell.exe", "powershell_ise.exe") | where ProcessCommandLine has "Net.WebClient" or ProcessCommandLine has "DownloadFile" @@ -8,5 +8,5 @@ ProcessCreationEvents or ProcessCommandLine has "Invoke-Shellcode" or ProcessCommandLine contains "http:" or ProcessCommandLine has "IEX" -| project EventTime, ComputerName, InitiatingProcessFileName, FileName, ProcessCommandLine -| top 100 by EventTime +| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine +| top 100 by Timestamp diff --git a/Execution/PowershellCommand - uncommon commands on machine.txt b/Execution/PowershellCommand - uncommon commands on machine.txt index b91087ec..532fad76 100644 --- a/Execution/PowershellCommand - uncommon commands on machine.txt +++ b/Execution/PowershellCommand - uncommon commands on machine.txt @@ -1,18 +1,18 @@ // Find which uncommon Powershell Cmdlets were executed on that machine in a certain time period. // This covers all Powershell commands executed in the Powershell engine by any process. -let machineId = "474908f457a1dc4c1fab568f808d5f77bf3bb951"; +let DeviceId = "474908f457a1dc4c1fab568f808d5f77bf3bb951"; let timestamp = datetime(2018-06-09T02:23:26.6832917Z); // Query for Powershell cmdlets let powershellCommands = - MiscEvents + DeviceEvents | where ActionType == "PowerShellCommand" // Extract the powershell command name from the Command field in the AdditionalFields JSON column - | project PowershellCommand=extractjson("$.Command", AdditionalFields, typeof(string)), InitiatingProcessCommandLine, InitiatingProcessParentFileName, EventTime, MachineId + | project PowershellCommand=extractjson("$.Command", AdditionalFields, typeof(string)), InitiatingProcessCommandLine, InitiatingProcessParentFileName, Timestamp, DeviceId | where PowershellCommand !endswith ".ps1" and PowershellCommand !endswith ".exe"; // Filter Powershell cmdlets executed on relevant machine and time period -powershellCommands | where MachineId == machineId and EventTime between ((timestamp-5min) .. 10min) +powershellCommands | where DeviceId == DeviceId and Timestamp between ((timestamp-5min) .. 10min) // Filter out common powershell cmdlets -| join kind=leftanti (powershellCommands | summarize MachineCount=dcount(MachineId) by PowershellCommand | where MachineCount > 20) on PowershellCommand +| join kind=leftanti (powershellCommands | summarize MachineCount=dcount(DeviceId) by PowershellCommand | where MachineCount > 20) on PowershellCommand // To learn more about queries on Powershell commands, take a look this post: https://techcommunity.microsoft.com/t5/Threat-Intelligence/Hunting-tip-of-the-month-PowerShell-commands/m-p/210898#M30 // Related queries: diff --git a/Execution/PowershellCommand footprint.txt b/Execution/PowershellCommand footprint.txt index cfdfefa8..f7d3ed2d 100644 --- a/Execution/PowershellCommand footprint.txt +++ b/Execution/PowershellCommand footprint.txt @@ -1,12 +1,12 @@ // Find all machines running a given Powersehll cmdlet. // This covers all Powershell commands executed in the Powershell engine by any process. let powershellCommandName = "Invoke-RickAscii"; -MiscEvents +DeviceEvents | where ActionType == "PowerShellCommand" // This filter improves query performance, as it avoids needing to parse Command from all rows and only then applying a filter | where AdditionalFields contains powershellCommandName // Extract the powershell command name from the Command field in the AdditionalFields JSON column -| project PowershellCommand=extractjson("$.Command", AdditionalFields, typeof(string)), InitiatingProcessCommandLine, InitiatingProcessParentFileName, EventTime, MachineId +| project PowershellCommand=extractjson("$.Command", AdditionalFields, typeof(string)), InitiatingProcessCommandLine, InitiatingProcessParentFileName, Timestamp, DeviceId // Do an exact case-insensitive match on the command name field | where PowershellCommand =~ powershellCommandName diff --git a/Exfiltration/Map external devices.txt b/Exfiltration/Map external devices.txt index 0d8b6ac8..165358f2 100644 --- a/Exfiltration/Map external devices.txt +++ b/Exfiltration/Map external devices.txt @@ -2,10 +2,10 @@ // read more online on event 6416: https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-6416 // Query #1: look for rare one-time devices connected to a specific machine -let computerNameParam = ""; +let DeviceNameParam = ""; // Query for device connection events let devices = - MiscEvents + DeviceEvents | where ActionType == "PnpDeviceConnected" | extend parsed=parse_json(AdditionalFields) | project @@ -13,11 +13,11 @@ let devices = ClassName=tostring(parsed.ClassName), DeviceId=tostring(parsed.VendorIds), VendorIds=tostring(parsed.VendorIds), - ComputerName, EventTime ; + DeviceName, Timestamp ; // Filter devices seen on the suspected machine -devices | where ComputerName == computerNameParam +devices | where DeviceName == DeviceNameParam // Get some stats on the device connections to that machine -| summarize TimesConnected=count(), FirstTime=min(EventTime), LastTime=max(EventTime) by DeviceId, DeviceDescription, ClassName, VendorIds, ComputerName +| summarize TimesConnected=count(), FirstTime=min(Timestamp), LastTime=max(Timestamp) by DeviceId, DeviceDescription, ClassName, VendorIds, DeviceName // Optional filter - looking for devices used in only within 24h | where LastTime - FirstTime < 1d // Filter out (antijoin) devices that are common in the organization. @@ -25,12 +25,12 @@ devices | where ComputerName == computerNameParam // So, a specific disk-on-key device which model is common in the org will still be shown in the results, // while built-in software devices (often have constant device ID) as well as common network devices (e.g. printer queues) will be excluded. | join kind=leftanti - (devices | summarize Machines=dcount(ComputerName) by DeviceId, DeviceDescription, VendorIds | where Machines > 5) + (devices | summarize Machines=dcount(DeviceName) by DeviceId, DeviceDescription, VendorIds | where Machines > 5) on DeviceId, DeviceDescription, VendorIds // Query #2: map uncommon storage devices across the org // This is a noisy query - but it can serve as reference for working with this event -MiscEvents +DeviceEvents | where ActionType == "PnpDeviceConnected" | extend parsed=parse_json(AdditionalFields) | extend @@ -41,5 +41,5 @@ MiscEvents or ClassName contains "nas" or ClassName contains "SCSI" or (ClassName == "USB" and DeviceDescription contains "storage") -| summarize ComputerCount=dcount(ComputerName) by ClassName, DeviceDescription +| summarize ComputerCount=dcount(DeviceName) by ClassName, DeviceDescription | where ComputerCount < 5 diff --git a/Exploits/AcroRd-Exploits.txt b/Exploits/AcroRd-Exploits.txt index ac226826..1c7db06e 100644 --- a/Exploits/AcroRd-Exploits.txt +++ b/Exploits/AcroRd-Exploits.txt @@ -2,8 +2,8 @@ // Search for persistence in Statup folder that's done by Adobe Acrobat Reader. // Normally, this behavior is not expected. -FileCreationEvents +DeviceFileEvents | where InitiatingProcessFileName =~ "acrord32.exe" and FolderPath contains "\\Start Menu\\Programs\\Startup" -| project FolderPath, ComputerName, EventTime, FileName, InitiatingProcessCommandLine, SHA1 +| project FolderPath, DeviceName, Timestamp, FileName, InitiatingProcessCommandLine, SHA1 diff --git a/Exploits/Electron-CVE-2018-1000006.txt b/Exploits/Electron-CVE-2018-1000006.txt index e70dbcf4..0d61cd42 100644 --- a/Exploits/Electron-CVE-2018-1000006.txt +++ b/Exploits/Electron-CVE-2018-1000006.txt @@ -12,9 +12,9 @@ // // Tags: #exploit #CVE-2018-1000006 #Electron -ProcessCreationEvents -| where EventTime > ago(14d) +DeviceProcessEvents +| where Timestamp > ago(14d) | where FileName in~ ("code.exe", "skype.exe", "slack.exe", "teams.exe") | where InitiatingProcessFileName in~ ("iexplore.exe", "runtimebroker.exe", "chrome.exe") | where ProcessCommandLine has "--gpu-launcher" -| summarize FirstEvent=min(EventTime), LastEvent=max(EventTime) by ComputerName, ProcessCommandLine, FileName, InitiatingProcessFileName +| summarize FirstEvent=min(Timestamp), LastEvent=max(Timestamp) by DeviceName, ProcessCommandLine, FileName, InitiatingProcessFileName diff --git a/Exploits/Flash-CVE-2018-4848.txt b/Exploits/Flash-CVE-2018-4848.txt index 57ea8bfb..1cad711a 100644 --- a/Exploits/Flash-CVE-2018-4848.txt +++ b/Exploits/Flash-CVE-2018-4848.txt @@ -9,9 +9,9 @@ // // Tags: #exploit #CVE-2018-4878 #0day #Korea #Flash -NetworkCommunicationEvents -| where EventTime > ago(14d) +DeviceNetworkEvents +| where Timestamp > ago(14d) | where InitiatingProcessFileName =~ "cmd.exe" and InitiatingProcessParentFileName =~ "excel.exe" | where RemoteUrl endswith ".kr" -| project EventTime, ComputerName, RemoteIP, RemoteUrl -| top 100 by EventTime +| project Timestamp, DeviceName, RemoteIP, RemoteUrl +| top 100 by Timestamp diff --git a/Exploits/Linux-DynoRoot-CVE-2018-1111.txt b/Exploits/Linux-DynoRoot-CVE-2018-1111.txt index cf63a92a..cf361547 100644 --- a/Exploits/Linux-DynoRoot-CVE-2018-1111.txt +++ b/Exploits/Linux-DynoRoot-CVE-2018-1111.txt @@ -10,9 +10,9 @@ // // Tags: #exploit #CVE-2018-1111 #DynoRoot -ProcessCreationEvents +DeviceProcessEvents | where InitiatingProcessCommandLine contains "/etc/NetworkManager/dispatcher.d/" and InitiatingProcessCommandLine contains "-dhclient" and isnotempty(ProcessCommandLine) and FileName !endswith ".exe" -| project EventTime, ComputerName , FileName, ProcessCommandLine, InitiatingProcessCommandLine +| project Timestamp, DeviceName , FileName, ProcessCommandLine, InitiatingProcessCommandLine diff --git a/Fun/EmojiHunt.txt b/Fun/EmojiHunt.txt index bb1ce599..43d157df 100644 --- a/Fun/EmojiHunt.txt +++ b/Fun/EmojiHunt.txt @@ -4,7 +4,7 @@ // You might be amused by the results, or perhaps angry if one of your systems or scripts was broken by this... // Note: this query will also return some machines with non-English charcters that are not Emojis // Credit for this query goes to miflower - thanks for bringing joy to our lives! :) -ProcessCreationEvents -| distinct ComputerName -| extend fakeescape=replace("%5f", "_", replace("%2d", "-", url_encode(ComputerName))) -| where fakeescape != ComputerName +DeviceProcessEvents +| distinct DeviceName +| extend fakeescape=replace("%5f", "_", replace("%2d", "-", url_encode(DeviceName))) +| where fakeescape != DeviceName diff --git a/General queries/Alert Events from Internal IP Address.txt b/General queries/Alert Events from Internal IP Address.txt index b3cf6a26..ca6c4fa8 100644 --- a/General queries/Alert Events from Internal IP Address.txt +++ b/General queries/Alert Events from Internal IP Address.txt @@ -1,17 +1,17 @@ -// Determines MachineId from internal IP address and outputs all alerts in events table associated to the MachineId +// Determines DeviceId from internal IP address and outputs all alerts in events table associated to the DeviceId // Example use case is Firewall determines Internal IP with suspicious network activity. Query WDATP based on date/time and Internal IP and see associated alerts for the endpoint. let PivotTime = datetime(2018-08-02 20:57:02); //Fill out time let TimeRangeStart = PivotTime-15m; // 15 Minutes Prior to Pivot Time let TimeRangeEnd = PivotTime+15m; // 15 Minutes After Pivot Time let IPAddress = "10.0.0.5"; // internal IP address to search -// Locate MachineIDs associated with IP -let FindMachineIDbyIP = MachineNetworkInfo -| where EventTime between ((TimeRangeStart) ..TimeRangeEnd) +// Locate DeviceIds associated with IP +let FindDeviceIdbyIP = DeviceNetworkInfo +| where Timestamp between ((TimeRangeStart) ..TimeRangeEnd) and IPAddresses contains strcat("\"", IPAddress, "\"") and NetworkAdapterStatus == "Up" -| project ComputerName, MachineId, EventTime, IPAddresses; -// Query Alerts matching MachineIDs -AlertEvents -| join kind = leftsemi FindMachineIDbyIP on MachineId +| project DeviceName, DeviceId, Timestamp, IPAddresses; +// Query Alerts matching DeviceIds +DeviceAlertEvents +| join kind = leftsemi FindDeviceIdbyIP on DeviceId // Summarizes alerts by AlertId with min and max event times -| summarize Title=any(Title), min(EventTime), max(EventTime), ComputerName=any(ComputerName) by AlertId +| summarize Title=any(Title), min(Timestamp), max(Timestamp), DeviceName=any(DeviceName) by AlertId diff --git a/General queries/Baseline Comparison.txt b/General queries/Baseline Comparison.txt index a4b7f54f..a7c2dc10 100644 --- a/General queries/Baseline Comparison.txt +++ b/General queries/Baseline Comparison.txt @@ -5,22 +5,22 @@ // It brings deltas between a baseline and another machine quickly to the analyst's view // This query supports multiple suspected bad machines and multiple "known good" machines // It also supports providing a timeframe for how far back in time to build a baseline as well as how far back in time to evaluate the suspected bad machines -// Each of the links provided by machineid/computername will go to the most recent entry for whatever entity is listed +// Each of the links provided by DeviceId/DeviceName will go to the most recent entry for whatever entity is listed // Average results for the pre-defined settings below with a single good host and a single bad host on a 'huge' tenant (300k+ machines): // Compute Time: ~10-20 seconds // Result Set Size: ~500 rows // The workflow is as follows: // 1. Establish Variables that are editable on a per-query basis // 2. Define functions for reuse -// 3. Calculate MachineIds for all machines in scope +// 3. Calculate DeviceIds for all machines in scope // 4. Derive deltas using the aforementioned functions // 5. Union together all results into a single view // The following datasets are returned: // 1. Alerts on the suspected bad machines (ignores known good machines, because...they're alerts, additional data has the triggered file) -// 2. Connected Networks (from MachineNetworkInfo table, additional data has full Connected Network details) +// 2. Connected Networks (from DeviceNetworkInfo table, additional data has full Connected Network details) // 3. File Creations (disabled by default due to volume, enable at your own risk, additional data has initiating processes) // 4. Image Loads (disabled by default due to volume, enable at your own risk, additional data has initiating processes) -// 5. Logon (derived from LogonEvents for the unique users logged on, additional data has logon types) +// 5. Logon (derived from DeviceLogonEvents for the unique users logged on, additional data has logon types) // 6. Network communication (grouped by 2nd level-domain, ie 'microsoft.com' in 'www.microsoft.com' and 'web.microsoft.com', additional data has the full list of URLs) // 7. Process creation (additional data has the full paths of the files) // 8. Powershell Commands (grouped by the cmdlet that was ran, additional data has the processes that ran the cmdlet) @@ -50,213 +50,213 @@ let ReturnSets=pack_array( ); // -------------End of variables, changing below this line will change query logic---------- // Function to get a mapping of machine IDs given a list of computer names -let GetMachineId=(InComputerName: dynamic) { - MachineInfo - | where ComputerName in~ (InComputerName) - | distinct ComputerName, MachineId +let GetDeviceId=(InDeviceName: dynamic) { + DeviceInfo + | where DeviceName in~ (InDeviceName) + | distinct DeviceName, DeviceId }; // Function to consolidate all machine IDs into a single set -let ConsolidateMachineId=(T:(MachineId: string)) { +let ConsolidateDeviceId=(T:(DeviceId: string)) { T - | summarize makeset(MachineId) + | summarize makeset(DeviceId) }; // Function to get network communications given a list of computer names and how far back to look -let GetNetworkEvents=(InMachineId: dynamic, LeftEventTime: datetime) { - NetworkCommunicationEvents +let GetNetworkEvents=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceNetworkEvents | where "Network Communication" in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) | where isnotempty(RemoteUrl) - | summarize EventTime=max(EventTime), count() by RemoteUrl, MachineId + | summarize Timestamp=max(Timestamp), count() by RemoteUrl, DeviceId | extend UrlSplit=split(RemoteUrl, ".") // Split the levels of the URL // If there is only one level (for an internal communication that uses your DNS search suffix), then only use that level // Otherwise combine the top two levels and use those as the URLRoot | extend UrlRoot=iff(UrlSplit[-2] == "", UrlSplit[0], strcat(tostring(UrlSplit[-2]), ".", tostring(UrlSplit[-1]))) - | summarize EventTime=max(EventTime), Count=sum(count_), AdditionalData=makeset(RemoteUrl, 5) by UrlRoot, MachineId - | project EventTime, Entity=UrlRoot, Count, AdditionalData=tostring(AdditionalData), MachineId, DataType="Network Communication" + | summarize Timestamp=max(Timestamp), Count=sum(count_), AdditionalData=makeset(RemoteUrl, 5) by UrlRoot, DeviceId + | project Timestamp, Entity=UrlRoot, Count, AdditionalData=tostring(AdditionalData), DeviceId, DataType="Network Communication" }; // Function to get process creates given a list of computer names and how far back to look -let GetProcessCreates=(InMachineId: dynamic, LeftEventTime: datetime) { - ProcessCreationEvents +let GetProcessCreates=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceProcessEvents | where "Process Creation" in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) // Replace known path for mpam files as they are dynamically named and likely to be unique on each machine | extend FileName=iff(FolderPath matches regex @"([A-Z]:\\Windows\\ServiceProfiles\\NetworkService\\AppData\\Local\\Temp\\mpam-)[a-z0-9]{7,8}\.exe", "mpam-RANDOM.exe", FileName) // Replace known path for AM delta patch files as they jump frequently and not likely to be exact on each machine | extend FileName=iff(FolderPath matches regex @"([A-Z]:\\Windows\\SoftwareDistribution\\Download\\Install\\AM_Delta_Patch_)[0-9\.]+\.exe", "AM_Delta_Patch_Version.exe", FileName) - | summarize EventTime=max(EventTime), Count=count(), AdditionalData=makeset(FolderPath) by FileName, MachineId + | summarize Timestamp=max(Timestamp), Count=count(), AdditionalData=makeset(FolderPath) by FileName, DeviceId // Replace various mbam executables that are semiunique-generated with some text to help reduce noise - | project EventTime, Entity=FileName, Count, AdditionalData=tostring(AdditionalData), MachineId, DataType="Process Creation" + | project Timestamp, Entity=FileName, Count, AdditionalData=tostring(AdditionalData), DeviceId, DataType="Process Creation" }; // Function to get powershell commands given a list of computer names and how far back to look -let GetPSCommands=(InMachineId: dynamic, LeftEventTime: datetime) { - MiscEvents +let GetPSCommands=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceEvents | where "PowerShell Command" in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) | where ActionType == 'PowerShellCommand' // Remove two different signatures for scripts being executed which cause a lot of noise // The first signature matches scripts generated as part of testing execution policy // The second signature matches scripts generated by SCCM | where not(AdditionalFields matches regex @"Script_[0-9a-f]{20}" and InitiatingProcessFileName =~ 'monitoringhost.exe') | where not(AdditionalFields matches regex @"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.ps1" and InitiatingProcessFileName =~ 'powershell.exe') - | summarize EventTime=max(EventTime), count(), IPFN_Set=makeset(InitiatingProcessFileName) by AdditionalFields, MachineId - | project EventTime, Entity=tostring(extractjson("$.Command", AdditionalFields)), Count=count_, AdditionalData=tostring(IPFN_Set), MachineId, DataType="PowerShell Command" + | summarize Timestamp=max(Timestamp), count(), IPFN_Set=makeset(InitiatingProcessFileName) by AdditionalFields, DeviceId + | project Timestamp, Entity=tostring(extractjson("$.Command", AdditionalFields)), Count=count_, AdditionalData=tostring(IPFN_Set), DeviceId, DataType="PowerShell Command" }; // Function to get file creations given a list of computer names and how far back to look -let GetFileCreates=(InMachineId: dynamic, LeftEventTime: datetime) { - FileCreationEvents +let GetFileCreates=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceFileEvents | where "File Creation" in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) // Remove temporary files created by office products | where not(FileName matches regex @"~.*\.(doc[xm]?|ppt[xm]?|xls[xm]?|dotm|rtf|xlam|lnk)") // Replace two different signatures for PS scripts being created which cause a lot of noise | extend iff(FileName matches regex @"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.ps1" or FileName matches regex @"[0-9a-z]{8}\.[0-9a-z]{3}\.ps1", "RANDOM.ps1", FileName) - | summarize EventTime=max(EventTime), FP_Set=makeset(FolderPath), count() by FileName, MachineId - | project EventTime, Entity=FileName, Count=count_, AdditionalData=tostring(FP_Set), MachineId, DataType="File Creation" + | summarize Timestamp=max(Timestamp), FP_Set=makeset(FolderPath), count() by FileName, DeviceId + | project Timestamp, Entity=FileName, Count=count_, AdditionalData=tostring(FP_Set), DeviceId, DataType="File Creation" }; // Function to get logon events given a list of computer names and how far back to look -let GetLogonEvents=(InMachineId: dynamic, LeftEventTime: datetime) { - LogonEvents +let GetDeviceLogonEvents=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceLogonEvents | where "Logon" in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) // Remove logons made by WDM or UMFD | where AccountDomain !in ('font driver host', 'window manager') - | summarize EventTime=max(EventTime), Count=count(), LT_Set=makeset(LogonType) by AccountName, AccountDomain, MachineId - | project EventTime, Entity=iff(AccountDomain == "", AccountName, strcat(AccountDomain, @"\", AccountName)), Count, AdditionalData=tostring(LT_Set), MachineId, DataType="Logon" + | summarize Timestamp=max(Timestamp), Count=count(), LT_Set=makeset(LogonType) by AccountName, AccountDomain, DeviceId + | project Timestamp, Entity=iff(AccountDomain == "", AccountName, strcat(AccountDomain, @"\", AccountName)), Count, AdditionalData=tostring(LT_Set), DeviceId, DataType="Logon" }; // Function to get registry events given a list of computer names and how far back to look -let GetRegistryEvents=(InMachineId: dynamic, LeftEventTime: datetime) { - RegistryEvents +let GetDeviceRegistryEvents=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceRegistryEvents | where "Registry Event" in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) | extend RegistryKey=iff(RegistryKey matches regex @"HKEY_CURRENT_USER\\S-[^\\]+\\", replace(@"(HKEY_CURRENT_USER\\)S-[^\\]+\\", @"\1SID\\", RegistryKey), RegistryKey) - | summarize EventTime=max(EventTime), RVD_Set=makeset(RegistryValueData), Count=count() by MachineId, RegistryKey - | project EventTime, Entity=RegistryKey, Count, AdditionalData=tostring(RVD_Set), MachineId, DataType="Registry Event" + | summarize Timestamp=max(Timestamp), RVD_Set=makeset(RegistryValueData), Count=count() by DeviceId, RegistryKey + | project Timestamp, Entity=RegistryKey, Count, AdditionalData=tostring(RVD_Set), DeviceId, DataType="Registry Event" }; // Function to get connected networks given a list of computer names and how far back to look -let GetConnectedNetworks=(InMachineId: dynamic, LeftEventTime: datetime) { - MachineNetworkInfo +let GetConnectedNetworks=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceNetworkInfo | where "Connected Networks" in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) - | summarize EventTime=max(EventTime), Count=count() by MachineId, ConnectedNetworks - | project EventTime, Entity=tostring(extractjson("$[0].Name", ConnectedNetworks)), Count, AdditionalData=ConnectedNetworks, MachineId, DataType="Connected Networks" + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) + | summarize Timestamp=max(Timestamp), Count=count() by DeviceId, ConnectedNetworks + | project Timestamp, Entity=tostring(extractjson("$[0].Name", ConnectedNetworks)), Count, AdditionalData=ConnectedNetworks, DeviceId, DataType="Connected Networks" }; // Function to get image load events given a list of computer names and how far back to look -let GetImageLoads=(InMachineId: dynamic, LeftEventTime: datetime) { - ImageLoadEvents +let GetImageLoads=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceImageLoadEvents | where "Image Loads" in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) - | summarize EventTime=max(EventTime), Set_FN=makeset(InitiatingProcessFileName), Count=count() by MachineId, FolderPath + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) + | summarize Timestamp=max(Timestamp), Set_FN=makeset(InitiatingProcessFileName), Count=count() by DeviceId, FolderPath // Replace various native windows DLL's that are guid-generated with some text to help reduce noise | extend Entity=replace(@"([wW]indows\\assembly\\NativeImages.*\\)[0-9a-f]{32}", @"\1GUID", FolderPath) - | project EventTime, Entity, Count, AdditionalData=tostring(Set_FN), MachineId, DataType="Image Loads" + | project Timestamp, Entity, Count, AdditionalData=tostring(Set_FN), DeviceId, DataType="Image Loads" }; // Function to get raw IP address network communications given a list of computer names and how far back to look -let GetRawIPCommunications=(InMachineId: dynamic, LeftEventTime: datetime) { - NetworkCommunicationEvents +let GetRawIPCommunications=(InDeviceId: dynamic, LeftTimestamp: datetime) { + DeviceNetworkEvents | where 'Raw IP Communication' in (ReturnSets) - | where EventTime > LeftEventTime - | where MachineId in~ (InMachineId) + | where Timestamp > LeftTimestamp + | where DeviceId in~ (InDeviceId) // Replace all v4 to v6 addresses with their v4 equivalent | extend RemoteIP=replace("^::ffff:", "", RemoteIP) - | summarize EventTime=max(EventTime), Set_RPort=makeset(RemotePort), Set_LPort=makeset(LocalPort), Set_FN=makeset(InitiatingProcessFileName), Set_URL=makeset(RemoteUrl), Count=count() by MachineId, RemoteIP + | summarize Timestamp=max(Timestamp), Set_RPort=makeset(RemotePort), Set_LPort=makeset(LocalPort), Set_FN=makeset(InitiatingProcessFileName), Set_URL=makeset(RemoteUrl), Count=count() by DeviceId, RemoteIP // Only include any IP addresses that do not have a resolved URL as resolved URLs are handled in network communications | where tostring(Set_URL) == '[""]' // Do not include machines that are only doing WUDO | where tostring(Set_RPort) != '[7680]' and tostring(Set_RPort) != '[7680]' - | project EventTime, Entity=RemoteIP, Count, AdditionalData=tostring(Set_FN), MachineId, DataType='Raw IP Communication' + | project Timestamp, Entity=RemoteIP, Count, AdditionalData=tostring(Set_FN), DeviceId, DataType='Raw IP Communication' }; // Calculate the left event time for "good" machines -let GoodLeftEventTime=ago(GoodTimeRange); +let GoodLeftTimestamp=ago(GoodTimeRange); // Calculate the left event time for suspected bad machines -let SuspectedBadLeftEventTime=ago(SuspectedBadTimeRange); +let SuspectedBadLeftTimestamp=ago(SuspectedBadTimeRange); // Calculate the machine IDs for "good" machines -let GoodHostNameMapping=GetMachineId(GoodHosts); +let GoodHostNameMapping=GetDeviceId(GoodHosts); // Reduce all of the good machine IDs into a single variable -let GoodHostMachineId=toscalar(ConsolidateMachineId(GoodHostNameMapping)); +let GoodHostDeviceId=toscalar(ConsolidateDeviceId(GoodHostNameMapping)); // Calculate the machine IDs for suspected bad machines -let SuspectedBadHostNameMapping=GetMachineId(SuspectedBadHosts); +let SuspectedBadHostNameMapping=GetDeviceId(SuspectedBadHosts); // Reduce all of the suspected bad machine IDs into a single variable -let SuspectedBadHostMachineId=toscalar(ConsolidateMachineId(SuspectedBadHostNameMapping)); +let SuspectedBadHostDeviceId=toscalar(ConsolidateDeviceId(SuspectedBadHostNameMapping)); // Calculate the delta in network events, keeping the bad ones -let NetworkDelta=GetNetworkEvents(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let NetworkDelta=GetNetworkEvents(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetNetworkEvents(GoodHostMachineId, GoodLeftEventTime) + GetNetworkEvents(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Calculate the delta in process create events, keeping the bad ones -let ProcessDelta=GetProcessCreates(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let ProcessDelta=GetProcessCreates(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetProcessCreates(GoodHostMachineId, GoodLeftEventTime) + GetProcessCreates(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Calculate the delta in powershell events, keeping the bad ones -let PSDelta=GetPSCommands(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let PSDelta=GetPSCommands(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetPSCommands(GoodHostMachineId, GoodLeftEventTime) + GetPSCommands(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Calculate the delta in file create events, keeping the bad ones -let FileDelta=GetFileCreates(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let FileDelta=GetFileCreates(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetFileCreates(GoodHostMachineId, GoodLeftEventTime) + GetFileCreates(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Calculate the delta in logon events, keeping the bad ones -let LogonDelta=GetLogonEvents(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let LogonDelta=GetDeviceLogonEvents(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetLogonEvents(GoodHostMachineId, GoodLeftEventTime) + GetDeviceLogonEvents(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Calculate the delta in registry events, keeping the bad ones -let RegistryDelta=GetRegistryEvents(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let RegistryDelta=GetDeviceRegistryEvents(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetRegistryEvents(GoodHostMachineId, GoodLeftEventTime) + GetDeviceRegistryEvents(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Calculate the delta in connected network events, keeping the bad ones -let ConnectedNetworkDelta=GetConnectedNetworks(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let ConnectedNetworkDelta=GetConnectedNetworks(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetConnectedNetworks(GoodHostMachineId, GoodLeftEventTime) + GetConnectedNetworks(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Calculate the delta in image load events, keeping the bad ones -let ImageLoadDelta=GetImageLoads(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let ImageLoadDelta=GetImageLoads(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetImageLoads(GoodHostMachineId, GoodLeftEventTime) + GetImageLoads(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Calculate the delta in raw IP address communications, keeping the bad ones -let RawIPCommunicationDelta=GetRawIPCommunications(SuspectedBadHostMachineId, SuspectedBadLeftEventTime) +let RawIPCommunicationDelta=GetRawIPCommunications(SuspectedBadHostDeviceId, SuspectedBadLeftTimestamp) | join kind=leftanti ( - GetRawIPCommunications(GoodHostMachineId, GoodLeftEventTime) + GetRawIPCommunications(GoodHostDeviceId, GoodLeftTimestamp) ) on Entity; // Get the alerts for the bad machines (no delta, we care about all alerts) -let Alerts=AlertEvents +let Alerts=DeviceAlertEvents | where "Alert" in (ReturnSets) -| where EventTime > SuspectedBadLeftEventTime -| where MachineId in (SuspectedBadHostMachineId) -| summarize EventTime=max(EventTime), Count=count() by Title, MachineId, FileName, RemoteUrl -| project EventTime, Entity=Title, Count, AdditionalData=coalesce(FileName, RemoteUrl), MachineId, DataType="Alert"; +| where Timestamp > SuspectedBadLeftTimestamp +| where DeviceId in (SuspectedBadHostDeviceId) +| summarize Timestamp=max(Timestamp), Count=count() by Title, DeviceId, FileName, RemoteUrl +| project Timestamp, Entity=Title, Count, AdditionalData=coalesce(FileName, RemoteUrl), DeviceId, DataType="Alert"; // String everything together let ResultDataWithoutMachineCount=union NetworkDelta, ProcessDelta, PSDelta, FileDelta, Alerts, LogonDelta, RegistryDelta, ConnectedNetworkDelta, ImageLoadDelta, RawIPCommunicationDelta // Join back against the machine info so the Computer Names can be reassociated | join kind=leftouter ( SuspectedBadHostNameMapping -) on MachineId +) on DeviceId // Remove duplicated column -| project-away MachineId1; +| project-away DeviceId1; // This is the start of the final result set that is shown // Calculate the number of machines that each entity/datatype pair have and join that data back into the data to add // an additional column for the number of bad machines ResultDataWithoutMachineCount | join kind=leftouter ( ResultDataWithoutMachineCount - | summarize BadMachinesCount=dcount(MachineId) by Entity, DataType + | summarize BadMachinesCount=dcount(DeviceId) by Entity, DataType ) on Entity, DataType // Remove duplicated columns | project-away Entity1, DataType1 // and sort by Machine, DataType, Entity -| order by BadMachinesCount desc, MachineId asc, DataType asc, Entity asc +| order by BadMachinesCount desc, DeviceId asc, DataType asc, Entity asc //| where BadMachinesCount > 1 \ No newline at end of file diff --git a/General queries/Events surrounding alert.txt b/General queries/Events surrounding alert.txt index f60e219f..68f11b63 100644 --- a/General queries/Events surrounding alert.txt +++ b/General queries/Events surrounding alert.txt @@ -3,23 +3,23 @@ // This is useful when you have queries that you run often - e.g. as part of your regular investigation of an alert. // Original query: filter for network logon events right before some timestamp -let machineId = "474908f457a1dc4c1fab568f808d5f77bf3bb951"; +let DeviceId = "474908f457a1dc4c1fab568f808d5f77bf3bb951"; let timestamp = datetime(2018-06-09T02:23:26.6832917Z); let lookupPeriod = 10m; -LogonEvents -| where EventTime between ((timestamp - lookupPeriod) .. lookupPeriod) - and MachineId == machineId +DeviceLogonEvents +| where Timestamp between ((timestamp - lookupPeriod) .. lookupPeriod) + and DeviceId == DeviceId and LogonType == "Network" // Modified query: instead of copy-pasting the timestamp, get the timestamp of some event you can filter // In this example, take the time of the first detected event in an alert. -// We filter on alertId - which you can get from all our APIs (SIEM, Graph API, PowerBI, AlertEvents table) or from the UI (the last part of the link to the alert page) +// We filter on alertId - which you can get from all our APIs (SIEM, Graph API, PowerBI, DeviceAlertEvents table) or from the UI (the last part of the link to the alert page) let alertId = "636641078490537577_-1905871543"; -let alert = AlertEvents | where AlertId == alertId | summarize AlertFirstEventTime=min(EventTime) by MachineId; -let machineId = toscalar(alert | project MachineId); -let timestamp = toscalar(alert | project AlertFirstEventTime); +let alert = DeviceAlertEvents | where AlertId == alertId | summarize AlertFirstTimestamp=min(Timestamp) by DeviceId; +let DeviceId = toscalar(alert | project DeviceId); +let timestamp = toscalar(alert | project AlertFirstTimestamp); let lookupPeriod = 10m; -LogonEvents -| where EventTime between ((timestamp - lookupPeriod) .. lookupPeriod) - and MachineId == machineId +DeviceLogonEvents +| where Timestamp between ((timestamp - lookupPeriod) .. lookupPeriod) + and DeviceId == DeviceId and LogonType == "Network" diff --git a/General queries/Failed Logon Attempt.txt b/General queries/Failed Logon Attempt.txt index 13bd2256..519cf365 100644 --- a/General queries/Failed Logon Attempt.txt +++ b/General queries/Failed Logon Attempt.txt @@ -1,10 +1,10 @@ // Sample query to detect If there are more then 3 failed logon authentications on high value assets. -// Update COMPUTERNAME to reflect your high value assets. +// Update DeviceName to reflect your high value assets. // For questions @MiladMSFT on Twitter or milad.aslaner@microsoft.com -LogonEvents -| where ComputerName in ("COMPUTERNAME1","COMPUTERNAME2") +DeviceLogonEvents +| where DeviceName in ("DeviceName1","DeviceName2") | where ActionType == "LogonFailed" -| summarize LogonFailures=count() by ComputerName, LogonType, InitiatingProcessCommandLine +| summarize LogonFailures=count() by DeviceName, LogonType, InitiatingProcessCommandLine | where LogonFailures > 3 -| project LogonFailures, ComputerName, LogonType, InitiatingProcessCommandLine +| project LogonFailures, DeviceName, LogonType, InitiatingProcessCommandLine | sort by LogonFailures desc diff --git a/General queries/File footprint.txt b/General queries/File footprint.txt index 25fda4a9..512ae28f 100644 --- a/General queries/File footprint.txt +++ b/General queries/File footprint.txt @@ -1,22 +1,22 @@ // Query #1 - Find the machines on which this file was seen // TODO - set file hash to be a SHA1 hash of your choice... let fileHash = "e152f7ce2d3a4349ac583580c2caf8f72fac16ba"; -find in (FileCreationEvents, ProcessCreationEvents, MiscEvents, RegistryEvents, NetworkCommunicationEvents, ImageLoadEvents) +find in (DeviceFileEvents, DeviceProcessEvents, DeviceEvents, DeviceRegistryEvents, DeviceNetworkEvents, DeviceImageLoadEvents) where SHA1 == fileHash or InitiatingProcessSHA1 == fileHash -project ComputerName, ActionType, FileName, InitiatingProcessFileName, EventTime, SHA1, InitiatingProcessSHA1 -| project ComputerName, ActionType, EventTime, +project DeviceName, ActionType, FileName, InitiatingProcessFileName, Timestamp, SHA1, InitiatingProcessSHA1 +| project DeviceName, ActionType, Timestamp, FileName = iff(SHA1 == fileHash, FileName, InitiatingProcessFileName), MatchedSide=iff(SHA1 == fileHash, iff(InitiatingProcessSHA1 == fileHash, "Both", "Child"), "Parent") -| summarize makeset(ActionType), FirstEventTime=min(EventTime), (LastEventTime, LastActionType)=arg_max(EventTime, ActionType) by FileName, MatchedSide, ComputerName -| top 1000 by LastEventTime desc -| sort by ComputerName, LastEventTime desc +| summarize makeset(ActionType), FirstTimestamp=min(Timestamp), (LastTimestamp, LastActionType)=arg_max(Timestamp, ActionType) by FileName, MatchedSide, DeviceName +| top 1000 by LastTimestamp desc +| sort by DeviceName, LastTimestamp desc // Query # 2 - Shows you a list of distinct IP addresses and DNS names the endpoint had network communication with through a specific file. // Use this list to whitelist/blacklist IP addresses or understand if there are communication with IP you are not aware of. // Update the filename to the name you wish to investigate network communication. let filename = "FILENAME GOES HERE"; // Builds table for distinct URLs based off filename -NetworkCommunicationEvents +DeviceNetworkEvents | where InitiatingProcessFileName =~ filename and ( isnotempty(RemoteIP) or isnotempty(RemoteUrl) ) | project DNS=RemoteUrl, IP=RemoteIP | distinct IP, DNS diff --git a/General queries/Machine info from IP address.txt b/General queries/Machine info from IP address.txt index 49cd80a3..67bcd5c0 100644 --- a/General queries/Machine info from IP address.txt +++ b/General queries/Machine info from IP address.txt @@ -4,14 +4,14 @@ // Query #1: get machines that have used a given local IP address at a given time - as configured on their network adapters let pivotTimeParam = datetime(2018-07-15 19:51:00); let ipAddressParam = "192.168.1.5"; -MachineNetworkInfo -| where EventTime between ((pivotTimeParam-15m) ..30m) and IPAddresses contains strcat("\"", ipAddressParam, "\"") and NetworkAdapterStatus == "Up" +DeviceNetworkInfo +| where Timestamp between ((pivotTimeParam-15m) ..30m) and IPAddresses contains strcat("\"", ipAddressParam, "\"") and NetworkAdapterStatus == "Up" //// Optional - add filters to make sure machine is part of the relevant network (and not using that IP address as part of another private network). //// For example: // and ConnectedNetworks contains "corp.contoso.com" // and IPv4Dhcp == "10.164.3.12" // and DefaultGateways contains "\"10.164.3.1\"" -| project ComputerName, EventTime, IPAddresses, TimeDifference=abs(EventTime-pivotTimeParam) +| project DeviceName, Timestamp, IPAddresses, TimeDifference=abs(Timestamp-pivotTimeParam) // In case multiple machines have reported from that IP address arround that time, start with the ones reporting closest to pivotTimeParam | sort by TimeDifference asc @@ -20,28 +20,28 @@ MachineNetworkInfo let pivotTimeParam = datetime(2018-07-15 19:51:00); let ipAddressParam = "192.168.1.5"; let matchingMachines = - MachineNetworkInfo - | where EventTime between ((pivotTimeParam-15m) ..30m) and IPAddresses contains strcat("\"", ipAddressParam, "\"") and NetworkAdapterStatus == "Up" + DeviceNetworkInfo + | where Timestamp between ((pivotTimeParam-15m) ..30m) and IPAddresses contains strcat("\"", ipAddressParam, "\"") and NetworkAdapterStatus == "Up" //// Optional - add filters to make sure machine is part of the relevant network (and not using that IP address as part of another private network). //// For example: // and ConnectedNetworks contains "corp.contoso.com" // and IPv4Dhcp == "10.164.3.12" // and DefaultGateways contains "\"10.164.3.1\"" - | project ComputerName, EventTime, IPAddresses, TimeDifference=abs(EventTime-pivotTimeParam); -MachineInfo -| where EventTime between ((pivotTimeParam-15m) ..30m) -| project ComputerName, EventTime, LoggedOnUsers -| join kind=inner (matchingMachines) on ComputerName, EventTime -| project EventTime, ComputerName, LoggedOnUsers, TimeDifference, IPAddresses + | project DeviceName, Timestamp, IPAddresses, TimeDifference=abs(Timestamp-pivotTimeParam); +DeviceInfo +| where Timestamp between ((pivotTimeParam-15m) ..30m) +| project DeviceName, Timestamp, LoggedOnUsers +| join kind=inner (matchingMachines) on DeviceName, Timestamp +| project Timestamp, DeviceName, LoggedOnUsers, TimeDifference, IPAddresses // In case multiple machines have reported from that IP address arround that time, start with the ones reporting closest to pivotTimeParam | sort by TimeDifference asc // Query #3: get machines that have used a given *public* IP address at a given time - as seen in their communications with the WDATP cloud let pivotTimeParam = datetime(2018-07-15 19:51:00); let ipAddressParam = "192.168.1.5"; -MachineInfo -| where EventTime between ((pivotTimeParam-15m) .. 30m) and PublicIP == ipAddressParam -| project ComputerName, LoggedOnUsers, EventTime, TimeDifference=abs(EventTime-pivotTimeParam) +DeviceInfo +| where Timestamp between ((pivotTimeParam-15m) .. 30m) and PublicIP == ipAddressParam +| project DeviceName, LoggedOnUsers, Timestamp, TimeDifference=abs(Timestamp-pivotTimeParam) // In case multiple machines have reported from that IP address arround that time, start with the ones reporting closest to pivotTimeParam | sort by TimeDifference asc @@ -49,9 +49,9 @@ MachineInfo // This includes IP addresses seen locally in their network adapters configuration or ones used to access the WDATP cloud. let pivotTimeParam = datetime(2018-07-15 19:51:00); let ipAddressParam = "192.168.1.5"; -MachineNetworkInfo -| where EventTime between ((pivotTimeParam-15m) ..30m) and IPAddresses contains strcat("\"", ipAddressParam, "\"") and NetworkAdapterStatus == "Up" -| project ComputerName, EventTime, Source="NetworkAdapterInfo" -| union (MachineInfo | where EventTime between ((pivotTimeParam-15m) .. 30m) and PublicIP == ipAddressParam | project ComputerName, EventTime, Source="Public IP address") -| extend TimeDifference=abs(EventTime-pivotTimeParam) +DeviceNetworkInfo +| where Timestamp between ((pivotTimeParam-15m) ..30m) and IPAddresses contains strcat("\"", ipAddressParam, "\"") and NetworkAdapterStatus == "Up" +| project DeviceName, Timestamp, Source="NetworkAdapterInfo" +| union (DeviceInfo | where Timestamp between ((pivotTimeParam-15m) .. 30m) and PublicIP == ipAddressParam | project DeviceName, Timestamp, Source="Public IP address") +| extend TimeDifference=abs(Timestamp-pivotTimeParam) | sort by TimeDifference asc diff --git a/General queries/Network footprint.txt b/General queries/Network footprint.txt index 54de2bf9..b22ff531 100644 --- a/General queries/Network footprint.txt +++ b/General queries/Network footprint.txt @@ -1,10 +1,10 @@ // Query 1 shows you any network communication happened from endpoints to a specific Remote IP or Remote URL // Ensure to update RemoteIP and RemoteURL variable. // For questions @MiladMSFT on Twitter or milad.aslaner@microsoft.com by email -NetworkCommunicationEvents +DeviceNetworkEvents | where RemoteIP == "IP ADDRESS GOES HERE" or RemoteUrl endswith "DNS ENTRY GOES HERE" -| project EventTime, ComputerName, ActionType, RemoteIP, RemoteUrl, InitiatingProcessFileName, InitiatingProcessCommandLine +| project Timestamp, DeviceName, ActionType, RemoteIP, RemoteUrl, InitiatingProcessFileName, InitiatingProcessCommandLine // Query 2 shows you any network communication that happened from endpoints through a specific file to an Remote IP or Remote URL //Ensure to update RemoteIP, RemoteURL and InitatingProcessFileName @@ -12,18 +12,18 @@ or RemoteUrl endswith "DNS ENTRY GOES HERE" let IP = "IP ADDRESS GOES HERE"; let DNS = "DNS ENTRY GOES HERE"; let FILENAME = "FILENAME GOES HERE"; -NetworkCommunicationEvents +DeviceNetworkEvents | where (RemoteIP == IP or RemoteUrl endswith DNS) and InitiatingProcessFileName =~ FILENAME -| project EventTime, ComputerName, ActionType, RemoteIP, RemoteUrl, InitiatingProcessFileName, InitiatingProcessCommandLine +| project Timestamp, DeviceName, ActionType, RemoteIP, RemoteUrl, InitiatingProcessFileName, InitiatingProcessCommandLine -// Query 3 allows you to find network communication to an IP or URL in the NetworkCommunicationEvents table, as well as in MiscEvents for other events (SmartScreen, launch browser with URL, more) +// Query 3 allows you to find network communication to an IP or URL in the DeviceNetworkEvents table, as well as in DeviceEvents for other events (SmartScreen, launch browser with URL, more) // Ensure to update RemoteIP and RemoteURL variable. -find in (MiscEvents, NetworkCommunicationEvents) +find in (DeviceEvents, DeviceNetworkEvents) where RemoteIP == "IP ADDRESS GOES HERE" or RemoteUrl =~ "URL GOES HERE" -project ComputerName, ActionType, FileName, EventTime +project DeviceName, ActionType, FileName, Timestamp // Query 4 Search for specific network communication of a Remote IP or URL that also discovers related file creation events // Ensure to update RemoteIP and RemoteURL variable. -FileCreationEvents +DeviceFileEvents | where FileOriginUrl == "IP ADDRESS GOES HERE" or FileOriginUrl contains "URL GOES HERE" or FileOriginReferrerUrl contains "URL GOES HERE" -| project ComputerName, EventTime, FileName, FileOriginUrl, FileOriginIP, FileOriginReferrerUrl, SHA1 +| project DeviceName, Timestamp, FileName, FileOriginUrl, FileOriginIP, FileOriginReferrerUrl, SHA1 diff --git a/General queries/Network info of machine.txt b/General queries/Network info of machine.txt index aac1dfff..6d969d4b 100644 --- a/General queries/Network info of machine.txt +++ b/General queries/Network info of machine.txt @@ -1,14 +1,14 @@ // Get information about the netwotk adapters of the given computer in the given time. // This could include the configured IP addresses, DHCP servers, DNS servers, and more. -let machineIdParam = "c0bfefec0bfefec0bfefec0bfefec0bfefecafe"; +let DeviceIdParam = "c0bfefec0bfefec0bfefec0bfefec0bfefecafe"; let pivotTimeParam = datetime(2018-07-15T19:51); -MachineNetworkInfo +DeviceNetworkInfo // Query for reports sent +-15 minutes around the time we are interested in -| where EventTime between ((pivotTimeParam-15m) .. 30m) and MachineId == machineIdParam and NetworkAdapterStatus == "Up" +| where Timestamp between ((pivotTimeParam-15m) .. 30m) and DeviceId == DeviceIdParam and NetworkAdapterStatus == "Up" // IPAddresses contains a list of the IP addresses configured on the network adapter, their subnets, and more. // Here we expand the list so that each value gets a separate row. All the other columns in the row, such as MacAddress, are duplicated. | mvexpand parse_json(IPAddresses) | project IPAddress=IPAddresses.IPAddress, AddressType=IPAddresses.AddressType, NetworkAdapterType, TunnelType, MacAddress, -ConnectedNetworks, EventTime, TimeDifference=abs(EventTime-pivotTimeParam) +ConnectedNetworks, Timestamp, TimeDifference=abs(Timestamp-pivotTimeParam) // In case multiple machines have reported from that IP address arround that time, start with the ones reporting closest to pivotTimeParam | sort by TimeDifference asc, NetworkAdapterType, MacAddress diff --git a/General queries/WD AV Signature and Platform Version.txt b/General queries/WD AV Signature and Platform Version.txt index f87b6d8b..11ea7473 100644 --- a/General queries/WD AV Signature and Platform Version.txt +++ b/General queries/WD AV Signature and Platform Version.txt @@ -5,22 +5,22 @@ // Define the time window // Please note that results will vary depending on startDate let startDate = ago(7d); -FileCreationEvents +DeviceFileEvents | where InitiatingProcessCommandLine has "MpSigStub.exe" //To exclude Engine Updates and non update events | where InitiatingProcessParentFileName !~ "AM_Engine.exe" and InitiatingProcessParentFileName !~ "wuauclt.exe" // Comment the below line if you're looking specifically for a computer -| where EventTime > startDate +| where Timestamp > startDate // Uncomment the line below when looking for info regarding a specific computer -//| and ComputerName == "COMPUTER" +//| and DeviceName == "COMPUTER" | extend NewVersion=tostring(split(InitiatingProcessCommandLine, " ")[4]) -| summarize arg_max(NewVersion, EventTime) by ComputerName -| project ComputerName , NewVersion -| join (FileCreationEvents +| summarize arg_max(NewVersion, Timestamp) by DeviceName +| project DeviceName , NewVersion +| join (DeviceFileEvents | where FileName == "MsMpEng.exe" | where FolderPath has @"C:\ProgramData\Microsoft\Windows Defender\Platform\" - | where EventTime > startDate + | where Timestamp > startDate | extend PlatformVersion=tostring(split(FolderPath, "\\", 5)) - | project ComputerName, PlatformVersion) - on ComputerName -| project ComputerName , NewVersion , PlatformVersion + | project DeviceName, PlatformVersion) + on DeviceName +| project DeviceName , NewVersion , PlatformVersion diff --git a/Lateral Movement/Account brute force.txt b/Lateral Movement/Account brute force.txt index 5738af05..d633d5fd 100644 --- a/Lateral Movement/Account brute force.txt +++ b/Lateral Movement/Account brute force.txt @@ -1,5 +1,5 @@ // Query #1: Look for public IP addresses that failed to logon to a computer multiple times, using multiple accounts, and eventually succeeded. -LogonEvents +DeviceLogonEvents | where isnotempty(RemoteIP) and AccountName !endswith "$" and RemoteIPType == "Public" @@ -11,22 +11,22 @@ LogonEvents SuccessfulAccountsCount = dcountif(Account, ActionType == "LogonSuccess"), FailedAccounts = makeset(iff(ActionType == "LogonFailed", Account, ""), 5), SuccessfulAccounts = makeset(iff(ActionType == "LogonSuccess", Account, ""), 5) - by ComputerName, RemoteIP, RemoteIPType + by DeviceName, RemoteIP, RemoteIPType | where Failed > 10 and Successful > 0 and FailedAccountsCount > 2 and SuccessfulAccountsCount == 1 // Query #2: Look for machines failing to log-on to multiple machines or using multiple accounts -// Note - RemoteComputerName is not available in all remote logon attempts -LogonEvents -| where isnotempty(RemoteComputerName) +// Note - RemoteDeviceName is not available in all remote logon attempts +DeviceLogonEvents +| where isnotempty(RemoteDeviceName) | extend Account=strcat(AccountDomain, "\\", AccountName) | summarize Successful=countif(ActionType == "LogonSuccess"), Failed = countif(ActionType == "LogonFailed"), FailedAccountsCount = dcountif(Account, ActionType == "LogonFailed"), SuccessfulAccountsCount = dcountif(Account, ActionType == "LogonSuccess"), - FailedComputerCount = dcountif(ComputerName, ActionType == "LogonFailed"), - SuccessfulComputerCount = dcountif(ComputerName, ActionType == "LogonSuccess") - by RemoteComputerName + FailedComputerCount = dcountif(DeviceName, ActionType == "LogonFailed"), + SuccessfulComputerCount = dcountif(DeviceName, ActionType == "LogonSuccess") + by RemoteDeviceName | where Successful > 0 and ((FailedComputerCount > 100 and FailedComputerCount > SuccessfulComputerCount) or diff --git a/Lateral Movement/ServiceAccountsPerformingRemotePS.txt b/Lateral Movement/ServiceAccountsPerformingRemotePS.txt index cb06eb38..d90682b7 100644 --- a/Lateral Movement/ServiceAccountsPerformingRemotePS.txt +++ b/Lateral Movement/ServiceAccountsPerformingRemotePS.txt @@ -2,11 +2,11 @@ // Author: miflower // The purpose behind this detection is for finding service accounts that are performing remote powershell sessions // There are two phases to the detection: Identify service accounts, Find remote PS cmdlets being ran by these accounts -// To accomplish this, we utilize LogonEvents and MiscEvents to find cmdlets ran that meet the criteria +// To accomplish this, we utilize DeviceLogonEvents and DeviceEvents to find cmdlets ran that meet the criteria // One of the main advantages of this method is that only requires server telemetry, and not the attacking client -// The first phase relies on the LogonEvents to determine whether an account is a service account or not, consider the following accounts with logons: -// random_user has LogonEvents with type 2, 3, 7, 10, 11 & 13 -// random_service_account 'should' only have LogonEvents with type 3,4 or 5 +// The first phase relies on the DeviceLogonEvents to determine whether an account is a service account or not, consider the following accounts with logons: +// random_user has DeviceLogonEvents with type 2, 3, 7, 10, 11 & 13 +// random_service_account 'should' only have DeviceLogonEvents with type 3,4 or 5 // let InteractiveTypes = pack_array( // Declare Interactive logon type names 'Interactive', @@ -25,7 +25,7 @@ let WhitelistedCmdlets = pack_array( // List of w 'TabExpansion2' ); let WhitelistedAccounts = pack_array('FakeWhitelistedAccount'); // List of accounts that are known to perform this activity in the environment and can be ignored -LogonEvents // Get all logon events... +DeviceLogonEvents // Get all logon events... | where AccountName !in~ (WhitelistedAccounts) // ...where it is not a whitelisted account... | where ActionType == "LogonSuccess" // ...and the logon was successful... | where AccountName !contains "$" // ...and not a machine logon. @@ -38,7 +38,7 @@ LogonEvents // Get all l // Now we need to find RemotePS sessions that were spawned by those accounts // Note that we look at all powershell cmdlets executed to form a 29-day baseline to evaluate the data on today | join kind=rightsemi ( // Start by dropping the account name and only tracking the... - MiscEvents // ... + DeviceEvents // ... | where ActionType == 'PowerShellCommand' // ...PowerShell commands seen... | where InitiatingProcessFileName =~ 'wsmprovhost.exe' // ...whose parent was wsmprovhost.exe (RemotePS Server)... | extend AccountName = InitiatingProcessAccountName // ...and add an AccountName field so the join is easier @@ -46,9 +46,9 @@ LogonEvents // Get all l // At this point, we have all of the commands that were ran by service accounts | extend Command = tostring(extractjson('$.Command', AdditionalFields)) // Extract the actual PowerShell command that was executed | where Command !in (WhitelistedCmdlets) // Remove any values that match the whitelisted cmdlets -| summarize (EventTime, ReportId)=argmax(EventTime, ReportId), // Then group all of the cmdlets and calculate the min/max times of execution... - makeset(Command), count(), min(EventTime) by // ...as well as creating a list of cmdlets ran and the count.. - AccountName, ComputerName, MachineId // ...and have the commonality be the account, computername and machineid +| summarize (Timestamp, ReportId)=argmax(Timestamp, ReportId), // Then group all of the cmdlets and calculate the min/max times of execution... + makeset(Command), count(), min(Timestamp) by // ...as well as creating a list of cmdlets ran and the count.. + AccountName, DeviceName, DeviceId // ...and have the commonality be the account, DeviceName and DeviceId // At this point, we have machine-account pairs along with the list of commands run as well as the first/last time the commands were ran | order by AccountName asc // Order the final list by AccountName just to make it easier to go through -| where min_EventTime > ago(1d) // Included to restrict the scope for the custom detection page +| where min_Timestamp > ago(1d) // Included to restrict the scope for the custom detection page diff --git a/Persistence/Accessibility Features.txt b/Persistence/Accessibility Features.txt index 2811cadf..0d87f8bf 100644 --- a/Persistence/Accessibility Features.txt +++ b/Persistence/Accessibility Features.txt @@ -9,32 +9,32 @@ let minTime = ago(7d); let accessibilityProcessNames = dynamic(["utilman.exe","osk.exe","magnify.exe","narrator.exe","displayswitch.exe","atbroker.exe","sethc.exe", "helppane.exe"]); // Query for debuggers attached using a Registry setting to the accessibility processes let attachedDebugger = - RegistryEvents - | where EventTime > minTime + DeviceRegistryEvents + | where Timestamp > minTime and RegistryKey startswith @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\" and RegistryValueName =~ "debugger" // Parse the debugged process name from the registry key | parse RegistryKey with @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\" FileName | where FileName in~ (accessibilityProcessNames) and isnotempty(RegistryValueData) - | project Technique="AttachedDebugger", FileName, AttachedDebuggerCommandline=RegistryValueData, InitiatingProcessCommandLine, EventTime, ComputerName; + | project Technique="AttachedDebugger", FileName, AttachedDebuggerCommandline=RegistryValueData, InitiatingProcessCommandLine, Timestamp, DeviceName; // Query for overwrites of the accessibility files let fileOverwiteOfAccessibilityFiles = - FileCreationEvents - | where EventTime > minTime + DeviceFileEvents + | where Timestamp > minTime and FileName in~ (accessibilityProcessNames) and FolderPath contains @"Windows\System32" - | project Technique="OverwriteFile", EventTime, ComputerName, FileName, SHA1, InitiatingProcessCommandLine; + | project Technique="OverwriteFile", Timestamp, DeviceName, FileName, SHA1, InitiatingProcessCommandLine; // Query for unexpected hashes of processes with names matching the accessibility processes. // Specifically, query for hashes matching cmd.exe and powershell.exe, as these MS-signed general-purpose consoles are often used with this technique. let executedProcessIsPowershellOrCmd = - ProcessCreationEvents - | project Technique="PreviousOverwriteFile", EventTime, ComputerName, FileName, SHA1 - | where EventTime > minTime + DeviceProcessEvents + | project Technique="PreviousOverwriteFile", Timestamp, DeviceName, FileName, SHA1 + | where Timestamp > minTime | where FileName in~ (accessibilityProcessNames) | join kind=leftsemi( - ProcessCreationEvents - | where EventTime > ago(14d) and (FileName =~ "cmd.exe" or FileName =~ "powershell.exe") - | summarize MachinesCount = dcount(ComputerName) by SHA1 + DeviceProcessEvents + | where Timestamp > ago(14d) and (FileName =~ "cmd.exe" or FileName =~ "powershell.exe") + | summarize MachinesCount = dcount(DeviceName) by SHA1 | where MachinesCount > 5 | project SHA1 ) on SHA1; diff --git a/Persistence/Create account.txt b/Persistence/Create account.txt index e5919bfb..5b2e32fe 100644 --- a/Persistence/Create account.txt +++ b/Persistence/Create account.txt @@ -5,11 +5,11 @@ // Query #1: Query for users being created using "net user" command // "net user" commands are noisy, so needs to be joined with another signal - // e.g. in this example we look for use of uncommon & undocumented commandline switches (e.g. /ad instead of /add) -ProcessCreationEvents +DeviceProcessEvents // Pro-tip: // There are many different ways to run a process from a file - e.g. by using full path, env. variables, ~1 annotation, more... // So, to find executions of a known filename, better filter on the filename (and possibly on folder path) than on the commandline. -| where FileName in~ ("net.exe", "net1.exe") and EventTime > ago(3d) +| where FileName in~ ("net.exe", "net1.exe") and Timestamp > ago(3d) // Parse the user name from the commandline. // To have case-insensitive parsing use the i flag, to have non-greedy match (e.g. CreatedUser as short as possible), specify U flag: // "kind=regex flags=i" @@ -23,14 +23,14 @@ ProcessCreationEvents // Also, any prefix that's longer than 1 char will also do the same, e.g. /do, /dom, /doma, .... | extend CreatedOnLocalMachine=(ProcessCommandLine !contains "/do") | where ProcessCommandLine !contains "/add" or (CreatedOnLocalMachine == 0 and ProcessCommandLine !contains "/domain") -| summarize MachineCount=dcount(ComputerName) by CreatedUser, CreatedOnLocalMachine, InitiatingProcessFileName, FileName, ProcessCommandLine, InitiatingProcessCommandLine +| summarize MachineCount=dcount(DeviceName) by CreatedUser, CreatedOnLocalMachine, InitiatingProcessFileName, FileName, ProcessCommandLine, InitiatingProcessCommandLine // Query #2: Query for accounts created on machines onboarded with Sense. // Create account event is noisy, so we need to join it with some other signal. // E.g. In this query we look for accounts created which name resembles "administrator". // Using account names similar to known common account names is a common way to be evade the human analyst eye. -MiscEvents +DeviceEvents | where ActionType == "UserAccountCreated" // To look for account names similar to administrator, we'll simply query for the prefix and suffix, // because these letters matter most to the human perception: https://en.wikipedia.org/wiki/Typoglycemia @@ -38,5 +38,5 @@ MiscEvents // and looking for prefix and suffix should work in this case pretty well. | where AccountName startswith "ad" and AccountName endswith "or" and AccountName !~ "administrator" // Note: For the UserAccountCreated event we do not know the details of the process / account that was used to create this new account. -| project AccountName, AccountDomain, ComputerName, EventTime +| project AccountName, AccountDomain, DeviceName, Timestamp | limit 100 diff --git a/Persistence/scheduled task creation.txt b/Persistence/scheduled task creation.txt index 91be16b3..7f7d3c0b 100644 --- a/Persistence/scheduled task creation.txt +++ b/Persistence/scheduled task creation.txt @@ -1,5 +1,5 @@ //Original Sigma Rule: https://github.com/Neo23x0/sigma/blob/master/rules/windows/process_creation/win_susp_schtask_creation.yml //Questions via Twitter: @janvonkirchheim -ProcessCreationEvents +DeviceProcessEvents | where FolderPath endswith "\\schtasks.exe" and ProcessCommandLine has " /create " and AccountName != "system" -| where EventTime > ago(7d) +| where Timestamp > ago(7d) diff --git a/Protection events/Antivirus detections.txt b/Protection events/Antivirus detections.txt index 5847eef0..4b3c2ec8 100644 --- a/Protection events/Antivirus detections.txt +++ b/Protection events/Antivirus detections.txt @@ -1,14 +1,14 @@ // Query for Windows Defender Antivirus detections. // Query #1: Query for Antivirus detection events -MiscEvents +DeviceEvents | where ActionType == "AntivirusDetection" | extend ParsedFields=parse_json(AdditionalFields) | project ThreatName=tostring(ParsedFields.ThreatName), WasRemediated=tobool(ParsedFields.WasRemediated), WasExecutingWhileDetected=tobool(ParsedFields.WasExecutingWhileDetected), FileName, SHA1, InitiatingProcessFileName, InitiatingProcessCommandLine, - ComputerName, EventTime + DeviceName, Timestamp | limit 100 // Query #2: @@ -18,9 +18,9 @@ MiscEvents // This query looks for alerts on Windows Defender Antivirus detections. // For most purposes it is probably better to query on the events themselves (see query #1). // However, this query might still be useful sometimes (e.g. to quickly parse the family name). -AlertEvents +DeviceAlertEvents | where Title contains "Defender AV detected" | parse Title with *"'"FamilyName"'"* -| summarize FamilyCount=dcount(FamilyName), Families=makeset(FamilyName), Titles=makeset(Title) by ComputerName, MachineId, bin(EventTime, 1d) +| summarize FamilyCount=dcount(FamilyName), Families=makeset(FamilyName), Titles=makeset(Title) by DeviceName, DeviceId, bin(Timestamp, 1d) | where FamilyCount > 1 | limit 100 diff --git a/Protection events/ExploitGuardAsrDescriptions.txt b/Protection events/ExploitGuardAsrDescriptions.txt index c38b5e4b..a168db69 100644 --- a/Protection events/ExploitGuardAsrDescriptions.txt +++ b/Protection events/ExploitGuardAsrDescriptions.txt @@ -1,10 +1,10 @@ -// Expanding on MiscEvents output with Attack Surface Reduction (ASR) rule descriptions +// Expanding on DeviceEvents output with Attack Surface Reduction (ASR) rule descriptions // The ActionType values of the ASR events already explain what rule was matched and if it was audited or blocked. // However, it could still be useful to have a more human-friendly description in the results. // Also, this query is a good example for how you could define your own lookup tables and join with them. -// The events in the MiscEvents table contain a GUID for the various ASR rules rather than a full description of the rule +// The events in the DeviceEvents table contain a GUID for the various ASR rules rather than a full description of the rule // This query will create a table which has the description for each ASR rule as per https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-exploit-guard/enable-attack-surface-reduction -// This table is then joined to the output of a query against the MiscEvents table and shows a summary count of the events by the newly defined description +// This table is then joined to the output of a query against the DeviceEvents table and shows a summary count of the events by the newly defined description // This query shows the ability to use joins and custom dimension tables // See https://docs.loganalytics.io/docs/Language-Reference/Tabular-operators/join-operator for more information on the join syntax // For more questions on this query, feel free to ping @FlyingBlueMonki on twitter or mattegen@microsoft.com via email @@ -26,8 +26,8 @@ let AsrDescriptionTable = datatable(RuleDescription:string, RuleGuid:string) "Block untrusted and unsigned processes that run from USB","b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4", "Block Office communication applications from creating child processes (available for beta testing)","26190899-1602-49e8-8b27-eb1d0a1ce869", ]; -// Now we query the MiscEvents table for events where the ActionType field starts with "Asr" - which should cover values such as AsrExecutableEmailContentAudited, AsrExecutableEmailContentBlocked, AsrOfficeChildProcessAudited, .... -MiscEvents +// Now we query the DeviceEvents table for events where the ActionType field starts with "Asr" - which should cover values such as AsrExecutableEmailContentAudited, AsrExecutableEmailContentBlocked, AsrOfficeChildProcessAudited, .... +DeviceEvents | where ActionType startswith "Asr" // since the RuleGuid is stored inside the additionlfields column, we need to extract it for the join // we extend the results to include a new "RuleGuid" column that is populated by the extracted RuleId from the json data in AdditionalFields. @@ -35,11 +35,11 @@ MiscEvents // and finally we also extend the results with the extracted "IsAudit" column populated from AdditionalFields. This allow us to determine if the event was blocked or merely audited | extend RuleGuid = tolower(tostring(parsejson(AdditionalFields).RuleId)) | extend IsAudit = parse_json(AdditionalFields).IsAudit -| project ComputerName, RuleGuid, MachineId, IsAudit +| project DeviceName, RuleGuid, DeviceId, IsAudit // Now we're making our join back to the earlier defined table of rule descriptions and guids (applying that tolower() statement for consistency) and finally outputting our summary counts // We're projecting both the RuleDescription and the RuleGuid. If there is a new rule that is *NOT* in our table above, we'll get a row with no description, but including the Guid so we can find it and update the table | join kind = leftouter (AsrDescriptionTable | project RuleGuid = tolower(RuleGuid), RuleDescription) on RuleGuid -| summarize MachinesWithAuditEvents = dcountif(MachineId,IsAudit==1), MachinesWithBlockEvents = dcountif(MachineId, IsAudit==0), AllEvents=count() by RuleDescription, RuleGuid +| summarize MachinesWithAuditEvents = dcountif(DeviceId,IsAudit==1), MachinesWithBlockEvents = dcountif(DeviceId, IsAudit==0), AllEvents=count() by RuleDescription, RuleGuid // an alternative summary line is commented out below. This would show us a count of each rule on each machine rather than a machine / event count -//| summarize count() by RuleDescription, ComputerName +//| summarize count() by RuleDescription, DeviceName diff --git a/Protection events/ExploitGuardBlockOfficeChildProcess.txt b/Protection events/ExploitGuardBlockOfficeChildProcess.txt index f11fd03f..66631b5d 100644 --- a/Protection events/ExploitGuardBlockOfficeChildProcess.txt +++ b/Protection events/ExploitGuardBlockOfficeChildProcess.txt @@ -7,10 +7,10 @@ // Tags: #ASR //Query #1: block stats -MiscEvents -| where ActionType == "AsrOfficeChildProcessBlocked" and EventTime > ago(7d) -| project BlockedProcess=FileName, ParentProcess=InitiatingProcessFileName, ComputerName -| summarize MachineCount=dcount(ComputerName), RuleHits=count() by BlockedProcess, ParentProcess +DeviceEvents +| where ActionType == "AsrOfficeChildProcessBlocked" and Timestamp > ago(7d) +| project BlockedProcess=FileName, ParentProcess=InitiatingProcessFileName, DeviceName +| summarize MachineCount=dcount(DeviceName), RuleHits=count() by BlockedProcess, ParentProcess | sort by MachineCount desc // Query #2: investigate audit events - before turning the rule on in block mode @@ -19,15 +19,15 @@ let minTime = ago(7d); // If there was an alert, so this is probably malware, and it's good that it will be blocked. // If there was no alert, so it requires further analysis to determine if this is a clean file or some malware that was missed. let alerts = - AlertEvents - | where EventTime > minTime - | project ComputerName, DetectedEventTime=EventTime; -MiscEvents -| where ActionType == "AsrOfficeChildProcessAudited" and EventTime > minTime -| project BlockedProcess=FileName, ParentProcess=InitiatingProcessFileName, ComputerName, EventTime -| join kind=leftouter (alerts) on ComputerName -| extend HasNearbyAlert = abs(EventTime - DetectedEventTime) between (0min .. 5min) -| summarize MachineCount=dcount(ComputerName), + DeviceAlertEvents + | where Timestamp > minTime + | project DeviceName, DetectedTimestamp=Timestamp; +DeviceEvents +| where ActionType == "AsrOfficeChildProcessAudited" and Timestamp > minTime +| project BlockedProcess=FileName, ParentProcess=InitiatingProcessFileName, DeviceName, Timestamp +| join kind=leftouter (alerts) on DeviceName +| extend HasNearbyAlert = abs(Timestamp - DetectedTimestamp) between (0min .. 5min) +| summarize MachineCount=dcount(DeviceName), RuleHits=count(), NearbyAlertPercent=countif(HasNearbyAlert)*100.0 / count() by BlockedProcess, ParentProcess diff --git a/Protection events/ExploitGuardNetworkProtectionEvents.txt b/Protection events/ExploitGuardNetworkProtectionEvents.txt index 29da5a29..06f69b19 100644 --- a/Protection events/ExploitGuardNetworkProtectionEvents.txt +++ b/Protection events/ExploitGuardNetworkProtectionEvents.txt @@ -1,7 +1,7 @@ //Simple query to show the unique network connections that were audited or blocked by ExploitGuard // For more questions on this query, feel free to ping @FlyingBlueMonki on twitter or mattegen@microsoft.com via email -MiscEvents -| where EventTime > ago(7d) +DeviceEvents +| where Timestamp > ago(7d) | where ActionType =~ "ExploitGuardNetworkProtectionBlocked" | summarize count(RemoteUrl) by InitiatingProcessFileName, RemoteUrl, Audit_Only=tostring(parse_json(AdditionalFields).IsAudit) | sort by count_RemoteUrl desc diff --git a/Protection events/ExploitGuardStats.txt b/Protection events/ExploitGuardStats.txt index 9930aa2b..eef6895c 100644 --- a/Protection events/ExploitGuardStats.txt +++ b/Protection events/ExploitGuardStats.txt @@ -1,11 +1,11 @@ // Get stats on ExploitGuard blocks - count events and machines per rule -MiscEvents +DeviceEvents | where ActionType startswith "ExploitGuard" and ActionType endswith "Blocked" // Count total stats - count events and machines per rule -| summarize EventCount=count(), MachinesCount=dcount(ComputerName) by ActionType +| summarize EventCount=count(), MachinesCount=dcount(DeviceName) by ActionType // View ExploitGuard audit events - but remove repeating events (e.g. multiple events with same machine, rule, file and process) -MiscEvents +DeviceEvents | where ActionType startswith "ExploitGuard" and ActionType endswith "Audited" -| summarize EventTime =max(EventTime) by ComputerName, ActionType,FileName, FolderPath, InitiatingProcessCommandLine, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessId, SHA1 +| summarize Timestamp =max(Timestamp) by DeviceName, ActionType,FileName, FolderPath, InitiatingProcessCommandLine, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessId, SHA1 diff --git a/Protection events/SmartScreen URL block ignored by user.txt b/Protection events/SmartScreen URL block ignored by user.txt index 499e9c65..bd4b80db 100644 --- a/Protection events/SmartScreen URL block ignored by user.txt +++ b/Protection events/SmartScreen URL block ignored by user.txt @@ -5,25 +5,25 @@ // Tags: #SmartScreen let minTimeRange = ago(7d); let smartscreenUrlBlocks = - MiscEvents - | where ActionType == "SmartScreenUrlWarning" and EventTime > minTimeRange + DeviceEvents + | where ActionType == "SmartScreenUrlWarning" and Timestamp > minTimeRange // Filter out SmartScreen test URLs under https://demo.smartscreen.msft.net/ and RemoteUrl !startswith "https://demo.smartscreen.msft.net/" | extend ParsedFields=parse_json(AdditionalFields) - | project EventTime, ComputerName, BlockedUrl=RemoteUrl, Recommendation=tostring(ParsedFields.Recommendation), Experience=tostring(ParsedFields.Experience), ActivityId=tostring(ParsedFields.ActivityId); + | project Timestamp, DeviceName, BlockedUrl=RemoteUrl, Recommendation=tostring(ParsedFields.Recommendation), Experience=tostring(ParsedFields.Experience), ActivityId=tostring(ParsedFields.ActivityId); // Query for UserDecision events - each one means the user has decided to ignore the warning and run the app. let userIgnoredWarning= - MiscEvents - | where ActionType == "SmartScreenUserOverride" and EventTime > minTimeRange - | project ComputerName, ActivityId=extractjson("$.ActivityId", AdditionalFields, typeof(string)); + DeviceEvents + | where ActionType == "SmartScreenUserOverride" and Timestamp > minTimeRange + | project DeviceName, ActivityId=extractjson("$.ActivityId", AdditionalFields, typeof(string)); // Join the block and user decision event using an ActivityId -let ignoredBlocks = smartscreenUrlBlocks | join kind=leftsemi (userIgnoredWarning) on ComputerName, ActivityId | project-away ActivityId; +let ignoredBlocks = smartscreenUrlBlocks | join kind=leftsemi (userIgnoredWarning) on DeviceName, ActivityId | project-away ActivityId; // Optional additional filter - look only for cases where a file was downloaded from Microsoft Edge following the URL block being ignored let edgeDownloads = - FileCreationEvents - | where EventTime > minTimeRange and InitiatingProcessFileName =~ "browser_broker.exe" - | summarize (DownloadTime, SHA1) = argmax(EventTime, SHA1) by FileName, ComputerName, FileOriginUrl, FileOriginReferrerUrl; + DeviceFileEvents + | where Timestamp > minTimeRange and InitiatingProcessFileName =~ "browser_broker.exe" + | summarize (DownloadTime, SHA1) = argmax(Timestamp, SHA1) by FileName, DeviceName, FileOriginUrl, FileOriginReferrerUrl; ignoredBlocks -| join kind=inner (edgeDownloads) on ComputerName -| where DownloadTime - EventTime between (0min .. 2min) -| project-away ComputerName1 +| join kind=inner (edgeDownloads) on DeviceName +| where DownloadTime - Timestamp between (0min .. 2min) +| project-away DeviceName1 diff --git a/Protection events/SmartScreen app block ignored by user.txt b/Protection events/SmartScreen app block ignored by user.txt index 5c1b81a7..54fc25e1 100644 --- a/Protection events/SmartScreen app block ignored by user.txt +++ b/Protection events/SmartScreen app block ignored by user.txt @@ -4,21 +4,21 @@ // Tags: #SmartScreen let minTimeRange = ago(7d); let smartscreenAppBlocks = - MiscEvents - | where ActionType == "SmartScreenAppWarning" and EventTime > minTimeRange + DeviceEvents + | where ActionType == "SmartScreenAppWarning" and Timestamp > minTimeRange // Filter out SmartScreen test files downloaded from https://demo.smartscreen.msft.net/ and not (FileName startswith "knownmalicious" and FileName endswith ".exe") | extend ParsedFields=parse_json(AdditionalFields) - | project EventTime, ComputerName, BlockedFileName=FileName, SHA1, Experience=tostring(ParsedFields.Experience), ActivityId=tostring(ParsedFields.ActivityId), InitiatingProcessFileName; + | project Timestamp, DeviceName, BlockedFileName=FileName, SHA1, Experience=tostring(ParsedFields.Experience), ActivityId=tostring(ParsedFields.ActivityId), InitiatingProcessFileName; // Query for UserDecision events - each one means the user has decided to ignore the warning and run the app. let userIgnoredWarning= - MiscEvents - | where ActionType == "SmartScreenUserOverride" and EventTime > minTimeRange - | project ComputerName, ActivityId=extractjson("$.ActivityId", AdditionalFields, typeof(string)); + DeviceEvents + | where ActionType == "SmartScreenUserOverride" and Timestamp > minTimeRange + | project DeviceName, ActivityId=extractjson("$.ActivityId", AdditionalFields, typeof(string)); // Join the block and user decision event using an ActivityId let ignoredBlocks = smartscreenAppBlocks - | join kind=leftsemi (userIgnoredWarning) on ComputerName, ActivityId + | join kind=leftsemi (userIgnoredWarning) on DeviceName, ActivityId | project-away ActivityId; ignoredBlocks // Select only blocks on "Malicious" files. diff --git a/Protection events/Windows filtering events (Firewall).txt b/Protection events/Windows filtering events (Firewall).txt index 1145121a..302e4474 100644 --- a/Protection events/Windows filtering events (Firewall).txt +++ b/Protection events/Windows filtering events (Firewall).txt @@ -1,8 +1,8 @@ // Get all filtering events done by the Windows filtering platform. // This includes any blocks done by Windows Firewall rules, but also blocks triggered by some 3rd party firewalls. // When no Firewall rules are configured, the default behavior is to block all incoming network connections. -MiscEvents +DeviceEvents | where ActionType in ("FirewallOutboundConnectionBlocked", "FirewallInboundConnectionBlocked", "FirewallInboundConnectionToAppBlocked") -| project MachineId , EventTime , InitiatingProcessFileName , InitiatingProcessParentFileName, RemoteIP, RemotePort, LocalIP, LocalPort -| summarize MachineCount=dcount(MachineId) by RemoteIP +| project DeviceId , Timestamp , InitiatingProcessFileName , InitiatingProcessParentFileName, RemoteIP, RemotePort, LocalIP, LocalPort +| summarize MachineCount=dcount(DeviceId) by RemoteIP | top 100 by MachineCount desc