← Back to SOC feed Coverage →

DNSPattern [Nobelium]

kql MEDIUM Azure-Sentinel
DeviceEventsDeviceNetworkEventsIdentityQueryEvents
huntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-05-07T23:00:00Z · Confidence: medium

Hunt Hypothesis

The hypothesis is that adversaries are using domain generation algorithms (DGA) associated with the Nobelium campaign to establish command and control channels, leveraging predictable domain patterns to evade detection. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential C2 infrastructure and disrupt ongoing malicious activities.

KQL Query

let cloudApiTerms = dynamic(["api", "east", "west"]);
let timeFrame = ago(1d);
let relevantDeviceNetworkEvents = 
  DeviceNetworkEvents  
  | where Timestamp >= timeFrame
  | where RemoteUrl !has "\\" and RemoteUrl !has "/"
  // performance filter
  | where RemoteUrl has_any(cloudApiTerms)
  | project-rename DomainName = RemoteUrl
  | project Timestamp, DomainName, DeviceId, DeviceName;
let relevantDeviceEvents =   
  DeviceEvents 
  | where Timestamp >= timeFrame
   | where ActionType == "DnsQueryResponse"
   // performance filter
   | where AdditionalFields has_any(cloudApiTerms)
   | extend query = extractjson("$.DnsQueryString", AdditionalFields)  
   | where isnotempty(query)
   | project-rename DomainName = query
   | project Timestamp, DomainName, DeviceId, DeviceName;
let relevantIdentityQueryEvents =
  IdentityQueryEvents 
  | where Timestamp >= timeFrame
  | where ActionType == "DNS query"
  | where Protocol == "Dns"
  // performance filter
  | where QueryTarget has_any(cloudApiTerms)
  | project-rename DomainName = QueryTarget   
  | project Timestamp, DomainName, DeviceId = "", DeviceName;
let relevantData =
  relevantIdentityQueryEvents
  | union
  relevantDeviceNetworkEvents  
  | union
  relevantDeviceEvents;
let tokenCreation =
  relevantData
  | extend domain_split = split(DomainName, ".")
  | where tostring(domain_split[-5]) != "" and tostring(domain_split[-6]) == ""
  | extend sub_domain = tostring(domain_split[0])
  | where sub_domain !contains "-"
  | extend sub_directories = strcat(domain_split[-3], " ", domain_split[-4])
  | where sub_directories has_any(cloudApiTerms);
tokenCreation
  //Based on sample communications the subdomain is always between 20 and 30 bytes
  | where strlen(domain_split) < 32 or strlen(domain_split) > 20
  | extend domain = strcat(tostring(domain_split[-2]), ".", tostring(domain_split[-1])) 
  | extend subdomain_no = countof(sub_domain, @"(\d)", "regex")
  | extend subdomain_ch = countof(sub_domain, @"([a-z])", "regex")
  | where subdomain_no > 1
  | extend percentage_numerical = toreal(subdomain_no) / toreal(strlen(sub_domain)) * 100
  | where percentage_numerical < 50 and percentage_numerical > 5
  | summarize rowcount = count(), make_set(DomainName), make_set(DeviceId), make_set(DeviceName), FirstSeen=min(Timestamp), LastSeen=max(Timestamp) by DomainName
  | order by rowcount asc

Analytic Rule Definition

id: 6c87bdb8-a44e-452a-b782-542640d985e3
name: DNSPattern [Nobelium]
description: |
  This query looks for the DGA pattern of the domain associated with the Nobelium campaign, in order to find other domains with the same activity pattern.
  This query is inspired by an Azure Sentinel detection.
  Reference - https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Hunting%20Queries/DnsEvents/Solorigate-DNS-Pattern.yaml
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
  dataTypes:
  - DeviceNetworkEvents
  - DeviceEvents
  - IdentityQueryEvents
tactics:
- Command and control
tags:
- Nobelium
query: |
  let cloudApiTerms = dynamic(["api", "east", "west"]);
  let timeFrame = ago(1d);
  let relevantDeviceNetworkEvents = 
    DeviceNetworkEvents  
    | where Timestamp >= timeFrame
    | where RemoteUrl !has "\\" and RemoteUrl !has "/"
    // performance filter
    | where RemoteUrl has_any(cloudApiTerms)
    | project-rename DomainName = RemoteUrl
    | project Timestamp, DomainName, DeviceId, DeviceName;
  let relevantDeviceEvents =   
    DeviceEvents 
    | where Timestamp >= timeFrame
     | where ActionType == "DnsQueryResponse"
     // performance filter
     | where AdditionalFields has_any(cloudApiTerms)
     | extend query = extractjson("$.DnsQueryString", AdditionalFields)  
     | where isnotempty(query)
     | project-rename DomainName = query
     | project Timestamp, DomainName, DeviceId, DeviceName;
  let relevantIdentityQueryEvents =
    IdentityQueryEvents 
    | where Timestamp >= timeFrame
    | where ActionType == "DNS query"
    | where Protocol == "Dns"
    // performance filter
    | where QueryTarget has_any(cloudApiTerms)
    | project-rename DomainName = QueryTarget   
    | project Timestamp, DomainName, DeviceId = "", DeviceName;
  let relevantData =
    relevantIdentityQueryEvents
    | union
    relevantDeviceNetworkEvents  
    | union
    relevantDeviceEvents;
  let tokenCreation =
    relevantData
    | extend domain_split = split(DomainName, ".")
    | where tostring(domain_split[-5]) != "" and tostring(domain_split[-6]) == ""
    | extend sub_domain = tostring(domain_split[0])
    | where sub_domain !contains "-"
    | extend sub_directories = strcat(domain_split[-3], " ", domain_split[-4])
    | where sub_directories has_any(cloudApiTerms);
  tokenCreation
    //Based on sample communications the subdomain is always between 20 and 30 bytes
    | where strlen(domain_split) < 32 or strlen(domain_split) > 20
    | extend domain = strcat(tostring(domain_split[-2]), ".", tostring(domain_split[-1])) 
    | extend subdomain_no = countof(sub_domain, @"(\d)", "regex")
    | extend subdomain_ch = countof(sub_domain, @"([a-z])", "regex")
    | where subdomain_no > 1
    | extend percentage_numerical = toreal(subdomain_no) / toreal(strlen(sub_domain)) * 100
    | where percentage_numerical < 50 and percentage_numerical > 5
    | summarize rowcount = count(), make_set(DomainName), make_set(DeviceId), make_s

Required Data Sources

Sentinel TableNotes
DeviceEventsEnsure this data connector is enabled
DeviceNetworkEventsEnsure this data connector is enabled
IdentityQueryEventsEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/Microsoft 365 Defender/Command and Control/DNSPattern [Nobelium].yaml