import customtkinter as ctk from tkinter import filedialog, messagebox import os import threading import subprocess import urllib.request import urllib.error import time import datetime import glob import hashlib import string import secrets import json import platform import shutil import base64 from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler # --- Imports pour le chiffrement AES-256 (Vault) --- from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC # ========================================== # CONFIGURATION GLOBALE # ========================================== UPDATE_URL = "https://git.7ka1.com/7ka1/7LnA_Antivirus_Linux_Free_ClamAV_Based/raw/branch/main/7lna.py" QUARANTINE_DIR = os.path.expanduser("~/.7lna_quarantine") WATCH_FOLDER = os.path.expanduser("~/Téléchargements") HISTORY_FILE = os.path.expanduser("~/.7lna_history.json") VAULT_FILE = os.path.expanduser("~/.7lna_vault.enc") # Fichier chiffré du coffre-fort ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") class RealTimeShieldHandler(FileSystemEventHandler): def __init__(self, app_instance): self.app = app_instance def on_created(self, event): if not event.is_directory: time.sleep(1.5) self.app.trigger_realtime_scan(event.src_path) # ========================================== # APPLICATION PRINCIPALE # ========================================== class Antivirus7LnA(ctk.CTk): def __init__(self): super().__init__() self.title("7LnA Security Suite - V10.3 Quantum Edition") self.geometry("1250x850") self.minsize(1000, 700) os.makedirs(QUARANTINE_DIR, exist_ok=True) if not os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, "w") as f: json.dump([], f) self.shield_observer = None self.shield_active = False self.zero_usb_mode = False self.gaming_mode = False # <--- État du mode Gaming/Discret self.check_dependencies() self.setup_ui() threading.Thread(target=self.monitor_usb_drives, daemon=True).start() # --- UTILITAIRES SYSTÈME (Intégré à la classe pour gérer le Gaming Mode) --- def send_desktop_notification(self, title, message, is_critical=False): # Si le mode Gaming est actif et que l'alerte n'est pas critique, on bloque la notification if self.gaming_mode and not is_critical: return try: urgency = 'critical' if is_critical else 'normal' subprocess.Popen(['notify-send', '-u', urgency, '-a', '7LnA Security', title, message]) except Exception: pass def check_dependencies(self): try: subprocess.run(['clamscan', '--version'], capture_output=True, check=True) self.clamav_installed = True except: self.clamav_installed = False def setup_ui(self): self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) # --- BARRE LATÉRALE --- self.sidebar = ctk.CTkFrame(self, width=260, corner_radius=0, fg_color="#111827") self.sidebar.grid(row=0, column=0, sticky="nsew") self.sidebar.grid_rowconfigure(11, weight=1) ctk.CTkLabel(self.sidebar, text="🛡️ 7LnA Sec", font=ctk.CTkFont(size=30, weight="bold"), text_color="#3B82F6").grid(row=0, column=0, padx=20, pady=(30, 20)) self.btn_dash = self.create_nav_button("📊 Tableau de Bord", 1, "dashboard") self.btn_scan = self.create_nav_button("🔍 Scanner Manuel", 2, "scanner") self.btn_shield = self.create_nav_button("⚡ Bouclier Actif", 3, "shield") self.btn_audit = self.create_nav_button("⚙️ Audit Réseau", 4, "audit") self.btn_tools = self.create_nav_button("🧰 Outils Avancés", 5, "tools") self.btn_vault = self.create_nav_button("🔐 Coffre-fort (AES)", 6, "vault") # <--- AJOUT BOUTON VAULT self.btn_quarantine = self.create_nav_button("📦 Quarantaine", 7, "quarantine") self.btn_schedule = self.create_nav_button("📅 Planification", 8, "schedule") self.btn_history = self.create_nav_button("📜 Rapports", 9, "history") self.btn_update = self.create_nav_button("🔄 Mise à jour", 10, "update") self.version_label = ctk.CTkLabel(self.sidebar, text="v10.3 - Quantum", text_color="#6B7280", font=ctk.CTkFont(weight="bold")) self.version_label.grid(row=11, column=0, pady=20, sticky="s") self.views = {} self.init_dashboard_view() self.init_scanner_view() self.init_realtime_view() self.init_audit_view() self.init_tools_view() self.init_vault_view() # <--- INIT VAULT VIEW self.init_quarantine_view() self.init_schedule_view() self.init_history_view() self.init_update_view() self.select_view("dashboard") def create_nav_button(self, text, row, view_name): btn = ctk.CTkButton(self.sidebar, text=text, anchor="w", fg_color="transparent", text_color="#D1D5DB", hover_color="#1F2937", font=ctk.CTkFont(size=15), height=40, command=lambda: self.select_view(view_name)) btn.grid(row=row, column=0, padx=20, pady=5, sticky="ew") return btn def select_view(self, view_name): for view in self.views.values(): view.grid_forget() if view_name in self.views: self.views[view_name].grid(row=0, column=1, sticky="nsew", padx=30, pady=30) if view_name == "quarantine": self.refresh_quarantine_list() if view_name == "history": self.refresh_history_list() def setup_console_tags(self, console): console.tag_config("danger", foreground="#EF4444") console.tag_config("success", foreground="#10B981") console.tag_config("warning", foreground="#F59E0B") console.tag_config("info", foreground="#3B82F6") def get_time_prefix(self): return datetime.datetime.now().strftime("[%H:%M:%S] ") def log_threat_to_history(self, filepath, threat_name): try: with open(HISTORY_FILE, "r") as f: data = json.load(f) data.append({"date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "file": filepath, "threat": threat_name}) with open(HISTORY_FILE, "w") as f: json.dump(data, f, indent=4) except: pass # --- AUTO-DETECT USB --- def monitor_usb_drives(self): media_dir = f"/media/{os.getlogin()}" if not os.path.exists(media_dir): return known_mounts = set(os.listdir(media_dir)) while True: time.sleep(3) try: current_mounts = set(os.listdir(media_dir)) new_mounts = current_mounts - known_mounts for mount in new_mounts: self.after(0, self.prompt_usb_scan, os.path.join(media_dir, mount), mount) known_mounts = current_mounts except: pass def prompt_usb_scan(self, path, name): if self.zero_usb_mode: self.send_desktop_notification("⚡ Zero USB Actif", f"Analyse automatique forcée pour : {name}", is_critical=True) self.select_view("scanner") threading.Thread(target=self.run_clamav_scan, args=(path, True, self.scan_console), daemon=True).start() else: self.send_desktop_notification("USB Détectée", f"Disque {name} branché.") if messagebox.askyesno("Protection USB", f"Nouveau périphérique USB détecté :\n{name}\n\nVoulez-vous l'analyser ?"): self.select_view("scanner") threading.Thread(target=self.run_clamav_scan, args=(path, True, self.scan_console), daemon=True).start() # --- VUE : TABLEAU DE BORD --- def init_dashboard_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["dashboard"] = frame ctk.CTkLabel(frame, text="État du Système", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) status_card = ctk.CTkFrame(frame, fg_color="#064E3B" if self.clamav_installed else "#7F1D1D", corner_radius=15) status_card.pack(fill="x", pady=10, ipady=20) status_text = "Moteur ClamAV Opérationnel" if self.clamav_installed else "Moteur ClamAV Introuvable" ctk.CTkLabel(status_card, text=f"{'✅' if self.clamav_installed else '❌'} {status_text}", font=ctk.CTkFont(size=24, weight="bold"), text_color="white").pack(expand=True) sys_frame = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) sys_frame.pack(fill="x", pady=10, ipady=10) sys_info = f"🖥️ OS : {platform.system()} {platform.release()} | 👤 Compte : {os.getlogin()}" ctk.CTkLabel(sys_frame, text=sys_info, font=ctk.CTkFont(size=16, weight="bold")).pack(padx=20, pady=10, anchor="w") # Switch Zero USB self.zero_usb_switch = ctk.CTkSwitch(frame, text="🛡️ Mode Zero USB (Scan auto des clés USB)", command=self.toggle_zero_usb, font=ctk.CTkFont(size=16, weight="bold"), progress_color="#DC2626") self.zero_usb_switch.pack(pady=(20, 10), anchor="w", padx=20) # Switch Mode Gaming/Discret self.gaming_switch = ctk.CTkSwitch(frame, text="🎮 Mode Gaming / Discret (Silencieux & Basse Priorité CPU)", command=self.toggle_gaming_mode, font=ctk.CTkFont(size=16, weight="bold"), progress_color="#8B5CF6") self.gaming_switch.pack(pady=10, anchor="w", padx=20) def toggle_zero_usb(self): self.zero_usb_mode = self.zero_usb_switch.get() etat = "ACTIVÉ" if self.zero_usb_mode else "DÉSACTIVÉ" self.send_desktop_notification("Paramètre de Sécurité", f"Mode Zero USB {etat}") def toggle_gaming_mode(self): self.gaming_mode = self.gaming_switch.get() if self.gaming_mode: # Envoi d'une dernière notification avant de se taire self.send_desktop_notification("🎮 Mode Gaming Activé", "Notifications muettes. L'antivirus tourne en basse priorité.", is_critical=True) else: self.send_desktop_notification("🎮 Mode Gaming Désactivé", "Les ressources système et notifications sont restaurées.") # --- FONCTION D'AUTHENTIFICATION GLOBALE --- def get_sudo_password(self, callback_func, title="Sécurité Administrateur", msg="Privilèges requis pour cette action.\nEntrez votre mot de passe session :"): dialog = ctk.CTkToplevel(self) dialog.title(title) dialog.geometry("400x230") ctk.CTkLabel(dialog, text="⚠️ Privilèges Requis", font=ctk.CTkFont(size=20, weight="bold"), text_color="#F59E0B").pack(pady=(20, 5)) ctk.CTkLabel(dialog, text=msg, justify="center").pack(pady=5) pwd_entry = ctk.CTkEntry(dialog, show="*", width=250) pwd_entry.pack(pady=10) def on_submit(event=None): pwd = pwd_entry.get() dialog.destroy() if pwd: callback_func(pwd) ctk.CTkButton(dialog, text="Confirmer", command=on_submit, fg_color="#DC2626", hover_color="#991B1B").pack(pady=10) dialog.bind("", on_submit) dialog.wait_visibility() dialog.attributes("-topmost", True) dialog.grab_set() pwd_entry.focus_set() # --- VUE : SCANNER MANUEL --- def init_scanner_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(4, weight=1) frame.grid_columnconfigure((0, 1, 2), weight=1) self.views["scanner"] = frame ctk.CTkLabel(frame, text="Analyse Profonde", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, columnspan=3, sticky="w", pady=(0, 20)) self.btn_scan_f = ctk.CTkButton(frame, text="📄 Analyser Fichier", command=lambda: self.start_manual_scan(is_dir=False), height=45, fg_color="#2563EB", hover_color="#1D4ED8") self.btn_scan_f.grid(row=1, column=0, padx=(0, 5), pady=5, sticky="ew") self.btn_scan_d = ctk.CTkButton(frame, text="📁 Analyser Dossier", command=lambda: self.start_manual_scan(is_dir=True), height=45, fg_color="#4F46E5", hover_color="#4338CA") self.btn_scan_d.grid(row=1, column=1, padx=(5, 5), pady=5, sticky="ew") self.btn_db_update = ctk.CTkButton(frame, text="🔄 MaJ Signatures", command=self.update_virus_db_prompt, height=45, fg_color="#059669", hover_color="#047857") self.btn_db_update.grid(row=1, column=2, padx=(5, 0), pady=5, sticky="ew") self.btn_rootkit = ctk.CTkButton(frame, text="🕵️ Chasse aux Rootkits (rkhunter)", command=self.run_rootkit_scan, height=45, fg_color="#7C3AED", hover_color="#6D28D9") self.btn_rootkit.grid(row=2, column=0, columnspan=3, pady=(5, 10), sticky="ew") self.scan_progress = ctk.CTkProgressBar(frame, mode="indeterminate", height=10) self.scan_progress.grid(row=3, column=0, columnspan=3, pady=(10, 0), sticky="ew") self.scan_progress.set(0) self.scan_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.scan_console.grid(row=4, column=0, columnspan=3, pady=20, sticky="nsew") self.setup_console_tags(self.scan_console) self.scan_console.insert("end", f"{self.get_time_prefix()}[*] Moteur de détection V10.3 prêt...\n", "info") def run_rootkit_scan(self): self.get_sudo_password(lambda pwd: threading.Thread(target=self._exec_rootkit, args=(pwd,), daemon=True).start()) def _exec_rootkit(self, pwd): self.scan_console.insert("end", f"\n{self.get_time_prefix()}[*] Lancement de rkhunter (cela peut prendre du temps)...\n", "info") self.scan_progress.start() try: cmd = ['sudo', '-S', 'rkhunter', '-c', '--sk', '--report-warnings-only'] process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) process.stdin.write(pwd + '\n') process.stdin.flush() for line in iter(process.stdout.readline, ''): if "incorrect password" in line.lower() or "try again" in line.lower(): self.scan_console.insert("end", f"{self.get_time_prefix()}[-] Mot de passe refusé par le système.\n", "danger") process.terminate() break else: self.scan_console.insert("end", line) self.scan_console.see("end") process.wait() if process.returncode in [0, 1]: self.scan_console.insert("end", f"{self.get_time_prefix()}[+] Analyse Rootkit terminée.\n", "success") except FileNotFoundError: self.scan_console.insert("end", f"{self.get_time_prefix()}[-] rkhunter n'est pas installé.\n", "warning") except Exception as e: self.scan_console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") finally: self.scan_progress.stop() self.scan_console.see("end") def update_virus_db_prompt(self): self.get_sudo_password(lambda pwd: threading.Thread(target=self._run_freshclam, args=(pwd,), daemon=True).start(), msg="La mise à jour des signatures ClamAV nécessite\nles droits sudo. Entrez votre mot de passe :") def _run_freshclam(self, pwd): self.scan_console.insert("end", f"\n{self.get_time_prefix()}[*] Lancement de la mise à jour (freshclam)...\n", "info") self.scan_progress.start() self.btn_db_update.configure(state="disabled") try: cmd = ['sudo', '-S', 'freshclam'] process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) process.stdin.write(pwd + '\n') process.stdin.flush() for line in iter(process.stdout.readline, ''): if "incorrect password" in line.lower() or "try again" in line.lower(): self.scan_console.insert("end", f"{self.get_time_prefix()}[-] Mot de passe refusé.\n", "danger") process.terminate() break else: self.scan_console.insert("end", line) self.scan_console.see("end") process.wait() if process.returncode == 0: self.scan_console.insert("end", f"{self.get_time_prefix()}[+] Signatures virales mises à jour avec succès.\n", "success") except Exception as e: self.scan_console.insert("end", f"{self.get_time_prefix()}❌ Erreur freshclam : {e}\n", "danger") finally: self.scan_progress.stop() self.btn_db_update.configure(state="normal") self.scan_console.see("end") def start_manual_scan(self, is_dir): path = filedialog.askdirectory() if is_dir else filedialog.askopenfilename() if path: threading.Thread(target=self.run_clamav_scan, args=(path, is_dir, self.scan_console), daemon=True).start() # --- VUE : BOUCLIER TEMPS RÉEL --- def init_realtime_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(2, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["shield"] = frame header = ctk.CTkFrame(frame, fg_color="transparent") header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) ctk.CTkLabel(header, text="⚡ Bouclier Actif", font=ctk.CTkFont(size=34, weight="bold")).pack(side="left") self.btn_toggle_shield = ctk.CTkButton(header, text="Démarrer la Surveillance", fg_color="#059669", hover_color="#047857", command=self.toggle_shield, height=45) self.btn_toggle_shield.pack(side="right") self.rt_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.rt_console.grid(row=2, column=0, sticky="nsew") self.setup_console_tags(self.rt_console) def toggle_shield(self): if not self.shield_active: self.shield_observer = Observer() self.shield_observer.schedule(RealTimeShieldHandler(self), WATCH_FOLDER, recursive=False) self.shield_observer.start() self.shield_active = True self.btn_toggle_shield.configure(text="Arrêter le Bouclier", fg_color="#DC2626", hover_color="#B91C1C") self.rt_console.insert("end", f"\n{self.get_time_prefix()}[+] BOUCLIER ARMÉ : {WATCH_FOLDER}\n", "success") else: self.shield_observer.stop() self.shield_active = False self.btn_toggle_shield.configure(text="Démarrer la Surveillance", fg_color="#059669") self.rt_console.insert("end", f"\n{self.get_time_prefix()}[-] Bouclier désactivé.\n", "warning") def trigger_realtime_scan(self, filepath): self.rt_console.insert("end", f"\n{self.get_time_prefix()}⚡ Nouveau fichier : {os.path.basename(filepath)}\n", "info") threading.Thread(target=self.run_clamav_scan, args=(filepath, False, self.rt_console), daemon=True).start() # --- MOTEUR CLAMAV --- def run_clamav_scan(self, path, is_dir, console): if not self.clamav_installed: return console.insert("end", f"{self.get_time_prefix()}[*] Analyse : {path}\n") if console == self.scan_console: self.scan_progress.start() try: cmd = [] # Si le Mode Gaming est actif, on utilise 'nice' pour baisser la priorité CPU du scan (sur Linux, nice 15 est très bas) if self.gaming_mode: cmd.extend(['nice', '-n', '15']) cmd.extend(['clamscan', '-i', '--no-summary', f'--move={QUARANTINE_DIR}']) if is_dir: cmd.append('-r') cmd.append(path) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) infected = 0 for line in process.stdout: clean_line = line.strip() if "FOUND" in clean_line: threat_name = clean_line.split(":")[-1].replace("FOUND", "").strip() console.insert("end", f"{self.get_time_prefix()}☠️ MENACE DÉTECTÉE : {clean_line}\n", "danger") infected += 1 self.log_threat_to_history(path, threat_name) elif clean_line: console.insert("end", f" -> {clean_line}\n") console.see("end") process.wait() if infected == 0: console.insert("end", f"{self.get_time_prefix()}[+] Fichier propre.\n", "success") else: self.send_desktop_notification("🚨 VIRUS NEUTRALISÉ", f"{infected} menace(s) trouvée(s).", is_critical=True) except Exception as e: console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") finally: if console == self.scan_console: self.scan_progress.stop() console.see("end") # --- VUE : AUDIT & PARE-FEU (AVEC WIFI GUARD V10.1) --- def init_audit_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(2, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["audit"] = frame ctk.CTkLabel(frame, text="Audit Réseau & Pare-feu", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 20)) btn_frame = ctk.CTkFrame(frame, fg_color="transparent") btn_frame.grid(row=1, column=0, sticky="ew", pady=(0, 10)) ctk.CTkButton(btn_frame, text="⚙️ Lancer l'Audit Processus", command=self.run_audit_thread, fg_color="#D97706", hover_color="#B45309", height=40).pack(side="left", padx=(0, 10)) ctk.CTkButton(btn_frame, text="🛡️ Vérifier UFW", command=self.check_firewall, fg_color="#4B5563", hover_color="#374151", height=40).pack(side="left", padx=(0, 10)) ctk.CTkButton(btn_frame, text="📡 WiFi Guard (Scanner Intrus)", command=self.run_wifi_guard_prompt, fg_color="#059669", hover_color="#047857", height=40).pack(side="left") self.audit_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.audit_console.grid(row=2, column=0, sticky="nsew") def run_audit_thread(self): self.audit_console.delete("0.0", "end") threading.Thread(target=self.perform_audit, daemon=True).start() def perform_audit(self): try: self.audit_console.insert("end", subprocess.check_output(['ss', '-tuln'], text=True)) except: pass try: res = subprocess.check_output(['ps', '-eo', 'pid,user,%cpu,%mem,cmd', '--sort=-%cpu'], text=True) self.audit_console.insert("end", "\n" + "\n".join(res.split('\n')[:11]) + "\n") except: pass def check_firewall(self): self.audit_console.delete("0.0", "end") try: res = subprocess.check_output(['systemctl', 'is-active', 'ufw'], text=True).strip() if res == "active": self.audit_console.insert("end", f"{self.get_time_prefix()}✅ PARE-FEU UFW ACTIF.\n") else: self.audit_console.insert("end", f"{self.get_time_prefix()}⚠️ PARE-FEU INACTIF.\n") except: pass # ----- LOGIQUE WIFI GUARD ----- def run_wifi_guard_prompt(self): self.get_sudo_password(lambda pwd: threading.Thread(target=self._exec_wifi_guard, args=(pwd,), daemon=True).start(), title="WiFi Guard", msg="Le scan réseau profond nécessite les droits sudo.\nEntrez votre mot de passe session :") def _exec_wifi_guard(self, pwd): self.audit_console.delete("0.0", "end") self.audit_console.insert("end", f"{self.get_time_prefix()}[*] Démarrage du scan réseau (arp-scan)... \n", "info") if not shutil.which("arp-scan"): self.audit_console.insert("end", f"{self.get_time_prefix()}[!] Erreur : arp-scan introuvable.\n", "danger") return try: cmd = ['sudo', '-S', 'arp-scan', '--localnet', '--ignoredups'] process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) process.stdin.write(pwd + '\n') process.stdin.flush() count = 0 for line in iter(process.stdout.readline, ''): if any(x in line.lower() for x in ["permission denied", "starting arp-scan", "packets received", "ending arp-scan", "[sudo]"]): continue if "incorrect password" in line.lower() or "try again" in line.lower(): self.audit_console.insert("end", f"{self.get_time_prefix()}[-] Mot de passe refusé.\n", "danger") break if line.strip() and (line[0].isdigit()): self.audit_console.insert("end", f" 📱 Appareil trouvé : {line.strip()}\n", "success") count += 1 self.audit_console.see("end") process.wait() self.audit_console.insert("end", f"\n{self.get_time_prefix()}[+] Scan terminé. {count} appareils détectés sur votre WiFi.\n", "info") if count > 15: self.send_desktop_notification("WiFi Guard", f"Attention : Beaucoup d'appareils ({count}) sont connectés à votre réseau.", is_critical=True) except Exception as e: self.audit_console.insert("end", f"❌ Erreur : {e}\n", "danger") # --- VUE : OUTILS AVANCÉS (V10) --- def init_tools_view(self): frame = ctk.CTkScrollableFrame(self, fg_color="transparent") self.views["tools"] = frame ctk.CTkLabel(frame, text="Boîte à Outils", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) # Vérificateur de Fuites de Données (OSINT) breach_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) breach_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(breach_card, text="🌐 Vérificateur de Fuites (OSINT)", font=ctk.CTkFont(size=18, weight="bold"), text_color="#A855F7").pack(anchor="w", padx=20, pady=(10,0)) ctk.CTkLabel(breach_card, text="Vérifiez si votre adresse e-mail a été compromise dans une fuite de données (Dark Web).", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 10)) input_frame = ctk.CTkFrame(breach_card, fg_color="transparent") input_frame.pack(fill="x", padx=20, pady=5) self.email_entry = ctk.CTkEntry(input_frame, placeholder_text="Entrez votre e-mail...", width=300) self.email_entry.pack(side="left", padx=(0, 10)) ctk.CTkButton(input_frame, text="Vérifier l'e-mail", fg_color="#9333EA", hover_color="#7E22CE", command=self.run_breach_check).pack(side="left") self.breach_result_var = ctk.StringVar(value="") self.breach_result_label = ctk.CTkLabel(breach_card, textvariable=self.breach_result_var, text_color="#D1D5DB", justify="left") self.breach_result_label.pack(anchor="w", padx=20, pady=10) # Nettoyeur clean_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) clean_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(clean_card, text="🧹 Nettoyeur Système", font=ctk.CTkFont(size=18, weight="bold"), text_color="#10B981").pack(anchor="w", padx=20, pady=(10,0)) ctk.CTkButton(clean_card, text="Vider le Cache et Corbeille", fg_color="#059669", hover_color="#047857", command=self.run_cleaner).pack(anchor="w", padx=20, pady=10) # Destructeur shred_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) shred_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(shred_card, text="🔥 Destructeur de Fichiers", font=ctk.CTkFont(size=18, weight="bold"), text_color="#EF4444").pack(anchor="w", padx=20, pady=(10,0)) ctk.CTkButton(shred_card, text="Détruire un fichier", fg_color="#DC2626", hover_color="#991B1B", command=self.run_shredder).pack(anchor="w", padx=20, pady=10) # --- LOGIQUE OSINT --- def run_breach_check(self): email = self.email_entry.get().strip() if not email or "@" not in email: self.breach_result_var.set("❌ Veuillez entrer une adresse e-mail valide.") self.breach_result_label.configure(text_color="#EF4444") return self.breach_result_var.set("⏳ Interrogation des bases de données...") self.breach_result_label.configure(text_color="#3B82F6") threading.Thread(target=self._exec_breach_check, args=(email,), daemon=True).start() def _exec_breach_check(self, email): try: url = f"https://api.xposedornot.com/v1/check-email/{email}" req = urllib.request.Request(url, headers={'User-Agent': '7LnA-Security-Suite'}) try: with urllib.request.urlopen(req) as response: data = json.loads(response.read().decode()) if "breaches" in data and data["breaches"]: breaches = data["breaches"][0] breach_count = len(breaches) breach_names = ", ".join(breaches[:4]) suffix = "..." if breach_count > 4 else "" self.breach_result_var.set(f"🚨 DANGER : E-mail trouvé dans {breach_count} fuite(s) !\nSources connues : {breach_names}{suffix}\nChangez vos mots de passe immédiatement.") self.breach_result_label.configure(text_color="#EF4444") else: self.breach_result_var.set("✅ Bonne nouvelle : Aucune fuite détectée pour cet e-mail.") self.breach_result_label.configure(text_color="#10B981") except urllib.error.HTTPError as e: if e.code == 404: self.breach_result_var.set("✅ Bonne nouvelle : Aucune fuite détectée pour cet e-mail.") self.breach_result_label.configure(text_color="#10B981") else: self.breach_result_var.set(f"❌ Erreur serveur lors de la vérification ({e.code}).") self.breach_result_label.configure(text_color="#F59E0B") except Exception as e: self.breach_result_var.set("❌ Impossible de contacter le serveur d'OSINT. Vérifiez votre connexion.") self.breach_result_label.configure(text_color="#F59E0B") def run_cleaner(self): paths_to_clean = [os.path.expanduser("~/.local/share/Trash/files"), os.path.expanduser("~/.cache/thumbnails")] for path in paths_to_clean: if os.path.exists(path): for item in os.listdir(path): item_path = os.path.join(path, item) try: if os.path.isfile(item_path): os.remove(item_path) elif os.path.isdir(item_path): shutil.rmtree(item_path) except: pass messagebox.showinfo("Succès", "Nettoyage terminé.") def run_shredder(self): filepath = filedialog.askopenfilename() if not filepath: return if messagebox.askyesno("DANGER", f"Détruire DÉFINITIVEMENT ?\n\n{filepath}"): try: file_size = os.path.getsize(filepath) for _ in range(3): with open(filepath, "ba+", buffering=0) as f: f.seek(0) f.write(os.urandom(file_size)) os.remove(filepath) messagebox.showinfo("Succès", "Fichier détruit.") except: pass # ========================================== # --- VUE : COFFRE-FORT (NOUVEAU) --- # ========================================== def init_vault_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(2, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["vault"] = frame ctk.CTkLabel(frame, text="🔐 Coffre-fort AES-256", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 20)) # Panel de connexion self.vault_login_frame = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) self.vault_login_frame.grid(row=1, column=0, sticky="ew", ipady=20) ctk.CTkLabel(self.vault_login_frame, text="Entrez le mot de passe maître :", font=ctk.CTkFont(size=16)).pack(pady=(20, 10)) self.vault_pwd_entry = ctk.CTkEntry(self.vault_login_frame, show="*", width=300) self.vault_pwd_entry.pack(pady=10) ctk.CTkButton(self.vault_login_frame, text="Déverrouiller / Créer", fg_color="#2563EB", hover_color="#1D4ED8", command=self.unlock_vault).pack(pady=10) ctk.CTkLabel(self.vault_login_frame, text="Note: Si le coffre n'existe pas, ce mot de passe sera utilisé pour le créer.", text_color="#9CA3AF").pack() # Panel de l'éditeur (caché par défaut) self.vault_editor_frame = ctk.CTkFrame(frame, fg_color="transparent") header_editor = ctk.CTkFrame(self.vault_editor_frame, fg_color="transparent") header_editor.pack(fill="x", pady=(0, 10)) ctk.CTkLabel(header_editor, text="Vos notes sécurisées :", font=ctk.CTkFont(size=18, weight="bold"), text_color="#10B981").pack(side="left") ctk.CTkButton(header_editor, text="🔒 Sauvegarder et Verrouiller", fg_color="#059669", hover_color="#047857", command=self.save_vault).pack(side="right") self.vault_textbox = ctk.CTkTextbox(self.vault_editor_frame, font=ctk.CTkFont(family="Consolas", size=14), fg_color="#111827", corner_radius=10) self.vault_textbox.pack(fill="both", expand=True) self.current_vault_pwd = None self.current_vault_salt = None def _derive_key(self, password, salt): kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=480000, ) return base64.urlsafe_b64encode(kdf.derive(password.encode())) def unlock_vault(self): pwd = self.vault_pwd_entry.get() if not pwd: return if os.path.exists(VAULT_FILE): try: with open(VAULT_FILE, "rb") as f: data = f.read() salt = data[:16] encrypted = data[16:] key = self._derive_key(pwd, salt) fernet = Fernet(key) decrypted = fernet.decrypt(encrypted).decode() # Succès ! self.current_vault_pwd = pwd self.current_vault_salt = salt self.show_vault_editor(decrypted) except Exception: messagebox.showerror("Accès Refusé", "Mot de passe incorrect ou fichier corrompu.") else: # Nouveau coffre self.current_vault_pwd = pwd self.current_vault_salt = os.urandom(16) self.show_vault_editor("=== VOTRE COFFRE EST VIDE ===\n\nÉcrivez vos identifiants ou notes secrètes ici...") def show_vault_editor(self, content): self.vault_login_frame.grid_forget() self.vault_editor_frame.grid(row=2, column=0, sticky="nsew") self.vault_pwd_entry.delete(0, 'end') self.vault_textbox.delete("0.0", "end") self.vault_textbox.insert("end", content) def save_vault(self): data = self.vault_textbox.get("0.0", "end") key = self._derive_key(self.current_vault_pwd, self.current_vault_salt) fernet = Fernet(key) encrypted = fernet.encrypt(data.encode()) with open(VAULT_FILE, "wb") as f_out: f_out.write(self.current_vault_salt + encrypted) self.current_vault_pwd = None self.current_vault_salt = None self.vault_textbox.delete("0.0", "end") self.vault_editor_frame.grid_forget() self.vault_login_frame.grid(row=1, column=0, sticky="ew", ipady=20) self.send_desktop_notification("Coffre-fort", "Fichier chiffré avec succès et verrouillé.") # ----- PLANIFICATION ----- def init_schedule_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["schedule"] = frame ctk.CTkLabel(frame, text="Planification de Scans", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) ctk.CTkLabel(frame, text="Automatisez l'analyse via les tâches Cron.", text_color="#9CA3AF").pack(anchor="w", pady=(0, 20)) card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(card, text="Programmer un scan automatique (Dossier Téléchargements)", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(10, 5)) time_frame = ctk.CTkFrame(card, fg_color="transparent") time_frame.pack(pady=(5, 15)) ctk.CTkLabel(time_frame, text="Heure d'exécution :").pack(side="left", padx=5) self.cron_h_var = ctk.StringVar(value="12") self.cron_m_var = ctk.StringVar(value="00") ctk.CTkComboBox(time_frame, values=[f"{i:02d}" for i in range(24)], variable=self.cron_h_var, width=70).pack(side="left", padx=5) ctk.CTkLabel(time_frame, text="h").pack(side="left") ctk.CTkComboBox(time_frame, values=["00", "15", "30", "45"], variable=self.cron_m_var, width=70).pack(side="left", padx=5) ctk.CTkButton(card, text="✅ Activer le Scan Quotidien", fg_color="#2563EB", hover_color="#1D4ED8", command=self.setup_cronjob).pack(pady=5) ctk.CTkButton(card, text="❌ Désactiver la planification", fg_color="#4B5563", hover_color="#374151", command=self.remove_cronjob).pack(pady=5) def setup_cronjob(self): h = self.cron_h_var.get() m = self.cron_m_var.get() cron_command = f"{int(m)} {int(h)} * * * /usr/bin/clamscan -r --move={QUARANTINE_DIR} {WATCH_FOLDER} > /dev/null 2>&1" try: os.system(f'(crontab -l 2>/dev/null | grep -v "clamscan -r --move={QUARANTINE_DIR}"; echo "{cron_command}") | crontab -') messagebox.showinfo("Succès", f"Scan configuré tous les jours à {h}:{m}.") except: messagebox.showerror("Erreur", "Impossible de configurer Cron.") def remove_cronjob(self): try: os.system(f'(crontab -l 2>/dev/null | grep -v "clamscan -r --move={QUARANTINE_DIR}") | crontab -') messagebox.showinfo("Succès", "Planification désactivée.") except: pass # --- VUE : HISTORIQUE --- def init_history_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(1, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["history"] = frame ctk.CTkLabel(frame, text="📜 Registre des Menaces", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 20)) self.history_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.history_console.grid(row=1, column=0, sticky="nsew") def refresh_history_list(self): self.history_console.delete("0.0", "end") try: with open(HISTORY_FILE, "r") as f: data = json.load(f) if not data: self.history_console.insert("end", "\n ✅ Historique vierge.\n") else: for entry in reversed(data): self.history_console.insert("end", f"[{entry['date']}] ☠️ {entry['threat']}\n -> Fichier : {entry['file']}\n\n") except: pass # --- VUE : QUARANTAINE --- def init_quarantine_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(1, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["quarantine"] = frame header = ctk.CTkFrame(frame, fg_color="transparent") header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) ctk.CTkLabel(header, text="📦 Quarantaine", font=ctk.CTkFont(size=34, weight="bold")).pack(side="left") ctk.CTkButton(header, text="🗑️ Vider", fg_color="#DC2626", hover_color="#991B1B", command=self.empty_quarantine, height=40).pack(side="right") self.quarantine_listbox = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=14), fg_color="#111827", corner_radius=10) self.quarantine_listbox.grid(row=1, column=0, sticky="nsew") def refresh_quarantine_list(self): self.quarantine_listbox.delete("0.0", "end") files = glob.glob(os.path.join(QUARANTINE_DIR, "*")) if not files: self.quarantine_listbox.insert("end", "\n ✅ Aucun fichier malveillant isolé.\n") else: for f in files: self.quarantine_listbox.insert("end", f" -> {os.path.basename(f)}\n") def empty_quarantine(self): files = glob.glob(os.path.join(QUARANTINE_DIR, "*")) if files and messagebox.askyesno("Purger", "Supprimer définitivement les virus isolés ?"): for f in files: try: os.remove(f) except: pass self.refresh_quarantine_list() # --- VUE : MISE À JOUR --- def init_update_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["update"] = frame ctk.CTkLabel(frame, text="Mise à jour Cloud", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) self.btn_update_soft = ctk.CTkButton(frame, text="⬇️ Mettre à jour", command=self.run_software_update, fg_color="#8B5CF6", hover_color="#7C3AED", height=45) self.btn_update_soft.pack(anchor="w") self.update_console = ctk.CTkTextbox(frame, height=200, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.update_console.pack(fill="x", pady=20) def run_software_update(self): self.btn_update_soft.configure(state="disabled") self.update_console.insert("end", "[*] Négociation avec serveur...\n") try: urllib.request.urlretrieve(UPDATE_URL, os.path.abspath(__file__)) self.update_console.insert("end", "[+] Mise à jour réussie !\n") messagebox.showinfo("Succès", "Redémarrage requis.") self.destroy() except: self.update_console.insert("end", "[-] Échec.\n") self.btn_update_soft.configure(state="normal") if __name__ == "__main__": app = Antivirus7LnA() app.mainloop()