High volume LSASS memory read
Description
This query identifies processes extracting an abnormally large volume of memory (>40MB) from LSASS. By focusing on physical bytes copied rather than process names, it detects credential dumping even if the malicious process runs with SYSTEM privileges.
Query · kql
// DETECTION STRATEGY:
// Identify credential dumping by measuring the physical volume of memory extracted from the LSASS process.
//
// THE MECHANIC:
// To extract plaintext credentials, NTLM hashes, or Kerberos tickets, an attacker's tool (like Mimikatz,
// Procdump, or a custom BYOVD (Bring Your Own Vulnerable Driver)) must parse the LSASS heap. This requires reading megabytes of continuous
// memory from the process using the ReadProcessMemory API.
//
// THE RESILIENCE:
// This detection is highly resilient because it ignores "Who" is doing the dumping and focuses on "What"
// is happening. It completely bypasses common evasions like privilege escalation (running as SYSTEM) or
// Handle Hijacking (duplicating an EDR's existing handle via DuplicateHandle), because the physical
// memory extraction itself is still tracked by the kernel callbacks fueling MDE.
// Define the extraction threshold.
// Why: A malicious dump needs the full credential tables. Legitimate API inspections (like DLP or AV)
// rarely copy continuous megabytes of data. 40MB (41943040 bytes) safely captures full process dumps.
let bytesThreshold = 41943040;
// Define the explicitly trusted tools for cryptographically-backed whitelisting.
// Why: Relying on ProcessName alone is unsafe, as attackers rename malware to "taskmgr.exe".
// We bind the expected name to the verified digital signer.
// Note: Internal tools like procdump.exe are often used by attackers. Whitelist with caution.
let allowedProcesses = datatable(ProcessNameLower:string, SignerNameLower:string)[
// Tier 1: Built-in Windows Administration Tools
"taskmgr.exe", "microsoft windows",
"procdump.exe", "microsoft corporation",
// Tier 2: Enterprise Backup and Security Agents
"mssense.exe", "microsoft windows publisher",
"wbengine.exe", "microsoft windows"
];
// Define the deduplicated Certificate dataset.
// Note: We use a 14-day lookback here specifically for the join to ensure we find the certificate
// even if the file was dropped/loaded days before the memory dump occurred.
let certInfo = DeviceFileCertificateInfo
| where Timestamp >= ago(14d)
| where isnotempty(SHA1) and isnotempty(Signer)
| summarize Signer = take_any(Signer) by SHA1;
// Base Query: Look for explicit memory reads against LSASS in MDE telemetry.
DeviceEvents
// STEP 1: Filter aggressively using string indexes before any heavy transformations.
| where ActionType =~ "ReadProcessMemoryApiCall"
| where isnotempty(AdditionalFields)
| where FileName =~ "lsass.exe" or AdditionalFields has "lsass.exe"
// STEP 2: Execute expensive JSON parsing ONLY on the heavily reduced dataset.
| extend TotalBytesCopied = tolong(parse_json(tostring(AdditionalFields)).TotalBytesCopied)
| where TotalBytesCopied >= bytesThreshold
// STEP 3: Cryptographic Whitelisting (The Join).
// Left side (DeviceEvents) is extremely small here, making the join highly efficient.
| join kind=leftouter (certInfo) on $left.InitiatingProcessSHA1 == $right.SHA1
| extend ProcessNameLower = tolower(InitiatingProcessFileName)
| extend SignerNameLower = tolower(Signer)
| join kind=leftanti (allowedProcesses) on ProcessNameLower, SignerNameLower
// EXCLUSION:
// Filter out poorly behaved legitimate software by ensuring the binary's internal version metadata
// or cryptographic signer does not belong to a trusted internal tool.
// Never whitelist purely by filename. Use FolderPath or Verified Signer.
// | where SignerLower !has "your internal company ca"
// | where InitiatingProcessFolderPath !has @"\Program Files\Your Approved Security Tool\"
// STEP 4: Output Engineering & Schema Alignment.
// Explicitly cast mapped entities to strings to guarantee type-safety for the Sentinel schema.
| extend HostName = tostring(DeviceName)
| extend AccountName = tostring(InitiatingProcessAccountName)
| extend InitiatingProcessAccountDomain = tostring(InitiatingProcessAccountDomain)
| extend ProcessIdString = tostring(InitiatingProcessId)
| extend VerifiedSigner = tostring(Signer)
// STEP 5: Format the output for triage.
// ANALYST ACTION: Review 'DumpingProcessPath' and 'VerifiedSigner'. If the execution path is a user profile
// directory (\AppData\, \Downloads\, \Temp\) and the VerifiedSigner is empty or anomalous, assume immediate
// compromise. The 'TotalBytesCopied' confirms credentials have been staged in memory.
| project Timestamp,
HostName,
AccountName,
InitiatingProcessAccountDomain,
DumpingProcess = InitiatingProcessFileName,
VerifiedSigner,
DumpingProcessPath = InitiatingProcessFolderPath,
InitiatingProcessCommandLine,
ProcessIdString,
TotalBytesCopied,
ActionType,
TargetProcess = FileName
| project-reorder Timestamp, HostName, AccountName, DumpingProcess, VerifiedSigner, InitiatingProcessCommandLine, TotalBytesCopied, ActionType, TargetProcess, DumpingProcessPath, InitiatingProcessAccountDomain, ProcessIdString