Detection rules › Kusto

Registry Run Keys - Suspicious Registry Run Keys

Group by
NormalizedRegistryValueData
Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

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

Event coverage

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
Threshold
le 5

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.

FieldKindExcluded values
RegistryValueDataends_with.rbf /restore
RegistryValueDataends_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.

FieldKindValues
ActionTypeeq
  • RegistryValueSet transforms: cased corpus 4 (kusto 4)
GlobalPrevalenceis_null
  • (no value, null check)
GlobalPrevalencelt
  • 100 transforms: cased corpus 4 (kusto 4)
InitiatingProcessFileNamein
  • cmd.exe corpus 15 (elastic 10, splunk 4, kusto 1)
  • excel.exe corpus 8 (elastic 8)
  • powerpnt.exe corpus 7 (elastic 7)
  • powershell.exe corpus 15 (elastic 12, kusto 2, splunk 1)
  • reg.exe
  • regedit.exe
  • winword.exe corpus 8 (elastic 8)
RegistryKeyends_with
  • InprocServer32
RegistryKeymatch
  • Microsoft\Windows NT\CurrentVersion\Windows transforms: term
  • Microsoft\Windows NT\CurrentVersion\Winlogon transforms: term
  • Microsoft\Windows NT\CurrentVersion\Winlogon\Notify transforms: term
  • Microsoft\Windows\CurrentVersion\Run transforms: term
  • Microsoft\Windows\CurrentVersion\RunOnce transforms: term
  • Microsoft\Windows\CurrentVersion\RunOnceEx transforms: term
  • SOFTWARE\Classes\CLSID transforms: term
  • Software\Microsoft\Windows\CurrentVersion\Policies\Explorer transforms: term
  • \Microsoft\Windows\CurrentVersion\Explorer\Shell Folders transforms: term
  • \Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders transforms: term
  • \Session Manager transforms: term
  • \Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run transforms: term corpus 4 (sigma 3, kusto 1)
  • \Software\Microsoft\Windows\CurrentVersion\RunServices transforms: term
  • \Software\Microsoft\Windows\CurrentVersion\RunServicesOnce transforms: term
RegistryValueNameeq
  • BootExecute transforms: cased
  • Run transforms: cased
  • Shell transforms: cased
  • Userinit transforms: cased
  • load transforms: cased
RegistryValueNameis_null
  • (no value, null check)
dcount_devicele
  • 5 transforms: cased
total_countlt
  • 20 transforms: cased

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.

FieldSource
NormalizedRegistryValueDatasummarize
dcount_devicesummarize
total_countsummarize