Deterministic DNS TXT tunnel detection.
It does not guess. It computes.
It does not alert. It explains.
Plumbum computes composite anomaly scores from PCAP and Zeek dns.log files, persists results in SQLite, and produces fully decomposable explanations for every finding. No machine learning. No black boxes. Every score is a deterministic function of six inspectable features.
cargo build --releaseTwo binaries are produced in target/release/:
| Binary | Purpose |
|---|---|
plumbum |
CLI for analysis, scoring, and export |
plumbum-mcp |
MCP server over stdio JSON-RPC |
plumbum init # create .plumbum/ working directory
plumbum validate capture.pcap # check file structure
plumbum plan capture.pcap # dry-run: preview scope
plumbum apply capture.pcap --c2-domains evil.tk # full analysis
plumbum show evil.tk # inspect a domain
plumbum explain evil.tk # score decomposition
plumbum export --format json # export findings
plumbum dashboard # interactive TUI
plumbum stream -i eth0 # live capture + scoringExample output
Plumbum Findings:
CRITICAL 94.4 evil.tk
Artifacts written to .plumbum/plumbum.db (run #1)
Summary: 1 domains scored, 1 CRITICAL, 0 HIGH, 0 MEDIUMDomain: evil.tk
Score: 94.4 (CRITICAL)
Components:
entropy norm=0.000 w=0.1500 contrib=0.000 ( 0.0%)
periodicity norm=1.000 w=0.1000 contrib=0.100 ( 3.7%)
volume norm=1.000 w=0.2500 contrib=0.250 ( 9.3%)
length norm=1.000 w=0.1000 contrib=0.100 ( 3.7%)
client_rarity norm=1.000 w=1.8000 contrib=1.800 (66.7%)
subdomain_diversity norm=1.000 w=0.3000 contrib=0.300 (11.1%)| Command | Description |
|---|---|
init |
Create .plumbum/ directory with config and database |
validate |
Parse inputs and report record counts |
plan |
Dry-run showing what will be analyzed |
apply |
Full analysis: parse, extract, score, persist |
show |
Display a domain's score and raw features |
explain |
Detailed score decomposition with per-feature contributions |
export |
Export as JSON, CSV, or Sigma rule |
dashboard |
Interactive TUI dashboard |
stream |
Live capture DNS from a network interface and score in real time |
version |
Print version |
Plumbum uses a weighted linear model over six normalized features:
| Feature | Signal | Range |
|---|---|---|
| Entropy | Shannon entropy of TXT content | 0 = low, 1 = high |
| Periodicity | Regularity of query timing | 0 = irregular, 1 = clockwork |
| Volume | Query count per parent domain | 0 = quiet, 1 = loudest in corpus |
| Length | Mean TXT response length | 0 = short, 1 = longest in corpus |
| Client Rarity | Inverse of unique source IPs | 1 = single host (most suspicious) |
| Subdomain Diversity | Unique subdomains per parent | 0 = few, 1 = most in corpus |
| Preset | Description |
|---|---|
default |
Balanced equal-ish weights |
optimized |
Tuned via simulated annealing on labeled data |
regularized |
SA-informed with enforced feature diversity (default) |
| Severity | Score |
|---|---|
| CRITICAL | ≥ 80 |
| HIGH | ≥ 60 |
| MEDIUM | ≥ 40 |
| LOW | < 40 |
| Format | Details |
|---|---|
| PCAP | Classic libpcap — both endianness, micro/nanosecond timestamps |
| pcapng | SHB / IDB / EPB blocks |
| Zeek dns.log | Tab-separated with standard #fields headers |
| Link layers | Ethernet (type 1), Linux SLL (type 113) |
plumbum init writes .plumbum/config.hcl:
analysis {
weight_preset = "regularized"
entropy_weight = 0.15
periodicity_weight = 0.10
volume_weight = 0.25
length_weight = 0.10
client_rarity_weight = 1.80
subdomain_diversity_weight = 0.30
}
thresholds {
critical = 80
high = 60
medium = 40
}Plumbum exposes analysis results via the Model Context Protocol over stdio JSON-RPC:
plumbum-mcp| Type | Endpoint | Description |
|---|---|---|
| Resource | plumbum://domains |
Scored domains from the latest run |
| Resource | plumbum://status |
Analysis state and run summary |
| Tool | plumbum_explain |
Score decomposition for a domain |
| Tool | plumbum_query |
Query scored domains with filters |
┌─────────────────────────────────────────────────────────┐
│ plumbum-cli │
│ init validate plan apply stream │
│ show explain export dashboard │
├────────────┬────────────┬───────────────┬───────────────┤
│plumbum-tui │plumbum-mcp │plumbum-config │plumbum-stream │
│ ratatui │ stdio rpc │ HCL parser │ live capture │
├────────────┴────────────┴───────────────┴───────────────┤
│ plumbum-store │ plumbum-score │
│ SQLite · WAL · batch ingest │ weights │
│ schema · queries · artifacts │ normalize │
│ │ composite │
│ │ explain │
├─────────────────────────────────────────┴───────────────┤
│ plumbum-core │
│ dns types · features · pcap · zeek │
└─────────────────────────────────────────────────────────┘
| Crate | Role |
|---|---|
plumbum-core |
DNS types, PCAP/pcapng + Zeek parsers, feature extraction |
plumbum-score |
Composite scoring, weight presets, normalization, explain |
plumbum-store |
SQLite schema, batch ingest, prepared queries, export artifacts |
plumbum-config |
HCL config parser, types, defaults |
plumbum-cli |
CLI binary (plumbum) |
plumbum-tui |
Interactive dashboard with ratatui |
plumbum-mcp |
MCP server binary (plumbum-mcp) |
plumbum-stream |
Live network capture, sliding window accumulator, real-time scoring |
Capture DNS traffic directly from a network interface and score domains in real time:
# Stream from eth0, 60s windows, alert on score >= 40
sudo plumbum stream -i eth0 --window 60 --threshold 40
# List available interfaces
plumbum stream --list-interfaces
# Pipe to jq for filtering
sudo plumbum stream -i any | jq 'select(.severity == "CRITICAL")'Output is newline-delimited JSON — one alert per domain per window:
{"domain":"evil.tk","score":94.4,"severity":"CRITICAL","query_count":1847,"client_count":1,"subdomain_count":1234,"mean_entropy":4.21,"cv":1.80,"mean_txt_length":189.0,"window_secs":60.0,"is_c2":true}Requires:
libpcap-dev(Linux), Npcap (Windows), built-in on macOS.
Privileges: root orCAP_NET_RAWfor raw socket access.
This project is licensed under the MIT License.