Detection Engineering & Threat Hunting (DE&TH) Guide
Detection Engineering & Threat Hunting (DE&TH) Guide
(DE&TH) Guide
Ver. 1.1.
March 2025
1. Process Execution or File Creation From Unusual Location
One of the most effective detection rule and/or threat hunt you can have on any intrusion incident, ransomware or
not. A lot of threat actors will drop files (executables or not, e.g.: script output) in publicly accessible folders where files
should rarely get written and/or processes rarely executed out of. To not say never in most cases. This query looks for any
files that would be created in these locations and/or processes executing out of these. This rule detects when a file gets
created and/or process executed out of the following folders:
• C:\ drive - Root
• C:\Intel - Recursive
• C:\PerfLogs - Recursive
• C:\ProgramData - Root
• C:\Program Files - Root
• C:\Program Files (x86) - Root
• C:\Users\Public - Recursive
• C:\Users\* - Root
• C:\Users\*\AppData - Root
• C:\Users\*\AppData\Local - Root
• C:\Users\*\AppData\Roaming - Root
• C:\Users\*\Favorites - Recursive
• C:\Users\*\Music - Recursive
• C:\Users\*\Pictures - Recursive
• C:\Users\*\Videos - Recursive
A lot of threat actors will end up dropping their binaries and/or files at the root of C:\ProgramData and/or
C:\Users\Public. So if you have to prioritize locations, pick these two. Other locations could be added, but be wary of false
positives (such as C:\Users*\Documents). A threat actor is staging his tools and/or files in one of these directories. False
positives can occur, but should be easily spottable. For instance, a specific Windows Update back in the day would drop
the install files at the root of the C:\ drive. LogMeIn Rescue drops .bat files in %LocalAppData%, etc.
References
• https://twitter.com/SecurityAura/status/1839489644020318342
Defender XDR
Query 1 - DeviceProcessEvents
DeviceProcessEvents
| where FolderPath matches regex @'(?i)C\:\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Intel\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\PerfLogs\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\ProgramData\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Program\ Files\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Program\ Files\ \(x86\)\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\Public\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\Local\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\Roaming\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\[^\\]+$'
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Favorites\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Music\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Pictures\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Videos\\(.*)?"
Query 2 - DeviceFileEvents
DeviceFileEvents
| where FolderPath matches regex @'(?i)C\:\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Intel\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\PerfLogs\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\ProgramData\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Program\ Files\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Program\ Files\ \(x86\)\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\Public\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\Local\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\Roaming\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\[^\\]+$'
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Favorites\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Music\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Pictures\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Videos\\(.*)?"
2
Microsoft Sentinel
Query 1 - DeviceProcessEvents
DeviceProcessEvents
| where FolderPath matches regex @'(?i)C\:\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Intel\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\PerfLogs\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\ProgramData\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Program\ Files\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Program\ Files\ \(x86\)\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\Public\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\Local\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\Roaming\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\[^\\]+$'
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Favorites\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Music\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Pictures\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Videos\\(.*)?"
Query 2 - DeviceFileEvents
DeviceFileEvents
| where FolderPath matches regex @'(?i)C\:\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Intel\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\PerfLogs\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\ProgramData\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Program\ Files\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Program\ Files\ \(x86\)\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\Public\\(.*)?'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\Local\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\AppData\\Roaming\\[^\\]+$'
or FolderPath matches regex @'(?i)C\:\\Users\\[^\\]+\\[^\\]+$'
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Favorites\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Music\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Pictures\\(.*)?"
or FolderPath matches regex @"(?i)C\:\\Users\\[^\\]+\\Videos\\(.*)?"
3
2. System Time Manipulation - Retrosigned Drivers EDR Bypass
Ransomware actors have been observed manipulating system time on endpoints in order to bypass the EDR.
MITRE ATT&CK Technique(s)
This rule detects either step in a chain of three (3) commands used to manipulate the system time on an endpoint:
• net.exe to stop the w32time service
• w32tm.exe to unregister the time service
• PowerShell to change the system date/time
A threat actor is attempting to change the system time of an endpoint which could allow it to use the Retrosigned
Drivers EDR Bypass.
References
• https://www.aon.com/en/insights/cyber-labs/bypassing-edr-through-retrosigned-drivers-and-system-time-
manipulation
• https://x.com/StrozDFIR/status/1835796156368195897 (Original Tweet)
• https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-
2012/ff799054(v=ws.11)
• https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/set-date?view=powershell-7.4
Defender XDR
Query 1 - DeviceProcessEvents
DeviceProcessEvents
| where FileName in~ ("net.exe","net1.exe") and ProcessCommandLine has_all
("stop","w32time")
or FileName =~ "w32tm.exe" and ProcessCommandLine has ("unregister")
or FileName in~ ("powershell.exe","pwsh.exe") and ProcessCommandLine has ("Set-Date")
Microsoft Sentinel
Query 1 - DeviceProcessEvents
DeviceProcessEvents
| where FileName in~ ("net.exe","net1.exe") and ProcessCommandLine has_all
("stop","w32time")
or FileName =~ "w32tm.exe" and ProcessCommandLine has ("unregister")
or FileName in~ ("powershell.exe","pwsh.exe") and ProcessCommandLine has ("Set-Date")
4
3. WBAdmin.exe - Sensitive File Dump or Collection
Encountered during an Akira Ransomware incident from Summer 2024, the threat actor installed the Windows
Server Backup optional feature, used wbadmin.exe to create a backup of NTDS.dit alongside the SECURITY and SYSTEM
hives and uninstalled the feature afterwards.
MITRE ATT&CK Technique(s)
This rule detects the use of wbadmin.exe with the "start" and "backup" parameters and the presence of sensitive
filenames:
• NTDS.dit
• SYSTEM (for the SYSTEM Registry Hive)
• SECURITY (for the SECURITY Registry Hive)
• SAM (for the SAM Registry Hive)
In this particular incident, only the NTDS.dit, SYSTEM and SECURITY Hives were targeted by the threat actor. A threat
actor is attempting to obtain copies of the ntds.dit (Active Directory database) and the SECURITY + SYSTEM + SAM Registry
Hives which would allow it to dump the content of ntds.dit and recover information such as password hashes.
References
• https://detection.fyi/sigmahq/sigma/windows/process_creation/proc_creation_win_wbadmin_dump_sensitive_fil
es/?query=wbadmin
• https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/wbadmin
Defender XDR
Query 1 - DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "wbadmin.exe"
| where ProcessCommandLine has_all ("start","backup")
| where ProcessCommandLine has_any ("ntds.dit","SYSTEM","SECURITY","SAM")
Microsoft Sentinel
Query 1 - DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "wbadmin.exe"
| where ProcessCommandLine has_all ("start","backup")
| where ProcessCommandLine has_any ("ntds.dit","SYSTEM","SECURITY","SAM")
5
4. Authentication From Suspicious WorkstationName
This query looks for authentication events where the remote workstation name has a suspicious string in it. This
query looks for authentication events where the remote workstation name could be a default Windows one, such as:
• DESKTOP-x
• LAPTOP-x
• WIN-x
When a threat actor connects to an environment through VDI (such as Citrix), VPN and/or via a compromised edge
appliance (such as firewall), its workstation name (hostname or else) can be included in authentication events (such as
4624s and 4625s). Therefore, actively detecting and/or seeking out authentication events where these strings are present
can help in identifying potentially suspicious systems in the environment. The caveat of this query is that, if the environment
doesn't have a standardized naming convention (e.g.: default Windows names are used) and/or one that overlaps with the
defined strings, there can be false positives. Therefore, adjust and/or fine-tune the queries accordingly.
Defender XDR
Query 1 - Defender for Endpoint (MDE) via DeviceLogonEvents
let SuspiciousWorkstationNameStrings = dynamic([
"DESKTOP",
"LAPTOP",
"WIN"
where RemoteDeviceName has_any (SuspiciousWorkstationNameStrings)
Query 2 - Defender for Identity (MDI) via IdentityLogonEvents
let SuspiciousWorkstationNameStrings = dynamic([
"DESKTOP",
"LAPTOP",
"WIN"
]);
IdentityLogonEvents
| extend RemoteWorkstationName = tostring(parse_json(AdditionalFields).["FROM.DEVICE"])
| where RemoteWorkstationName has_any (SuspiciousWorkstationNameStrings)
Microsoft Sentinel
Query 1 - Defender for Endpoint (MDE) via DeviceLogonEvents
let SuspiciousWorkstationNameStrings = dynamic([
"DESKTOP",
"LAPTOP",
"WIN"
]);
DeviceLogonEvents
| where RemoteDeviceName has_any (SuspiciousWorkstationNameStrings)
Query 2 - Defender for Identity (MDI) via IdentityLogonEvents
let SuspiciousWorkstationNameStrings = dynamic([
"DESKTOP",
"LAPTOP",
"WIN"
]);
IdentityLogonEvents
| extend RemoteWorkstationName = tostring(parse_json(AdditionalFields).["FROM.DEVICE"])
| where RemoteWorkstationName has_any (SuspiciousWorkstationNameStrings)
Query 3 - Security Event ID 4624 (Successful Logon) and 4625 (Failed Logon)
let SuspiciousWorkstationNameStrings = dynamic([
"DESKTOP",
"LAPTOP",
"WIN"
]);
SecurityEvent
| where EventID in ("4624","4625")
| where WorkstationName has_any (SuspiciousWorkstationNameStrings)
6
5. Split or Part Archive Files
This query look for file events where split and/or part file archives are involved. When it comes to data exfiltration,
many threat actors may rely on archiving utilities to collect/stage the data they want to exfiltrate beforehand. For instance,
put in a RAR or 7z archive all the Office and PDF related files from a network share. With applications such as WinRAR and
7-Zip, you also have the option to create split or part archives. Basically, instead of putting inside a single archive a folder
with around 10 GB of data, you can break it down in smaller archives of maximum 1 GB each. Depending on which
application is used, the split/part archives created have a distinctive naming scheme:
• WinRAR - Ends with partX.rar (e.g.: Finances.part1.rar, Finances.part11.rar, Finances.part111.rar, etc.)
• 7-Zip - Ends with 7z.X (e.g.: Finances.7z.001, Finances.7z.011, Finances.7z.111, etc.)
Since we know how these files are named, we can then look for them via Defender for Endpoint (MDE) using the
DeviceFileEvents table. Split archives can be created using other format/extensions. For instance, you can also create split
archives in a ZIP format using 7-Zip. The query below only showcases the RAR and 7z extensions, which are the most
popular.
Defender XDR
Query 1 - Defender for Endpoint (MDE) via DeviceFileEvents
DeviceFileEvents
| where FileName matches regex @'(?i)\.part[0-9]{1,}\.rar'
or FileName matches regex @'(?i)\.7z\.[0-9]{1,}'
Microsoft Sentinel
Query 1 - Defender for Endpoint (MDE) via DeviceFileEvents
DeviceFileEvents
| where FileName matches regex @'(?i)\.part[0-9]{1,}\.rar'
or FileName matches regex @'(?i)\.7z\.[0-9]{1,}'
7
6. Potential Cleartext Credentials in Command Line
This query look for process execution events which may contain/have credentials (cleartext or not) in their command
line. This is a query which I discussed before and/or even shared a pseudo-code snippet on Twitter. I think Nathan McNulty
(@NathanMcNulty) may also have shared something similar at some point. The idea here is to look for credentials in
process command line, which can offer a lot of insights into an organization inherent risk:
• Identify weak passwords
• Identify accounts whose credentials are being passed in the command line directly
• Identify potential password re-use if a password is being used across multiple accounts spotted in the command
lines
• Identify potentially unsecured scripts (files) that have credentials in them (e.g.: script on a public share that runs a
command as a privileged user)
• Identify accounts whose credentials could be used if a threat actor was to get their hands on that information
(e.g.: Event ID 4688, Process Creation event with command line logging, on systems)
More processes may return false positives (e.g.: Chromium-based processes). Simply add them to the
ExcludedProcesses variable according to your needs.
Defender XDR
Query 1 - Defender for Endpoint (MDE) via DeviceProcessEvents
// Excluding known false positive processes
let ExcludedProcesses = dynamic([
"WerFault.exe",
"WerFaultSecure.exe",
"SenseNDR.exe"
]);
// You can add more strings as needed
let PossibleUserCLI = dynamic([
"/U",
"/User",
"/username",
"-u",
"-user",
"--user",
"--username"
]);
// You can add more strings as needed
let PossiblePasswordCLI = dynamic([
"/P",
"/password",
"/pass",
"-p",
"-password",
"-pw",
"-pass",
"--pass",
"--password"
]);
DeviceProcessEvents
| where not (FileName in~ (ExcludedProcesses))
| where ProcessCommandLine has_any (PossibleUserCLI)
| where ProcessCommandLine has_any (PossiblePasswordCLI)
// Uncomment if you get too many results, just to get a pre-filtering of the results
//| distinct ProcessCommandLine
Microsoft Sentinel
Query 1 - Defender for Endpoint (MDE) via DeviceProcessEvents
// Excluding known false positive processes
let ExcludedProcesses = dynamic([
"WerFault.exe",
"WerFaultSecure.exe",
"SenseNDR.exe"
]);
// You can add more strings as needed
let PossibleUserCLI = dynamic([
"/U",
"/User",
"/username",
"-u",
8
"-user",
"--user",
"--username"
]);
// You can add more strings as needed
let PossiblePasswordCLI = dynamic([
"/P",
"/password",
"/pass",
"-p",
"-password",
"-pw",
"-pass",
"--pass",
"--password"
]);
DeviceProcessEvents
| where not (FileName in~ (ExcludedProcesses))
| where ProcessCommandLine has_any (PossibleUserCLI)
| where ProcessCommandLine has_any (PossiblePasswordCLI)
// Uncomment if you get too many results, just to get a pre-filtering of the results
//| distinct ProcessCommandLine
9
7. Interactive or RemoteInteractive Session From Service Account
This query looks for remote interactive (RDP) sessions from service accounts. This query looks for remote interactive
(RDP) sessions from service accounts. Service accounts, as "understood" by most SMBs are often just an account that
powers a service, task, process, etc. They may or may not use a specific naming convention as well, for instance:
• svcExchange
• svc-MSSQL
• Web.svc
A lot of these accounts are often just created as normal users and plugged in a Windows service or configured in
an application. Therefore, unless they've been explicitly restricted via GPOs or else, they can still be used for RDP. When a
threat actor compromise a service account, he may use it to perform lateral movement throughout an environment, even
RDP. These accounts should never be used for such tasks, but because they're often not configured appropriately, threat
actor can abuse them. They'll also often have high-privilege (e.g.: local Administrator on server(s) or even be part of the
Domain Admins group). Depending on your environment, these queries can be used to look for remote interactive sessions
from service accounts based on a naming convention or a regex. You could even push this further by grabbing their name
from another table (e.g.: IdentityInfo) or an external source (a Watchlist) if their name aren't standardized.
Defender XDR
Query 1 - Defender for Endpoint (MDE) via DeviceLogonEvents
// Replace the values in the variable by the ones used for your service accounts, if it's
a tokenized value
let ServiceAccountStrings = dynamic([
"svc",
"service",
]);
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| where AccountName has_any (ServiceAccountStrings)
// Use this variant if the service account naming convention cannot be tokenized
// Replace the value of the "contains" filter by the string you're lookign for
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| where AccountName contains "svc"
// Use this variant if you're really good at regex and want to flex it
// For instance, if the Account Name starts with "svc" or "service"
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| where AccountName matches regex @'(?i)(svc|service)'
Query 2 - Defender for Identity (MDI) via IdentityLogonEvents
// Replace the values in the variable by the ones used for your service accounts, if it's
a tokenized value
let ServiceAccountStrings = dynamic([
"svc",
"service",
]);
IdentityLogonEvents
| where Application == "Active Directory"
| where LogonType == "Remote desktop"
| where AccountName has_any (ServiceAccountStrings)
// Use this variant if the service account naming convention cannot be tokenized
// Replace the value of the "contains" filter by the string you're lookign for
IdentityLogonEvents
| where Application == "Active Directory"
| where LogonType == "Remote desktop"
| where AccountName contains "svc"
// Use this variant if you're really good at regex and want to flex it
// For instance, if the Account Name starts with "svc" or "service"
IdentityLogonEvents
| where Application == "Active Directory"
| where LogonType == "Remote desktop"
| where AccountName matches regex @'(?i)(svc|service)'
Microsoft Sentinel
Query 1 - Defender for Endpoint (MDE) via DeviceLogonEvents
// Replace the values in the variable by the ones used for your service accounts, if it's
a tokenized value
let ServiceAccountStrings = dynamic([
10
"svc",
"service",
]);
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| where AccountName has_any (ServiceAccountStrings)
// Use this variant if the service account naming convention cannot be tokenized
// Replace the value of the "contains" filter by the string you're lookign for
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| where AccountName contains "svc"
// Use this variant if you're really good at regex and want to flex it
// For instance, if the Account Name starts with "svc" or "service"
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| where AccountName matches regex @'(?i)(svc|service)'
Query 2 - Defender for Identity (MDI) via IdentityLogonEvents
// Replace the values in the variable by the ones used for your service accounts, if it's
a tokenized value
let ServiceAccountStrings = dynamic([
"svc",
"service",
]);
IdentityLogonEvents
| where Application == "Active Directory"
| where LogonType == "Remote desktop"
| where AccountName has_any (ServiceAccountStrings)
// Use this variant if the service account naming convention cannot be tokenized
// Replace the value of the "contains" filter by the string you're lookign for
IdentityLogonEvents
| where Application == "Active Directory"
| where LogonType == "Remote desktop"
| where AccountName contains "svc"
// Use this variant if you're really good at regex and want to flex it
// For instance, if the Account Name starts with "svc" or "service"
IdentityLogonEvents
| where Application == "Active Directory"
| where LogonType == "Remote desktop"
| where AccountName matches regex @'(?i)(svc|service)'
Query 3 - Security Event ID 4624 (Successful Logon)
// Replace the values in the variable by the ones used for your service accounts, if it's
a tokenized value
let ServiceAccountStrings = dynamic([
"svc",
"service",
]);
SecurityEvent
| where EventID == "4624"
| where LogonType == "10"
| where TargetUserName has_any (ServiceAccountStrings)
// Use this variant if the service account naming convention cannot be tokenized
// Replace the value of the "contains" filter by the string you're lookign for
SecurityEvent
| where EventID == "4624"
| where LogonType == "10"
| where TargetUserName contains "svc"
// Use this variant if you're really good at regex and want to flex it
// For instance, if the Account Name starts with "svc" or "service"
SecurityEvent
| where EventID == "4624"
| where LogonType == "10"
| where TargetUserName matches regex @'(?i)(svc|service)'
Query 4 - Microsoft-Windows-TerminalServices-LocalSessionManager (Event ID 21 to 25)
// Replace the values in the variable by the ones used for your service accounts, if it's
a tokenized value
let ServiceAccountStrings = dynamic([
"svc",
"service",
]);
Event
11
| where Source == "Microsoft-Windows-TerminalServices-LocalSessionManager"
| where EventID in ("21","22","23","24","25")
| extend ParsedAccount = tostring(parse_xml(EventData).DataItem.UserData.EventXML.User)
| extend AccountName = tostring(split(ParsedAccount,"\\")[1])
| where AccountName has_any (ServiceAccountStrings)
// Use this variant if the service account naming convention cannot be tokenized
// Replace the value of the "contains" filter by the string you're lookign for
Event
| where Source == "Microsoft-Windows-TerminalServices-LocalSessionManager"
| where EventID in ("21","22","23","24","25")
| extend ParsedAccount = tostring(parse_xml(EventData).DataItem.UserData.EventXML.User)
| extend AccountName = tostring(split(ParsedAccount,"\\")[1])
| where AccountName contains "svc"
// Use this variant if you're really good at regex and want to flex it
// For instance, if the Account Name starts with "svc" or "service"
Event
| where Source == "Microsoft-Windows-TerminalServices-LocalSessionManager"
| where EventID in ("21","22","23","24","25")
| extend ParsedAccount = tostring(parse_xml(EventData).DataItem.UserData.EventXML.User)
| extend AccountName = tostring(split(ParsedAccount,"\\")[1])
| where AccountName matches regex @'(?i)(svc|service)'
12
8. Command Line Interpreters Launched via Scheduled Tasks
This query looks for command line interpreters that were launched by Scheduled Tasks on Windows systems. This
query looks for command line interpreters, and therefore, scripts (e.g.: BAT, CMD, PS1, etc.) that were launched by
Scheduled Tasks on Windows systems. Threat actors can leverage Scheduled Tasks for Execution and Persistence, but also
Privilege Escalation. The Privilege Escalation angle is interesting, because if a Scheduled Task set to run in the NT
AUTHORITY\SYSTEM context executes a file that is modifiable by low-privileged users, it could lead to the user modifying
this file to execute any commands he wants. Think of files that are located on servers where a user is not an Administrator,
but can modify the content/files within the folder where the called script file is located. As for Execution, you could typically
look for "unique", kind of "one-off" commands that are ran (e.g.: cmd.exe /c net BLABLA). For Persistence, a lot of popular
malware nowaday leveraged Scheduled Tasks for persistence, think uncommon/suspicious paths under %APPDATA%,
%LOCALAPPDATA%, C:\ProgramData, etc.
Defender XDR
Defender for Endpoint (MDE) via DeviceProcessEvents
let CommandLineInterpreters = dynamic([
"cmd.exe",
"powershell.exe",
"pwsh.exe",
"wmic.exe",
"mshta.exe",
"cscript.exe",
"wscript.exe"
]);
DeviceProcessEvents
// For anything pre-Windows 10 version 1511
| where ((InitiatingProcessFileName =~ "taskeng.exe")
// For anything post Windows 10 version 1511
or (InitiatingProcessFileName =~ "svchost.exe" and InitiatingProcessCommandLine has
"Schedule"))
| where FileName in~ (CommandLineInterpreters)
// Summarize the output to make it cleaner to read and identify possible outliers
// However, you can remove this summarization if you want to get the "raw" results and
simply start with a simple "distinct" for instance
| summarize ["Devices"]=make_set(DeviceName),
["Number of Devices"]=dcount(DeviceName)
by ProcessCommandLine
Microsoft Sentinel
Defender for Endpoint (MDE) via DeviceProcessEvents
let CommandLineInterpreters = dynamic([
"cmd.exe",
"powershell.exe",
"pwsh.exe",
"wmic.exe",
"mshta.exe",
"cscript.exe",
"wscript.exe"
]);
DeviceProcessEvents
// For anything pre-Windows 10 version 1511
| where ((InitiatingProcessFileName =~ "taskeng.exe")
// For anything post Windows 10 version 1511
or (InitiatingProcessFileName =~ "svchost.exe" and InitiatingProcessCommandLine has
"Schedule"))
| where FileName in~ (CommandLineInterpreters)
// Summarize the output to make it cleaner to read and identify possible outliers
// However, you can remove this summarization if you want to get the "raw" results and
simply start with a simple "distinct" for instance
| summarize ["Devices"]=make_set(DeviceName),
["Number of Devices"]=dcount(DeviceName)
by ProcessCommandLine
13
9. Files Potentially Holding Sensitive Information (MDE)
This query look for file events involving files that may potentially hold sensitive information, such as credentials. A
query similar to the one shared on Day 4 of #100DaysOfKQL, but for file-based activity. You can define a list of sensitive
strings (e.g.: pass, password, passwords, etc.) and look for files that have these strings. You can also define which kind of
files you're looking for, based on their extension (e.g.: DOC, DOCX, TXT, etc.). This query can help identify potentially
unsecured files that may hold sensitive information such as: credentials, secrets, API tokens and the likes. Files that a threat
actor could find as well when running basic searches for files with these strings.
Defender XDR
Defender for Endpoint (MDE) via DeviceFileEvents
// You can add interesting filename strings as needed
let FileNameStrings = dynamic([
"pass",
"password",
"passwords",
"cred",
"creds",
"credential",
"credentials",
"secret",
"secrets",
"keys"
]);
// You can add file extensions you may be looking for as needed
let FileExtensions = dynamic([
"txt",
"doc",
"docx",
"bat",
"cmd",
"ps1",
"rtf",
"png",
"jpg",
"jpeg"
]);
DeviceFileEvents
| where FileName has_any (FileNameStrings)
| extend FileExtension = split(FileName,".")[-1]
| where FileExtension in~ (FileExtensions)
Microsoft Sentinel
Defender for Endpoint (MDE) via DeviceFileEvents
// You can add interesting filename strings as needed
let FileNameStrings = dynamic([
"pass",
"password",
"passwords",
"cred",
"creds",
"credential",
"credentials",
"secret",
"secrets",
"keys"
]);
// You can add file extensions you may be looking for as needed
let FileExtensions = dynamic([
"txt",
"doc",
"docx",
"bat",
"cmd",
"ps1",
"rtf",
"png",
"jpg",
"jpeg"
14
]);
DeviceFileEvents
| where FileName has_any (FileNameStrings)
| extend FileExtension = split(FileName,".")[-1]
| where FileExtension in~ (FileExtensions)
15
10. DeviceNetworkEvents From Windows Processes and Domains by TLD (Summarized)
This query is a summarization query which provides, by TLD, DeviceNetworkEvents from processes in the
C:\Windows folder (and subfolders). A very simple query I came with today as I was looking for a way to cast a "wide" net
on which TLDs have been observed for DeviceNetworkEvents from processes coming in the C:\Windows folder. Think of it
as a very "large" query that, if you review the results, could show processes in C:\Windows which may have been the target
of process injection and are now beaconing to C2s. Assuming these C2s are under an uncommon and/or known abused
TLD, think: .top, .cc, .shop, etc. It may also allow you to identify weird, but legitimate, applications (think WTFBins, but on a
network level maybe). At the very least, this is what happened today for me! Obviously, you can edit that query quite easily
to target another folder (or even other folders). The summarization used is one that fits my needs, but you may adjust it
depending on yours.
Defender XDR
Defender for Endpoint (MDE) via DeviceNetworkEvents
DeviceNetworkEvents
| where RemoteIPType == "Public"
// You can change the path as needed
| where InitiatingProcessFolderPath startswith @"C:\Windows\"
| extend RootDomain = extract(@"[^.]+\.[^.]+$",0,
extract(@"^(?:https?://)?([^/]+)",1,RemoteUrl))
| extend DomainTLD = tostring(split(RootDomain,".")[-1])
| summarize count(),
["RootDomains"] = make_set(RootDomain),
["Processes"]= make_set(InitiatingProcessFolderPath),
["ProcessesCount"] = dcount(InitiatingProcessFolderPath)
by DomainTLD
// If you want to filter out very popular TLDs (e.g.: com, net, org, etc.) simply run the
query once, find out where your "count" limit would be then uncomment and adjust the
value
//| where count_ < 2000
Microsoft Sentinel
Defender for Endpoint (MDE) via DeviceNetworkEvents
DeviceNetworkEvents
| where RemoteIPType == "Public"
// You can change the path as needed
| where InitiatingProcessFolderPath startswith @"C:\Windows\"
| extend RootDomain = extract(@"[^.]+\.[^.]+$",0,
extract(@"^(?:https?://)?([^/]+)",1,RemoteUrl))
| extend DomainTLD = tostring(split(RootDomain,".")[-1])
| summarize count(),
["RootDomains"] = make_set(RootDomain),
["Processes"]= make_set(InitiatingProcessFolderPath),
["ProcessesCount"] = dcount(InitiatingProcessFolderPath)
by DomainTLD
// If you want to filter out very popular TLDs (e.g.: com, net, org, etc.) simply run the
query once, find out where your "count" limit would be then uncomment and adjust the
value
//| where count_ < 2000
16
11. Silent cmd.exe Execution With Redirected STDERR & STDOUT.md
This query returns instances of cmd.exe that were executed silently where the STDERR and STDOUT outputs are
redirected. A lot of reverse shells and/or C2s will execute commands that are passed to them via cmd.exe. They may also
use the following arguments to keep it stealthy:
• /Q which turns Echo off
• /C which runs the command and then close the window
Additionally, depending on the tool used, the various outputs of the command may be captured in a file (or pipe)
which is then sent over and/or read from the remote host (where the command is being sent).
• 2>&1 will redirect the stderr to the stdout (https://learn.microsoft.com/en-
us/troubleshoot/developer/visualstudio/cpp/language-compilers/redirecting-error-command-prompt)
This way, you can still get the stderr/stdout. This method of cmd.exe execution is also part of many open-source
projects that are (ab)used by threat actors:
• Impacket https://github.com/search?q=repo%3Afortra%2Fimpacket%202%3E%261&type=code
• CrackMapExec
https://github.com/search?q=repo%3Abyt3bl33d3r%2FCrackMapExec%202%3E%261&type=code
• NetExec (it's CME successor after all)
https://github.com/search?q=repo%3APennyw0rth%2FNetExec%202%3E%261&type=code
While not specific to OSTs, it's pretty common to see traces of this when it comes to incidents that lead to data
exfiltration, ransomware deployment or even where a threat actor was able to get a foothold in an environment. Which
makes it a good candidate for a threat hunting query. Run it, review the results and determine if any of the commands
launched that way could be reverse-shell related and/or examine their execution context (e.g.: user, parent process,
execution timeframe, etc.).
Defender XDR
Defender for Endpoint (MDE) via DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "cmd.exe"
| where ProcessCommandLine has_all ("/Q","/C")
| where ProcessCommandLine has_any ("&1","2>&1")
Microsoft Sentinel
Defender for Endpoint (MDE) via DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "cmd.exe"
| where ProcessCommandLine has_all ("/Q","/C")
| where ProcessCommandLine has_any ("&1","2>&1")
SecurityEvent - Event ID 4688 (Process Creation)
SecurityEvent
| where EventID == "4688"
| where Process =~ "cmd.exe"
| where CommandLine has_all ("/Q","/C")
| where CommandLine has_any ("&1","2>&1")
17
12. Low Prevalence DLL Loaded From Process In User Downloads Folder
This query returns events where a DLL with a low prevalence was loaded from a user's Downloads folder, by a
process also in the Downloads folder. This query returns events where a DLL with a low prevalence, per Defender XDR
FileProfile() was loaded from a user's Downloads folder, by a process in the same folder (or subfolder). This is a detection
or hunting query aimed at detecting initial access from malware that uses DLL sideloading for execution. The chain of
events here is basically a user would go on the Internet, download an archive (e.g.: ZIP) that has multiple files in it: legit
EXE, MSI, etc., a few legit DLLs and a malicious one. He extracts the content of the archive where it is right now, often in
the Downloads folder, where it was downloaded and then proceeds to execute the EXE, MSI, etc. This is what you would
see in Nitrogen-related infection. The downside of this query, obviously, is that it only works in Advanced Hunting (Defender
XDR console) since it makes use of FileProfile() (https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-
fileprofile-function)
Reference(s)
• https://www.threatdown.com/blog/nitrogen-shelling-malware-from-hacked-sites/
• https://thedfirreport.com/2024/09/30/nitrogen-campaign-drops-sliver-and-ends-with-blackcat-ransomware/
• https://news.sophos.com/en-us/2023/07/26/into-the-tank-with-nitrogen/
Defender XDR
Defender for Endpoint (MDE) via DeviceImageLoadEvents
let LoadedDLLs = (
DeviceImageLoadEvents
| where InitiatingProcessFolderPath matches regex
@"(?i)\\Users\\[^\\]+\\Downloads\\(.*)?"
| where FolderPath matches regex @"(?i)\\Users\\[^\\]+\\Downloads\\(.*)?"
| where FileName endswith ".dll"
| distinct SHA1
// The FileProfile() has a limit of 1000 lookup/query.
| invoke FileProfile("SHA1",1000)
);
DeviceImageLoadEvents
| where InitiatingProcessFolderPath matches regex
@"(?i)\\Users\\[^\\]+\\Downloads\\(.*)?"
| where FolderPath matches regex @"(?i)\\Users\\[^\\]+\\Downloads\\(.*)?"
| where FileName endswith ".dll"
| join kind=inner LoadedDLLs on SHA1
// You can add a filter on the GlobalPrevalence column if you wish to reduce the number
of results, though I suggest to simply order them from lowest to highest and look for the
ones with the lowest prevalence
//| where GlobalPrevalence < 500
//| order by GlobalPrevalence asc
18
13. Virtual Drive Mounted From Archive
This query returns events where a virtual drive file, hidden inside an archive, would've been mounted by a user on
an endpoint. This query returns events where a virtual drive file, hidden inside an archive (virtual drive smuggling?), would've
been mounted by a user who double-clicked on it, within that archive. When you double-click on file inside an archive
without extracting it, may it be ZIP, 7z, RAR, etc. it'll be temporarily extracted in the user's %TEMP% folder and launched.
The same goes for virtual drive. They'll be residing in the %TEMP% folder, underneat the folder of whatever application
was used to go "inside" the archive and double-click on the virtual drive as long as it's mounted. Therefore, we can look
for file events where a file with a "virtual drive" (or image) extension such as VHD, VHDX, VMDK, ISO, etc. is created within
an extracted archive folder in the user's %TEMP% folder. You may want to read this article from Palo Alto which references
CVE-2023-36884 and what happened to the use of the "Temp1" folder in %TEMP%.
https://unit42.paloaltonetworks.com/new-cve-2023-36584-discovered-in-attack-chain-used-by-russian-apt/
Defender XDR
Defender for Endpoint (MDE) via DeviceFileEvents
let DiskImageFileExtensions = dynamic([
"iso",
"img",
"vhd",
"vhdx",
"wim"
]);
DeviceFileEvents
| where FolderPath matches regex @"(?i)\\Users\\[^\\]+\\AppData\\Local\\Temp\\(.*)?"
| where FolderPath has_any ("7zo","Rar$",".zip","Temp1_")
| extend FileExtension = split(FileName,".")[-1]
| where FileExtension in~ (DiskImageFileExtensions)
Microsoft Sentinel
Defender for Endpoint (MDE) via DeviceFileEvents
let DiskImageFileExtensions = dynamic([
"iso",
"img",
"vhd",
"vhdx",
"wim"
]);
DeviceFileEvents
| where FolderPath matches regex @"(?i)\\Users\\[^\\]+\\AppData\\Local\\Temp\\(.*)?"
| where FolderPath has_any ("7zo","Rar$",".zip","Temp1_")
| extend FileExtension = split(FileName,".")[-1]
| where FileExtension in~ (DiskImageFileExtensions)
19
14. Script Execution From User's Downloads Folder
This query returns events where a user launched a script from his Downloads folder. This query returns events where
a user launched a script (BAT, PS1, JS, JSE, VB, VBE, VBS, etc.) from his Downloads folder. Typical phishing technique which
is still around but probably less effective now. Basically, get a user to download a JS file masquerading as something else
(fake browser update, SocGholish-like) and then execute it. It'll also work if a user download an archive (e.g.: ZIP) with a
script inside, but extracted the content of the archive in the Downloads folder first, and then went in it and double-clicked
to launch the script. Most scripts will be executed with either wscript.exe or cscript.exe, except for the obvious BAT, CMD,
PS1, and since the script is in the command line, we can look for events where it's located within a user's Downloads folder.
Defender XDR
Defender for Endpoint (MDE) via DeviceProcessEvents
let ScriptInterpreters = dynamic([
"cmd.exe",
"powershell.exe",
"pwsh.exe",
"mshta.exe"
"cscript.exe",
"wscript.exe"
]);
DeviceProcessEvents
| where FileName in~ (ScriptInterpreters)
| extend ScriptPath = extract(@"(?i)[aA-
zZ]\:\\Users\\[^\\]+\\Downloads\\(.*)+\b",0,ProcessCommandLine)
| where isnotempty(ScriptPath)
Microsoft Sentinel
Defender for Endpoint (MDE) via DeviceProcessEvents
let ScriptInterpreters = dynamic([
"cmd.exe",
"powershell.exe",
"pwsh.exe",
"mshta.exe"
"cscript.exe",
"wscript.exe"
]);
DeviceProcessEvents
| where FileName in~ (ScriptInterpreters)
| extend ScriptPath = extract(@"(?i)[aA-
zZ]\:\\Users\\[^\\]+\\Downloads\\(.*)+\b",0,ProcessCommandLine)
| where isnotempty(ScriptPath)
20
15. Script Execution From User's Downloads Folder
This query returns all successful sign-ins in Entra ID for the OfficeHome application with ASN enrichment on the IP
the sign-in came from. This query returns all successful, whether the access was granted or not, sign-ins in Entra ID for the
OfficeHome (portal.office.com) application with ASN enrichment on the IP the sign-in came from. The ASN enrichment is
done through GypTheCat[.]com awesome Kusto ASN Table. Once again, thank you to Matt Zorich (@reprise99) for showing
me that site and resource!
https://firewalliplists.gypthecat.com/kusto-tables/kusto-asn-table/
This is something you'll often see in "Attacker-in-the-middle" phishing, where a successful sign-in will be generated
from an IP associated with a bad and/or mostly server hosting/colocation ASN and the associated app will be OfficeHome.
See the reference(s) below. That specific behavior, while it can be changed, is still quite common/popular and therefore,
can be easily detectable and/or hunted for in most environments (small, or large).
Reference(s)
• https://github.com/Cloud-Architekt/AzureAD-Attack-Defense/blob/main/Adversary-in-the-Middle.md#hunting-
of-officehome-application-sign-ins-by-dart-team-query
• https://www.microsoft.com/en-us/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-
phishing-sites-as-entry-point-to-further-financial-fraud/
• https://jeffreyappel.nl/aitm-mfa-phishing-attacks-in-combination-with-new-microsoft-protections-2023-edt/
Microsoft Sentinel
Microsoft Entra ID via SigninLogs
let CIDRASN = (
externaldata (CIDR:string, CIDRASN:int, CIDRASNName:string, CIDRSource:string)
['https://firewalliplists.gypthecat.com/lists/kusto/kusto-cidr-asn.csv.zip']
with (ignoreFirstRecord=true)
);
// Taken from Matt Zorich (@reprise99) awesome website:
https://learnsentinel.blog/2021/08/30/azure-sentinel-and-the-story-of-a-very-persistent-
attacker/
let SuccessCodes = dynamic([0, 50055, 50057, 50155, 50105, 50133, 50005, 50076, 50079,
50173, 50158, 50072, 50074, 53003, 53000, 53001, 50129]);
SigninLogs
| where AppDisplayName == "OfficeHome"
| where ResultType in (SuccessCodes)
| evaluate ipv4_lookup(CIDRASN, IPAddress, CIDR, return_unmatched=true)
21
16. Inbox Rule With Non-Alphanumeric Characters or Short Name Created
This query return events were an Inbox Rule (in Exchange, Office 365) was created with a name that is only non-
alphanumeric characters (e.g.: "..", "--", "-.-", etc.) or very short. This query return events were an Inbox Rule (in Exchange,
Office 365) was created with a name that is only non-alphanumeric characters (e.g.: "..", "--", "-.-", etc.) or very short, for
instance, less than 5 characters. When threat actors compromise mailboxes in Business Email Compromise (BEC) incidents,
they'll often create inbox rules to start hiding emails from the user. They can hide emails based on sender, sender domain,
strings in the email subject or body, etc. The main goal behind this is for the threat actor to start interacting with these
emails to setup a financial fraud of some kind. For instance, send to the victim new bank account information for their due
invoice payment. Since these emails are hidden from the legitimate user, the fraud may just go unnoticed until the payment
gets sent/transferred/etc.
Most of the time, these inbox rules will be created with either non-alphanumeric characters as name or with very
short names (under 5 characters, even 1 or 2). The creation of inbox rules with non-alphanumeric characters is pretty
atypical for normal users. As for inbox rules with really short names, it can happen, but when you review what the rule does,
for instance, where the emails are moved (e.g.: RSS Feed, Conversation History, etc.), it makes it easy to determine if the hit
is a true positive or a false positive. There are two (2) versions of that query, one that leverages the Microsoft Defender for
Cloud Apps (MCAS) table, while the other one leverages the OfficeActivity table from the Microsoft 365 connector in
Microsoft Sentinel. These queries only focus on one aspect of a suspicious inbox rule: the name. There are other properties
you could look into that may make a rule stand out as being suspicious/malicious. See the Expel.io blog post in the
Reference(s) section.
Reference(s)
• https://expel.com/blog/suspicious-outlook-rules-high-fidelity-patterns-to-watch-for/
• https://www.truesec.com/hub/blog/understanding-the-threat-what-is-business-email-compromise (Step 5 -
Inbox Rules)
• https://www.huntress.com/blog/one-msp-three-microsoft-365-compromises-72-hours
Defender XDR
Microsoft Defender for Cloud Apps via CloudAppEvents
let ActionTypes = dynamic([
"New-InboxRule",
"Set-InboxRule",
"Enable-InboxRule"
]);
// You can adjust this value to tune-down possible false positives.
let RuleNameLength = 5;
CloudAppEvents
| where ActionType in~ (ActionTypes)
| mv-expand ActObj = ActivityObjects
| where ActObj.Name in~ ("Name","RuleName")
| extend RuleName = ActObj.Value
| where isnotempty(RuleName)
| where RuleName matches regex @"^[^a-zA-Z0-9]*$"
or strlen(RuleName) < RuleNameLength
Microsoft Sentinel
Microsoft Defender for Cloud Apps via CloudAppEvents
let ActionTypes = dynamic([
"New-InboxRule",
"Set-InboxRule",
"Enable-InboxRule"
]);
// You can adjust this value to tune-down possible false positives.
let RuleNameLength = 5;
CloudAppEvents
| where ActionType in~ (ActionTypes)
| mv-expand ActObj = ActivityObjects
| where ActObj.Name in~ ("Name","RuleName")
| extend RuleName = ActObj.Value
| where isnotempty(RuleName)
| where RuleName matches regex @"^[^a-zA-Z0-9]*$"
or strlen(RuleName) < RuleNameLength
Microsoft 365 via OfficeActivity
let OperationTypes = dynamic([
"New-InboxRule",
"Set-InboxRule",
22
"Enable-InboxRule"
]);
// You can adjust this value to tune-down possible false positives.
let RuleNameLength = 5;
OfficeActivity
| where Operation in~ (OperationTypes)
| mv-expand Params = todynamic(Parameters)
| where Params.Name in~ ("Name","RuleName")
| extend RuleName = Params.Value
| where isnotempty(RuleName)
| where RuleName matches regex @"^[^a-zA-Z0-9]*$"
or strlen(RuleName) < RuleNameLength
23
17. Potential Tunneled RDP Session
This query return events where a RDP session may have been opened through a tunnel on a Windows host. This
query return events where a RDP session may have been opened through a tunnel, set-up with tools such as plink and
ngrok, or built-in binaries such as SSH on a Windows host. Identification of these session come from the distinct IP address
that is present in these logon events, namely:
• 127.0.0.1
• ::1
• ::%16777216
The last one is quite distinctive when the RDP session is opened through a ngrok tunnel. See the tweet below from
Stephan Berger (@malmoeb), in which I was also tagged back then (feelold.png).
https://x.com/malmoeb/status/1519710302820089857?lang=ar-x-fm
Reference(s)
• https://x.com/malmoeb/status/1519710302820089857?lang=ar-x-fm
• https://www.logpoint.com/en/blog/a-deep-look-at-the-darkside-ransomware-operators-and-their-affiliates/
• https://news.sophos.com/en-us/2022/07/14/rapid-response-the-ngrok-incident-guide/
• https://cloud.google.com/blog/topics/threat-intelligence/bypassing-network-restrictions-through-rdp-
tunneling
Defender XDR
Microsoft Defender for Endpoint via DeviceLogonEvents
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| where RemoteIP in ("127.0.0.1","::1","::%16777216")
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceLogonEvents
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| where RemoteIP in ("127.0.0.1","::1","::%16777216")
Microsoft Sentinel via SecurityEvents (Event ID 4624)
SecurityEvent
| where EventID == "4624"
| where LogonType == "10"
| where IpAddress in ("127.0.0.1","::1","::%16777216")
Microsoft Sentinel via Microsoft-Windows-TerminalServices-LocalSessionManager/Operational (Event ID 21 to 25)
Event
| where Source == "Microsoft-Windows-TerminalServices-LocalSessionManager"
| where EventID in ("21","22","23","24","25")
| where ParameterXml has_any ("127.0.0.1","::1","::%16777216")
24
18. PowerShell Invoke-WebRequest, IWR or Net.WebClient
This query return events where the PowerShell Invoke-WebRequest/IWR (shortened version) cmdlet or the
WebClient class was used. This query return events where the PowerShell Invoke-WebRequest/IWR (shortened version)
cmdlet or the WebClient class was used. Useful for Ingress Tool Transfer (T1105) but also for data exfiltration if you want
to send data out (e.g.: Registry Hive dumps, LSASS dump, etc.). Often used by malware and threat actor alike (even APTs,
see below). The kind of query that you want to run, look at the various unique iterations of the command (use distinct) and
just see if you can spot anything suspicious/malicious. Depending on their use in an environment, can act as a detection
and/or can be fine-tuned to become one if more filters are added: execution context, destination IPs/domains, etc.
Reference(s)
• https://www.huntress.com/blog/the-hunt-for-redcurl-2
• https://azeria-labs.com/data-exfiltration/
Defender XDR
Microsoft Defender for Endpoint via DeviceNetworkEvents
DeviceNetworkEvents
| where InitiatingProcessFileName in~ ("powershell.exe", "pwsh.exe")
| where InitiatingProcessCommandLine has_any ("Invoke-WebRequest", "IWR",
"Net.WebClient")
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "PowerShellCommand"
| where AdditionalFields has_any ("Invoke-WebRequest", "IWR", "Net.WebClient")
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceNetworkEvents
DeviceNetworkEvents
| where InitiatingProcessFileName in~ ("powershell.exe", "pwsh.exe")
| where InitiatingProcessCommandLine has_any ("Invoke-WebRequest", "IWR",
"Net.WebClient")
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "PowerShellCommand"
| where AdditionalFields has_any ("Invoke-WebRequest", "IWR", "Net.WebClient")
25
19. Processes Launched by PowerShell Remoting (WSMProvHost.exe)
This query returns a quick summarized view of processes that were launched through PowerShell Remoting. This
query returns a quick summarized view of processes that were launched through PowerShell Remoting where the parent
process is WSMProvHost.exe.
PowerShell Remoting allows you to connect/establish a session to a remote system and execute commands through
PowerShell. On the remote (target) system, this will return in processes being launched by the WSMProvHost.exe process.
It is therefore quite easy to get a list of all the processes it launched and review them for ones that could be
suspicious/malicious. Such as a threat actor attempting to move laterally around the environment. By summarizing the
various ProcessCommandLine involved by FolderPath involved, you end up with a lot less results to review. And you can
also order results by the number of unique ProcessCommandLine in order to find unique/uncommon ones.
Reference(s)
• https://learn.microsoft.com/en-us/powershell/scripting/security/remoting/powershell-remoting-
faq?view=powershell-7.4
• https://www.splunk.com/en_us/blog/security/powershell-web-access-your-network-s-backdoor-in-plain-
sight.html (because it works for PSWA too!)
• https://threathunterplaybook.com/hunts/windows/190511-RemotePwshExecution/notebook.html
Defender XDR
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where InitiatingProcessFileName =~ "wsmprovhost.exe"
| summarize ["ProcessCommandLines"]=make_set(ProcessCommandLine),
["ProcessCommandLineCount"]=dcount(ProcessCommandLine)
by FolderPath
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceNetworkEvents
DeviceProcessEvents
| where InitiatingProcessFileName =~ "wsmprovhost.exe"
| summarize ["ProcessCommandLines"]=make_set(ProcessCommandLine),
["ProcessCommandLineCount"]=dcount(ProcessCommandLine)
by FolderPath
26
20. DeviceNetworkEvents from LOLBAS with Download or Upload Functions
This query returns network events for LOLBAS that can be used to Download and/or Upload data. This query returns
network events for LOLBAS, from the LOLBAS Project, that can be used to Download and/or Upload data, such as files. This
can be achieved through the newly (well, 2022 news) published CSV feed. See the tweet below:
https://x.com/Wietze/status/1576950693789077506
Back then, Nathan McNulty (@NathanMcNulty) had already shared a first iteration of a KQL query that could be
used to find network events from LOLBAS: https://x.com/NathanMcNulty/status/1577184175765213185
The version I'm proposing below comes with two (2) adjustements:
• We're only interested in LOLBAS that have Download and/or Upload capabilities, since not all LOLBAS do but they
would still be returned because they are processes that can make network connection
• We filter on LOLBAS whose process command line indicate they're trying to reach out to http/https URLs and/or
UNC paths
The second point is based on the review of how each LOLBAS can be used for Download/Upload (their example).
And most, if not all of them, either use an http/https URL (protocol handler) in the command line or a UNC path. Though
it is possible that other protocol/file handlers could be used. This query is best used for hunting, since there are going to
be way too many results and not enough fine-tuning to make it into a detection.
Reference(s)
• https://lolbas-project.github.io/
Defender XDR
Microsoft Defender for Endpoint via DeviceNetworkEvents
let LOLBINS = (
externaldata ( Filename:string, Description:string, Author:string, Date:datetime,
Command:string, CommandDescription:string, CommandUsecase:string, CommandCategory:string
) [ "https://lolbas-project.github.io/api/lolbas.csv" ]
with (format=csv, ignoreFirstRecord=true)
| where CommandCategory in ("Download","Upload")
| distinct Filename);
let ExcludedDomainUrlStrings = dynamic([
"ADD DOMAINS TO EXCLUDE HERE"
]);
DeviceNetworkEvents
| where InitiatingProcessFileName in~ (LOLBINS)
// We're filtering out msedge.exe and msedgewebview2.exe for obvious reasons, but if you
want them to be included in the results, comment out that filter.
| where InitiatingProcessFileName !in~ ("msedge.exe","msedgewebview2.exe")
| where InitiatingProcessCommandLine has_any ("http","https",@"\\")
// We're excluding common/known good domains that may be in command lines, such as your
SharePoint and/or OneDrive sites. Even FQDNs and the likes. If you're not interested in
that filtering, comment that filter.
| where not (InitiatingProcessCommandLine has_any (ExcludedDomainUrlStrings))
// We're only targeting network connections to the Internet (Public IP). Though if you
want to look for internal download/upload of data through these LOLBAS, comment that
filter.
| where RemoteIPType == "Public"
| project-reorder TimeGenerated, ActionType, InitiatingProcessCommandLine,
InitiatingProcessFolderPath, RemoteUrl, RemoteIP, RemotePort
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceNetworkEvents
let LOLBINS = (
externaldata ( Filename:string, Description:string, Author:string, Date:datetime,
Command:string, CommandDescription:string, CommandUsecase:string, CommandCategory:string
) [ "https://lolbas-project.github.io/api/lolbas.csv" ]
with (format=csv, ignoreFirstRecord=true)
| where CommandCategory in ("Download","Upload")
| distinct Filename);
let ExcludedDomainUrlStrings = dynamic([
"ADD DOMAINS TO EXCLUDE HERE"
]);
DeviceNetworkEvents
| where InitiatingProcessFileName in~ (LOLBINS)
// We're filtering out msedge.exe and msedgewebview2.exe for obvious reasons, but if you
want them to be included in the results, comment out that filter.
| where InitiatingProcessFileName !in~ ("msedge.exe","msedgewebview2.exe")
| where InitiatingProcessCommandLine has_any ("http","https",@"\\")
27
// We're excluding common/known good domains that may be in command lines, such as your
SharePoint and/or OneDrive sites. Even FQDNs and the likes. If you're not interested in
that filtering, comment that filter.
| where not (InitiatingProcessCommandLine has_any (ExcludedDomainUrlStrings))
// We're only targeting network connections to the Internet (Public IP). Though if you
want to look for internal download/upload of data through these LOLBAS, comment that
filter.
| where RemoteIPType == "Public"
| project-reorder TimeGenerated, ActionType, InitiatingProcessCommandLine,
InitiatingProcessFolderPath, RemoteUrl, RemoteIP, RemotePort
28
21. Unique DLL With Low Prevalence Loaded From Commonly Abused Folder
This query returns events where unique DLLs with low prevalence that are loaded from commonly abused folders.
This query returns events where unique DLLs, based on event count of their SHA1, with low prevalence, per FileProfile() are
loaded from commonly abused folders. In this context, abused folders are basically folders where malware would either
drop files directly and/or create subfolders and then drop their files. E.g.:
• C:\ProgramData\
• %AppData% (C:\Users$USERNAME\AppData\Roaming)
• %LocalAppData% (C:\Users$USERNAME\AppData\Local)
You'll often see this with malware that drops "legitimate" application and then use them to side-load a malicious
DLL that was added in the package. For instance, a Lumma Stealer/Amadey Bot/Hijack Loader campaign at the end of Fall
2024 or even WikiLoader (see Reference(s) below). There are separate queries for C:\ProgramData and
%AppData%/%LocalAppData%. Reason being that, in the environment I tested this one, with a few thousand endpoints, it
is very hard to get the initial filtering for unique DLLs under 1,000 in %AppData%/%LocalAppData%. And you want that
filtering to be under 1,000 so it can be passed to FileProfile(). Additional filtering opportunities may exists. I simply excluded
folders which are noisy and/or haven't seen being abused that way. As for C:\ProgramData, I wasn't getting even near 1,000
results. These queries are only available in Defender XDR (Advanced Hunting) since they rely on FileProfile().
Reference(s)
• https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-fileprofile-function
• https://cyble.com/blog/threat-actor-targets-manufacturing-industry-with-malware/
• https://asec.ahnlab.com/en/64106/
29
| where not (InitiatingProcessFolderPath matches regex
@"(?i)C\:\\Users\\[^\\]+\\AppData\\Local\\Temp\\(.*)?")
| where not (InitiatingProcessFolderPath matches regex
@"(?i)C\:\\Users\\[^\\]+\\AppData\\Local\\Programs\\(.*)?")
| where FolderPath matches regex
@"(?i)C\:\\Users\\[^\\]+\\AppData\\(Local|Roaming)\\(.*)?"
| join kind=inner LowPrevDLLs on SHA1
30
22. Summarized Defender for Endpoint AntivirusDetection By Endpoint
This query returns a summarized list of Antivirus detections by endpoint. This query returns a summarized list of
AntivirusDetections events by DeviceName, with highlighted (read: interesting) properties to look at. This is more of an ...
investigative kind of query as well when you want to get an idea of how many threats were detected in X days in an
environment and maybe even identify the devices with the most detection.
It's also a good example for the bag_pack() KQL function, showing how you can create an arbitrary dynamic object
with properties (fields) of your choosing. There are many more fields that can be added and/or formatted directly this
query's bag_pack(). Hopefully it'll serve as a good base for you to start playing with it!
Reference(s)
• https://learn.microsoft.com/en-us/kusto/query/pack-function?view=microsoft-sentinel
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "AntivirusDetection"
| extend ThreatName = tostring(parse_json(AdditionalFields).ThreatName)
| extend DetectedObject = strcat(FolderPath,"\\",FileName)
| extend ThreatDetails = bag_pack(
"ThreatName", ThreatName,
"DetectedObject", DetectedObject,
"DetectedObjectOrigin", FileOriginUrl,
"InitiatingProcess", InitiatingProcessFolderPath,
"InitiatingProcessCommandLine", InitiatingProcessCommandLine
)
| summarize ["Threats"]=make_set(ThreatDetails),
["ThreatsCount"]=dcount(tostring(ThreatDetails))
by DeviceName
31
23. Potential CrackMapExec or secretsdump.py LSA and SAM Dump Artifact
This query return events which may indicates that CrackMapExec or secretsdump.py was used against a system. This
query return events which may indicates that secretsdump.py, to dump LSA & SAM, was used against a queried system.
This is actually something I randomly found/noticed while testing secretsdump.py a while back. Even turned it into a Sigma
rule (my first and only one to this day):
https://github.com/SigmaHQ/sigma/blob/fb27bee6d8d6eaac4b4d2875ae81b643553fc413/rules/windows/file/file_event/f
ile_event_win_hktl_remote_cred_dump.yml#L8
Basically, back then, when used against a remote endpoint, CrackMapExec --lsa and secretsdump.py would end up
creating a very specific temporary file on the remote system, following this regex pattern:
C:\Windows\System32\[a-zA-Z0-9]{8}.tmp
This file would be created by the svchost.exe process for the RemoteRegistry service. A few months later, maybe
even a year or so later, when I tested it again (after Fortra took over development from SecureAuthCorp), the path had
changed to this:
C:\Windows\[a-zA-Z0-9]{8}.tmp
Now I would have to test the most recent version to see if the path changed, but we all know how threat actors likes
to use old version of toolkits at time. So this may still be a good detection/hunting opportunity. I'll update this query with
a new regex, if there's indeed a new path (which may be in C:\Windows\Temp this time ...). And I should also check out
NetExec (the successor of CME) to see how it looks...
Edit: Confirmed that it's indeed in C:\Windows\Temp (same file regex) now on NetExec 1.3.0 and Impacket
0.12.0.dev1. Added the new FolderPath regex to the query.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceFileEvents
DeviceFileEvents
| where InitiatingProcessFileName =~ "svchost.exe"
| where InitiatingProcessCommandLine has "RemoteRegistry"
| where FolderPath matches regex @'(?i)C\:\\Windows\\System32\\[a-zA-Z0-9]{8}.tmp'
or FolderPath matches regex @'(?i)C\:\\Windows\\[a-zA-Z0-9]{8}.tmp'
or FolderPath matches regex @'(?i)C\:\\Windows\\Temp\\[a-zA-Z0-9]{8}.tmp'
32
24. Password of Newly Created User Used Through The CommandLine
This query return events where the password of a newly created user may have been used in a subsequent
command. This query returns events where the password of a newly created user through "net.exe user" may have been
used in a subsequent process creation (through its command line). A bit of an experimental query I was thinkering with
yesterday. A lot of threat actors will create new accounts using "net.exe user" and may afterwards, use it to launch further
command while passing along their credentials (e.g.: PsExec.exe). In these kind of situations, you could potentially identify
these commands by simply searching for these passwords in the various processes command lines! Side effect of that
query?
• You may spot password reuse, e.g.: multiple users created with the same password
• You may spot weak passwords (e.g.: Password1, Winter2025, etc.)
• Anything fun I haven't thought of yet
The downside with this query is that it will only work if the most basic, but also the most common, form of the
"net.exe user" command is used, like so: net user TestUser #Password1! /add
This is mostly due to the fact that the various net.exe options aren't position dependent. Which means, this
command also works (creates the user): net user /active:yes TestUser /comment:"Hello" #Password1! /add
So in this query, we're using the parse_command_line() function to break down the ProcessCommandLine column
in a dynamic array, where technically, index 3 would hold our password. Your results are also going to be skewed if someone
uses the wrong command, such as trying to add a user to a group, using "net.exe user". I'll probably go back at some point
to try to fix this query, but since it's experimental, it should do the job to get you started/exploring!
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
let Passwords = (DeviceProcessEvents
| where FileName =~ "net.exe"
| where ProcessCommandLine has_all ("user","/add")
| extend ParsedCLI = parse_command_line(ProcessCommandLine, "windows")
| extend Password = tostring(ParsedCLI[3])
| distinct Password);
DeviceProcessEvents
| where ProcessCommandLine has_any (Passwords)
33
25. Windows Service Creation or Modification With binpath via sc.exe
This query returns events where a Windows service is created and/or modified via sc.exe. This query returns events
where a Windows service is created with its binpath and/or its binpath is modified via sc.exe. Threat actors can leverage
Windows Services for persistence, privilege escalation or even simple execution (think Impacket).
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-create
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/sc-config
One way of creating and/or modifying a service is through the sc.exe utility. To create a service, you need to specify
the binpath option, which is the binary, executable, file, etc. that will be launched by that service. Threat actors can create
a service to launch whatever binary they want, depending on their goal. They can also modify an existing service to replace
its binpath (permanently or temporarily) with a binary, executable, file, etc. of their choosing. Therefore, you can easily set
up a detection and/or run scheduled hunts for new services that gets created or services whose binpath gets modified. You
can even improve the query below by adding a filter in the ProcessCommandLine for paths where it would be unsual to
have a service binary. Which may be the same paths you can find in the query below.
https://github.com/SecurityAura/DE-TH-
Aura/blob/main/Defender%20for%20Endpoint/Process%20Execution%20or%20File%20Creation%20From%20Unu
sual%20Location.md
Reference(s)
• https://redcanary.com/threat-detection-report/techniques/windows-service/
• https://arcticwolf.com/resources/blog/tellmethetruth-exploitation-of-cve-2023-46604-leading-to-ransomware/
• https://www.forescout.com/resources/common-ransomware-ttps-threat-briefing/
• https://offsec.blog/hidden-danger-how-to-identify-and-mitigate-insecure-windows-services/
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where InitiatingProcessFileName =~ "cmd.exe"
| where FileName =~ "sc.exe"
| where ProcessCommandLine has_any ("create","config")
| where ProcessCommandLine has "binpath"
34
26. Workstations with Public IP Assigned to Network Interface
This query returns events where a workstation has a public IP address assigned to one of its network interfaces. This
query returns events where a workstation (e.g.: Windows 10, Windows 11) has a public IP address assigned to one of its
network interfaces. This information is actually readily available in the DeviceNetworkInfo table in the dynamic property
IPAddresses column. It even tells us if an IP is public or private, how awesome is that!
The use case here is, I still see in 2024 (haven't seen it in 2025 yet but, it's just January), users that somehow ends
up getting a public IP assigned to their endpoint (corporate ones, though they are not at the office). And what happens
when a Windows device (servers aside too) is directly exposed to the Internet? There are so many correct answers here,
you most likely guessed one of them.
Therefore, before this situation turns into an RDP bruteforcing alert, or an "Internet facing device" tag in MDE, you
can actually look for this and get notified in almost real-time when a workstation gets assigned a public IP. This allows you
to react right away, contact the user and possibly ask them to take a photo of how they're currently setup to have gotten
that IP assignation.
If I had a penny for everytime I responded to that kind of incident, I would probably be able to buy an Arizona Tea
(reference: https://x.com/DrinkAriZona/status/1882181201987035591)
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceInfo and DeviceNetworkInfo
let Workstations = (DeviceInfo
| where DeviceType == "Workstation"
| distinct DeviceName);
DeviceNetworkInfo
| where DeviceName in~ (Workstations)
| mv-expand IPAddresses
| where IPAddresses.AddressType == "Public"
35
27. LOLDRIVERS Malicious Driver Observed or Loaded
This query returns events where a malicious driver is either seen on a system or loaded. This query returns events
where a malicious driver listed in the LOLDRIVERS project is either seen through file events on a system or loaded.
https://www.loldrivers.io/
A popular technique now for threat actor to disable/impair defenses is to use a BYOVD (Bring Your Own Vulnerable
Driver) technique where they bring a vulnerable driver that they can control/abuse in order to kill and/or cripple endpoint
security solution. This allows them to execute their payloads and/or perform their malicious commands without risk of
being detected, nor blocked. In ransomware deployments, we've now seen threat actors using that BYOVD technique to
kill the resident Antivirus/EDR on the system just before launching the ransomware, and not just at the beginning of their
intrusion. This makes the ransomware deployment more effective by disabling whatever defense is on a system before it
gets encrypted (assuming Microsoft Defender doesn't get enabled as a result and crash the party). The queries below can
be adjusted to look for known vulnerable drivers as well if needed. Simply swap the URL for the appropriate one from the
LOLDRIVERS Github repo https://github.com/magicsword-io/LOLDrivers/tree/main/detections/hashes
References
• https://blogs.vmware.com/security/2023/04/bring-your-own-backdoor-how-vulnerable-drivers-let-hackers-
in.html
• https://www.crowdstrike.com/en-us/blog/scattered-spider-attempts-to-avoid-detection-with-bring-your-own-
vulnerable-driver-tactic/
• https://techcommunity.microsoft.com/blog/microsoftsecurityexperts/strategies-to-monitor-and-prevent-
vulnerable-driver-attacks/4103985
• https://www.huntress.com/blog/readtext34-ransomware-incident
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
let MaliciousLOLDrivers = (
externaldata(SHA1:string)
["https://raw.githubusercontent.com/magicsword-
io/LOLDrivers/refs/heads/main/detections/hashes/samples_vulnerable.sha1"]
);
DeviceEvents
| where ActionType == "DriverLoad"
| where SHA1 in (MaliciousLOLDrivers)
Microsoft Defender for Endpoint via DeviceFileEvents
let MaliciousLOLDrivers = (
externaldata(SHA1:string)
["https://raw.githubusercontent.com/magicsword-
io/LOLDrivers/refs/heads/main/detections/hashes/samples_vulnerable.sha1"]
);
DeviceFileEvents
| where ActionType == "FileCreated"
| where SHA1 in (MaliciousLOLDrivers)
36
28. Suspicious Child Process Launched by Office Process
This query returns events where a suspicious child process has been launched by a Microsoft Office process. This
query returns events where a suspicious child process, from a pre-defined list has been launched by a Microsoft Office
process (Word, Excel, PowerPoint, OneNote, Outlook). This query is exactly as it looks like. A kind of, "back to the roots"
query. From an era where SEO poisoning and complicated smuggling techniques were much less prevalent than the good
ol' Office files with macros that ended up launching suspicious processes. They're not dead however, still quite active today.
However, the number of these that actually make it past Email Security Gateways (is that how Gartner is calling them in
2025?) is a lot less than before. I can't even remember the last incident I had that originated from a malicious Office file.
As always, you can modify the list of "suspicious" child processes and Microsoft Office processes as you wish in the
dynamic variables. You may even find some interesting, not suspicious/malicious stuff, in some environments. Like someone
that has a macro in Excel which, when executed, would launch a PowerShell command with cleartext credentials,
authenticating to a remote server to grab data files as input...
In term of references and/or how to enrich that query further, there's probably 10,000 variations of it on the Sigma
Github repo. You can probably aggregate them all together to have "one query to rule them all" in the Office space.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
let SuspiciousChildProcesses = dynamic([
"cmd.exe",
"powershell.exe",
"pwsh.exe",
"wmic.exe",
"cscript.exe",
"wscript.exe",
"mshta.exe",
"rundll32.exe",
"regsvr32.exe"
]);
let OfficeProcesses = dynamic([
"winword.exe",
"excel.exe",
"powerpnt.exe",
"onenote.exe"
"outlook.exe"
]);
DeviceProcessEvents
| where InitiatingProcessFileName in~ (OfficeProcesses)
| where FileName in~ (SuspiciousChildProcesses)
37
29. Successful Sign-in to Target Accounts From Non-TrustedNamedLocation
This query returns events where successful sign-ins to target account(s) are observed from non-trusted named
locations. This query returns events where successful sign-ins (per Matt Zorich (@reprise99) awesome list of ResultTypes)
to target account(s) (user-defined) are observed from non-trusted named locations. Named Locations can be defined in
Conditional Access as countries or IP ranges to mark them as trusted if needed in certain Conditional Access Policies. For
instance, you may want a breakglass account to only be used from an IP range which is associated with your primary
datacenter site. Or you may want to only allow "service" accounts* (e.g.: UPN starting with "svc") to sign-in from your
datacenter IP ranges (primary, secondary, DR, etc.). Which is exactly the use case I had when I came up with this query
earlier this week. See if any "service" accounts were used outside of Trusted Named Locations, which any of the company's
sites around the world and more-so, if they were accessed programmatically based on the user-agent (such as Python).
Another use case would be to look for high-privilege accounts (e.g.: accounts used to manage Azure/Entra with high-
privileges/roles) that are succesfully signed-in from non trusted named locations which could be company offices and/or
networks. What you can use this query for here really is only limited by what you want/need to look for, depending on your
use case(s).
*The term "service" account is used lightly here, as some organizations are still making the same mistakes today in
Azure/Entra ID as they did on-prems: create normal user accounts and using them for "services"-like purposes.
Reference(s)
• https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-assignment-network
Microsoft Sentinel
Microsoft Entra ID via SigninLogs
let UPNs = dynamic([
"UPN1",
"UPN2"
]);
// Taken from Matt Zorich (@reprise99) awesome website:
https://learnsentinel.blog/2021/08/30/azure-sentinel-and-the-story-of-a-very-persistent-
attacker/
let SuccessCodes = dynamic([0, 50055, 50057, 50155, 50105, 50133, 50005, 50076, 50079,
50173, 50158, 50072, 50074, 53003, 53000, 53001, 50129]);
SigninLogs
| where UserPrincipalName in~ (UPNs)
// Alternatively, you can target UPns based on naming scheme if you want. For instance,
if their UPN starts with "svc". If you use that kind of filter, comment the line above,
and uncomment the one below.
//| where UserPrincipalName startswith "svc"
| where ResultType in (SuccessCodes)
// If you want to filter on specific User-Agents, uncomment the filter below and add the
ones you're looking for.
//| where UserAgent has_any ("python","powershell")
| where NetworkLocationDetails !has "trustedNamedLocation"
38
30. Network Connection From Python-related Process
This query returns events when a network event involving a process associated with Python is observed. This query
returns events when a network event (e.g.: outbound connection) involving a process associated with Python (from a known
list of processes on Windows) is observed. This was observed recently in an incident Huntress responded to involving
RedCurl (AKA RedWolf), a cyber-espionnage APT:
https://www.huntress.com/blog/the-hunt-for-redcurl-2
And I can also say that I've personally observed the same in a RedCurl incident from 2023/2024. Depending on how
Python is leveraged in your environment, this would be more of a threat hunting query rather than a detection. In the
events that Huntress (and myself) observed, an IP address was passed with the rpivot client script. Which means, you can
also adjust the query to look only for events where an IP address (per a regex) is present in the command line. That kind of
technique is also well alive in malware distributed through SEO poisoning and the likes. See the Reference(s) section below.
Reference(s)
• https://www.securonix.com/blog/seolurker-attack-campaign-uses-seo-poisoning-fake-google-ads-to-install-
malware/
• https://www.esentire.com/blog/batloader-continues-to-abuse-google-search-ads-to-deliver-vidar-stealer-and-
ursnif
• https://www.guidepointsecurity.com/blog/ransomhub-affiliate-leverage-python-based-backdoor/
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceNetworkEvents
let PythonProcesses = dynamic([
"python.exe",
"pythonw.exe",
"py.exe",
"pyw.exe"
]);
DeviceNetworkEvents
| where InitiatingProcessFileName in~ (PythonProcesses)
| where RemoteIPType == "Public"
// You can uncomment the line below if you're looking for processes where an IP address
is specified in the command line
//| where InitiatingProcessCommandLine matches regex @"(?i)(\b25[0-5]|\b2[0-4][0-
9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}"
39
31. Large Number of Files Downloaded From OneDrive or SharePoint
These queries returns events where a large number of files would have been downloaded from OneDrive or
SharePoint. These queries returns events where a large number of files, based on the OfficeObjectId or FileSize, would have
been downloaded from OneDrive or SharePoint. When files are downloaded from OneDrive and/or SharePoint, we're
interested in how many unique files (OfficeObjectId) were downloaded in an set (X) amount of time. While at the endpoint
level, we're interested in the FileSize of the resulting ZIP archive as a hint of how many files it could have (even though the
FileSize can always be skewed because of large files with a low compression ratio). In terms of data exfil, an easy way for
threat actors (or even insiders) to collect files is simply to download them from the OneDrive or SharePoint web interface
directly. When multiple files, or even folders, are downloaded from OneDrive/SharePoint online you will have:
• At the Office level: A specific user agent, which is OneDriveMpc-Transform_Zip/1.0
• At the endpoint level: An archive named OneDrive_yyyy_MM_dd.zip will be created, normally in the user's
downloads folder
You therefore have two (2) ways to investigate large/bulk downloads of files from OneDrive/SharePoint. The
disadvantage with the 2nd query, which is based on DeviceFileEvents, is that technically, you don't know always knows if
these are your organization files, as it could be an archive that was generated from files/folders downloaded from another
organization's OneDrive and/or SharePoint. The FileOriginUrl and FileOriginReferrerUrl columns of the DeviceFileEvents
table will sometimes give insight on the original URL, which may contain the OneDrive/SharePoint site the .zip archive was
generated/downloaded from. These queries are more suited for hunting, rather than pure detection. In terms of detection,
you would need to add additional filters and/or correlation. For instance, at the Office level, see if these files are downloaded
to an unmanaged device (IsManagedDevice) or do enrichment on the IP address to help assess the legitimacy of the action.
At the endpoint level, you could correlate further to see if these archives are moved to an external storage media (e.g.: USB
Flash Drive) or see if the device access any kind of file sharing website (e.g.: Mega, PCloud, etc.) shortly after the creation
of the archive.
Don't sleep on the FileOriginUrl and FileOriginReferrerUrl in the DeviceFileEvents. You may find interesting things,
even for situations you would not expect. A bit of teasing for a next #100DaysOfKQL query maybe?
Reference(s)
• https://www.varonis.com/blog/sidestepping-detection-while-exfiltrating-sharepoint-data
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceFileEvents
DeviceFileEvents
// You can remove the FolderPath regex if you want events where the OneDrive zip could've
been saved elsewhere on the system.
| where FolderPath matches regex @"(?i)\\Users\\[^\\]+\\Downloads\\(.*)?"
| where FileName startswith "OneDrive_"
| where FileName endswith ".zip"
Microsoft 365 via OfficeActivity
// Threshold based query, where you need to adjust the Timespan for the bin() and number
of unique OfficeObjectIds to fit your needs
let Timespan = 10m;
let DistinctOfficeObjectIdThreshold = 1000;
OfficeActivity
| where OfficeWorkload in ("OneDrive","SharePoint")
| where Operation == "FileDownloaded"
| where UserAgent has "OneDriveMpc-Transform_Zip"
| summarize ["OfficeObjectIds"]=make_set(OfficeObjectId),
["OfficeObjectIdsCount"]=dcount(OfficeObjectId),
["OfficeWorkloads"]=make_set(OfficeWorkload),
["OfficeWorkloadsCount"]=dcount(OfficeWorkload)
by UserId, bin(TimeGenerated, Timespan)
| where OfficeObjectIdsCount > DistinctOfficeObjectIdThreshold
40
32. Execution of PSEXESVC via a Remote System
These queries returns events where a PSEXESVC service install was triggered by a remote system. This query has
been posted quickly due to exceptional conditions on my end today which made it that I only have a small windows of
time left to post it on January 29, 2025. I'll update/enchance that page tomorrow or later this week. These queries returns
events where a PSEXESVC service install, which would have executed a command/process on the host system, was triggered
by a remote system, who sent a PsExec command. This is achieved by maching the LogonId present in the ServiceInstall
(MDE) or SecurityEvents (Event ID 4697) to the LogonId of the DeviceLogonEvents that happened on the endpoint. As I was
posting this, I realized that another query would be possible that doesn't rely on MDE, but uses two (2) SecurityEvents:
4624 (Successful Logon) and 4697 (New service install). I'll add it later. The additional SecurityEvents only query has been
added!
Reference(s)
• https://intel471.com/blog/threat-hunting-case-study-psexec
• https://aboutdfir.com/the-key-to-identify-psexec/
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents, DeviceLogonEvents
let PSEXESVCInstallEvents = (DeviceEvents
| where ActionType == "ServiceInstalled"
| where AdditionalFields.ServiceName =~ "PSEXESVC"
| project DeviceName, LogonId);
DeviceLogonEvents
| join PSEXESVCInstallEvents on DeviceName, LogonId
SecurityEvents x Defender for Endpoint (DeviceLogonEvents)
let PSEXESVCInstallEvents = (SecurityEvent
| where EventID == "4697"
| extend ParsedXML = parse_xml(EventData)
| extend LogonIdHex =
parse_json(tostring(parse_json(tostring(ParsedXML.EventData)).Data))[3].["#text"]
| where parse_json(tostring(parse_json(tostring(ParsedXML.EventData)).Data))[4].["#text"]
== "PSEXESVC"
| extend LogonIdDec = tolong(LogonIdHex)
| extend ComputerLower = tolower(Computer)
| project ComputerLower, LogonIdDec);
DeviceLogonEvents
| join PSEXESVCInstallEvents on $left.DeviceName == $right.ComputerLower, $left.LogonId
== $right.LogonIdDec
SecurityEvents
let PSEXESVCInstallEvents = (SecurityEvent
| where EventID == "4697"
| extend ParsedXML = parse_xml(EventData)
| extend LogonIdHex =
parse_json(tostring(parse_json(tostring(ParsedXML.EventData)).Data))[3].["#text"]
| where parse_json(tostring(parse_json(tostring(ParsedXML.EventData)).Data))[4].["#text"]
== "PSEXESVC"
| extend LogonIdDec = tolong(LogonIdHex)
| project Computer, LogonIdDec);
SecurityEvent
| where EventID == "4624"
| extend LogonIdDec = tolong(TargetLogonId)
| join PSEXESVCInstallEvents on Computer, $left.LogonIdDec == $right.LogonIdDec
41
33. Inbox Rule Involving Suspicious or Commonly Abused Folder
This query return events were an Inbox Rule (in Exchange, Office 365) was created and involves a suspicious and/or
commonly abused folder. This query return events were an Inbox Rule (in Exchange, Office 365) was created and involves
a folder where threat actors often redirect emails as to hide them from the owner's inbox and/or other folders. The goal
here for the threat actor is to remain undetected in the mailbox and hide emails that would be part of chains the use to
setup fraud (e.g.: send fake bank account information). It could also be to hide emails that would warn the user about the
account compromise, by redirecting security alerts from the Help Desk, Microsoft, etc. in one of these folders. A query
whose goal is quite similar and/or would return similar events to the ones in "Inbox Rule With Non-Alphanumeric Characters
or Short Name Created".
Reference(s)
• https://expel.com/blog/suspicious-outlook-rules-high-fidelity-patterns-to-watch-for/
• https://www.truesec.com/hub/blog/understanding-the-threat-what-is-business-email-compromise (Step 5 -
Inbox Rules)
• https://www.huntress.com/blog/one-msp-three-microsoft-365-compromises-72-hours
Defender XDR
Microsoft Defender for Cloud Apps via CloudAppEvents
let ActionTypes = dynamic([
"New-InboxRule",
"Set-InboxRule",
"Enable-InboxRule"
]);
// You can adjust this value to tune-down possible false positives.
let RuleNameLength = 5;
CloudAppEvents
| where ActionType in~ (ActionTypes)
| mv-expand ActObj = ActivityObjects
| where ActObj.Name in~ ("Name","RuleName")
| extend RuleName = ActObj.Value
| where isnotempty(RuleName)
| where RuleName matches regex @"^[^a-zA-Z0-9]*$"
or strlen(RuleName) < RuleNameLength
Microsoft Sentinel
Microsoft Defender for Cloud Apps via CloudAppEvents
let InboxRuleActionTypes = dynamic([
"New-InboxRule",
"Set-InboxRule",
"Enable-InboxRule",
"UpdateInboxRules"
]);
let Folders = dynamic([
"Archive",
"Conversation History",
"RSS"
]);
CloudAppEvents
| where ActionType in (InboxRuleActionTypes)
| where ActivityObjects has_any (Folders)
Microsoft 365 via OfficeActivity
let InboxRuleOperations = dynamic([
"New-InboxRule",
"Set-InboxRule",
"Enable-InboxRule",
"UpdateInboxRules"
]);
let Folders = dynamic([
"Archive",
"RSS",
"Conversation History"
]);
OfficeActivity
| where OfficeWorkload == "Exchange"
| where Operation in (InboxRuleOperations)
| where Parameters has_any (Folders)
42
34. Network Event From rundll32.exe With Suspicious Entry Point
This query return network events generated by a rundll32.exe instance launched with a suspicious entry point. This
query return network events generated by a rundll32.exe instance executing a file using a suspicious entry point. The
(ab)use of rundll32.exe by malware and threat actors alike is well documented, such as Cobalt Strike
(https://www.cobaltstrike.com/blog/why-is-rundll32-exe-connecting-to-the-internet). A lot of beacons and/or DLL
payloads that are launched through rundll32.exe may use entry points that overlap with known/benign Windows calls in
order to blend in. However, when you look at the actual DLL file that is called, you may find one that is totally different
from the one that should be called with that entry point. A lot of these entry points have been documented over the years.
You can simple Google "rundll32 + $ANY_OF_THE_ENTRY_POINT_IN_THE_QUERY" and you're guaranteed to find reports
about them. The query below look for instances of rundll32.exe in the DeviceNetworkEvents table, since most of the time,
we'll be looking at beacons/backdoors launch, where the ProcessCommandLine has a such entry point.
Technically, this query can be improved further by using a datatable which links known association of Windows DLLs
and the expected entry point and from there use the set_has_element() function to check for negative matches (where the
association is NOT present in the datatable). For now, I'll leave it as an exercise to the reader. Yes, I'll come back at some
point and complete that exercise for you after a certain set of time.
Reference(s)
• https://www.microsoft.com/en-us/security/blog/2022/10/18/defenders-beware-a-case-for-post-ransomware-
investigations/
• https://cloud.google.com/blog/topics/threat-intelligence/unc2596-cuba-ransomware/
• https://thedfirreport.com/2021/06/28/hancitor-continues-to-push-cobalt-strike/
Defender XDR
Microsoft Defender for Endpoint via DeviceNetworkEvents
// You can add more entry points as they get documented in public reports, or even ones
you come across privately.
let EntryPoints = dynamic([
"AllocConsole",
"CleanMyTracksByProcess",
"DllMain",
"DllRegisterServer",
"OpenQueryWindow",
"StartW",
"TstSec",
"VirtualAlloc",
]);
DeviceNetworkEvents
| where InitiatingProcessFileName =~ "rundll32.exe"
| where RemoteIPType == "Public"
| where InitiatingProcessCommandLine has_any (EntryPoints)
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceNetworkEvents
// You can add more entry points as they get documented in public reports, or even ones
you come across privately.
let EntryPoints = dynamic([
"AllocConsole",
"CleanMyTracksByProcess",
"DllMain",
"DllRegisterServer",
"OpenQueryWindow",
"StartW",
"TstSec",
"VirtualAlloc",
]);
DeviceNetworkEvents
| where InitiatingProcessFileName =~ "rundll32.exe"
| where RemoteIPType == "Public"
| where InitiatingProcessCommandLine has_any (EntryPoints)
43
35. BITS Jobs via PowerShell or BITSAdmin.exe
These queries returns events where BITS Jobs are observed through PowerShell or bitsadmin.exe These queries
returns events where BITS Jobs are observed through PowerShell (Start-BitsTransfer) or bitsadmin.exe. BITS remains a
popular and easy way for threat actors to perform Ingress Tool Transfer (T1105). BITS Jobs can be created through
PowerShell, using the Start-BitsTransfer cmdlet (there are other BITS-related cmdlets) or the now deprecated bitsadmin.exe
utility. From a detection standpoint, triggering on bitsadmin.exe should make a pretty good use case, since it's deprecated
and not commonly used anymore (if at all). The only instances of it I've seen used in the last years were, well you guessed
it: threat actors or malware.
There's also a dedicated Event Log, Microsoft-Windows-Bits-Client/Operational, where events related to BITS are
logged. Amongst them is Event ID 16403, which contains:
• User associated with the BITS Job
• BITS Job name
• BITS Job remote name (download source)
• BITS Job local name (saved file location)
Which can be used for hunting. You can grab all these events in Microsoft Sentinel and from there, do a distinct on either
the RemoteName or LocalName field to hunt for suspicious URLs, domains, file locations, etc.
Reference(s)
• https://www.logpoint.com/en/blog/emerging-threats/hiding-in-plain-sight-the-subtle-art-of-loki-malwares-
obfuscation/
• https://isc.sans.edu/diary/23281
• https://redcanary.com/blog/threat-detection/bitsadmin/
• https://cloud.google.com/blog/topics/threat-intelligence/attacker-use-of-windows-background-intelligent-
transfer-service/
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "PowerShellCommand"
| where AdditionalFields has "Start-BitsTransfer"
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "bitsadmin.exe"
or (FileName in~ ("powershell.exe","pwsh.exe") and ProcessCommandLine has "Start-
BitsTransfer")
SecurityEvents
Event
| where EventLog == "Microsoft-Windows-Bits-Client/Operational"
| where EventID == "16403"
| extend ParsedXML = parse_xml(EventData)
| extend RemoteName = ParsedXML.DataItem.EventData.Data[5]["#text"]
| extend LocalName = ParsedXML.DataItem.EventData.Data[6]["#text"]
44
36. Suspicious String in Service Creation ImagePath
These queries returns events where a suspicious string was found in the ImagePath of a service creation event. These
queries returns events where a suspicious string, defined in a dynamic property, was found in the ImagePath of a service
creation event. Another "low-hanging fruit" detection and/or hunting query. A lot of tools allowing for lateral movement
and/or remote execution (think Cobalt Strike, Impacket, etc.) will stuff the same "execution template" in the ImagePath of
a Windows service they create. May it be calling %COMSPEC% or calling the ImagePath from the ADMIN$ share directly.
Looking for service creation where these strings are present allows you to detect or uncover such lateral movement
and/or remote execution events. Luckily for us, some of these patterns already triggers some good detection from Defender
for Endpoint (MDE). But, we're looking to get 100% guaranteed alert trigger for these here, or simply hunting for them
when they fell through the crack.
Reference(s)
• https://www.crowdstrike.com/en-us/blog/getting-the-bacon-from-cobalt-strike-beacon/
• https://redcanary.com/threat-detection-report/techniques/windows-admin-shares/
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
let SuspiciousStrings = dynamic([
"COMSPEC",
"cmd",
"powershell",
"ADMIN$",
"C$",
"127.0.0.1"
]);
DeviceEvents
| where ActionType == "ServiceInstalled"
| extend FullPath = strcat(FolderPath,@"\",FileName)
| where FullPath has_any (SuspiciousStrings)
or ProcessCommandLine has_any (SuspiciousStrings)
SecurityEvents
let SuspiciousStrings = dynamic([
"COMSPEC",
"cmd",
"powershell",
"ADMIN$",
"C$",
"127.0.0.1"
]);
SecurityEvent
| where EventID == 4697
| where ServiceFileName has_any (SuspiciousStrings)
45
37. Scheduled Task Creation
These queries returns events where a Scheduled Task was created. These queries returns events where a Scheduled
Task was created on Windows, through various different methods. Scheduled Tasks remain one of the best way to set up
stealthy persistence for malware and/or threat actors. They're easy to configure, malleable and you can also define exactly
when they should trigger and what they should run in response. Some people have done also pretty nifty tricks with
Scheduled Tasks in the past for good reasons (https://x.com/NathanMcNulty/status/1775072655139574042).
You can imagine that if the good peeps can use create Scheduled Tasks to fit their needs, so can malware/threat
actors. And they can do so in a few different ways under Windows. You can create Scheduled Tasks via, at least:
• schtasks.exe
• at.exe (deprecated)
• Windows PowerShell
Fortunately for us, most of these methods can be detected and/or hunted for using the DeviceEvents or
DeviceProcessEvents tables in Defender for Endpoint (MDE). The queries below are more suited for hunting rather than
detection, though they could probably be easily turned into those depending on the prevalence of some of these behavior
in your environment. Not everyday will you see at.exe being called (it shouldn't anyway).
Reference(s)
• https://app.any.run/tasks/072483fe-b0d3-4296-a877-fb9bff58b3ed?p=672b6f94d59bb4c86f7be95d (look at
that neat little update_task_ad.ps1)
• https://redcanary.com/threat-detection-report/techniques/scheduled-task/
• https://www.pwndefend.com/2023/01/17/malicious-scheduled-tasks/ (go give him a like and follow on Twitter
folks, one of the good guys: https://x.com/UK_Daniel_Card)
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "PowerShellCommand"
| where AdditionalFields has_any ("Register-ScheduledTask","New-ScheduledTask")
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "ScheduledTaskCreated"
// To make it easier to filter/read the results
| extend Command =
parse_json(tostring(parse_json(tostring(parse_json(tostring(AdditionalFields.TaskContent)
).Actions)).Exec)).Command
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
// If this query return any events involving at.exe, be sure to investigate them
| where FileName in~ ("schtasks.exe","at.exe")
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where FileName in~ ("powershell.exe","pwsh.exe")
| where ProcessCommandLine has_any ("Register-ScheduledTask","New-ScheduledTask")
46
38. File With a Misleading Double Extension Observed
These queries returns events where a file with a misleading double extension is observed on a system. These queries
returns events where a file with a misleading double extension (e.g.: masquerading as an Office or PDF file) is observed on
a system. The double file extension trick seems to have been making a comeback lately (at the very least, .pdf.lnk) with
malware such as DarkGate, Lumma Stealer, Amadey Bot, HijackLoader (the last 3 being delivered in the same campaign). It
is therefore interesting to see if any of these files would've been created at some point in locations where user are likely to
execute these once delivered via a certain method (Web, Email, etc.). False positives here may usually include shortcut files
that are created from an actual file in File Explorer. Depending on your system language, you can usually filter out files with
specific strings: Raccourci.lnk (in French) and Shortcut.lnk (in English).
Reference(s)
• https://cyble.com/blog/threat-actor-targets-manufacturing-industry-with-malware/
• https://www.truesec.com/hub/blog/darkgate-loader-delivered-via-teams
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
// You can add more extensions as needed
let FileExtensions = dynamic([
"doc",
"docx",
"xls",
"xlsx",
"ppt",
"pptx",
"pdf",
"zip",
"rar",
"7z",
"rtf"
]);
DeviceFileEvents
| extend FileExtension = tostring(strcat((split(FileName,".")[-
2]),".",strcat(split(FileName,".")[-1])))
| where FileExtension has_any (FileExtensions)
| where FileExtension endswith "lnk"
| where FolderPath matches regex @'(?i)\\Users\\[^\\]+\\AppData\\Local\\Temp\\(.*)?'
or FolderPath matches regex @"(?i)\\Users\\[^\\]+\\Downloads\\(.*)?"
or FolderPath matches regex
@"(?i)\\Users\\[^\\]+\\AppData\\Local\\Microsoft\\Windows\\INetCache\\Content.Outlook\\[^
\\]+\\(.*)?"
47
39. 7-Zip or WinRAR Used With Password-Protected Archives
These queries returns events where 7-Zip or WinRAR is seen interacting with a password-protected archive. These
queries returns events where 7-Zip or WinRAR is seen interacting with a password-protected archive based on the use of
the "password" parameter. It is no secret that threat actors likes to use archives, may it be for ingress (pull payloads or
additional tools in a network) or egress (data exfil). Sometimes, they'll even password-protect these archives to protect
them against prying eyes. For instance, prevent incident responders from grabbing an archive with their tools, scripts or
payloads. Or prevent these same incident responders from being able to extract the content of an archive they created
which hold data (e.g.: files) they collected from a system. Or even ... simply "ransom" companies using password-protected
archives (https://news.sophos.com/en-us/2021/11/18/new-ransomware-actor-uses-password-protected-archives-to-
bypass-encryption-protection/).
All in all, interacting with password-protected archives from the command line using 7-Zip or WinRAR may be
unusual in some environments. Therefore, hunting or detecting this kind of activity could help you detect threat actors (or
even malware) in your network going around, playing with these archives. The good news is that, if you're able to detect
this activity, it also means that you have the archive password in the event (telemetry). So if you can get your hands on
whatever archive was involved, you can get its content by using the password.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
DeviceProcessEvents
| where (FileName in~ ("7z.exe","7zr.exe","7za.exe")
and ProcessCommandLine contains " -p"
and ProcessCommandLine has_any (" a "," x "))
or (FileName in~ ("WinRAR.exe","RAR.exe") and ProcessCommandLine has_all ("a","-p"))
48
40. Net.exe Use Against a Remote External ComputerName
This query return events where the net.exe use command was used against a remote, external ComputerName. The
term "ComputerName" here is used because it is actually the name of the parameter in the "net.exe use" context:
https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-
2012/gg651155(v=ws.11)
This query return events where the net.exe use command was used against a remote, external ComputerName, may
it be a FQDN or an IP address. The idea behind this query is to catch this kind of behavior:
https://x.com/filescan_itsec/status/1886739550673707322
Now that malware and even threat actors (coughAPTcough) are abusing WebDAV, this query can be used either as
a detection or a threat hunting query to find instances of a "net.exe use" command where a remote ComputerName has
been used. From there, you'll want to investigate/determine what is that ComputerName:
• Is it legit (e.g.: some organizations will use Azure SMB file shares:
https://learn.microsoft.com/en-us/azure/storage/files/files-smb-protocol?tabs=azure-portal)
• If not, what caused that command to be executed (user-initiated, script-initiated, etc.)
• What lies at the end of that connection (e.g.: what files, folders, etc. are present in that remote share)
The query may need to be tweaked if you run it in an environment where multiple FQDNs are used internally (e.g.:
olddomain.local, newdomain.local). It is just a matter of changing how the filter condition works or chaining as many as
needed with new variables and adding filters.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
// Taken fom Bert-Jan with love: https://github.com/Bert-JanP/Hunting-Queries-Detection-
Rules/blob/main/KQL%20Regex/RegexExamples.md
let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
let FQDN = "YOUR_FQDN_HERE";
DeviceProcessEvents
| where FileName =~ "net.exe"
| where ProcessCommandLine has_all ("use","\\\\")
// Extracting the ComputerName from the ProcessCommandLine (e.g.: \\SERVER1\,
\\192.168.10.100\, \\NAS.DOMAIN.LOCAL\, etc.)
| extend ComputerName = extract(@"(?i)\\\\([^\\]+)\\",1,ProcessCommandLine)
// Ensuring we only keep ComputerName with dots so IP addresses and FQDNs
| where ComputerName has "."
// Create a new field which will tell us if the ComputerName is an IP address or a FQDN
| extend ComputerNameType = iif(ComputerName matches regex IPRegex, "IP", "FQDN")
| extend IPType = iif((ComputerNameType == "IP" and ipv4_is_private( ComputerName)),
"Private", "Public")
// Filter out connections to ComputerNames where the FQDN ends with our internal FQDN
| where ComputerName !endswith FQDN
// Filter out private IP addresses (internal use)
| where IPType != "Private"
49
41. Command Line Interpreter Invoked by Web Application Process (Windows)
This query return events where a command line interpreter is invoked by a Web Application process on Windows.
This query return events where a command line interpreter (cmd.exe, powershell.exe or pwsh.exe) is invoked by a Web
Application process (e.g.: IIS, Java or Apache) on Windows. In terms of "low-level detection" or threat hunting, this query
definitely fits the bill. One of the most simple way to summarize it is: when a Web application gets exploited and a threat
actor uses it to run/launch arbitrary command (e.g.: RCE), most of the time, that command will be launched by the process
powering that application. IIS, Java and Apache are three (3) of the most popular technologies that powers a multitude of
websites, applications, etc. (mostly on-premises). Therefore, when a CVE leading to RCE is discovered in any of the
application that uses IIS, Java or Apache, or even these technologies themselves, exposed WebApps are often quickly
targeting. What they get targeted by however varies, but for some reason, coinminers/cryptominers are always the first to
the party (coughKinsingcough).
You can definitely modify that query to include the name of the processes that are powering your Web applications
to increase coverage. This query is more suited for Threat Hunting rather than detection. To turn it into a detection, you
would need to modify the logic to filter on these processes at the InitiatingParentProcessFileName level, the command line
interpreters at the InitiatingProcessFileName level AND look for known discovery commands that are quickly executed
through RCEs, such as: whoami.exe, hostname.exe, net.exe, nltest.exe Etc.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
let CommandLineInterpreters = dynamic([
"cmd.exe",
"powershell.exe",
"pwsh.exe"
]);
let WebApplicationProcesses = dynamic([
"apache",
"tomcat",
"ws_tomcatservice.exe",
"httpd.exe",
"nginx.exe",
"php-cgi.exe",
"w3wp.exe",
"java.exe",
"javaw.exe"
]);
DeviceProcessEvents
| where InitiatingProcessFolderPath has_any (WebApplicationProcesses)
| where FileName in~ (CommandLineInterpreters)
// You may want to start with a distinct filtering to review the results. If so,
uncomment the line below.
//| distinct ProcessCommandLine, InitiatingProcessCommandLine
50
42. Windows Event Logs Cleared
This query return events where Windows Event Logs were cleared. This query return events where Windows Event
Logs were cleared through wevtutil.exe, PowerShell or from the specific Event IDs this action leaves behind. One of the
worst thing a threat actor can do in an intrusion to hinder the follow-up investigation (or response) is clear the Windows
Event Logs. When hitting organizations that do not even have any kind of logging in place (think EDR, Event ID 4688,
Sysmon, SIEM, etc.), the Windows Event Log becomes a goldmine of information. And when that goldmine is just blasted
away shut, it hurts. The two (2) most popular ways to clear Windows Events Logs are:
• Through wevtutil.exe (https://learn.microsoft.com/en-us/windows-server/administration/windows-
commands/wevtutil)
• Through PowerShell Clear-EventLog cmdlet (https://learn.microsoft.com/en-
us/powershell/module/microsoft.powershell.management/clear-eventlog?view=powershell-5.1)
Luckily for us, we can easily detect and/or hunt for these in Defender for Endpoint (MDE). When clearing the Security
Event Log, a helpful Event ID 1102 (The audit log was cleared) will also be generated. This event also contains information
about who cleared the event log, such as the Account Domain, Account Name and Logon ID. Should you have access to
previous events of the Security Event Log before it was cleared (e.g.: they were sent to a SIEM such as Microsoft Sentinel),
you can then associate that Logon ID to an Event ID 4624 (Successful logon) and try to get more insight into where that
user came from. Should you create a detection for that behavior, you better be ready to react fast. In typical ransomware
attacks, clearing the Windows Event Log is one of the last step a threat actor go through before deploying the ransomware
(e.g.: one of the command in the ransomware deployment script). Before or after deleting all the volume shadow copies
on a system too.
References
• https://www.microsoft.com/en-us/security/blog/2022/06/13/the-many-lives-of-blackcat-ransomware/
• https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-find-ransomware (found this while looking
for references and saw that it has a lot of neat little Threat Hunting queries, including one for Windows Event
Log clear!)
• https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-319a
• https://arcticwolf.com/resources/blog/lorenz-ransomware-chiseling-in/
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "PowerShellCommand"
| where AdditionalFields has "Clear-EventLog"
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where (FileName =~ "wevtutil.exe" and ProcessCommandLine has_any ("cl","clear-log"))
or (FileName in~ ("powershell.exe","pwsh.exe") and ProcessCommandLine has "Clear-
EventLog")
SecurityEvent
SecurityEvent
| where EventID == 1102
51
43. ClickFix - PowerShell Command Launched via Windows Run Box
This query return events where a PowerShell command was launched via the Windows Run box. For more
information about ClickFix, what is it, how it works, what kind of malware/threats it delivers, etc. refer to the articles linked
in the References section before continuing. This query return events where a PowerShell command, with key emphasis on
"command", was launched via the Windows Run box. A nice little intersection between the world of EDR telemetry and
forensics. The RunMRU Registry Key keeps track of commands that have been launched using the Windows Run box
(Windows key + R). One of the ClickFix campaign variant instructs users to open that Run box (through the Start Menu, or
simply the key combo), paste (Ctrl + V) the content of their clipboard in it and then press Enter. This will not only result in
the execution of that PowerShell command, but also that command being written to the RunMRU Registry key of that user.
It is therefore possible to "hunt" or "detect" when a new Registry Value is set under that RunMRU Key which has the string
"powershell" in it and from there, exclude these two (2) possibilities:
• powershell\1
• powershell.exe\1
With \1 acting as the end of the string, we know that if we can find the substring "powershell" and exclude these
values, our results will be more likely to be commands (e.g.: powershell IWR "ifconfig.me/ip") Alternatively, you can just
run a query which looks for the string "powershell" in addition to one of any of the strings below, which are commonly
used in these campaigns: Hidden, IWR, IEX, Captcha, reCAPTCHA, Robot, Invoke. I've also provided that query here.
References
• https://www.proofpoint.com/us/blog/threat-insight/security-brief-clickfix-social-engineering-technique-floods-
threat-landscape
• https://www.cyderes.com/blog/fake-gemini-security-alerts-lead-to-powershell-based-malware
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceRegistryEvents
DeviceRegistryEvents
| where ActionType == "RegistryValueSet"
| where RegistryKey endswith @"Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"
| where RegistryValueName != "MRUList"
| where RegistryValueData has "powershell"
| where RegistryValueData !in~ (@"powershell\1",@"powershell.exe\1")
Microsoft Defender for Endpoint via DeviceRegistryEvents
let InterestingStrings = dynamic([
"Hidden",
"IWR",
"IEX"
"Captcha",
"reCAPTCHA",
"Robot",
"Invoke"
52
]);
DeviceRegistryEvents
| where ActionType == "RegistryValueSet"
| where RegistryKey endswith @"Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"
| where RegistryValueName != "MRUList"
| where RegistryValueData has "powershell"
| where RegistryValueData has_any (InterestingStrings)
53
44. Events Initiated From Targeted Remote DeviceName or SessionIP
This query return events that are associated with a remote connection associated with a defined DeviceName or
SessionIP. This query return events that are associated with a remote connection (read: Remote Desktop Protocol or RDP)
associated with a defined (known) DeviceName or SessionIP. Thanks to the addition of RDP session data to the various
Defender for Endpoint (MDE) tables last summer (see References section below), we can now hunt for events (processes,
network connection, file events, etc.) that were generated during specific RDP sessions! This is useful in incidents where
you know the hostname of the device and/or IP of the device the threat actor used to RDP on certain systems and from
there, "dump" all the events related to their activity on these systems. You can identify these hostnames and/or IPs in a few
ways, the easiest one being to query DeviceLogonEvents first to identify suspicious RemoteDeviceName and RemoteIP.
Be careful with the RemoteIP field, since it'll also match DeviceNetworkEvents and/or DeviceEvents where network
connections involving these IPs are observed. So be sure to properly assess the significance of these events before including
them in your analysis.
References
• https://techcommunity.microsoft.com/blog/microsoftdefenderatpblog/detect-compromised-rdp-sessions-with-
microsoft-defender-for-endpoint/4201003
Microsoft Sentinel
Microsoft Defender for Endpoint via everything
let RemoteDeviceNames = dynamic([
"ADD",
"DEVICENAMES"
"OF INTEREST",
"HERE"
]);
let RemoteIPs = dynamic([
"ADD",
"REMOTE IPs"
"OF INTEREST",
"HERE"
]);
union DeviceEvents, DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents,
DeviceRegistryEvents, DeviceLogonEvents, DeviceImageLoadEvents
| where InitiatingProcessRemoteSessionDeviceName in~ (RemoteDeviceNames)
or InitiatingProcessRemoteSessionIP in (RemoteIPs)
or ProcessRemoteSessionDeviceName in~ (RemoteDeviceNames)
or ProcessRemoteSessionIP in (RemoteIPs)
or RemoteDeviceName in~ (RemoteDeviceNames)
or RemoteIP in (KnownRemoteIPs)
54
45. nltest.exe Execution
This query returns events where nltest.exe was executed. This query returns events where nltest.exe was executed.
That's it, that's the description. At this point, it should be well-known to any instance of nltest.exe should be investigated
because:
• It really is not run that often in the day-to-day
• Once again, it really is not run that often in the day-to-day
• It is quite easy to find out if its execution was done legitimately (benign, expected behavior or not)
If you get a hit on this, it only takes a few seconds to look at all the DeviceProcessEvents surrounding that event to
see if it's coupled with other discovery related commands, come from an account that should not be running that command,
was executed at odd hours, etc. In terms of "low-hanging fruit" indicators of an intrusion (e.g.: ransomware attack), this is
easily in the Top 5.
Reference(s)
• https://attack.mitre.org/software/S0359/
• https://www.microsoft.com/en-us/security/blog/2024/09/26/storm-0501-ransomware-attacks-expanding-to-
hybrid-cloud-environments/
• https://www.threatdown.com/blog/5-early-signs-of-a-ransomware-attack-based-on-real-examples/
• https://news.sophos.com/en-us/2025/01/21/sophos-mdr-tracks-two-ransomware-campaigns-using-email-
bombing-microsoft-teams-vishing/
• There are just SO many
Microsoft Sentinel
Microsoft Defender for Endpoint DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "nltest.exe"
55
46. Scheduled Task Created Remotely
This query returns events where a Scheduled Task was created remotely. This query returns events where a Windows
Scheduled Task was created remotely from another system. Threat Actors (or even malware) can create Scheduled Tasks
on remote systems for execution and/or persistence. This has the advantage on limiting their footprint in the network since
they would stay one one host instead of moving laterally to another and therefore, leaving breadcrumbs. However, creating
a Scheduled Task on a remote system will still end up leaving one of our favorite event: a logon event. This logon event
with its LogonId, and the Scheduled Task creation event with the LogonId of the user that created it can then be matched
together to see if a Scheduled Task was created by a user from a remote system. You can test this yourself by simply going
on a Windows system, opening the Microsoft Management Console (mmc.exe), adding the Task Scheduler add-in, selecting
a remote system and from there, create a new Scheduled Task. This can be detected both via Defender for Endpoint (MDE)
and native Security Event Logs on Windows. Yes, the same logic can be applied to Windows Service creation events.
Reference(s)
• https://redcanary.com/blog/threat-intelligence/blue-mockingbird-cryptominer/
• https://lolbas-project.github.io/lolbas/Binaries/Schtasks/
Microsoft Sentinel
Microsoft Defender for Endpoint DeviceEvents, DeviceLogonEvents
DeviceEvents
| where ActionType == "ScheduledTaskCreated"
| join (
DeviceLogonEvents
| where LogonType == "Network"
// Filtering out some weird edge case where the logon would come from a link-local
IPv6 address, seen in my testing
| where not (ipv6_is_in_range(RemoteIP, "fe80::/10"))
) on DeviceName, $left.InitiatingProcessLogonId == $right.LogonId
SecurityEvents
SecurityEvent
| where EventID == 4698
| extend ParsedEventData = parse_xml(EventData)
| extend SubjectLogonId =
tostring(parse_json(tostring(parse_json(tostring(ParsedEventData.EventData)).Data))[3].["
#text"])
| join (
SecurityEvent
| where EventID == 4624
| where LogonType == 3
// Filtering out some weird edge case where the logon would come from a link-local
IPv6 address, seen in my testing
| where not (ipv6_is_in_range(IpAddress, "fe80::/10"))
) on Computer, $left.SubjectLogonId == $right.TargetLogonId
56
47. Windows Service Created Remotely
This query returns events where a Windows Service was created remotely. This query returns events where a
Windows Service was created following a connection from a remote system. The "sister query" to "Scheduled Task Created
Remotely". It is there normal for the description to be similar. However, what we're looking at here is more use cases like
PsExec, Cobalt Strike PsExec, Metasploit PsExec, etc. Where a Threat Actor would be executing commands from a remote
system through a Windows Service. However, creating a Windows Service on a remote system will still end up leaving one
of our favorite event: a logon event. This logon event with its LogonId, and the Windows Service creation event with the
LogonId of the user that created it can then be matched together to see if a Windows Service was created by a user from
a remote system. You can test this yourself by simply using PsExec (psexec64.exe \COMPUTER1 cmd.exe) and looking at
your Defender for Endpoint (MDE) telemetry or Security Event Logs. This can be detected both via Defender for Endpoint
(MDE) and native Security Event Logs on Windows.
Reference(s)
• https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/post-
exploitation_lateral-movement.htm
• https://threathunterplaybook.com/hunts/windows/190815-RemoteServiceInstallation/notebook.html
Microsoft Sentinel
Microsoft Defender for Endpoint DeviceEvents, DeviceLogonEvents
DeviceEvents
| where ActionType == "ServiceInstalled"
| join (
DeviceLogonEvents
| where LogonType == "Network"
// Filtering out some weird edge case where the logon would come from a link-local
IPv6 address, seen in my testing
| where not (ipv6_is_in_range(RemoteIP, "fe80::/10"))
) on DeviceName, $left.InitiatingProcessLogonId == $right.LogonId
SecurityEvents
SecurityEvent
| where EventID == 4697
| extend ParsedEventData = parse_xml(EventData)
| extend SubjectLogonId =
tostring(parse_json(tostring(parse_json(tostring(ParsedEventData.EventData)).Data))[3].["
#text"])
| join (
SecurityEvent
| where EventID == 4624
| where LogonType == 3
// Filtering out some weird edge case where the logon would come from a link-local
IPv6 address, seen in my testing
| where not (ipv6_is_in_range(IpAddress, "fe80::/10"))
) on Computer, $left.SubjectLogonId == $right.TargetLogonId
57
48. Potentially Renamed Binaries
This query returns events where a binary that was executed is potentially renamed.
CREDIT IS GIVEN WHERE CREDIT IS DUE
I came up with this query some time ago already and at that time, I ended up discovering the set_has_element()
function in KQL. Like with every function I've never used before, I browse various KQL repos to get an idea of how to use
them. The reason I do this is because the Microsoft Learn docs content for these functions is often very sparse and not that
helpful. So it's better for me to just find an actual example of the function used for security-related queries. This being said,
I ended up finding the following query in FalconForce's FalconFriday Github repo. Which you should definitely go star and
follow them on all their socials, they are just amazing:
https://github.com/FalconForceTeam/FalconFriday/blob/355de126c6cc707d1a62970a5989b06c8cd686b9/0xFF-0080-
Parent_Child_Mismatch_Common_Windows_Process-Win.md
Therefore, the query shown here is very similar to what they have because I used their as a model for what I wanted
to accomplish. This query returns events where a binary that was executed is potentially renamed, per a datatable that is
prepopulated with the right, expected values to identify that binary. It is not uncommon for threat actors and/or malware
to rename legitimate binaries in order to fly under the radar. They may even move them out (well, copy them) of their usual
location and then call them from that new location. The use case we're targeting here, is a very simple one where a threat
actor executed a renamed copy of rclone.exe on an system. Since it has just been renamed, all the information (metadata)
related to the file, and captured by Defender for Endpoint (MDE) will show up in the telemetry. And we can then do
something that basically amounts: If process has FileInfo of rclone, but EXE is not named rclone.exe, BAD!
To do so, we define a datatable with the expected "pair" of values we're expecting, basically: a file named rclone.exe,
with a OriginalFileName that has the string "rclone". We then look through DeviceProcessEvents for that kind of mismatch
and if found, return the results. You can add any "pair" of expected values in the datatable, which makes it useful if you
want to monitor for specific binaries that would've been renamed: rclone, 7-Zip, WinRAR being some of the most common
and interesting ones. To make this query less resource intensive, you could add a filter first that only looks for processes
where the ProcessVersionInfoOriginalFileName has any of the strings you're looking for (e.g.: rclone).
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
let PotentiallyRenamedBinaries = datatable(ProcessVersionInfoOriginalFileNameLower:
string,ExpectedFileName: dynamic)
[
"rclone.exe",dynamic(["rclone"])
];
DeviceProcessEvents
| extend FileNameLower = tolower(FileName)
| extend ProcessVersionInfoOriginalFileNameLower =
tolower(ProcessVersionInfoOriginalFileName)
| lookup kind=inner PotentiallyRenamedBinaries on ProcessVersionInfoOriginalFileNameLower
| where not(set_has_element(ExpectedFileName,FileNameLower))
58
49. Azure Subscription Ready Email
This query returns events where a "Azure Subscription is ready" email is sent to a user. This query returns events
where a "New Azure Subscription is ready" email is sent to a user. The actual title of this email, if I remember correctly (and
based on what I could find online is): "Your Azure Subscription is ready". What's the idea behind this query? Well, when a
user within a tenant creates a new Azure subscription using the free $200 credit they're "entitled" too
(https://azure.microsoft.com/en-ca/pricing/purchase-options/azure-account?icid=azurefreeaccount), they'll receive an
email with that subject to tell them that their new Azure Subscription is ready. While it is interesting to know if any of your
users received emails because they actually went through that process themselves for whatever reason there is, do you
know who also takes advantage of this? Threat Actors. In tenants where the ability to spin up new Azure Subscription is not
disabled and/or restricted in some fashion, threat actors, once they compromise an account, can spin up a new Azure
Subscription using that free $200 credit. From there, they've been observed spinning up resources such as VMs to conduct
phishing campaigns against other organizations. And by "they've been" I mean, I also observed them doing so in a few
mandates that started as BECs (Business Email Compromise).
References
• https://learn.microsoft.com/en-us/microsoft-365/commerce/subscriptions/manage-self-service-signup-
subscriptions?view=o365-worldwide
Microsoft Sentinel
Microsoft Defender for Office 365 via EmailEvents
EmailEvents
| where Subject =~ "Your Azure Subscription is ready"
// In case the wording of this email changes overtime, you could also use a combination
of these three (3) words together just in case
// or Subject has_all ("Azure","Subscription","Ready")
59
50. Credential Discovery Activity Through findstr.exe and reg.exe
This query returns events where findstr.exe and reg.exe are potentially being used to search for credentials. This
query returns events where findstr.exe (for files, folders, etc.) and reg.exe (for the Registry) are potentially being used to
search for credentials (passwords, secrets, keys, etc.). This query is as simple as it sounds: some malware, or most often,
threat actor, trying to look for these "low-hanging" fruits credentials using findstr.exe and reg.exe. They'll search for patterns
such as: pass, password, secret, key, etc. in hope of finding these unsecured credentials that will allow them to get their
hands on other, and hopefully (for them), more privileged accounts. Probably one of easiest way to look for these old,
legacy cPasswords that hadn't been removed from Group Policy Preferences (GPP) files in Sysvol as well. You can add any
interesting string to be alerted on (or returned as result) in this query as well. There may be an overlap with Defender for
Endpoint (MDE) built-in detections however, as I've seen alerts triggering by simple findstr.exe for "password" in the past.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
let InterestingStrings = dynamic([
"pass",
"password",
"passwords",
"secret",
"secrets",
"key",
"keys",
"creds",
"credential",
"credentials"
]);
DeviceProcessEvents
| where FileName =~ "findstr.exe"
or (FileName =~ "reg.exe" and ProcessCommandLine has " query ")
| where ProcessCommandLine has_any (InterestingStrings)
60
51. runas.exe Usage
This query returns events where runas.exe was used. This query returns events where runas.exe was used. In
Windows, runas.exe is a nifty little utility that allows you to run specific tools, programs, etc. as a different user. Different
user being, other than the one that is currently logged in. Which means that you can spawn a cmd.exe or powershell.exe
process using another set of credentials for instance and end up in a newly created process running at that user. From a
threat actor's perspective, this can allow either for Defense Evasion or Privilege Escalation. For Defense Evasion, they may
be spawning new processes as another user that may be less suspicious or monitored, depending on what processes, and
therefore, subsequent commands, they launch. As for the Privilege Escalation part, they could spawn a new process that
has higher privilege or level of access than their current user, and use it to perform operations/commands that couldn't be
executed before. For instance, they may use runas.exe to spawn a cmd.exe shell with a user that can login/access Domain
Controllers. And from there, simply "net use" the main Windows drive (C$) and from there, get remote access to a DC. The
use of runas.exe with /savecred is also important/dangerous. As it means that any credential that would be entered in this
command will be saved in the Windows Credential Manager and subsequent runas.exe commands calling that user will not
require/prompt the user for the password of that target account. Akira still uses runas.exe in 2025, don't sleep on it!
References
• https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-
2012/cc771525(v=ws.11)
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "runas.exe"
or InitiatingProcessFileName =~ "runas.exe"
61
52. Multiple net.exe Discovery Commands Launched
This query returns events when multiple net.exe discovery commands are launched. Today's query was documented
quite quickly due to a personal emergency on my end. I'll enhance it later as I have time. This query returns events when
multiple net.exe discovery commands (for users and groups, may they be local or domain) are launched within a short
timespan. net.exe remains one of the most commonly used utility used by threat actors (and even malware at some point)
for discovery of:
• Local users
• Local groups
• Domain users
• Domain groups
It is therefore not rare to see net.exe commands being launched by threat actors as their first actions in a
compromised environment. May it be to get more information on the user they currently have, users they may have
compromised, users they are interested in compromising, etc. These commands can be launched in rapid succession, under
a matter of minutes. It is therefore interesting to identify events where X number of net.exe discovery commands are
launched within Y minutes. And from there:
• Identify which account launched them
• Identify on which device these commands were executed
• Expand the scope and look at commands (e.g.: process execution) and/or logon events before and after these
commands were launched for anything out of the ordinary etc.
This query is more suited as a "hunting query" and is what we call a "threshold-based" query. A query in which you need
to define threshold (arbitrary values) that are used to define the scope of the query. Which means, these values may have
to be baselined for your own environment.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
let TimeBin = 5m;
let ProcessCount = 5;
DeviceProcessEvents
| where FileName =~ "net.exe"
| where ProcessCommandLine has_any (" user ", " localgroup ", " group ")
// We are not interested in manipulation operations here where users are created and/or
added to groups
// Though other manipulation-related parameters exists, but are more rare. Should they be
returned, they are definitely worth looking into, such as /active:{yes|no}
| where ProcessCommandLine !has "/add"
| summarize count(),
["ProcessCommandLines"]=make_set(ProcessCommandLine),
["FirstCommandTime"]=min(TimeGenerated),
["LastCommandTime"]=max(TimeGenerated)
by DeviceName, AccountDomain, AccountName, bin(TimeGenerated, TimeBin)
| where ProcessCount >= 5
62
53. Windows Remote Management Command Targeting a Remote Endpoint
This query returns events where a Windows Remote Management (WinRM) command is targeting a remote
endpoint. This query returns events where a Windows Remote Management (WinRM) command is targeting (read: used
against) a remote endpoint. Windows Remote Management or WinRM is, as the name implies, Microsoft's implementation
of the WS-Management protocol. To put it simply, in the context of our queries and what we're looking for here: it allows
you to execute commands, start programs, etc. on Windows systems, may it be the local system (where the command is
run from) or a remote system (which the command would target). There are multiple ways to launch commands against
remote endpoints using Windows Remote Management. The most known, and therefore, common ones are:
• wmic.exe - The WMI Command-Line Utility
• winrs.exe - A utility similar to wmic.exe
• PowerShell Remoting (which leverages Windows Remote Management)
Each of these allow you to execute command on remote endpoints, assuming the WinRM is not disabled.
• For winrs.exe, the WinRM service needs to be running
• For PowerShell Remoting, it needs to be enabled first (Enable-PSRemoting), which will configure the system with
a listener to accept inbound connections and set the WinRM service start type to "Automatic (Delayed)"
alongside starting it.
From there, it's just a matter of targeting a remote endpoint with arbitrary commands of your choice (well, the threat
actor's choice). Maybe there will be an upcoming #100DaysOfKQL day showing how to spot these incoming remote
commands from the target system POV, who knows!
References
• https://lolbas-project.github.io/lolbas/Binaries/Wmic/
• https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/winrs
• https://learn.microsoft.com/en-us/powershell/scripting/learn/ps101/08-powershell-remoting?view=powershell-
7.5
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "PowerShellCommand"
| where AdditionalFields has_any ("Enter-PSSession", "New-PSSession", "Invoke-Command")
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where (FileName =~ "winrs.exe" and ProcessCommandLine has_any ("/r:", "/remote:"))
or (FileName =~ "wmic.exe" and ProcessCommandLine has "/node:")
or (FileName in~ ("powershell.exe", "pwsh.exe") and ProcessCommandLine has_any
("Enter-PSSession", "New-PSSession", "Invoke-Command"))
63
54. Windows Remote Management Command Targeting a Remote Endpoint
This query returns events where a Windows Remote Management (WinRM) command was executed on the
endpoint. I sadly also have to post this very quickly today. I'll come back later to update this page with more
information/details. For now, see the description from this query's "sister" query (Day 50):
https://github.com/SecurityAura/DE-TH-Aura/blob/main/100DaysOfKQL/Day%2050%20-
%20Windows%20Remote%20Management%20Command%20Targeting%20a%20Remote%20Endpoint.md
This query belows look for traces of command/process execution on an endpoint that was the TARGET of Windows
Remote Management commands. Summary for now:
• wmic.exe leads to WmiPrvSE.exe launching the command on the target
• PowerShell Remoting leads to WSMProvHost.exe launching the command on the target
• winrs.exe leads to winrshost.exe launching the command on the target
Going to add queries to trace back the logon associated with the remote command execution as well.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where InitiatingProcessFileName in~ ("wmiprvse.exe", "wsmprovhost.exe",
"winrshost.exe")
64
55. RDP Logon Outside Work Hours or During The Weekend
This query returns events when a RDP session is observed outside of work hours or during the weekend. This query
returns events when a RDP session is observed to be initiated outside of (your defined) work hours or during the weekend.
Depending on the threat actors, being stealthy during an intrusion isn't always their number one goal. At the very least,
most ransomware operators don't seem to care one bit about it. Which is quite obvious for Incident Responders who've
investigated these incidents in the past. If there's one thing that threat actors still loves for lateral movement, it's using the
Remote Desktop Protocol (RDP). It's fast, easy, convenient and ... it just works. It is therefore very common for threat actors
to move laterally from system to system within a network using RDP. May it be between systems in the environment itself
(e.g.: SERVER1 to SERVER2) or from their own machine (e.g.: they managed to login to the VPN) to a system in the
environment (e.g.: SERVER1). Now here's the interesting bit, threat actors have a schedule like you and me. They work
during certain hours/days, go live their life, and then get back to work. Should you be located in a timezone different than
their, with little to no overlap during their work hours and yours, you're bound to see them being active at these odd hours.
Doesn't a RDP logon from a Domain Admin account to a Domain Controller at 3 AM on a Thursday seem odd to you? With
KQL, and Defender for Endpoint, Defender for Identity and SecurityEvent, it is possible to craft KQL queries that will return
RDP logon events that happens outside the hours and/or days you define. For instance, outside 7 AM to 5 PM from Monday
to Friday, and nothing over the weekend. Simply adjust the queries below for your timezone and working hours and
weekend days, and you'll have a pretty good threat hunting query looking for RDP logon at odd hours/days. Or even a
detection, if you run a very tight ship (though do not discount these overnight deployments, manual updates, etc.). If you
have systems and/or users across different timezone geographically ... well, it's still possible to use these queries, they'll
just require "some" fine-tuning and adjustments.
65
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceLogonEvents
let DaysOfWeek = dynamic([
"Sunday", // 0
"Monday", // 1
"Tuesday", // 2
"Wednesday", // 3
"Thursday", // 4
"Friday", // 5
"Saturday" // 6
]);
// Define your timezone: https://learn.microsoft.com/en-
us/kusto/query/timezone?view=microsoft-sentinel
let Timezone = "YOUR_TIMEZONE_HERE";
DeviceLogonEvents
| where LogonType == "RemoteInteractive"
| extend LocalDateTime = datetime_utc_to_local(TimeGenerated,Timezone)
| extend DayOfWeek = toint((dayofweek(LocalDateTime))/1d)
| extend HourOfDay = datetime_part("hour", LocalDateTime)
| where DayOfWeek in (0, 6)
or (DayOfWeek between ( 1 .. 5 ) and (HourOfDay between (17 .. 23) or HourOfDay
between ( 00 .. 07)))
| extend ActualDay = DaysOfWeek[DayOfWeek]
Microsoft Defender for Identity via IdentityLogonEvents
let DaysOfWeek = dynamic([
"Sunday", // 0
"Monday", // 1
"Tuesday", // 2
"Wednesday", // 3
"Thursday", // 4
"Friday", // 5
"Saturday" // 6
]);
// Define your timezone: https://learn.microsoft.com/en-
us/kusto/query/timezone?view=microsoft-sentinel
let Timezone = "YOUR_TIMEZONE_HERE";
IdentityLogonEvents
| where LogonType == "Remote desktop"
| extend LocalDateTime = datetime_utc_to_local(TimeGenerated,Timezone)
| extend DayOfWeek = toint((dayofweek(LocalDateTime))/1d)
| extend HourOfDay = datetime_part("hour", LocalDateTime)
| where DayOfWeek in (0, 6)
or (DayOfWeek between ( 1 .. 5 ) and (HourOfDay between (17 .. 23) or HourOfDay
between ( 00 .. 07)))
| extend ActualDay = DaysOfWeek[DayOfWeek]
SecurityEvent
let DaysOfWeek = dynamic([
"Sunday", // 0
"Monday", // 1
"Tuesday", // 2
"Wednesday", // 3
"Thursday", // 4
"Friday", // 5
"Saturday" // 6
]);
// Define your timezone: https://learn.microsoft.com/en-
us/kusto/query/timezone?view=microsoft-sentinel
let Timezone = "YOUR_TIMEZONE_HERE";
SecurityEvent
| where EventID == 4624
| where LogonType == 10
| extend LocalDateTime = datetime_utc_to_local(TimeGenerated,Timezone)
| extend DayOfWeek = toint((dayofweek(LocalDateTime))/1d)
| extend HourOfDay = datetime_part("hour", LocalDateTime)
| where DayOfWeek in (0, 6)
or (DayOfWeek between ( 1 .. 5 ) and (HourOfDay between (17 .. 23) or HourOfDay
between ( 00 .. 07)))
| extend ActualDay = DaysOfWeek[DayOfWeek]
66
56. Investigate Device or Account Across All MDE Tables At Once
This query returns all Defender for Endpoint (MDE) related events for a target device or account. Similar to Day 41
query, this is more of an "investigation" query and/or a query you would most likely run when you're investigating an
incident in an environment where Defender for Endpoint (MDE) was deployed beforehand. This query returns all Defender
for Endpoint (MDE) related events for a target device (DeviceName) or account (AccountDomain, AccountName). When
investigating incidents through Defender for Endpoint (MDE), at some point, you'll want to get a "bird's eye view" of
everything. With the two (2) most common scenarios (which are included in this page), in my opinion, being:
• Every event that occured on an endpoint
• Every event involving a user of interest
Luckily for us, this is what exactly what the KQL "union" operator allows us to do. We can "unite" all the MDE-related
tables in a single query, and then use one (common) or multiple (depend on the tables) denominator to capture all the
events that interest us. The MDE-related tables of interest here are:
• DeviceEvents
• DeviceFileEvents
• DeviceImageLoadEvents
• DeviceLogonEvents
• DeviceProcessEvents
• DeviceRegistryEvents
Querying all these tables at once can easily return a number of events superior to the limit of the console you're
using. For instance, over 30,000 events in Microsoft Sentinel. Therefore, depending on what filters you are using, what time
period you are covering, the size of the environment and the number of events generated by each onboarded system, you
may be forced to:
• Target a specific time range
• Do a pre-filtering by using "distinct" on certain interesting columns, depending on what you're looking for before
fine-tuning your query and running it again
In regard to the 2nd point, let's say you're looking for any event involving UserA as the AccountName or
InitiatingProcessAccountName and over 30,000+ results are returned. You may want to finish your initial query by doing a
simple "| distinct DeviceName" to see which devices that account has been observed on. And from there, run that query a
single time for each device. Everyone has their way of doing things and there's no wrong answer here. Just do whatever
works best for you, as long as it works obviously.
67
Microsoft Sentinel
Microsoft Defender for Endpoint via Everything (search by DeviceName)
// Modify the StartDate and EndDate based on your needs.
let StartDate = todate("2025-02-22");
let EndDate = todate("2025-02-23");
// Set the DeviceName you want to get events from
let TargetDevice = "TARGET_DEVICE_NAME";
union DeviceEvents, DeviceFileEvents, DeviceImageLoadEvents, DeviceLogonEvents,
DeviceProcessEvents, DeviceRegistryEvents
| where TimeGenerated between ( StartDate .. EndDate )
| where DeviceName =~ TargetDevice
Microsoft Defender for Endpoint via Everything (search by AccountName)
// Modify the StartDate and EndDate based on your needs.
let StartDate = todate("2025-02-22");
let EndDate = todate("2025-02-23");
// Set the AccountName you want to get events for
let TargetAccount = "TARGET_ACCOUNT_NAME";
// Should you want to query a specific account whose name can resolve to a local account
or domain account, add the AccountDomain in the mix
let TargetAccountDomain = "TARGET_ACCOUNT_DOMAIN";
union DeviceEvents, DeviceFileEvents, DeviceImageLoadEvents, DeviceLogonEvents,
DeviceProcessEvents, DeviceRegistryEvents
| where TimeGenerated between ( StartDate .. EndDate )
| where AccountName =~ TargetAccount
or InitiatingProcessAccountName =~ TargetAccount
// If you also need to target the AccountName by AccountDomain, use the following filter
instead, and comment the two (2) lines above
//| where (AccountDomain =~ TargetAccountDomain and AccountName =~ TargetAccount)
// or (InitiatingProcessAccountDomain =~ TargetAccountDomain and
InitiatingProcessAccountName =~ TargetAccount)
68
57. Identify Accounts Used From a Threat Actor Device
This query returns a summarization of the accounts that are used from a Threat Actor's device. More of an
"investigation" query and/or a query you would most likely run when you're investigating an incident in an environment
where Defender for Endpoint (MDE) was deployed beforehand. This query returns a summarization of the accounts that
are used from a threat actor's device on an environment where MDE is deployed. The scenario we're talking about here is
one where the threat actor managed to connect his device to the network. Most often, through the VPN. For more
information about this, you can go down a little #TBT (even though I'm posting this on Sunday, anyway) and read the 1st
query of the #100DaysOfKQL:
https://github.com/SecurityAura/DE-TH-Aura/blob/main/100DaysOfKQL/Day%201%20-
%20Authentication%20From%20Suspicious%20WorkstationName.md
This query can quickly help grasp (read: identify) which accounts were successfully used by the threat actor from his
device. In order to use that query however, you need to know at least one piece of information:
• The threat actor's device name (once again, #TBT to Day 1 query to help you get started)
• The threat actor's device IP (e.g.: the IP in the VPN subnet that was assigned to its device when it connected to
the network)
From there, you can simply query the DeviceLogonEvents (MDE) or IdentityLogonEvents (MDI) table for all successful
authentication related events coming from that device name or remote IP, and get a list of compromised accounts. One
thing to consider as well is the association between the threat actor's device name and its IP address. Not all events are
created equal, which means, not all events have BOTH the device name AND remote IP column populated, though the
remote IP one should always have a value in it. Therefore, if in your results, you get a set of accounts being used from a
remote IP with no remote device name, make sure that when these events occured, that IP really was associated to the
threat actor's machine.,I strongly suggest to also run the query without the "LogonSuccess" filter, to see what other
accounts were tried by the threat actor. Just because those logon failed, it doesn't mean that the account wasn't
compromised (e.g.: logon failed because the account is locked, doesn't mean the credentials aren't valid and therefore,
compromised). There are dozen of ways to get that information, and then some, here in this scenario. These queries should
be seen as the most ... basic way to get that information and nothing more. I do plan on enhancing this page with more
queries in the future. Such as ones that may return the LogonType for each account, on which DeviceNames they were
tried, etc. OR, if you're following these queries to learn... you can consider it as a challenge!
69
| extend Account = strcat(AccountDomain,"\\",AccountName)
| summarize ["Accounts"]=make_set(Account),
["AccountCount"]=dcount(Account)
by tostring(AdditionalFields.["FROM.DEVICE"])
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceLogonEvents
let TARemoteDeviceNames = dynamic([
"THREAT ACTOR",
"DEVICE NAMES",
"HERE"
]);
let TARemoteIPs = dynamic([
"THREAT ACTOR",
"REMOTE IPs",
"HERE"
]);
DeviceLogonEvents
| where RemoteDeviceName has_any (TARemoteDeviceNames)
or RemoteIP has_any (TARemoteIPs)
| where ActionType == "LogonSuccess"
| extend Account = strcat(AccountDomain,"\\",AccountName)
| summarize ["Accounts"]=make_set(Account),
["AccountCount"]=dcount(Account)
by RemoteDeviceName, RemoteIP
Microsoft Defender for Endpoint via IdentityLogonEvents
let TARemoteDeviceNames = dynamic([
"THREAT ACTOR",
"DEVICE NAMES",
"HERE"
]);
let TARemoteIPs = dynamic([
"THREAT ACTOR",
"REMOTE IPs",
"HERE"
]);
IdentityLogonEvents
| where AdditionalFields.["FROM.DEVICE"] has_any (TARemoteDeviceNames)
or AdditionalFields.["FROM.DEVICE"] has_any (TARemoteIPs)
| where ActionType == "LogonSuccess"
| extend Account = strcat(AccountDomain,"\\",AccountName)
| summarize ["Accounts"]=make_set(Account),
["AccountCount"]=dcount(Account)
by tostring(AdditionalFields.["FROM.DEVICE"])
70
58. Executable File With Short Numerical Name Observed
This query returns events where an executable file with a short numerical name was observed. This query returns
events where an executable file, per its extension, with a short numerical name (less than 3 numbers) was observed. The
query basically speaks for itself. It is not uncommon during incidents to see that threat actors dropped and/or leveraged
binaries with extremely short numerical names: 1.exe, 2.ps1, def.bat, etc. Therefore, we're looking for file events involving
files with executable extensions (common ones) whose name are 3 numbers or less (excluding the extension). You can add
extensions as needed and even play with the number of characters to increment the minimum (4, 5, etc.) to see what comes
up.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceFileEvents
let FileNameRegex = @'^[0-9]{1,3}\.(exe|msi|dll|ps1|bat|cmd)';
DeviceFileEvents
| where FileName matches regex FileNameRegex
71
59. ngrok Usage
This query returns events where ngrok was observed. This query returns events where ngrok was observed through
various means. ngrok is a legitimate reverse proxy tool that can be used to create secure tunnels and whose main purpose
is basically to expose endpoints and applications so that they can be used externally. The way the threat actors use it
however, is also to set up reverse tunnels so that they can then remotely connect to these internal endpoints externally.
One of their common use case is to setup a ngrok agent on a compromised system and from the tunnel that has been set
up, RDP into that system. Which is much more confortable to work with when you have access to a GUI at this point. A lot
of these reverse proxy tool/reverse tunnel tools exists (you can do the same with SSH and PLINK for instance), though
ngrok remains one of the most popular ones. There are a few ways to detect ngrok usage on a system through Defender
for Endpoint (MDE) telemetry, luckily for us.
References
• https://www.huntress.com/blog/abusing-ngrok-hackers-at-the-end-of-the-tunnel
• https://attack.mitre.org/software/S0508/
• https://news.sophos.com/en-us/2022/07/14/rapid-response-the-ngrok-incident-guide/
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "ngrok.exe"
or ProcessVersionInfoProductName has "ngrok"
or ProcessVersionInfoFileDescription has "ngrok"
or InitiatingProcessFileName =~ "ngrok.exe"
or InitiatingProcessVersionInfoProductName has "ngrok"
or InitiatingProcessVersionInfoFileDescription has "ngrok"
Microsoft Defender for Endpoint via DeviceNetworkEvents
DeviceNetworkEvents
| where InitiatingProcessFileName =~ "ngrok.exe"
or InitiatingProcessVersionInfoProductName has "ngrok"
or InitiatingProcessVersionInfoFileDescription has "ngrok"
// There are many ngrok-related domains and URLs, but they all have the ngrok string
in it and it's pretty unique
or RemoteUrl has "ngrok"
Microsoft Defender for Endpoint via DeviceFileEvents
DeviceFileEvents
// The default filename used for ngrok config file
| where FileName =~ "ngrok.conf"
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
72
| where ActionType == "ServiceInstalled"
| where AdditionalFields.ServiceName has "ngrok"
or FileName =~ "ngrok.exe"
SecurityEvent
SecurityEvent
| where EventID == 4697
| where ServiceName has "ngrok"
or ServiceFileName has "ngrok.exe"
Event
Event
| where TimeGenerated > ago(90d)
| where EventID == 7045
| extend ParsedXML = parse_xml(EventData)
| where
parse_json(tostring(parse_json(tostring(parse_json(tostring(ParsedXML.DataItem)).EventDat
a)).Data))[0].["#text"] has "ngrok"
or
parse_json(tostring(parse_json(tostring(parse_json(tostring(ParsedXML.DataItem)).EventDat
a)).Data))[1].["#text"] has "ngrok.exe"
73
60. Non-Sucking Service Manager (nssm) Usage
This query returns events where the Non-Sucking Service Manager (nssm) application was observed. I once again
have to put up that query of the day very fast today. I'll come back to it to add more queries to hunt for NSSM. This query
returns events where the Non-Sucking Service Manager (nssm) application was observed. NSSM is a popular little
application (free, the way we like them) that allows you to basically turn anything into a service, may it be a binary, script,
command, etc. If you read between the lines of the previous sentence, you'll quickly understand that NSSM can also be
abused by threat actors to basically persist in the form of a service. May it be a backdoor, ransomware, BYVOD, etc. For
instance, threat actors can use NSSM with ngrok (Day 56 query), even though ngrok can be installed as a service by itself,
to make sure that the ngrok agent constantly stays up and running and restarted even after reboot, the process gets killed,
the process crashes, etc. NSSM has also been seen to persisently launch coinminers such as XMRig on compromised
systems.
References
• https://nssm.cc/
• https://news.sophos.com/en-us/2023/12/21/akira-again-the-ransomware-that-keeps-on-taking/
• https://thedfirreport.com/2020/04/20/sqlserver-or-the-miner-in-the-basement/
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "nssm.exe"
or InitiatingProcessFileName =~ "nssm.exe"
Microsoft Defender for Endpoint via DeviceFileEvents
DeviceFileEvents
| where FileName =~ "nssm.exe"
Microsoft Defender for Endpoint via DeviceEvents
DeviceEvents
| where ActionType == "ServiceInstalled"
| where FileName =~ "nssm.exe"
or FolderPath has "nssm"
74
61. Non-Sucking Service Manager (nssm) Usage
This query returns events where rundll32.exe or regsvr32.exe loads a DLL with an invalid signature. This query returns
events where rundll32.exe or regsvr32.exe loads a file with an invalid signature, per Defender for Endpoint (MDE)
FileProfile(). Loading DLLs through regsvr32.exe or rundll32.exe is a threat actor's (or malware) favorite. They are LOLBAS,
they hide in plain sight, you just have to pass them an innocuous filename so from a ProcessCommandLine perspective, it
doesn't attract attention. Same thing with the entry points which can be the same as known/benign ones (see the
References section for an example). It does not mean that these files are always signed, or that their signature will remain
valid forever (#ImposeCost and Squiblidoo's certReport amirite?). In Defender for Endpoint, you can use the FileProfile()
function to get interesting information about a file, namely:
• It's signature state
• Whether the certificate is valid or not
Therefore, it is possible to single out files loaded by rundll32.exe or regsvr32.exe, pass them through FileProfile()
and from there, get files whose signature or certificate are not valid. As with every query that uses FileProfile(), be mindful
of the number of hashes you're going to pass through it since it has a 1,000 lookup limit per query.
References
• https://cloud.google.com/blog/topics/threat-intelligence/unc2596-cuba-ransomware/
• https://github.com/Squiblydoo/certReport
• https://medium.com/falconforce/microsoft-defender-for-endpoint-internals-0x03-mde-telemetry-unreliability-
and-log-augmentation-ec6e7e5f406f (awareness on the limitations of the DeviceImageLoadEvents table)
75
62. DeviceNetworkEvents From FileZilla with Enriched IP Information
This query returns events from network connections by FileZilla with enriched IP information. This query returns
events from network connections (DeviceNetworkEvents) by FileZilla with enriched IP (RemoteIP) information, such as its
geolocation and ASN. The ASN enrichment is done through GypTheCat[.]com awesome Kusto ASN Table. Once again,
thank you to Matt Zorich (@reprise99) for showing me that site and resource!
https://firewalliplists.gypthecat.com/kusto-tables/kusto-asn-table/
The geolocation information is provided by Microsoft Sentinel built-in geo_info_from_ip_address() function. This
query is more suited for hunting purposes, unless you want to combine it with an IP and/or domain whitelist (through a
Watchlist in Microsoft Sentinel for instance) if you know exactly where your users (or servers) should be connecting in
outbound on ports such as 21 or 22 using FileZilla. Though if you knew about it, you would simply allowlist these
connections in your firewall and block the rest amirite? This being said, when it comes to data exfil, "simple is always best"
and FileZilla, like WinSCP (tomorrow's spoiler maybe?), still remain a threat actor's favorite when it comes to exfiltrating
data. Therefore, looking into which RemoteIPs or RemoteURLs FileZilla connects to, where the process is being launched
from, was its filename changed (not FileZilla.exe, etc.) can help uncover non-legitimate use of FileZilla.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceNetworkEvents
let CIDRASN = (
externaldata (CIDR:string, CIDRASN:int, CIDRASNName:string, CIDRSource:string)
['https://firewalliplists.gypthecat.com/lists/kusto/kusto-cidr-asn.csv.zip']
with (ignoreFirstRecord=true)
);
DeviceNetworkEvents
| where InitiatingProcessFileName =~ "filezilla.exe"
or InitiatingProcessVersionInfoCompanyName has "FileZilla"
or InitiatingProcessVersionInfoFileDescription has "FileZilla"
or InitiatingProcessVersionInfoInternalFileName has "FileZilla"
or InitiatingProcessVersionInfoOriginalFileName has "FileZilla"
or InitiatingProcessVersionInfoProductName has "FileZilla"
| where RemoteIPType == "Public"
| where RemoteUrl !endswith ".filezilla-project.org"
| extend RemoteIPGeoLoc = geo_info_from_ip_address(RemoteIP)
| evaluate ipv4_lookup(CIDRASN, RemoteIP, CIDR, return_unmatched=true)
76
63. DeviceNetworkEvents From WinSCP with Enriched IP Information
This query returns events from network connections by WinSCP with enriched IP information. This query returns
events from network connections (DeviceNetworkEvents) by WinSCP with enriched IP (RemoteIP) information, such as its
geolocation and ASN. The ASN enrichment is done through GypTheCat[.]com awesome Kusto ASN Table. Once again,
thank you to Matt Zorich (@reprise99) for showing me that site and resource!
https://firewalliplists.gypthecat.com/kusto-tables/kusto-asn-table/
The geolocation information is provided by Microsoft Sentinel built-in geo_info_from_ip_address() function. This
query is more suited for hunting purposes, unless you want to combine it with an IP and/or domain whitelist (through a
Watchlist in Microsoft Sentinel for instance) if you know exactly where your users (or servers) should be connecting in
outbound on ports such as 21 or 22 using WinSCP. Though if you knew about it, you would simply allowlist these
connections in your firewall and block the rest amirite?
This being said, when it comes to data exfil, "simple is always best" and WinSCP, like FileZilla (yesterday's spoiler
mayb-see what I did there?), still remain a threat actor's favorite when it comes to exfiltrating data. Therefore, looking into
which RemoteIPs or RemoteURLs WinSCP connects to, where the process is being launched from, was its filename changed
(not WinSCP.exe, etc.) can help uncover non-legitimate use of WinSCP.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceNetworkEvents
let CIDRASN = (
externaldata (CIDR:string, CIDRASN:int, CIDRASNName:string, CIDRSource:string)
['https://firewalliplists.gypthecat.com/lists/kusto/kusto-cidr-asn.csv.zip']
with (ignoreFirstRecord=true)
);
DeviceNetworkEvents
| where InitiatingProcessFileName =~ "WinSCP.exe"
or InitiatingProcessVersionInfoCompanyName has "Martin Prikryl"
or InitiatingProcessVersionInfoFileDescription has "WinSCP"
or InitiatingProcessVersionInfoInternalFileName has "WinSCP"
or InitiatingProcessVersionInfoOriginalFileName has "WinSCP"
or InitiatingProcessVersionInfoProductName has "WinSCP"
| where RemoteIPType == "Public"
| where RemoteUrl !endswith ".WinSCP-project.org"
| extend RemoteIPGeoLoc = geo_info_from_ip_address(RemoteIP)
| evaluate ipv4_lookup(CIDRASN, RemoteIP, CIDR, return_unmatched=true)
77
64. SoftPerfect Network Scanner Usage
This query returns events where SoftPerfect Network Scanner (netscan.exe) is used. This query returns events where
SoftPerfect Network Scanner (netscan.exe) is used. That tool needs no introduction and there's literally nothing to say about
it other than, just make sure that if you have a hit for it, make sure that it's actually used legitimately.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceEvents, DeviceFileEvents, DeviceProcessEvents, DeviceRegistryEvents,
DeviceNetworkEvents, DeviceImageLoadEvents (union)
union DeviceEvents, DeviceFileEvents, DeviceProcessEvents, DeviceRegistryEvents,
DeviceNetworkEvents, DeviceImageLoadEvents
| where FileName =~ "netscan.exe"
or ProcessVersionInfoCompanyName has "SoftPerfect"
or InitiatingProcessFileName =~ "netscan.exe"
or InitiatingProcessVersionInfoCompanyName has "SoftPerfect"
78
65. PortableApps Application Observed
This query returns events where a PortableApps application was observed. This query returns events where a
PortableApps application (.paf.exe extension) was observed. "Portable Apps", typically associated with PortableApps.com
are "self contained" applications that are installed within an arbitrary folder by an installer. It allows you to download and
use applications without having to install them, and that can be basically "live" within the folder they are "installed" in,
without interfacing with the Windows Registry or else. Everything they need to work: configuration, settings, files, etc. are
in their "installation" folder. In a certain way, you can see portable apps as a way to "bypass" installation restrictions, since
you can "install" an application in a folder of your choosing, and it would not be subject to certain restrictions that are in
place, such as GPOs that are acting/enforcing certain Registry Keys. Threat actors have been observed using portable apps
(.paf.exe) in certain attacks, though it does not seem to be that popular. Even I can probably only count on one hand the
number of ransomware-related IRs where I've seen such apps being used. Even though at least one of these fingers would
be used for a ransomware engagement in 2025. In Defender for Endpoint (MDE), you can look for portable apps through
their distinctive ".paf.exe" extension (which is just an .exe, with a .paf appended in the filename) and/or the files being
downloaded from PortableApps.com. If while you're hunting for these, you find a user using a portable version of a Web
Browser, in a corporate setting, you may want to ask them what they are trying to achieve.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceFileEvents
DeviceFileEvents
| where FileName endswith ".paf.exe"
or FileOriginUrl has "portableapps.com"
or FileOriginReferrerUrl has "portableapps.com"
Microsoft Defender for Endpoint via DeviceProcessEvents
DeviceProcessEvents
| where FileName endswith ".paf.exe"
79
66. File Added to Startup Folder
This query returns events where a file was added to Windows' Startup folder. This query returns events where a file
was added to Windows' Startup folder, may it be for all users (C:\ProgramData\Microsoft\Windows\Start
Menu\Programs\Startup) or a specific user (%AppData%\Microsoft\Windows\Start Menu\Programs\Startup). A few years
back, adding a file in the Startup folder to kick-off a malware and/or process used to be all the rage with commodity
malware. It seems to be less common these days, but it can still be seen in certain malware families.
Depending on the environment, it should be pretty quick to see whether a file that gets created in a Startup folder
is legitimate or not. Even more depending on the extension it has. For instance, script files such as PS1 and CMD/BAT are
suspicious, but so are JS, JSE, WS, WSE, etc. Nowaday, with LNKs being abused for all kind of things, including being
dropped in Startup folders, they also should not be overlooked.
Microsoft Sentinel
Microsoft Defender for Endpoint via DeviceFileEvents
DeviceFileEvents
| extend FileExtension = tostring(split(FileName,".")[-1])
| where FolderPath matches regex @'(?i)\\Microsoft\\Windows\\Start\
Menu\\Programs\\Startup\\(.*)?'
// We "normalize" the FolderPath if it's a single user one
(C:\Users\USERNAME\AppData\[...]) to assist in our summarization (clustering)
| extend NormalizedFolderPath =
replace(@'(?i)C\:\\Users\\[^\\]+\\',@"C:\Users\USERNAME\",FolderPath)
| summarize ["Devices"]=make_set(DeviceName),
["Number of Devices"]=dcount(DeviceName)
by NormalizedFolderPath
80
67. Emails With Company Name in Display Name Sent From Non-Company Domains
This query returns events where an email was sent to organization's users where the company name is in the display
name, but that email doesn't originate from that company's domains. I'm currently sick and fighting sleepiness as I post
this. As usual, I'll enhance that page with more information when I get better/get back. For now, consider this as a hunting
query. You also may need to exclude more domains if you use services that send emails from their domains/stack but use
your name, such as Jira, Amazon SES, Adobe Sign, etc. This query returns events where an email was sent to organization's
users where the company name is in the display name, but that email doesn't originate from that company's domains and
where the email was not blocked.,For more immediate context, this is a query I used to find multiple QR-code phishing
emails targeting an organization where they were let through by MDO.
Microsoft Sentinel
Microsoft Defender for Office 365 via EmailEvents
let CompanyNames = dynamic([
"CompanyName"
]);
let OrgDomains = dynamic([
"CompanyDomainA.com",
"CompanyDomainB.net"
]);
EmailEvents
| where SenderDisplayName has_any (CompanyNames)
| where SenderFromDomain !in~ (OrgDomains)
and SenderMailFromDomain !in~ (OrgDomains)
| where DeliveryAction != "Blocked"
81
68. OfficeActivity - Emails involved in Operations from flagged IPs.md
The queries below can be used to obtain information about about emails that were involved in events coming from
flagged IP addresses. MailItemsAccessed events are explicitely excluded since they need to be parsed differently. See the
MailItemsAccessed queries page for these. Prerequisite(s): list of IP addresses that you identified as being malicious,
suspicious and/or of interest.
Microsoft Sentinel
Query 1 - Emails involved in Operations from flagged IPs (Item, raw format)
This query works for the following Operations, which uses the Item nested property:
• Create
• Send
• SendAs
• SendOnBehalf
• Update
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| where OfficeWorkload == "Exchange"
| where Operation != "MailItemsAccessed"
| where isnotempty( Item)
| extend ParsedItem = parse_json(Item)
| extend ItemId = ParsedItem.Id
| extend ItemInternetMessageId = tostring(ParsedItem.InternetMessageId)
| extend ItemSubject = tostring(ParsedItem.Subject)
| extend ItemParentFolder = tostring(parse_json(ParsedItem.ParentFolder).Path)
Query 2 - Emails involved in Operations from flagged IPs (Item, unique)
Summarized (unique InternetMessageId) version of Query 1. This query has a flaw because there are events that
don’t have an InternetMessageId nor Subject, so it skew the results. Looks like the events are missing information. The
only ID that exists for them is a unique, Microsoft-related id called “Id” under the Item column.
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| where OfficeWorkload == "Exchange"
| where Operation != "MailItemsAccessed"
| where isnotempty( Item)
| extend ParsedItem = parse_json(Item)
| extend ItemId = ParsedItem.Id
| extend ItemInternetMessageId = tostring(ParsedItem.InternetMessageId)
| extend ItemSubject = tostring(ParsedItem.Subject)
| extend ItemParentFolder = tostring(parse_json(ParsedItem.ParentFolder).Path)
Query 3 - Emails involved in Operations from flagged IPs (AffectedItems, raw format)
This query works for the following Operations, which uses the Item nested property:
• HardDelete
• Move
• MoveToDeletedItems
• SoftDelete
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| where OfficeWorkload == "Exchange"
82
| where Operation != "MailItemsAccessed"
| where isnotempty( AffectedItems)
| extend SourceFolder = tostring(parse_json(Folder).Path)
| extend DestinationFolder = tostring(parse_json(DestFolder).Path)
| mv-expand todynamic(AffectedItems)
| extend AffectedItemInternetMessageId =
tostring(parse_json(AffectedItems).InternetMessageId)
| extend AffectedItemSubject = tostring(parse_json(AffectedItems).Subject)
| extend AffectedItemParentFolderPath =
tostring(parse_json(parse_json(AffectedItems).ParentFolder).Path)
Query 4 - Emails involved in Operations from flagged IPs (AffectedItems, unique)
Summarized (unique InternetMessageId) version of Query 3. This query has a flaw because there are events that
don’t have an InternetMessageId nor Subject, so it skew the results. Looks like the events are missing information. The
only ID that exists for them is a unique, Microsoft-related id called “Id” under the Item column.
let FlaggedIPs = dynamic([
"1.1.1.1"
"2.2.2.2"
]);
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| where OfficeWorkload == "Exchange"
| where isnotempty( AffectedItems)
| extend SourceFolder = tostring(parse_json(Folder).Path)
| extend DestinationFolder = tostring(parse_json(DestFolder).Path)
| mv-expand todynamic(AffectedItems)
| extend AffectedItemInternetMessageId =
tostring(parse_json(AffectedItems).InternetMessageId)
| extend AffectedItemSubject = tostring(parse_json(AffectedItems).Subject)
| extend AffectedItemParentFolderPath =
tostring(parse_json(parse_json(AffectedItems).ParentFolder).Path)
| summarize ["Operations"]=make_set(Operation),
["Number of Operations"]=dcount(Operation),
["SourceFolders"]=make_set(SourceFolder),
["Number of SourceFolders"]=dcount(SourceFolder),
["DestinationFolders"]=make_set(DestinationFolder),
["Number of DestinationFolders"]=dcount(DestinationFolder),
["Subjects"]=make_set(AffectedItemSubject),
["Number of Subjects"]=dcount(AffectedItemSubject)
by AffectedItemInternetMessageId
83
69. OfficeActivity - Get Unique Client IPs Summary, List and Geolocation
The queries below can be used to obtain a summary of the various IPs (ClientIP, Client_IPAddress and
ActorIpAddress) involved in OfficeActivity events for users of interest. The ClientIP and Client_IPAddress fields are the most
populated, ActorIpAddress rarely is. From what I've seen so far, if both fields are populated (e.g.: ClientIP and
Client_IPAddress), they'll have the same value. Though most of the time, only one of them will be populated and the two
(2) others will be blank. From there, you can export that list from Microsoft Sentinel and enrich it externally (through a
script, MISP or whatever you want to use). Prerequisite(s): list of users you want to investigate further for which you want
to get a list of all IP adresses involved in their OfficeActivity events.
Microsoft Sentinel
Query 1 - Summary of ClientIP, Client_IPs
let Users = dynamic([
"[email protected]",
"[email protected]"
]);
OfficeActivity
| where UserId in~ (Users)
| where not (ipv4_is_in_any_range( ClientIP,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
or not (ipv4_is_in_any_range( Client_IPAddress,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
or not (ipv4_is_in_any_range( ActorIpAddress,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
| summarize ClientIPs=make_set(ClientIP),
ClientIPCount = dcount(ClientIP),
Client_IPs= make_set(Client_IPAddress),
Client_IPCount = dcount(Client_IPAddress),
ActorIps= make_set(ActorIpAddress),
ActorIPCount = dcount(ActorIpAddress)
by tolower(UserId)
| extend SharedIps=set_intersect(ClientIPs,Client_IPs)
| extend SharedIpCount=array_length(SharedIps)
| extend AllIPs = array_concat(ClientIPs, Client_IPs)
Query 2 - List of unique IPs involved in the events
Unless you filter it out, the returned count and list may always have a "blank" (empty) field.
let Users = dynamic([
"[email protected]",
"[email protected]"
]);
OfficeActivity
| where UserId in~ (Users)
| where not (ipv4_is_in_any_range( ClientIP,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
or not (ipv4_is_in_any_range( Client_IPAddress,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
or not (ipv4_is_in_any_range( ActorIpAddress,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
| summarize ClientIPs=make_set(ClientIP),
ClientIPCount = dcount(ClientIP),
Client_IPs= make_set(Client_IPAddress),
Client_IPCount = dcount(Client_IPAddress),
ActorIps= make_set(ActorIpAddress),
ActorIPCount = dcount(ActorIpAddress)
by tolower(UserId)
| extend SharedIps=set_intersect(ClientIPs,Client_IPs)
| extend SharedIpCount=array_length(SharedIps)
| extend AllIPs = array_concat(ClientIPs, Client_IPs)
| mv-expand AllIPs
| distinct tostring(AllIPs)
Query 3 - List of unique IPs involved in the events (with geolocation)
let Users = dynamic([
"[email protected]",
"[email protected]"
]);
OfficeActivity
| where UserId in~ (Users)
84
| where not (ipv4_is_in_any_range( ClientIP,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
or not (ipv4_is_in_any_range( Client_IPAddress,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
or not (ipv4_is_in_any_range( ActorIpAddress,
dynamic(["192.168.0.0/16","172.16.0.0/12","10.0.0.0/8"])))
| summarize ClientIPs=make_set(ClientIP),
ClientIPCount = dcount(ClientIP),
Client_IPs= make_set(Client_IPAddress),
Client_IPCount = dcount(Client_IPAddress),
ActorIps= make_set(ActorIpAddress),
ActorIPCount = dcount(ActorIpAddress)
by tolower(UserId)
| extend SharedIps=set_intersect(ClientIPs,Client_IPs)
| extend SharedIpCount=array_length(SharedIps)
| extend AllIPs = array_concat(ClientIPs, Client_IPs)
| mv-expand AllIPs
| distinct tostring(AllIPs)
| extend geo_info_from_ip_address(AllIPs)
85
70. OfficeActivity - MailItemsAccessed Breakdown
The queries below can be used to obtain information about MailItemsAccessed events from flagged IP addresses.
There are two (2) types of MailAccessType: Bind and Sync. Bind events refers to single access to an email (e.g.: email
viewed in OWA) while Sync events refers (technically) to the download of an email by a Microsoft Outlook client on either
Windows or macOS (though this could be challenged).
https://learn.microsoft.com/en-us/purview/audit-log-investigate-accounts
In BEC incidents, if you want to know which emails were accessed by a threat actor, you'll want to look into and
breakdown these MailItemsAccessed events. Prerequisite(s): list of IP addresses that you identified as being malicious,
suspicious and/or of interest.
Microsoft Sentinel
Query 1 - List of emails (InternetMessageId) involved in Bind operations
This query will give you the list of emails, by their unique InternetMessageId that were involved in Bind operations
by UserId. You should assume, per Microsoft's documentation, that any email listed in this output has been accessed by
an unauthorized third-party and therefore, leaked/exfiltrated.
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| where Operation == "MailItemsAccessed"
| extend MailAccessType = tostring(parse_json(OperationProperties)[0].Value)
| where MailAccessType == "Bind"
| extend FolderItems = parse_json(Folders)
| mv-expand todynamic ( FolderItems)
| mv-expand todynamic ( parse_json(FolderItems).FolderItems)
| extend FolderName = tostring(parse_json(FolderItems).Path)
| extend InternetMessageId =
tostring(parse_json(FolderItems_FolderItems).InternetMessageId)
| summarize ["Folders"]=make_set(FolderName),
["Number of Folders"]=dcount(FolderName)
by InternetMessageId, UserId
Query 2 - List of Folders involved in Sync operations
This query will give you the list of Folders that were involved in Sync operations by UserId. You should assume, per
Microsoft's documentation, that the content of any Folder listed in this output has been fully synchronized externally and
therefore, the emails exfiltrated.
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| where Operation == "MailItemsAccessed"
| extend MailAccessType = tostring(parse_json(OperationProperties)[0].Value)
| where MailAccessType == "Sync"
| extend SyncedFolderName = tostring(parse_json(parse_json(Item).ParentFolder).Name)
| extend SyncedFolderPath = tostring(parse_json(parse_json(Item).ParentFolder).Path)
| distinct MailAccessType, SyncedFolderName, SyncedFolderPath, UserId
86
71. OfficeActivity - OfficeWorkload and Operations Summary from Flagged IPs
The queries below can be used to obtain a summary of OfficeActivity Operations and their associated
OfficeWorkload from the OfficeActivity table in Microsoft Sentinel. Prerequisite(s): list of IP addresses that you identified as
being malicious, suspicious and/or of interest.
Microsoft Sentinel
Query 1 - Operations and RecordTypes summarization by OfficeWorkload
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| summarize count(),
["Operations"]=make_set(Operation),
["Number of Operations"]=dcount(Operation),
["RecordTypes"]=make_set(RecordType),
["Number of RecordTypes"]=dcount(RecordType)
by OfficeWorkload
Query #2 - Count of events per OfficeWorkload, Operation and UserId
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| summarize count() by OfficeWorkload, Operation, UserId
87
72. OfficeActivity - SharePoint & OneDrive Accessed Files Breakdown.md
The queries below can be used to obtain information about SharePoint/OneDrive OfficeWorkload events from
flagged IP addresses. It'll give you a list of unique files, based on their path, that were involved in operations from the
flagged IP addresses. You should assume that, depending on the Operation, or if you don't want to take any chance, all of
them, that the content of every file listed in the results is not private anymore and has been accessed by an unauthorized
third-party. Prerequisite(s): list of IP addresses that you identified as being malicious, suspicious and/or of interest.
Microsoft Sentinel
Query 1 - SharePoint/OneDrive files involved in Operations from flagged IPs
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
OfficeActivity
| where OfficeWorkload in~ ("SharePoint","OneDrive")
| summarize ["Operations"]=make_set(Operation),
["Number of Operations"]=dcount(Operation),
["SiteURLs"]=make_set(Site_Url),
["Number of SiteURLs"]=dcount(Site_Url),
["SourceFileNames"]=make_set(SourceFileName),
["Number of SourceFileNames"]=dcount(SourceFileName)
by OfficeObjectId, UserId
88
73. OfficeActivity x AuditLogs - Containment of Users with flagged IPs events
The query below can assist in getting a summary of the containment actions taken on users who had activities from
flagged IP addresses. It works by getting a list of all users who had OfficeActivity events from a predefined list of flagged
IP addresses and from there, cross-reference them in the AuditLogs table to see if various containment actions (e.g.:
password reset, account disable, etc.) was taken. Prerequisite(s):
• A list of IP addresses that you identified as being malicious, suspicious and/or of interest.
• Ajust the timerange for the period of time that covers both the incident and the containment actions that would
have been taken
Microsoft Sentinel
Query 1 - OfficeActivity x AuditLogs - Containment of Users with flagged IPs events
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
let CompromisedUsers = (
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| distinct UserId
);
AuditLogs
| where OperationName in~ ("Disable account","Reset user password","Reset password (self-
service)","Reset password (by admin)")
| extend Id = tostring(parse_json(TargetResources)[0].id)
| extend TargetUPN = tostring(parse_json(TargetResources)[0].userPrincipalName)
| join kind=rightouter CompromisedUsers on $left.TargetUPN == $right.UserId
| summarize ["Operations"] = make_set(OperationName)
by UserId
| extend AccountDisabled = iif(Operations has "Disable account", "Yes", "No")
| extend AccountPasswordReset = iif(Operations has "Reset user password", "Yes", "No")
| extend AccountPasswordResetBySelfService = iif(Operations has "Reset password (self-
service)", "Yes", "No")
| extend AccountPasswordResetByAdmin = iif(Operations has "Reset password (by admin)",
"Yes", "No")
| extend AtLeastOnePasswordResetOperation = iif (Operations has "password", "Yes", "No")
| project UserId, AccountDisabled, AccountPasswordReset,
AccountPasswordResetBySelfService, AccountPasswordResetByAdmin,
AtLeastOnePasswordResetOperation
89
74. OfficeActivity x EmailEvents - Get Emails sent by Compromised Users.md
The queries below can be used to obtain a summary of all the emails that were sent by a user from a flagged IP
address. Prerequisite(s):
• A list of IP addresses that you identified as being malicious, suspicious and/or of interest.
• The auditing of the Send operation for the Exchange OfficeWorkload must be enabled
Microsoft Sentinel
Query 1 - Summary of emails sent from flagged Sent operations
This query provides a summary of emails, per their InternetMessageIds, that were sent by Send operations coming
from flagged IPs. The summarization provides you numbers on how many recipients have been targeted "Intra-org" and
"Outbound" (external) by InternetMessageId and Subject.
let FlaggedIPs = dynamic([
"1.1.1.1",
"2.2.2.2"
]);
let InternetMessageIds = (
OfficeActivity
| where ClientIP has_any (FlaggedIPs)
or Client_IPAddress has_any (FlaggedIPs)
or ActorIpAddress has_any (FlaggedIPs)
| where Operation == "Send"
| extend InternetMessageId = tostring(parse_json(Item).InternetMessageId)
| distinct InternetMessageId
);
EmailEvents
| where InternetMessageId in~ (InternetMessageIds)
| summarize ["ExternalRecipients"]=make_set_if(RecipientEmailAddress,EmailDirection =~
"Outbound"),
["InternalRecipients"]=make_set_if(RecipientEmailAddress,EmailDirection =~
"Intra-org")
by InternetMessageId, Subject
| extend ExternalRecipientsCount = array_length(ExternalRecipients)
| extend InternalRecipientsCount = array_length(InternalRecipients)
| extend SharedRecipients =set_intersect(ExternalRecipients,InternalRecipients)
| extend SharedRecipientsCount=array_length(SharedRecipients)
90