Detection rules › Kusto
LSASS Dumping using Debug Privileges
This query searches for a process that requests the SeDebugPrivilege privilege and opens LSASS memory using specific permission 0x1fffff which represents PROCESS_ALL_ACCESS.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Execution | T1106 Native API |
| Credential Access | T1003.001 OS Credential Dumping: LSASS Memory |
References
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Sysmon | Event ID 10 | ProcessAccess |
| Defender-DeviceEvents | OpenProcessApiCall | Process opened (OpenProcess API call) |
| Defender-DeviceEvents | ProcessPrimaryTokenModified | Process primary token modified |
| Kernel-Audit-API-Calls | Event ID 5 | OpenProcess API call audited |
Rule body kusto
let timeframe = 2*1h;
let SeDebugPrivilege = binary_shift_left(1, 20); // Value for SeDebugPrivilege is 2**20 = 0x100000.
let LSASSOpen=materialize (
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "OpenProcessApiCall"
| where FileName =~ "lsass.exe"
| extend AccessRights=parse_json(AdditionalFields).DesiredAccess
| where AccessRights == 0x1fffff // PROCESS_ALL_ACCESS.
| summarize by DeviceId, InitiatingProcessId, InitiatingProcessSHA1
);
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "ProcessPrimaryTokenModified"
| where isnotempty(InitiatingProcessSHA1)
// Look for processes that request debug privilege that also opened LSASS
| where InitiatingProcessSHA1 in ((LSASSOpen | project InitiatingProcessSHA1)) // Speeds up the query.
| lookup kind=inner LSASSOpen on DeviceId, InitiatingProcessSHA1, InitiatingProcessId
// Check that debug privilege is enabled.
| extend AdditionalFields=parse_json(AdditionalFields)
| extend CurrentTokenPrivEnabled = toint(AdditionalFields.CurrentTokenPrivEnabled)
| extend OriginalTokenPrivEnabled = toint(AdditionalFields.OriginalTokenPrivEnabled)
// Value for SeDebugPrivilege is 2**20 = 0x100000.
// Refer to https://downloads.volatilityfoundation.org//omfw/2012/OMFW2012_Gurkok.pdf for numeric values for privileges.
| extend DebugPrivCurrent = binary_and(CurrentTokenPrivEnabled,SeDebugPrivilege) == SeDebugPrivilege
| extend DebugPrivOrig = binary_and(OriginalTokenPrivEnabled,SeDebugPrivilege) == SeDebugPrivilege
// Check for processes that have debug privilege after the event, but did not have it before.
| where not(DebugPrivOrig) and DebugPrivCurrent
| extend CleanCmdLine = parse_command_line(InitiatingProcessCommandLine, "windows")
| where not(InitiatingProcessFileName =~ "tasklist.exe" and CleanCmdLine has_any ("/m", "-m"))
| extend HostName=tostring(split(DeviceName,".")[0]),DnsDomain=iif(DeviceName contains ".", substring(DeviceName, indexof(DeviceName, ".") + 1, strlen(DeviceName)),"")
| project-reorder Timestamp, DeviceId, InitiatingProcessFileName
// Begin environment-specific filter.
// End environment-specific filter.
Stages and Predicates
Parameters
let timeframe = 2*1h;
let SeDebugPrivilege = binary_shift_left(1, 20);
Let binding: LSASSOpen
let LSASSOpen = materialize (
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "OpenProcessApiCall"
| where FileName =~ "lsass.exe"
| extend AccessRights=parse_json(AdditionalFields).DesiredAccess
| where AccessRights == 0x1fffff
| summarize by DeviceId, InitiatingProcessId, InitiatingProcessSHA1
);
Derived from timeframe.
Stage 1: source
DeviceEvents
Stage 2: where
| where ingestion_time() >= ago(timeframe)
Stage 3: where
| where ActionType == "ProcessPrimaryTokenModified"
Stage 4: where
| where isnotempty(InitiatingProcessSHA1)
Stage 5: where
| where InitiatingProcessSHA1 in ((LSASSOpen | project InitiatingProcessSHA1))
Stage 6: kusto:lookup
| lookup kind=inner LSASSOpen on DeviceId, InitiatingProcessSHA1, InitiatingProcessId
Stage 7: extend (5 consecutive steps)
| extend AdditionalFields=parse_json(AdditionalFields)
| extend CurrentTokenPrivEnabled = toint(AdditionalFields.CurrentTokenPrivEnabled)
| extend OriginalTokenPrivEnabled = toint(AdditionalFields.OriginalTokenPrivEnabled)
| extend DebugPrivCurrent = binary_and(CurrentTokenPrivEnabled,SeDebugPrivilege) == SeDebugPrivilege
| extend DebugPrivOrig = binary_and(OriginalTokenPrivEnabled,SeDebugPrivilege) == SeDebugPrivilege
Stage 8: where
| where not(DebugPrivOrig) and DebugPrivCurrent
Stage 9: extend
| extend CleanCmdLine = parse_command_line(InitiatingProcessCommandLine, "windows")
Stage 10: where
| where not(InitiatingProcessFileName =~ "tasklist.exe" and CleanCmdLine has_any ("/m", "-m"))
Stage 11: extend
| extend HostName=tostring(split(DeviceName,".")[0]),DnsDomain=iif(DeviceName contains ".", substring(DeviceName, indexof(DeviceName, ".") + 1, strlen(DeviceName)),"")
Stage 12: project-reorder
| project-reorder Timestamp, DeviceId, InitiatingProcessFileName
Stage 13: summarize
summarize by DeviceId, InitiatingProcessId, InitiatingProcessSHA1
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
CleanCmdLine | match | /m, -m |
InitiatingProcessFileName | eq | tasklist.exe |
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
ActionType | eq |
|
InitiatingProcessSHA1 | is_not_null |
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
DeviceId | summarize |
InitiatingProcessId | summarize |
InitiatingProcessSHA1 | summarize |