675 lines
38 KiB
Python
675 lines
38 KiB
Python
import customtkinter as ctk
|
|
from tkinter import filedialog, messagebox
|
|
import os, threading, subprocess, urllib.request, urllib.error, time, datetime, glob, json, platform, shutil
|
|
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")
|
|
HISTORY_FILE = os.path.expanduser("~/.7lna_history.json")
|
|
|
|
# Palette High-Tech 2026
|
|
BG_MAIN = "#0B0F19" # Fond ultra sombre
|
|
BG_CARD = "#161E2E" # Fond des cartes
|
|
BG_SIDEBAR = "#090B10" # Fond barre latérale
|
|
ACCENT_BLUE = "#3B82F6" # Bleu électrique
|
|
ACCENT_VIOLET = "#8B5CF6" # Violet néon (Zero USB)
|
|
TEXT_MAIN = "#F3F4F6"
|
|
TEXT_MUTED = "#9CA3AF"
|
|
|
|
ctk.set_appearance_mode("dark")
|
|
|
|
# ==========================================
|
|
# UTILITAIRES SYSTÈME
|
|
# ==========================================
|
|
def send_desktop_notification(title, message, is_critical=False):
|
|
try:
|
|
urgency = 'critical' if is_critical else 'normal'
|
|
subprocess.Popen(['notify-send', '-u', urgency, '-a', '7lna Antivirus', title, message])
|
|
except Exception:
|
|
pass
|
|
|
|
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 Antivirus")
|
|
self.geometry("1250x850")
|
|
self.minsize(1050, 750)
|
|
self.configure(fg_color=BG_MAIN)
|
|
|
|
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.check_dependencies()
|
|
self.setup_ui()
|
|
|
|
threading.Thread(target=self.monitor_usb_drives, daemon=True).start()
|
|
|
|
def check_dependencies(self):
|
|
try:
|
|
subprocess.run(['clamscan', '--version'], capture_output=True, check=True)
|
|
self.clamav_installed = True
|
|
except:
|
|
self.clamav_installed = False
|
|
|
|
# ==========================================
|
|
# INTERFACE GRAPHIQUE (DESIGN 2026)
|
|
# ==========================================
|
|
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=280, corner_radius=0, fg_color=BG_SIDEBAR)
|
|
self.sidebar.grid(row=0, column=0, sticky="nsew")
|
|
self.sidebar.grid_rowconfigure(10, weight=1)
|
|
|
|
# Logo
|
|
logo_frame = ctk.CTkFrame(self.sidebar, fg_color="transparent")
|
|
logo_frame.grid(row=0, column=0, pady=(40, 30), sticky="ew")
|
|
ctk.CTkLabel(logo_frame, text="🛡️ 7lna", font=ctk.CTkFont(size=36, weight="bold"), text_color=ACCENT_BLUE).pack()
|
|
ctk.CTkLabel(logo_frame, text="A N T I V I R U S", font=ctk.CTkFont(size=12, weight="bold"), text_color=ACCENT_VIOLET).pack()
|
|
|
|
# Navigation
|
|
self.nav_buttons = {}
|
|
self.create_nav_button("📊 Tableau de Bord", 1, "dashboard")
|
|
self.create_nav_button("🔍 Scanner Système", 2, "scanner")
|
|
self.create_nav_button("⚡ Bouclier Actif", 3, "shield")
|
|
self.create_nav_button("🌐 Audit & Réseau", 4, "audit")
|
|
self.create_nav_button("🧰 Boîte à Outils", 5, "tools")
|
|
self.create_nav_button("📦 Quarantaine", 6, "quarantine")
|
|
self.create_nav_button("📅 Automatisation", 7, "schedule")
|
|
self.create_nav_button("📜 Rapports", 8, "history")
|
|
|
|
# Bouton Mise à jour (Mise en évidence)
|
|
self.btn_update = ctk.CTkButton(self.sidebar, text="🔄 Rechercher MAJ", command=lambda: self.select_view("update"),
|
|
fg_color="#1E3A8A", hover_color=ACCENT_BLUE, font=ctk.CTkFont(size=14, weight="bold"), height=45)
|
|
self.btn_update.grid(row=9, column=0, padx=20, pady=20, sticky="ew")
|
|
|
|
self.version_label = ctk.CTkLabel(self.sidebar, text="V10.1 Quantum Edition", text_color=TEXT_MUTED, font=ctk.CTkFont(size=11))
|
|
self.version_label.grid(row=10, column=0, pady=20, sticky="s")
|
|
|
|
# --- 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_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=TEXT_MUTED, hover_color=BG_CARD, font=ctk.CTkFont(size=15, weight="bold"), height=45,
|
|
command=lambda: self.select_view(view_name))
|
|
btn.grid(row=row, column=0, padx=15, pady=4, sticky="ew")
|
|
self.nav_buttons[view_name] = btn
|
|
|
|
def select_view(self, view_name):
|
|
# Réinitialiser les couleurs des boutons
|
|
for name, btn in self.nav_buttons.items():
|
|
btn.configure(fg_color="transparent", text_color=TEXT_MUTED)
|
|
|
|
# Activer le bouton cliqué (s'il existe dans le dictionnaire)
|
|
if view_name in self.nav_buttons:
|
|
self.nav_buttons[view_name].configure(fg_color=BG_CARD, text_color=TEXT_MAIN)
|
|
|
|
# Afficher la vue
|
|
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=40, pady=40)
|
|
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=ACCENT_BLUE)
|
|
console.tag_config("violet", foreground=ACCENT_VIOLET)
|
|
|
|
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
|
|
|
|
# ==========================================
|
|
# LOGIQUE USB & MOTEUR
|
|
# ==========================================
|
|
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:
|
|
send_desktop_notification("⚡ Zero USB Actif", f"Analyse auto forcée : {name}", is_critical=True)
|
|
self.select_view("scanner")
|
|
self.scan_console.insert("end", f"\n{self.get_time_prefix()}[⚡ ZERO USB] Scan automatique déclenché.\n", "violet")
|
|
threading.Thread(target=self.run_clamav_scan, args=(path, True, self.scan_console), daemon=True).start()
|
|
else:
|
|
send_desktop_notification("USB Détectée", f"Disque {name} branché.")
|
|
if messagebox.askyesno("Protection USB", f"Nouveau périphérique 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()
|
|
|
|
def get_sudo_password(self, callback_func, title="Sécurité Administrateur", msg="Privilèges requis.\nEntrez votre mot de passe :"):
|
|
dialog = ctk.CTkToplevel(self)
|
|
dialog.title(title)
|
|
dialog.geometry("400x250")
|
|
dialog.configure(fg_color=BG_CARD)
|
|
|
|
ctk.CTkLabel(dialog, text="⚠️ Élévation de Privilèges", font=ctk.CTkFont(size=20, weight="bold"), text_color="#F59E0B").pack(pady=(20, 10))
|
|
ctk.CTkLabel(dialog, text=msg, justify="center", text_color=TEXT_MUTED).pack(pady=5)
|
|
|
|
pwd_entry = ctk.CTkEntry(dialog, show="*", width=250, height=40, border_color=ACCENT_BLUE)
|
|
pwd_entry.pack(pady=15)
|
|
|
|
def on_submit(event=None):
|
|
pwd = pwd_entry.get()
|
|
dialog.destroy()
|
|
if pwd: callback_func(pwd)
|
|
|
|
ctk.CTkButton(dialog, text="Autoriser", command=on_submit, fg_color=ACCENT_BLUE, hover_color="#2563EB", height=40).pack(pady=5)
|
|
dialog.bind("<Return>", on_submit)
|
|
dialog.wait_visibility()
|
|
dialog.attributes("-topmost", True)
|
|
dialog.grab_set()
|
|
pwd_entry.focus_set()
|
|
|
|
# ==========================================
|
|
# VUES (DASHBOARD, SCANNER, ETC.)
|
|
# ==========================================
|
|
def init_dashboard_view(self):
|
|
frame = ctk.CTkFrame(self, fg_color="transparent")
|
|
frame.grid_columnconfigure((0, 1), weight=1)
|
|
self.views["dashboard"] = frame
|
|
|
|
ctk.CTkLabel(frame, text="Vue d'ensemble", font=ctk.CTkFont(size=38, weight="bold"), text_color=TEXT_MAIN).grid(row=0, column=0, columnspan=2, sticky="w", pady=(0, 30))
|
|
|
|
# Carte Statut
|
|
status_color = "#064E3B" if self.clamav_installed else "#7F1D1D"
|
|
status_card = ctk.CTkFrame(frame, fg_color=status_color, corner_radius=15)
|
|
status_card.grid(row=1, column=0, sticky="nsew", padx=(0, 10), pady=10, ipady=30)
|
|
ctk.CTkLabel(status_card, text="Moteur Antivirus", font=ctk.CTkFont(size=16), text_color=TEXT_MUTED).pack(pady=(20, 5))
|
|
status_text = "OPÉRATIONNEL" if self.clamav_installed else "HORS LIGNE"
|
|
ctk.CTkLabel(status_card, text=f"{'✅' if self.clamav_installed else '❌'} {status_text}", font=ctk.CTkFont(size=28, weight="bold"), text_color="white").pack()
|
|
|
|
# Carte Système
|
|
sys_card = ctk.CTkFrame(frame, fg_color=BG_CARD, corner_radius=15)
|
|
sys_card.grid(row=1, column=1, sticky="nsew", padx=(10, 0), pady=10, ipady=30)
|
|
ctk.CTkLabel(sys_card, text="Environnement", font=ctk.CTkFont(size=16), text_color=TEXT_MUTED).pack(pady=(20, 5))
|
|
sys_info = f"{platform.system()} {platform.release()}"
|
|
ctk.CTkLabel(sys_card, text=sys_info, font=ctk.CTkFont(size=22, weight="bold"), text_color=ACCENT_BLUE).pack()
|
|
ctk.CTkLabel(sys_card, text=f"Session : {os.getlogin()}", font=ctk.CTkFont(size=14), text_color=TEXT_MUTED).pack(pady=5)
|
|
|
|
# Carte Zero USB
|
|
usb_card = ctk.CTkFrame(frame, fg_color=BG_CARD, corner_radius=15, border_width=2, border_color=ACCENT_VIOLET)
|
|
usb_card.grid(row=2, column=0, columnspan=2, sticky="nsew", pady=20, ipady=20)
|
|
ctk.CTkLabel(usb_card, text="Protection des Périphériques", font=ctk.CTkFont(size=20, weight="bold")).pack(pady=(20,10))
|
|
|
|
self.zero_usb_switch = ctk.CTkSwitch(usb_card, text="Activer le Mode Zero USB (Scan Auto)",
|
|
command=self.toggle_zero_usb, font=ctk.CTkFont(size=16, weight="bold"),
|
|
progress_color=ACCENT_VIOLET, button_color="#FFFFFF")
|
|
self.zero_usb_switch.pack(pady=10)
|
|
|
|
def toggle_zero_usb(self):
|
|
self.zero_usb_mode = self.zero_usb_switch.get()
|
|
etat = "ARMÉ" if self.zero_usb_mode else "DÉSACTIVÉ"
|
|
send_desktop_notification("Bouclier Quantum", f"Mode Zero USB {etat}")
|
|
|
|
def init_scanner_view(self):
|
|
frame = ctk.CTkFrame(self, fg_color="transparent")
|
|
self.views["scanner"] = frame
|
|
|
|
# 1. En-tête
|
|
ctk.CTkLabel(frame, text="Analyse Système", font=ctk.CTkFont(size=38, weight="bold"), text_color=TEXT_MAIN).pack(anchor="w", pady=(0, 20))
|
|
|
|
# 2. Le Panneau de Contrôle (Une carte qui regroupe les actions)
|
|
control_card = ctk.CTkFrame(frame, fg_color=BG_CARD, corner_radius=15, border_width=1, border_color="#374151")
|
|
control_card.pack(fill="x", pady=(0, 20), ipady=10)
|
|
control_card.grid_columnconfigure((0, 1, 2), weight=1)
|
|
|
|
# Boutons avec des couleurs profondes et professionnelles
|
|
self.btn_scan_f = ctk.CTkButton(control_card, text="📄 Analyser Fichier", command=lambda: self.start_manual_scan(is_dir=False),
|
|
height=45, fg_color="#1E3A8A", hover_color=ACCENT_BLUE, font=ctk.CTkFont(weight="bold"))
|
|
self.btn_scan_f.grid(row=0, column=0, padx=15, pady=15, sticky="ew")
|
|
|
|
self.btn_scan_d = ctk.CTkButton(control_card, text="📁 Analyser Dossier", command=lambda: self.start_manual_scan(is_dir=True),
|
|
height=45, fg_color="#1E3A8A", hover_color=ACCENT_BLUE, font=ctk.CTkFont(weight="bold"))
|
|
self.btn_scan_d.grid(row=0, column=1, padx=15, pady=15, sticky="ew")
|
|
|
|
self.btn_db_update = ctk.CTkButton(control_card, text="🔄 MaJ Signatures", command=self.update_virus_db_prompt,
|
|
height=45, fg_color="#064E3B", hover_color="#059669", font=ctk.CTkFont(weight="bold"))
|
|
self.btn_db_update.grid(row=0, column=2, padx=15, pady=15, sticky="ew")
|
|
|
|
self.btn_rootkit = ctk.CTkButton(control_card, text="🕵️ Chasse aux Rootkits (rkhunter)", command=self.run_rootkit_scan,
|
|
height=45, fg_color="#4C1D95", hover_color=ACCENT_VIOLET, font=ctk.CTkFont(weight="bold"))
|
|
self.btn_rootkit.grid(row=1, column=0, columnspan=3, padx=15, pady=(0, 15), sticky="ew")
|
|
|
|
# 3. La barre de progression (Subtile, juste en dessous des contrôles)
|
|
self.scan_progress = ctk.CTkProgressBar(frame, mode="indeterminate", height=4, progress_color=ACCENT_BLUE, fg_color="transparent")
|
|
self.scan_progress.pack(fill="x", pady=(0, 15))
|
|
self.scan_progress.set(0)
|
|
|
|
# 4. Le Terminal Géant (Prend tout l'espace restant)
|
|
self.scan_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=14), fg_color=BG_CARD,
|
|
corner_radius=15, border_width=1, border_color="#374151")
|
|
self.scan_console.pack(fill="both", expand=True)
|
|
self.setup_console_tags(self.scan_console)
|
|
self.scan_console.insert("end", f"{self.get_time_prefix()}[*] Moteur d'analyse prêt.\n", "info")
|
|
|
|
# (Toutes tes méthodes internes restent identiques ici pour garantir le fonctionnement)
|
|
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", "violet")
|
|
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é.\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 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 nécessite les droits sudo :")
|
|
|
|
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()
|
|
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, ''):
|
|
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 mises à jour.\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()
|
|
|
|
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()
|
|
|
|
def run_clamav_scan(self, path, is_dir, console):
|
|
if not self.clamav_installed: return
|
|
console.insert("end", f"\n{self.get_time_prefix()}[*] Analyse de cible : {path}\n")
|
|
if console == self.scan_console: self.scan_progress.start()
|
|
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:
|
|
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()}[+] Cible propre.\n", "success")
|
|
else: 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")
|
|
|
|
# (J'ai appliqué le design "Carte" à toutes les autres vues pour un rendu homogène)
|
|
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, 30))
|
|
ctk.CTkLabel(header, text="Bouclier Temps Réel", font=ctk.CTkFont(size=38, weight="bold")).pack(side="left")
|
|
self.btn_toggle_shield = ctk.CTkButton(header, text="Activer 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=14), fg_color=BG_CARD, corner_radius=15, border_width=1, border_color="#374151")
|
|
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="Désactiver", 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="Activer 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()
|
|
|
|
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="Réseau & Pare-feu", font=ctk.CTkFont(size=38, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 30))
|
|
|
|
btn_frame = ctk.CTkFrame(frame, fg_color="transparent")
|
|
btn_frame.grid(row=1, column=0, sticky="ew", pady=(0, 20))
|
|
|
|
ctk.CTkButton(btn_frame, text="⚙️ Audit Processus", command=lambda: threading.Thread(target=self.perform_audit, daemon=True).start(), height=45, fg_color=BG_CARD, border_width=1).pack(side="left", padx=(0, 10))
|
|
ctk.CTkButton(btn_frame, text="🛡️ Statut UFW", command=self.check_firewall, height=45, fg_color=BG_CARD, border_width=1).pack(side="left", padx=(0, 10))
|
|
ctk.CTkButton(btn_frame, text="📡 WiFi Guard", command=self.run_wifi_guard_prompt, height=45, fg_color=ACCENT_BLUE, hover_color="#2563EB").pack(side="left")
|
|
|
|
self.audit_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=14), fg_color=BG_CARD, corner_radius=15, border_width=1, border_color="#374151")
|
|
self.audit_console.grid(row=2, column=0, sticky="nsew")
|
|
self.setup_console_tags(self.audit_console)
|
|
|
|
def perform_audit(self):
|
|
self.audit_console.delete("0.0", "end")
|
|
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", "success")
|
|
else: self.audit_console.insert("end", f"{self.get_time_prefix()}⚠️ PARE-FEU INACTIF.\n", "warning")
|
|
except: pass
|
|
|
|
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="Scan réseau (sudo) :")
|
|
|
|
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 arp-scan... \n", "info")
|
|
if not shutil.which("arp-scan"):
|
|
self.audit_console.insert("end", f"{self.get_time_prefix()}[!] 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 : {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.\n", "info")
|
|
except Exception as e: self.audit_console.insert("end", f"❌ Erreur : {e}\n", "danger")
|
|
|
|
def init_tools_view(self):
|
|
frame = ctk.CTkScrollableFrame(self, fg_color="transparent")
|
|
self.views["tools"] = frame
|
|
ctk.CTkLabel(frame, text="Outils Avancés", font=ctk.CTkFont(size=38, weight="bold")).pack(anchor="w", pady=(0, 30))
|
|
|
|
# OSINT Card
|
|
breach_card = ctk.CTkFrame(frame, fg_color=BG_CARD, corner_radius=15)
|
|
breach_card.pack(fill="x", pady=10, ipady=15)
|
|
ctk.CTkLabel(breach_card, text="🌐 OSINT : Vérificateur de Fuites", font=ctk.CTkFont(size=20, weight="bold"), text_color=ACCENT_VIOLET).pack(anchor="w", padx=20, pady=(10,5))
|
|
ctk.CTkLabel(breach_card, text="Le Dark Web contient-il votre adresse e-mail ?", text_color=TEXT_MUTED).pack(anchor="w", padx=20, pady=(0, 15))
|
|
|
|
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="exemple@domaine.com", width=350, height=45)
|
|
self.email_entry.pack(side="left", padx=(0, 15))
|
|
ctk.CTkButton(input_frame, text="Rechercher", fg_color=ACCENT_VIOLET, hover_color="#7C3AED", height=45, 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, font=ctk.CTkFont(size=14), justify="left")
|
|
self.breach_result_label.pack(anchor="w", padx=20, pady=15)
|
|
|
|
# Nettoyeur
|
|
clean_card = ctk.CTkFrame(frame, fg_color=BG_CARD, corner_radius=15)
|
|
clean_card.pack(fill="x", pady=10, ipady=15)
|
|
ctk.CTkLabel(clean_card, text="🧹 Optimisation", font=ctk.CTkFont(size=20, weight="bold"), text_color="#10B981").pack(anchor="w", padx=20, pady=(10,15))
|
|
ctk.CTkButton(clean_card, text="Purger le Cache Système", fg_color="#059669", hover_color="#047857", height=45, command=self.run_cleaner).pack(anchor="w", padx=20)
|
|
|
|
# Destructeur
|
|
shred_card = ctk.CTkFrame(frame, fg_color=BG_CARD, corner_radius=15)
|
|
shred_card.pack(fill="x", pady=10, ipady=15)
|
|
ctk.CTkLabel(shred_card, text="🔥 Suppression Militaire", font=ctk.CTkFont(size=20, weight="bold"), text_color="#EF4444").pack(anchor="w", padx=20, pady=(10,15))
|
|
ctk.CTkButton(shred_card, text="Détruire un fichier définitivement", fg_color="#DC2626", hover_color="#991B1B", height=45, command=self.run_shredder).pack(anchor="w", padx=20)
|
|
|
|
def run_breach_check(self):
|
|
email = self.email_entry.get().strip()
|
|
if not email or "@" not in email:
|
|
self.breach_result_var.set("❌ Adresse e-mail invalide.")
|
|
self.breach_result_label.configure(text_color="#EF4444")
|
|
return
|
|
self.breach_result_var.set("⏳ Interrogation des serveurs mondiaux...")
|
|
self.breach_result_label.configure(text_color=ACCENT_BLUE)
|
|
threading.Thread(target=self._exec_breach_check, args=(email,), daemon=True).start()
|
|
|
|
def _exec_breach_check(self, email):
|
|
try:
|
|
req = urllib.request.Request(f"https://api.xposedornot.com/v1/check-email/{email}", headers={'User-Agent': '7lna-Antivirus'})
|
|
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]
|
|
b_str = ", ".join(breaches[:4]) + ("..." if len(breaches) > 4 else "")
|
|
self.breach_result_var.set(f"🚨 DANGER : {len(breaches)} fuites trouvées !\nSources : {b_str}")
|
|
self.breach_result_label.configure(text_color="#EF4444")
|
|
else:
|
|
self.breach_result_var.set("✅ Identité sécurisée. Aucune fuite détectée.")
|
|
self.breach_result_label.configure(text_color="#10B981")
|
|
except urllib.error.HTTPError as e:
|
|
if e.code == 404:
|
|
self.breach_result_var.set("✅ Identité sécurisée. Aucune fuite détectée.")
|
|
self.breach_result_label.configure(text_color="#10B981")
|
|
else:
|
|
self.breach_result_var.set(f"❌ Erreur API ({e.code}).")
|
|
except: self.breach_result_var.set("❌ Impossible de joindre les serveurs.")
|
|
|
|
def run_cleaner(self):
|
|
paths = [os.path.expanduser("~/.local/share/Trash/files"), os.path.expanduser("~/.cache/thumbnails")]
|
|
for p in paths:
|
|
if os.path.exists(p):
|
|
for i in os.listdir(p):
|
|
item = os.path.join(p, i)
|
|
try: shutil.rmtree(item) if os.path.isdir(item) else os.remove(item)
|
|
except: pass
|
|
messagebox.showinfo("Optimisation", "Fichiers temporaires purgés.")
|
|
|
|
def run_shredder(self):
|
|
filepath = filedialog.askopenfilename()
|
|
if filepath and messagebox.askyesno("Destruction", f"Détruire DÉFINITIVEMENT ?\n\n{filepath}"):
|
|
try:
|
|
size = os.path.getsize(filepath)
|
|
for _ in range(3):
|
|
with open(filepath, "ba+", buffering=0) as f:
|
|
f.seek(0); f.write(os.urandom(size))
|
|
os.remove(filepath)
|
|
messagebox.showinfo("Succès", "Fichier détruit.")
|
|
except: pass
|
|
|
|
def init_schedule_view(self):
|
|
frame = ctk.CTkFrame(self, fg_color="transparent")
|
|
self.views["schedule"] = frame
|
|
ctk.CTkLabel(frame, text="Automatisation Cron", font=ctk.CTkFont(size=38, weight="bold")).pack(anchor="w", pady=(0, 30))
|
|
|
|
card = ctk.CTkFrame(frame, fg_color=BG_CARD, corner_radius=15)
|
|
card.pack(fill="x", ipady=20)
|
|
ctk.CTkLabel(card, text="Scan Journalier (Dossier Téléchargements)", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(20, 15))
|
|
|
|
time_frame = ctk.CTkFrame(card, fg_color="transparent")
|
|
time_frame.pack(pady=10)
|
|
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=80, height=40).pack(side="left", padx=5)
|
|
ctk.CTkLabel(time_frame, text=":").pack(side="left")
|
|
ctk.CTkComboBox(time_frame, values=["00", "15", "30", "45"], variable=self.cron_m_var, width=80, height=40).pack(side="left", padx=5)
|
|
|
|
ctk.CTkButton(card, text="Activer la planification", fg_color=ACCENT_BLUE, hover_color="#2563EB", height=45, command=self.setup_cronjob).pack(pady=(20,10))
|
|
ctk.CTkButton(card, text="Désactiver", fg_color="transparent", border_width=1, border_color="#EF4444", text_color="#EF4444", hover_color="#7F1D1D", height=45, command=self.remove_cronjob).pack()
|
|
|
|
def setup_cronjob(self):
|
|
cmd = f"{int(self.cron_m_var.get())} {int(self.cron_h_var.get())} * * * /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 "{cmd}") | crontab -')
|
|
messagebox.showinfo("Cron", "Scan quotidien configuré.")
|
|
except: pass
|
|
|
|
def remove_cronjob(self):
|
|
try: os.system(f'(crontab -l 2>/dev/null | grep -v "clamscan -r --move={QUARANTINE_DIR}") | crontab -')
|
|
except: pass
|
|
|
|
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 Sécurité", font=ctk.CTkFont(size=38, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 30))
|
|
self.history_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=14), fg_color=BG_CARD, corner_radius=15, border_width=1, border_color="#374151")
|
|
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 ✅ Aucun incident enregistré.\n")
|
|
else:
|
|
for e in reversed(data): self.history_console.insert("end", f"[{e['date']}] ☠️ {e['threat']}\n -> Fichier : {e['file']}\n\n")
|
|
except: pass
|
|
|
|
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, 30))
|
|
ctk.CTkLabel(header, text="Zone de Quarantaine", font=ctk.CTkFont(size=38, weight="bold")).pack(side="left")
|
|
ctk.CTkButton(header, text="Purger Tout", fg_color="#DC2626", hover_color="#991B1B", height=45, command=self.empty_quarantine).pack(side="right")
|
|
|
|
self.quarantine_listbox = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=14), fg_color=BG_CARD, corner_radius=15, border_width=1, border_color="#374151")
|
|
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 ✅ Zone propre.\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", "Détruire définitivement les virus ?"):
|
|
for f in files:
|
|
try: os.remove(f)
|
|
except: pass
|
|
self.refresh_quarantine_list()
|
|
|
|
def init_update_view(self):
|
|
frame = ctk.CTkFrame(self, fg_color="transparent")
|
|
self.views["update"] = frame
|
|
ctk.CTkLabel(frame, text="Mise à jour Logicielle", font=ctk.CTkFont(size=38, weight="bold")).pack(anchor="w", pady=(0, 30))
|
|
|
|
card = ctk.CTkFrame(frame, fg_color=BG_CARD, corner_radius=15)
|
|
card.pack(fill="x", ipady=20)
|
|
ctk.CTkLabel(card, text="Synchronisation Serveur (git.7ka1.com)", font=ctk.CTkFont(size=18, weight="bold")).pack(pady=(20, 15))
|
|
|
|
self.btn_update_soft = ctk.CTkButton(card, text="Télécharger la dernière version", fg_color=ACCENT_BLUE, hover_color="#2563EB", height=45, command=self.run_software_update)
|
|
self.btn_update_soft.pack(pady=10)
|
|
|
|
self.update_console = ctk.CTkTextbox(card, height=150, font=ctk.CTkFont(family="Consolas", size=13), fg_color=BG_MAIN, corner_radius=10)
|
|
self.update_console.pack(fill="x", padx=20, pady=15)
|
|
|
|
def run_software_update(self):
|
|
self.btn_update_soft.configure(state="disabled")
|
|
self.update_console.insert("end", "[*] Connexion au serveur Git...\n")
|
|
def fetch():
|
|
try:
|
|
urllib.request.urlretrieve(UPDATE_URL, os.path.abspath(__file__))
|
|
self.update_console.insert("end", "[+] Code source téléchargé et appliqué !\n")
|
|
messagebox.showinfo("Mise à jour", "Redémarrage de l'application requis.")
|
|
os._exit(0) # Force la fermeture propre pour relancer via le menu
|
|
except:
|
|
self.update_console.insert("end", "[-] Échec de la connexion.\n")
|
|
self.btn_update_soft.configure(state="normal")
|
|
threading.Thread(target=fetch).start()
|
|
|
|
if __name__ == "__main__":
|
|
app = Antivirus7LnA()
|
|
app.mainloop() |