Detect command-and-control (C2) traffic tunneled over DNS from tools like Iodine, dnscat2, dns2tcp, and Cobalt Strike DNS beacon, using Shannon entropy analysis of query subdomains, ML-based DGA classification, passive DNS correlation, and Zeek/Suricata signatures. Use when investigating suspected DNS tunneling, classifying DGA domains, detecting DNS beaconing, or building DNS anomaly rules for a SOC/SIEM.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Detect command-and-control (C2) traffic tunneled over DNS from tools like Iodine, dnscat2, dns2tcp, and Cobalt Strike DNS beacon, using Shannon entropy analysis of query subdomains, ML-based DGA classification, passive DNS correlation, and Zeek/Suricata signatures. Use when investigating suspected DNS tunneling, classifying DGA domains, detecting DNS beaconing, or building DNS anomaly rules for a SOC/SIEM.
Investigating suspected DNS tunneling used for C2 communication or data exfiltration
Analyzing DNS query logs for signs of encoded payloads in subdomain strings
Classifying domains as DGA-generated vs. legitimate using statistical or ML methods
Detecting DNS beaconing patterns (regular intervals, consistent query sizes)
Hunting for Iodine, dnscat2, dns2tcp, Cobalt Strike DNS, or Sliver DNS traffic
Monitoring TXT record abuse for command delivery or staged payload download
Building DNS anomaly detection rules for SOC/SIEM deployment
Do not use for general DNS performance monitoring or DNS configuration auditing; use DNS health monitoring tools for those. For HTTP/HTTPS-based C2 detection, use network traffic analysis skills focused on web protocols.
DISCLAIMER: DNS tunneling tools referenced in this skill (Iodine, dnscat2, dns2tcp) are dual-use. They have legitimate uses (bypassing captive portals, security research) and malicious uses (C2 channels, exfiltration). Only deploy detection in networks you are authorized to monitor. Testing tunneling tools requires explicit authorization.
Prerequisites
DNS query logs from recursive resolver, Zeek/Bro, Suricata, or passive DNS tap
Python 3.9+ with numpy, scikit-learn, pandas, tldextract, and dnspython
Zeek (formerly Bro) with dns.log output or Suricata with DNS EVE JSON logging
SIEM access (Splunk, Elastic, Microsoft Sentinel) for log correlation
Passive DNS database access (CIRCL pDNS, Farsight DNSDB, or internal) for enrichment
Wireshark/tshark for packet-level DNS inspection
Known-good domain whitelist (Alexa/Tranco top 1M or Majestic Million)
Workflow
Step 1: Collect and Parse DNS Query Logs
Ingest DNS traffic from network sensors and parse into analyzable format:
Splunk SPL - DNS Tunneling Detection:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-- High entropy subdomain queries
index=dns sourcetype="bro:dns:json" OR sourcetype="suricata:dns"
| eval subdomain=mvindex(split(query,"."),0)
| eval sub_len=len(subdomain)
| where sub_len > 30
| eval char_counts=mvmap(split(subdomain,""),1)
| lookup dns_entropy_lookup subdomain OUTPUT entropy
| where entropy > 3.5
| stats count as query_count dc(query) as unique_queries
avg(sub_len) as avg_sub_len values(query) as sample_queries
by src_ip, domain
| where query_count > 20
| sort -query_count
-- DNS TXT record abuse
index=dns (qtype="TXT" OR qtype_name="TXT")
NOT (query="*._domainkey.*" OR query="*._dmarc.*" OR query="*._spf.*")
| stats count as txt_queries dc(query) as unique_txt_queries
values(query) as domains
by src_ip
| where txt_queries > 50
| sort -txt_queries
-- DNS beaconing (regular interval queries)
index=dns sourcetype="bro:dns:json"
| bin _time span=60s
| stats count by src_ip, query, _time
| streamstats window=10 current=t avg(count) as avg_count stdev(count) as std_count by src_ip, query
| eval cv = if(avg_count>0, (std_count/avg_count)*100, 100)
| where cv < 20 AND avg_count > 0
| stats count as beacon_windows avg(cv) as avg_jitter
min(_time) as first_seen max(_time) as last_seen
by src_ip, query
| where beacon_windows > 10
| sort -beacon_windows
-- Unusual record type volume (NULL, KEY, SRV for tunneling)
index=dns (qtype_name="NULL" OR qtype_name="KEY" OR qtype_name="SRV"
OR qtype_name="CNAME" OR qtype_name="MX")
NOT qtype_name="A" NOT qtype_name="AAAA" NOT qtype_name="PTR"
| stats count by src_ip, qtype_name, query
| where count > 10
| sort -count
Elastic KQL - DNS C2 Detection:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-- Long subdomain queries (potential tunneling)
dns.question.name: * and not dns.question.name: *.in-addr.arpa
| where length(dns.question.subdomain) > 40
-- High volume DNS to single domain
event.dataset: "zeek.dns" or event.dataset: "suricata.dns"
| stats count by source.ip, dns.question.registered_domain
| where count > 500
-- TXT record queries to non-standard domains
dns.question.type: "TXT"
and not dns.question.name: (*._domainkey.* or *._dmarc.* or *._spf.*)
Zeek Script - DNS Tunneling Indicator:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# dns_tunnel_detect.zeek
@load base/protocols/dns
module DNSTunnel;
export {
redef enum Notice::Type += {
DNS_Tunneling_Suspected,
DNS_High_Entropy_Query,
DNS_Excessive_TXT_Queries,
};
const entropy_threshold = 3.5 &redef;
const subdomain_length_threshold = 40 &redef;
const txt_query_threshold = 50 &redef;
const tracking_interval = 5min &redef;
}
global txt_query_tracker: table[addr] of count &create_expire=5min &default=0;
global domain_query_tracker: table[addr, string] of count &create_expire=10min &default=0;
function shannon_entropy(s: string): double
{
local counts: table[string] of count;
local total = |s|;
if (total == 0) return 0.0;
for (i in s)
{
local c = s[i];
if (c !in counts) counts[c] = 0;
++counts[c];
}
local ent = 0.0;
for (ch, cnt in counts)
{
local p = cnt * 1.0 / total;
ent -= p * log2(p);
}
return ent;
}
event dns_request(c: connection, msg: dns_msg, query: string, qtype: count,
qclass: count)
{
if (|query| == 0) return;
# Track TXT queries
if (qtype == 16) # TXT
{
++txt_query_tracker[c$id$orig_h];
if (txt_query_tracker[c$id$orig_h] == txt_query_threshold)
{
NOTICE([
$note=DNS_Excessive_TXT_Queries,
$conn=c,
$msg=fmt("Host %s made %d TXT queries in tracking window",
c$id$orig_h, txt_query_threshold),
$identifier=cat(c$id$orig_h),
]);
}
}
# Extract subdomain and check entropy
local parts = split_string(query, /\./);
if (|parts| < 3) return;
# Subdomain = everything except last two labels
local subdomain = "";
local i = 0;
for (idx in parts)
{
if (i < |parts| - 2)
subdomain += parts[idx];
++i;
}
if (|subdomain| > subdomain_length_threshold)
{
local ent = shannon_entropy(subdomain);
if (ent > entropy_threshold)
{
NOTICE([
$note=DNS_High_Entropy_Query,
$conn=c,
$msg=fmt("High entropy DNS query: entropy=%.2f len=%d query=%s",
ent, |subdomain|, query),
$identifier=cat(c$id$orig_h, query),
]);
}
}
}
Step 8: Suricata Rules for Known DNS C2 Tools
# suricata-dns-c2.rules
# DNS Tunneling and C2 Detection Rules
# Iodine DNS tunnel detection
alert dns any any -> any any (msg:"ET TROJAN Iodine DNS Tunnel Activity - NULL Query"; \
dns.query; content:"."; pcre:"/^[a-z0-9]{50,}\.[a-z0-9.-]+$/i"; \
dns_query; content:"|00 0a|"; \
classtype:trojan-activity; sid:2030001; rev:1;)
# dnscat2 DNS tunnel detection
alert dns any any -> any any (msg:"ET TROJAN dnscat2 DNS Tunnel - Handshake"; \
dns.query; content:"dnscat."; nocase; fast_pattern; \
classtype:trojan-activity; sid:2030002; rev:1;)
alert dns any any -> any any (msg:"ET TROJAN dnscat2 DNS Tunnel - Data Channel"; \
dns.query; pcre:"/^[a-f0-9]{16,}\./i"; \
dns_query; content:"|00 10|"; \
classtype:trojan-activity; sid:2030003; rev:1;)
# Cobalt Strike DNS beacon
alert dns any any -> any any (msg:"ET TROJAN Cobalt Strike DNS Beacon - A Record"; \
dns.query; pcre:"/^[a-f0-9]{12,}\.[a-z0-9.-]+$/i"; \
threshold:type both, track by_src, count 20, seconds 60; \
classtype:trojan-activity; sid:2030004; rev:1;)
# Generic DNS tunneling - high volume TXT queries to single domain
alert dns any any -> any any (msg:"ET POLICY Excessive TXT DNS Queries - Possible Tunneling"; \
dns_query; content:"|00 10|"; \
threshold:type threshold, track by_src, count 50, seconds 300; \
classtype:policy-violation; sid:2030005; rev:1;)
# Long subdomain query (generic tunneling indicator)
alert dns any any -> any any (msg:"ET POLICY Unusually Long DNS Subdomain - Possible Tunneling"; \
dns.query; pcre:"/^[a-z0-9-]{52,}\./i"; \
threshold:type limit, track by_src, count 1, seconds 60; \
classtype:policy-violation; sid:2030006; rev:1;)
# DNS query for known C2 TXT payload staging
alert dns any any -> any any (msg:"ET TROJAN DNS TXT Record Staged Payload Request"; \
dns_query; content:"|00 10|"; \
dns.query; pcre:"/^(stage|payload|cmd|exec|download|update|config)\d*\./i"; \
classtype:trojan-activity; sid:2030007; rev:1;)
Key Concepts
Term
Definition
DNS Tunneling
Technique of encoding data within DNS queries and responses to create a covert communication channel, bypassing firewalls that allow DNS traffic
Shannon Entropy
Information theory metric measuring randomness in a string; legitimate domains typically have entropy below 3.5, while encoded tunnel data exceeds 3.8-4.5
Domain Generation Algorithm (DGA)
Malware technique that algorithmically generates thousands of pseudo-random domain names for C2 rendezvous, making domain-based blocking impractical
DNS Beaconing
Regular, periodic DNS queries from a compromised host to a C2 domain, identifiable by consistent inter-query intervals and low timing jitter
TXT Record Abuse
Using DNS TXT records to deliver encoded C2 commands or staged payloads, exploiting the large payload capacity (up to 65535 bytes across multiple strings)
Iodine
Open-source DNS tunneling tool that tunnels IPv4 traffic through DNS using NULL, TXT, or CNAME records, commonly used to bypass captive portals
dnscat2
Encrypted C2 tool that creates a command channel over DNS, supporting file transfer, port forwarding, and shell access through DNS queries
Cobalt Strike DNS Beacon
Commercial C2 framework's DNS communication mode that uses A, AAAA, and TXT records to receive tasks and return results via DNS resolution
Passive DNS (pDNS)
Database of historical DNS resolution data collected by monitoring DNS traffic; used to identify infrastructure reuse and domain history
Response Policy Zone (RPZ)
DNS firewall mechanism that allows real-time blocking of malicious domains by injecting override responses at the recursive resolver level
Coefficient of Variation
Standard deviation divided by mean, expressed as percentage; used to measure beacon jitter -- lower CV indicates more regular (suspicious) timing
NXDOMAIN
DNS response code indicating the queried domain does not exist; high NXDOMAIN rates from a host suggest DGA activity where most generated domains are unregistered
Tools & Systems
Zeek (Bro): Network security monitor that produces structured dns.log with query/response details for offline analysis
Suricata: IDS/IPS with DNS protocol parsing and signature-based detection of tunneling patterns
tshark/Wireshark: Packet capture and analysis tools for deep DNS protocol inspection
tldextract: Python library for accurate domain/subdomain extraction using the Public Suffix List
dnspython: Python DNS toolkit for programmatic query resolution and record parsing
scikit-learn: ML library used to train DGA classifiers (Random Forest, Gradient Boosting)
Farsight DNSDB / CIRCL pDNS: Passive DNS databases for historical domain resolution lookups
DNS Response Policy Zone (RPZ): Recursive resolver feature for real-time DNS blocking of identified C2 domains
Splunk / Elastic: SIEM platforms for DNS log aggregation, entropy calculation, and beacon detection queries
Common Scenarios
Scenario: Investigating Suspected DNS Tunneling from an Internal Host
Context: The SOC receives an alert from the DNS firewall showing a single internal host (10.1.5.42) making 15,000+ DNS queries to the domain c8a3f1e2.tunnelsvc.example.com in the past hour. All queries are TXT type with long, random-looking subdomains. Normal DNS volume for this host is ~200 queries/hour.
Approach:
Extract all DNS queries from 10.1.5.42 for the past 24 hours from Zeek dns.log
Run entropy analysis on subdomain strings -- expect Shannon entropy > 4.0 for encoded tunnel data
Check query timing intervals for beaconing pattern (likely sub-second for active tunnel)
Examine TXT record responses for size anomalies (tunnel tools use maximum-size TXT responses)
Compare subdomain patterns against known tool signatures (Iodine, dnscat2, dns2tcp)
Query passive DNS for tunnelsvc.example.com registration date, nameserver, and historical resolutions
If confirmed, add domain to DNS RPZ blocklist and isolate endpoint via EDR
Capture full packet trace for forensic analysis of tunnel payload content
Pitfalls:
Blocking the domain before capturing evidence (need packet captures for forensics)
Assuming all high-entropy DNS is malicious (CDN subdomains like Akamai can have high entropy)
Not checking for multiple tunnel domains (attacker may have fallback C2 channels)
Missing the initial compromise vector by focusing only on the DNS channel
Not checking other hosts for similar patterns (lateral movement may have already occurred)
Scenario: Building a DGA Detection Model for SOC Deployment
Context: The threat intelligence team identified that a botnet family active in the industry uses DGA for C2 domain generation. The SOC needs an automated way to classify DNS queries as potentially DGA-generated and alert on matches.
Approach:
Collect training data: Tranco/Alexa top 1M for legitimate domains, DGArchive or OSINT feeds for known DGA domains