Files
7LnA_Antivirus_Linux_Free_C…/7lna.py
2026-03-19 14:12:08 +00:00

806 lines
43 KiB
Python

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("<Return>", 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()