Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Detect DNS tunneling and data exfiltration by analyzing Zeek dns.log for high-entropy subdomain queries, excessive query volume, long query lengths, and unusual DNS record types indicating covert channel communication.
.claude/skills/hunting-for-dns-tunneling-with-zeek/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | — | — |
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| Concept | Description | |---------|-------------| | T1071.004 | Application Layer Protocol: DNS | | T1048.003 | Exfiltration Over Alternative Protocol: DNS | | T1572 | Protocol Tunneling | | Shannon Entropy | Measure of randomness in subdomain strings | | Zeek dns.log | DNS query/response metadata | | RITA | Automated DNS tunneling detection from Zeek logs | | iodine | IPv4-over-DNS tunneling tool | | dnscat2 | DNS-based command-and-control tool | | DNSExfiltrator | Data exfiltration tool using DNS requests |
zeek@load base/protocols/dns module DNSTunnel; export { redef enum Notice::Type += { DNSTunnel::Long_DNS_Query }; const query_length_threshold = 50 &redef; const query_count_threshold = 100 &redef; } event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count) { if ( |query| > query_length_threshold ) { NOTICE([$note=DNSTunnel::Long_DNS_Query, $msg=fmt("Long DNS query detected: %s (%d chars)", query, |query|), $conn=c]); } }
splindex=zeek sourcetype=bro_dns | rex field=query "(?<subdomain>[^.]+)\.(?<basedomain>[^.]+\.[^.]+)$" | stats count dc(subdomain) as unique_subs avg(len(query)) as avg_len max(len(query)) as max_len by src basedomain | where count > 100 AND (unique_subs > 50 OR avg_len > 40) | sort -unique_subs
splindex=zeek sourcetype=bro_dns | rex field=query "^(?<subdomain>[^.]+)" | where len(subdomain) > 20 | eval char_count=len(subdomain) | stats count dc(query) as unique_queries avg(char_count) as avg_sub_len by src query_type_name basedomain | where unique_queries > 30 AND avg_sub_len > 25 | sort -unique_queries
bashrita import /path/to/zeek/logs dataset_name rita show-dns-fqdn-ips-long dataset_name rita show-exploded-dns dataset_name rita show-dns-tunneling dataset_name --csv > dns_tunnel_results.csv
aGVsbG8gd29ybGQ.exfil.attacker.com), sent as A or TXT queries. Each query carries ~63 bytes of data.Hunt ID: TH-DNSTUNNEL-[DATE]-[SEQ]
Source IP: [Internal IP]
Source Host: [Hostname]
Target Domain: [Base domain]
Query Count: [Total queries in window]
Unique Subdomains: [Count]
Avg Query Length: [Characters]
Max Query Length: [Characters]
Subdomain Entropy: [Bits per character]
Primary Record Type: [A/TXT/CNAME/NULL]
Data Volume Estimate: [Bytes exfiltrated]
Risk Level: [Critical/High/Medium/Low]| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-24 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted, and 23 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +38 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.