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 |
Telemetry coverage
Rule body
// 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
let dataset is inlined into the numbered stages below.
Stage 1: source
let dataset
Stage 2: source
DeviceRegistryEvents
Stage 3: where
where ActionType =~ "RegistryValueSet"
Stage 4: where
where ((RegistryKey endswith "InprocServer32" and RegistryKey contains @"SOFTWARE\Classes\CLSID" and isempty(RegistryValueName)) or (RegistryKey contains @"Microsoft\Windows NT\CurrentVersion\Windows" and RegistryValueName =~ "load") or (RegistryKey contains @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName =~ "Shell") or (RegistryKey contains @"Microsoft\Windows NT\CurrentVersion\Winlogon" and RegistryValueName =~ "Userinit") or (RegistryKey contains @"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" and RegistryValueName =~ "Run") or (RegistryKey contains @"\Session Manager" and RegistryValueName =~ "BootExecute") or RegistryKey contains @"Microsoft\Windows NT\CurrentVersion\Winlogon\Notify" or RegistryKey contains @"Microsoft\Windows\CurrentVersion\Run" or RegistryKey contains @"Microsoft\Windows\CurrentVersion\RunOnce" or RegistryKey contains @"Microsoft\Windows\CurrentVersion\RunOnceEx" or RegistryKey contains @"\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" or RegistryKey contains @"\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" or RegistryKey contains @"\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run" or RegistryKey contains @"\Software\Microsoft\Windows\CurrentVersion\RunServices" or RegistryKey contains @"\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce")
Stage 5: where
where not (RegistryValueData endswith ".rbf /restore")
Stage 6: extend (9 consecutive steps)
extend NormalizedRegistryValueData
Stage 7: summarize
summarize dcount_device, total_count by NormalizedRegistryValueData
Stage 8: where
where dcount_device <= 5 and total_count < 20
Stage 9: join
join kind=inner (dataset) on NormalizedRegistryValueData
Stage 10: invoke
invoke
Stage 11: where
where (isempty(GlobalPrevalence) or GlobalPrevalence < 100 or InitiatingProcessFileName in~ ("cmd.exe", "excel.exe", "powerpnt.exe", "powershell.exe", "reg.exe", "regedit.exe", "winword.exe"))
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RegistryValueData | ends_with | .rbf /restore | excludes:RegistryValueData field:"RegistryValueData" value:".rbf /restore" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | eq |
| field:"EventType" kind:eq value:"RegistryValueSet" |
GlobalPrevalence | is_null | field:"GlobalPrevalence" kind:is_null | |
GlobalPrevalence | lt |
| field:"GlobalPrevalence" kind:lt value:"100" |
InitiatingProcessFileName | in |
| field:"parent_process_name" kind:in |
RegistryKey | ends_with |
| field:"TargetObject" kind:ends_with value:"InprocServer32" |
RegistryKey | match |
| field:"TargetObject" kind:match |
RegistryValueName | eq |
| field:"RegistryValueName" kind:eq |
RegistryValueName | is_null | field:"RegistryValueName" kind:is_null | |
dcount_device | le |
| field:"dcount_device" kind:le value:"5" |
total_count | lt |
| field:"total_count" kind:lt value:"20" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
NormalizedRegistryValueData | summarize |
dcount_device | summarize |
total_count | summarize |