Files
2026-03-02 15:28:49 +00:00

343 lines
17 KiB
Python

import customtkinter as ctk
from tkinter import filedialog, messagebox
import os
import threading
import subprocess
import urllib.request
import time
import random
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
# ==========================================
# 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")
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
# ==========================================
# GESTIONNAIRE DU BOUCLIER TEMPS RÉEL
# ==========================================
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 - Advanced Edition")
self.geometry("1100x700")
self.minsize(900, 600)
os.makedirs(QUARANTINE_DIR, exist_ok=True)
self.shield_observer = None
self.shield_active = False
self.check_dependencies()
self.setup_ui()
def check_dependencies(self):
try:
subprocess.run(['clamscan', '--version'], capture_output=True, check=True)
self.clamav_installed = True
except (subprocess.CalledProcessError, FileNotFoundError):
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=240, corner_radius=0, fg_color="#111827")
self.sidebar.grid(row=0, column=0, sticky="nsew")
self.sidebar.grid_rowconfigure(7, weight=1)
ctk.CTkLabel(self.sidebar, text="🛡️ 7LnA Sec", font=ctk.CTkFont(size=28, weight="bold"), text_color="#3B82F6").grid(row=0, column=0, padx=20, pady=30)
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_update = self.create_nav_button("🔄 Mise à jour", 6, "update")
self.version_label = ctk.CTkLabel(self.sidebar, text="v6.0 - Advanced", text_color="#6B7280")
self.version_label.grid(row=7, column=0, pady=20, sticky="s")
# --- CONTENEUR DES VUES ---
self.views = {}
self.init_dashboard_view()
self.init_scanner_view()
self.init_realtime_view()
self.init_audit_view()
self.init_tools_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=14),
command=lambda: self.select_view(view_name))
btn.grid(row=row, column=0, padx=20, pady=10, 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)
# --- UTILITAIRES DE CONSOLE (COULEURS) ---
def setup_console_tags(self, console):
"""Ajoute le support des couleurs dans les consoles textuelles"""
console.tag_config("danger", foreground="#EF4444") # Rouge
console.tag_config("success", foreground="#10B981") # Vert
console.tag_config("warning", foreground="#F59E0B") # Orange
console.tag_config("info", foreground="#3B82F6") # Bleu
# --- 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=28, 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=20, weight="bold"), text_color="white").pack(expand=True)
info_frame = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10)
info_frame.pack(fill="x", pady=20, ipady=10)
ctk.CTkLabel(info_frame, text=f"📂 Dossier Quarantaine : {QUARANTINE_DIR}\n📁 Dossier Surveillé : {WATCH_FOLDER}", justify="left", font=ctk.CTkFont(size=14)).pack(padx=20, pady=10, anchor="w")
# --- VUE : SCANNER MANUEL ---
def init_scanner_view(self):
frame = ctk.CTkFrame(self, fg_color="transparent")
frame.grid_rowconfigure(2, weight=1)
frame.grid_columnconfigure((0, 1), weight=1)
self.views["scanner"] = frame
ctk.CTkLabel(frame, text="Analyse Profonde", font=ctk.CTkFont(size=28, weight="bold")).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 20))
self.btn_scan_f = ctk.CTkButton(frame, text="📄 Analyser un 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, 10), sticky="ew")
self.btn_scan_d = ctk.CTkButton(frame, text="📁 Analyser un 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=(10, 0), sticky="ew")
self.scan_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827")
self.scan_console.grid(row=2, column=0, columnspan=2, pady=20, sticky="nsew")
self.setup_console_tags(self.scan_console)
self.scan_console.insert("end", "[*] Moteur prêt. En attente de cible...\n", "info")
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=28, 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=40)
self.btn_toggle_shield.pack(side="right")
self.rt_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827")
self.rt_console.grid(row=2, column=0, sticky="nsew")
self.setup_console_tags(self.rt_console)
self.rt_console.insert("end", "[-] Bouclier en attente.\n")
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[+] BOUCLIER ARMÉ sur {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", hover_color="#047857")
self.rt_console.insert("end", "\n[-] Bouclier désactivé.\n", "warning")
self.rt_console.see("end")
def trigger_realtime_scan(self, filepath):
self.rt_console.insert("end", f"\n⚡ Fichier capté : {os.path.basename(filepath)}\n", "info")
threading.Thread(target=self.run_clamav_scan, args=(filepath, False, self.rt_console), daemon=True).start()
# --- MOTEUR CLAMAV (Cœur) ---
def run_clamav_scan(self, path, is_dir, console):
if not self.clamav_installed:
console.insert("end", "❌ Erreur critique : Moteur introuvable.\n", "danger")
return
console.insert("end", f"[*] Analyse en cours : {path}\n")
try:
cmd = ['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:
console.insert("end", f"☠️ MENACE : {clean_line}\n", "danger")
console.insert("end", "🛡️ Action : Isolé en quarantaine.\n", "warning")
infected += 1
else:
console.insert("end", clean_line + "\n")
console.see("end")
process.wait()
if infected == 0:
console.insert("end", "[+] Cible sécurisée (Propre).\n", "success")
else:
console.insert("end", f"🚨 {infected} virus neutralisé(s) !\n", "danger")
except Exception as e:
console.insert("end", f"❌ Panne moteur : {e}\n", "danger")
console.see("end")
# --- VUE : AUDIT & PARE-FEU ---
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=28, 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").pack(side="left", padx=(0, 10))
ctk.CTkButton(btn_frame, text="Vérifier Statut UFW (Pare-feu)", command=self.check_firewall, fg_color="#4B5563", hover_color="#374151").pack(side="left")
self.audit_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827")
self.audit_console.grid(row=2, column=0, sticky="nsew")
self.setup_console_tags(self.audit_console)
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):
self.audit_console.insert("end", "[*] Recherche de connexions fantômes (Backdoors)...\n", "info")
try:
self.audit_console.insert("end", subprocess.check_output(['ss', '-tuln'], text=True))
except: pass
self.audit_console.insert("end", "\n[*] Top 10 Processus (Charge CPU)...\n", "info")
try:
res = subprocess.check_output(['ps', '-eo', 'pid,user,%cpu,%mem,cmd', '--sort=-%cpu'], text=True)
self.audit_console.insert("end", "\n".join(res.split('\n')[:11]) + "\n")
except: pass
def check_firewall(self):
self.audit_console.delete("0.0", "end")
self.audit_console.insert("end", "[*] Interrogation du pare-feu Ubuntu (UFW)...\n", "info")
try:
# systemctl est utilisé pour éviter le prompt sudo de ufw
res = subprocess.check_output(['systemctl', 'is-active', 'ufw'], text=True).strip()
if res == "active":
self.audit_console.insert("end", "✅ PARE-FEU UFW ACTIF ET EN LIGNE.\n", "success")
else:
self.audit_console.insert("end", "⚠️ ATTENTION : PARE-FEU INACTIF.\nActivez-le via terminal avec 'sudo ufw enable'.\n", "danger")
except Exception:
self.audit_console.insert("end", "[-] Impossible de déterminer l'état de UFW.\n", "warning")
# --- VUE : OUTILS AVANCÉS (NOUVEAU !) ---
def init_tools_view(self):
frame = ctk.CTkFrame(self, fg_color="transparent")
self.views["tools"] = frame
ctk.CTkLabel(frame, text="Boîte à Outils", font=ctk.CTkFont(size=28, weight="bold")).pack(anchor="w", pady=(0, 20))
# Outil 1 : Destructeur de fichiers
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 sécurisé (File Shredder)", font=ctk.CTkFont(size=16, weight="bold"), text_color="#EF4444").pack(anchor="w", padx=20)
ctk.CTkLabel(shred_card, text="Écrase un fichier avec des données aléatoires (3 passes) avant de le supprimer.\nEmpêche toute récupération par un logiciel tiers.", justify="left", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 15))
ctk.CTkButton(shred_card, text="Détruire un fichier à jamais", fg_color="#DC2626", hover_color="#991B1B", command=self.run_shredder).pack(anchor="w", padx=20)
def run_shredder(self):
filepath = filedialog.askopenfilename(title="SÉLECTIONNEZ LE FICHIER À DÉTRUIRE")
if not filepath: return
confirm = messagebox.askyesno("DANGER IRRÉVERSIBLE", f"Voulez-vous vraiment DÉTRUIRE ce fichier ?\n\n{filepath}\n\nCette action est permanente et ne va PAS dans la corbeille.")
if confirm:
threading.Thread(target=self.shred_file, args=(filepath,), daemon=True).start()
def shred_file(self, filepath):
try:
file_size = os.path.getsize(filepath)
# 3 passages de réécriture
for i in range(3):
with open(filepath, "ba+", buffering=0) as f:
f.seek(0)
f.write(os.urandom(file_size))
# Suppression finale du système
os.remove(filepath)
messagebox.showinfo("Succès", "Fichier pulvérisé avec succès. Récupération impossible.")
except Exception as e:
messagebox.showerror("Erreur Shredder", f"Impossible de détruire le fichier.\nErreur : {e}")
# --- VUE : MISE À JOUR (OTA) ---
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=28, weight="bold")).pack(anchor="w", pady=(0, 20))
ctk.CTkLabel(frame, text="Restez synchronisé avec le serveur officiel 7ka1 Git.", text_color="#9CA3AF").pack(anchor="w", pady=(0, 20))
self.btn_update = ctk.CTkButton(frame, text="⬇️ Forcer la mise à jour depuis Gitea", command=self.run_software_update, fg_color="#8B5CF6", hover_color="#7C3AED", height=45)
self.btn_update.pack(anchor="w")
self.update_console = ctk.CTkTextbox(frame, height=150, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827")
self.update_console.pack(fill="x", pady=20)
self.setup_console_tags(self.update_console)
def run_software_update(self):
self.btn_update.configure(state="disabled")
self.update_console.insert("end", "[*] Négociation avec git.7ka1.com...\n", "info")
try:
current_file_path = os.path.abspath(__file__)
urllib.request.urlretrieve(UPDATE_URL, current_file_path)
self.update_console.insert("end", "[+] Code source remplacé avec succès !\n", "success")
messagebox.showinfo("Mise à jour Appliquée", "Redémarrage requis pour initialiser la nouvelle version.")
self.destroy()
except Exception as e:
self.update_console.insert("end", f"[-] Échec de la liaison avec le serveur : {e}\n", "danger")
self.btn_update.configure(state="normal")
if __name__ == "__main__":
app = Antivirus7LnA()
app.mainloop()