Detection rules › Kusto
Registry Run Keys - Suspicious Registry Run Keys
Below query looks for suspicious additions to Run, RunOnce and several other registry keys. The query analyzes all values in the specified registry keys and finds anomalous ones based on commonality in the environment and excludes possible legitimate activities like software installations. The query might require tuning according to the environment.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | T1547.001 Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder |
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Sysmon | Event ID 13 | RegistryEvent (Value Set) |
| Security-Auditing | Event ID 4657 | A registry value was modified. |
| Defender-DeviceRegistryEvents | RegistryValueSet | Registry value set |
Rule body kusto
// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
// Link to original post: https://mergene.medium.com/threat-hunting-with-data-science-registry-run-keys-9ae329d1ad85
// Description: This query looks for suspicious additions to Run, RunOnce and several other registry keys.
// The query analyzes all values in the specified registry keys and finds anomalous ones based on
// commonality in the environment and excludes possible legitimate activities like software installations.
// The query might requiure tuning according to the environment.
let dataset= materialize (
DeviceRegistryEvents
| where ActionType == "RegistryValueSet"
// registry keys to be monitored
| where RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnce"
or RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnceEx"
or RegistryKey has @"Microsoft\Windows\CurrentVersion\Run"
or RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
or (RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders")
or (RegistryKey has @"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" and RegistryValueName == "Run")
or (RegistryKey has @"\Session Manager" and RegistryValueName == "BootExecute")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Userinit")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Shell")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Windows" and RegistryValueName == "load")
or RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon\Notify"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServices"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run"
// below is related to the persistence using CLSID (junction folders, etc.)
or (RegistryKey has @"SOFTWARE\Classes\CLSID" and RegistryKey endswith "InprocServer32" and isempty(RegistryValueName))
// exclude Config.Msi folder items
| where RegistryValueData !endswith @".rbf /restore"
// create NormalizedRegistryValueData field
| extend NormalizedRegistryValueData = replace(@'(C|D):\\Users\\.*?\\', @'C:\\Users\\userxx\\',RegistryValueData )
| extend NormalizedRegistryValueData = replace(@'\{.*\}', @'\{xxxxxxxxxx\}',NormalizedRegistryValueData ) //{fe07d7-d438-4dd9-bb0f-5721658f4f}
| extend NormalizedRegistryValueData = replace(@'\\[A-Za-z0-9-]+-[A-Za-z0-9]+\\', @'\\xxxxxxxxxx\\',NormalizedRegistryValueData ) //\fe07d7-d438-4dd9-bb0f-5721658f4f\
| extend NormalizedRegistryValueData = replace(@'\d+\.\d+\.\d+\.\d+', @'X.Y.Z.T',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'-\d+\.\d+\.\d+', @'-X.Y.Z',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'_\d+\.log', @'_XYZT.log',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'--quiet|--passive', @'',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'installSessionId\s[A-Za-z0-9-]+', @'installSessionId xxxxxx',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'C:\\ProgramData\\.*?\\Microsoft\\Teams\\',@'C:\\ProgramData\\userxxx\\Microsoft\\Teams\\',NormalizedRegistryValueData)
);
dataset
| summarize dcount_device = dcount(DeviceId), total_count = count() by NormalizedRegistryValueData
| where dcount_device <=5 and total_count <20
| join kind=inner (dataset| where Timestamp > ago(1d)) on NormalizedRegistryValueData
| invoke FileProfile(InitiatingProcessSHA1,1000)
| where GlobalPrevalence <100
or isempty(GlobalPrevalence)
// inlcude processes that are involved in malicious attacks(e.g. office macro creating the registry key)
or InitiatingProcessFileName in~ ("powershell.exe","reg.exe", "regedit.exe", "cmd.exe","winword.exe","excel.exe","powerpnt.exe")
Stages and Predicates
Stage 0: let
let dataset = materialize(<inlined as stages below>);
The stages below define let dataset (the rule's main pipeline source).
Stage 1: source
DeviceRegistryEvents
Stage 2: where
| where ActionType == "RegistryValueSet"
Stage 3: where
| where RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnce"
or RegistryKey has @"Microsoft\Windows\CurrentVersion\RunOnceEx"
or RegistryKey has @"Microsoft\Windows\CurrentVersion\Run"
or RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
or (RegistryKey has @"\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders")
or (RegistryKey has @"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" and RegistryValueName == "Run")
or (RegistryKey has @"\Session Manager" and RegistryValueName == "BootExecute")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Userinit")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName == "Shell")
or (RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Windows" and RegistryValueName == "load")
or RegistryKey has @"Microsoft\Windows NT\CurrentVersion\Winlogon\Notify"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServices"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce"
or RegistryKey has @"\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run"
or (RegistryKey has @"SOFTWARE\Classes\CLSID" and RegistryKey endswith "InprocServer32" and isempty(RegistryValueName))
Stage 4: where
| where RegistryValueData !endswith @".rbf /restore"
Stage 5: extend (9 consecutive steps)
| extend NormalizedRegistryValueData = replace(@'(C|D):\\Users\\.*?\\', @'C:\\Users\\userxx\\',RegistryValueData )
| extend NormalizedRegistryValueData = replace(@'\{.*\}', @'\{xxxxxxxxxx\}',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'\\[A-Za-z0-9-]+-[A-Za-z0-9]+\\', @'\\xxxxxxxxxx\\',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'\d+\.\d+\.\d+\.\d+', @'X.Y.Z.T',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'-\d+\.\d+\.\d+', @'-X.Y.Z',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'_\d+\.log', @'_XYZT.log',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'--quiet|--passive', @'',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'installSessionId\s[A-Za-z0-9-]+', @'installSessionId xxxxxx',NormalizedRegistryValueData )
| extend NormalizedRegistryValueData = replace(@'C:\\ProgramData\\.*?\\Microsoft\\Teams\\',@'C:\\ProgramData\\userxxx\\Microsoft\\Teams\\',NormalizedRegistryValueData)
The stages below run on dataset (the outer pipeline).
Stage 6: summarize
dataset
| summarize dcount_device = dcount(DeviceId), total_count = count() by NormalizedRegistryValueData
Stage 7: where
| where dcount_device <=5 and total_count <20
Stage 8: join
| join kind=inner (dataset| where Timestamp > ago(1d)) on NormalizedRegistryValueData
Stage 9: invoke
| invoke FileProfile(InitiatingProcessSHA1,1000)
Stage 10: where
| where GlobalPrevalence <100
or isempty(GlobalPrevalence)
or InitiatingProcessFileName in~ ("powershell.exe","reg.exe", "regedit.exe", "cmd.exe","winword.exe","excel.exe","powerpnt.exe")
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
RegistryValueData | ends_with | .rbf /restore |
RegistryValueData | ends_with | .rbf /restore |
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 |
|
GlobalPrevalence | is_null | |
GlobalPrevalence | lt |
|
InitiatingProcessFileName | in |
|
RegistryKey | ends_with |
|
RegistryKey | match |
|
RegistryValueName | eq |
|
RegistryValueName | is_null | |
dcount_device | le |
|
total_count | lt |
|
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 |
|---|---|
NormalizedRegistryValueData | summarize |
dcount_device | summarize |
total_count | summarize |