# Composants Internes de Windows
> Une plongée technique complète dans l'architecture des composants internes de Windows pour développer des techniques d'évasion et des contre-mesures efficaces.
Publié le 2025-04-11 | Mis à jour le 2025-03-23 | Tags: cetp, windows, internals
https://xsec.fr/evasion/windows-internals/
---
import Callout from '@shared/components/Callout.astro'
```mermaid
graph TB
WA[Windows Architecture]
WA --> KM[Kernel Mode]
WA --> UM[User Mode]
KM --> EX[Executive]
KM --> K[Kernel]
KM --> HAL[HAL]
KM --> DD[Device Drivers]
UM --> SS[Subsystems]
UM --> APP[Applications]
EX --> PM[Process Manager]
EX --> MM[Memory Manager]
EX --> IOM[I/O Manager]
```
Windows divise son architecture en deux domaines de privilèges distincts : le **Mode Noyau** et le **Mode Utilisateur**.
En **Mode Noyau**, la couche **Executive** implémente les services principaux du système d'exploitation à travers des gestionnaires spécialisés :
- Le **Gestionnaire de Processus** gère la création, terminaison et planification des tâches
- Le **Gestionnaire de Mémoire** contrôle les espaces d'adressage virtuels et les opérations de pagination
- Le **Gestionnaire E/S** orchestre le flux de données entre les applications et le matériel en routant les **Paquets de Requête E/S** (IRP) à travers les piles de pilotes appropriées
- Le **Gestionnaire d'Objets** fournit un cadre unifié pour créer et gérer les ressources du noyau comme les mutex, événements et sémaphores via des tables de handles
- L'**application de la sécurité** incombe au **Moniteur de Référence de Sécurité** (SRM), qui valide les jetons d'accès contre les Listes de Contrôle d'Accès pour chaque opération sur ressource protégée
Sous l'Executive se trouve le **Noyau** lui-même, gérant la planification fondamentale du CPU, la distribution des interruptions et la synchronisation multiprocesseur en utilisant des primitives comme les spinlocks et objets dispatcher. La **Couche d'Abstraction Matérielle** (HAL) crée une interface cohérente vers diverses plateformes matérielles, isolant le noyau des détails spécifiques au processeur comme la gestion d'alimentation ACPI et les protocoles de communication multiprocesseur. Les **Pilotes de Périphériques**, bien qu'exécutés en Mode Noyau, sont des composants modulaires pouvant être chargés et déchargés dynamiquement pour contrôler les périphériques matériels ou fournir des services système.
En **Mode Utilisateur**, les applications s'exécutent avec des privilèges restreints, incapables d'accéder directement au matériel ou aux structures système critiques. Les **Sous-systèmes d'Environnement** fournissent des implémentations d'API que les applications utilisent pour demander des services système :
- Le **sous-système Win32** sert d'interface primaire pour les applications Windows traditionnelles
- Des ajouts plus récents comme **WSL2** permettent la compatibilité avec les binaires GNU/Linux grâce à une virtualisation légère
Les applications utilisateur n'interagissent jamais directement avec les composants du noyau ; elles invoquent plutôt des fonctions API documentées exposées par ces sous-systèmes. Ces appels sont traduits en requêtes de services système qui transitent du Mode Utilisateur vers le Mode Noyau à travers des points d'entrée soigneusement contrôlés. Par exemple, lorsqu'une application appelle `CreateFile()`, la requête passe par *kernel32.dll* et finalement *ntdll.dll*, qui exécute une transition supervisée vers le Mode Noyau où le service système `NtCreateFile` est effectué.
## Architecture des Processus Windows
```mermaid
graph TB
subgraph EPROCESS[Windows Process - EPROCESS]
direction TB
VAS[Virtual Address Space] --> PID[Process ID]
VAS --> PTE[Page Table Entries]
VAS --> CDS[Code/Data Sections]
EI[Executable Image] --> PEH[PE Headers]
subgraph Threads
ETH1[ETHREAD 1]
ETH2[ETHREAD 2]
end
subgraph Handles
FH[File Handles]
RK[Registry Keys]
SO[Synchronization Objects]
SH[Section Handles]
end
subgraph AT[Access Token]
USID[User SID]
GSID[Group SIDs]
PRIV[Privileges]
IL[Integrity Level]
end
subgraph PEB[Process Environment Block]
ENV[Environment Variables]
CLA[Command Line Arguments]
LM[Loaded Modules]
PH[Process Heaps]
end
end
```
Ce diagramme illustre la structure interne et les composants qui constituent un processus Windows. Au centre se trouve la structure `EPROCESS`, qui agit comme la représentation du processus par le noyau. Elle fournit des informations sur l'identité du processus, l'espace mémoire, les threads, les handles, les jetons d'accès et les détails d'environnement.
Le `Process ID` sert d'identifiant unique attribué à chaque processus, permettant au système de suivre et gérer les processus individuellement. L'`Espace d'Adressage Virtuel` est le bac à sable mémoire alloué au processus, contenant des sections comme le code, les données, la pile et le tas. Cet espace est géré via des tables de pages, qui mappent les adresses virtuelles vers la mémoire physique.
Les Threads, représentés par des objets `ETHREAD`, sont les unités d'exécution au sein d'un processus. Chaque thread a un `TEB` (Thread Environment Block) associé, qui contient des informations en mode utilisateur telles que la pile et le stockage local au thread. Plusieurs threads peuvent appartenir au même processus, supportant le parallélisme.
Les Handles font référence aux ressources avec lesquelles le processus interagit, telles que les fichiers ouverts, clés de registre, mutex et sections de mémoire partagée. Ces handles lient le processus aux objets et ressources du noyau qu'il utilise durant l'exécution.
Le `Jeton d'Accès` contient le contexte de sécurité du processus, définissant les `SID` d'utilisateur et de groupe, les `Privilèges` spécifiques, et le `Niveau d'Intégrité` du processus. Ce jeton gouverne ce que le processus peut accéder et exécuter en fonction des politiques de sécurité du système.
Le `PEB` (Process Environment Block) est une structure en mode utilisateur contenant des métadonnées spécifiques au processus comme les variables d'environnement, arguments de ligne de commande, modules chargés (DLL) et tas mémoire. Il est crucial pour lier les fonctionnalités entre le noyau et les applications en mode utilisateur.
Enfin, l'image exécutable décrit le code et les données du programme chargés en mémoire pour exécution. Elle inclut les `En-têtes PE` (format Portable Executable) et les sections telles que le code compilé et les données initialisées.
## Moteur de Gestion Processus/Thread
```mermaid
graph LR
W1[Start] --> W2[Ready]
W2 -->|Dispatch| W3[Running]
W3 -->|Preempt| W2
W3 -->|I/O Request| W4[Waiting]
W4 -->|I/O Complete| W2
W3 -->|Exit| W5[Terminated]
W5 --> W6[End]
```
Le Moteur de Gestion Processus/Thread gouverne le cycle de vie et l'exécution des threads via un modèle piloté par états. Les threads transitent entre les états **Prêt**, **En Cours**, **En Attente** et **Terminé**. Un thread commence dans l'état *Prêt*, entre en *Cours* lorsque le planificateur le distribue à un cœur CPU, et peut revenir à *Prêt* s'il est préempté par un thread de priorité supérieure. Lorsqu'un thread émet une requête E/S, il passe à l'état *En Attente* jusqu'à ce que l'opération se termine. La terminaison se produit lorsqu'un thread se termine ou est forcé à s'arrêter, déclenchant le nettoyage des ressources.
Windows emploie un **planificateur préemptif basé sur les priorités** avec 32 niveaux de priorité (0-31). Les priorités sont divisées en deux catégories : *priorité de base*, attribuée statiquement via des API comme `SetPriorityClass`, et *priorité dynamique*, ajustée par le planificateur pour optimiser la réactivité (par ex., augmenter les threads liés aux E/S dans les applications en premier plan). Les tranches de temps d'exécution (*quantum*) sont configurables via les paramètres de registre sous `HKLM\SYSTEM\CurrentControlSet\Control\PriorityControl`, permettant un réglage fin pour des charges de travail spécifiques.
Les structures de données clés du noyau sous-tendent ce système. La structure **KPROCESS** stocke les métadonnées à l'échelle du processus, incluant les statistiques de mémoire virtuelle, tables de handles et contexte de sécurité. **KTHREAD** suit les détails spécifiques au thread tels que le Thread Environment Block (TEB), les états de registres CPU et les files d'attente Asynchronous Procedure Call (APC). Le **Dispatcher Header** gère la synchronisation en signalant les transitions d'état des threads (par ex., acquisition de mutex) entre états *signalé* et *non-signalé*.
## Sous-système de Mémoire Virtuelle
```mermaid
graph TB
VM1[Virtual Address Space] --> VM2[Page Table]
VM2 --> VM3[Working Set]
VM3 --> VM4[Valid Pages]
VM3 --> VM5[Transition Pages]
VM6[Page Frame Number DB] --> VM7[Physical Memory]
VM8[Memory Manager] --> VM9[Demand Paging]
VM8 --> VM10[Modified Page Writer]
```
Le Sous-système de Mémoire Virtuelle gère comment Windows alloue, mappe et optimise la mémoire à travers les espaces physique et virtuel. À son cœur, le **Gestionnaire de Mémoire** traduit les adresses virtuelles utilisées par les applications en adresses physiques via une **structure de pagination à 4 niveaux** (PML4 → Pointeur de Répertoire de Pages → Répertoire de Pages → Table de Pages). Cette hiérarchie prend en charge les tailles de pages de 4KB et 2MB, équilibrant granularité et efficacité pour diverses charges de travail.
L'**Espace d'Adressage Virtuel** d'un processus est partitionné en sections logiques (code, données, pile) et mappé aux **Tables de Pages**, qui suivent l'emplacement de chaque page en mémoire physique ou disque. L'**Ensemble de Travail** - un sous-ensemble de ces pages résidant activement en RAM - est géré par un **algorithme d'horloge** qui évince les pages les moins récemment utilisées pour libérer de l'espace. Les pages dans l'ensemble de travail sont catégorisées comme **Valides** (immédiatement accessibles) ou **Transition** (en cours de chargement ou d'écriture sur disque).
La **Base de Données de Numéro de Cadre de Page (PFN)** agit comme un index global de toutes les pages de mémoire physique, enregistrant leurs états (actif, standby, modifié ou libre). Lorsqu'un **Défaut de Page** se produit, le Gestionnaire de Mémoire détermine son type : un **Défaut Dur** nécessite de récupérer la page depuis le disque (pagefile.sys), tandis qu'un **Défaut Doux** implique de remapper une page déjà en mémoire physique (par ex., opérations copy-on-write).
## Mécanique du Sous-système E/S
```mermaid
sequenceDiagram
participant App as Application
participant IO as I/O Manager
participant FSD as NTFS Driver
participant Vol as Volume Manager
participant HW as Hardware
App->>IO: IRP Creation
IO->>FSD: IRP Routing
FSD->>Vol: IRP Completion
Vol->>HW: DMA Transfer
HW-->>Vol: Interrupt
Vol-->>IO: Status Return
IO-->>App: I/O Complete
```
La Mécanique du Sous-système E/S dans Windows orchestre le flux complexe de données entre les applications et le matériel via une architecture par paquets en couches. Lorsqu'une application initie une opération E/S, le Gestionnaire E/S crée un **Paquet de Requête E/S (IRP)**, une structure de données représentant l'opération tout au long de son cycle de vie. Cet IRP contient des champs essentiels : le code **MajorFunction** identifiant le type d'opération (tel que `IRP_MJ_READ` ou `IRP_MJ_WRITE`), les **Paramètres** spécifiques au périphérique et à l'opération (adresses de buffer, offsets, longueurs), et une adresse de callback **Routine de Complétion** pour notification asynchrone lorsque l'opération se termine.
Une fois créé, l'IRP traverse une pile de pilotes, chacun gérant une couche spécifique d'abstraction. Le Gestionnaire E/S route l'IRP vers le pilote de système de fichiers approprié, tel que NTFS, qui interprète les métadonnées de fichier via sa **Table de Fichiers Maîtres (MFT)** pour localiser l'emplacement de stockage physique. NTFS passe ensuite l'IRP traité aux pilotes de gestion de volumes comme **VolMgr**, qui traduisent les volumes logiques en partitions physiques. Enfin, l'IRP atteint les pilotes de port de stockage tels que **Storport**, qui communiquent avec des pilotes miniport spécifiques au matériel conçus pour des contrôleurs de stockage particuliers.
Au niveau matériel, les opérations de stockage modernes exploitent l'**Accès Direct à la Mémoire (DMA)** pour transférer des données entre la mémoire et les périphériques sans intervention du CPU. Le pilote de disque prépare des **listes scatter/gather** qui mappent des buffers mémoire potentiellement non contigus dans un format que le matériel peut traiter efficacement. Ce mappage permet au contrôleur de stockage de lire ou écrire directement dans la mémoire système, réduisant significativement la charge CPU lors des transferts de données.
Lorsque le matériel termine l'opération, il signale le système via une interruption. Cette interruption déclenche le pilote de disque pour traiter l'état de complétion et propager les résultats vers le haut de la pile de pilotes. Chaque pilote dans la chaîne peut effectuer un post-traitement avant de passer le contrôle vers le haut. Finalement, le Gestionnaire E/S reçoit l'IRP complété et soit notifie l'application directement (pour les appels synchrones) soit invoque la routine de complétion enregistrée (pour les opérations asynchrones).
## Couche d'Application de la Sécurité
```mermaid
graph LR
subgraph Security Components
AT2[Access Token]
SD[Security Descriptor]
AT2 --- PRIV2[Privileges]
AT2 --- SID2[SID Groups]
SD --- DACL[DACL]
SD --- SACL[SACL]
end
AR[Access Request] --> SRM[Security Reference Monitor]
SRM --> CT{Check Token}
CT -->|Allowed| ACC[Access Granted]
CT -->|Denied| DEN[Access Denied]
```
La Couche d'Application de la Sécurité dans Windows implémente un modèle de contrôle d'accès centré sur le **Moniteur de Référence de Sécurité (SRM)**, un composant en mode noyau qui valide toutes les tentatives d'accès aux ressources. Lorsqu'une application ou un processus tente d'accéder à un objet sécurisé, le SRM évalue la requête en comparant le **Jeton d'Accès** du demandeur contre le **Descripteur de Sécurité** de l'objet. Ce jeton sert de contexte de sécurité du processus, contenant l'**Identifiant de Sécurité (SID)** de l'utilisateur, les appartenances aux groupes, et des **Privilèges** spécifiques tels que `SeDebugPrivilege` ou `SeBackupPrivilege` qui accordent des capacités spéciales au-delà des permissions standard.
Les Descripteurs de Sécurité attachés à chaque objet sécurisable contiennent deux listes de contrôle d'accès critiques. La **Liste de Contrôle d'Accès Discrétionnaire (DACL)** définit quels utilisateurs ou groupes peuvent accéder à l'objet et quelles opérations ils peuvent effectuer via des **Entrées de Contrôle d'Accès (ACE)**. Chaque ACE spécifie un SID et les permissions autorisées ou refusées. La **Liste de Contrôle d'Accès Système (SACL)** active l'audit de sécurité en définissant quelles tentatives d'accès doivent générer des entrées dans le journal d'événements de sécurité, essentiel pour la surveillance de conformité et l'analyse forensique.
Windows étend ce modèle avec le **Contrôle d'Intégrité Obligatoire**, qui attribue des niveaux d'intégrité (Bas, Moyen, Haut, Système) aux processus et objets. Un processus ne peut pas modifier des objets avec des niveaux d'intégrité supérieurs indépendamment des permissions DACL, créant un système de protection hiérarchique. Cela empêche les processus moins privilégiés de falsifier des composants système plus sensibles. Pour une protection améliorée des données d'identification, Windows implémente **Credential Guard**, qui isole le **Service de Sous-système d'Autorité de Sécurité Locale (LSASS)** dans un environnement sécurisé virtualisé utilisant la technologie Hyper-V, empêchant les malwares d'extraire les jetons d'authentification et hashes même s'ils obtiennent un accès au niveau système (normalement)...
### Exemple pratique
Toutes les actions suivantes sont effectuées dans un contexte de haute intégrité.
En utilisant `WinDbg`, nous pouvons interagir avec le noyau et modifier le **Token** d'un **processus Windows** :

Changeons par exemple le **Token** du processus **cmd.exe**. Nous devons d'abord récupérer son `EPROCESS_ID` :
```txt title="Énumérer le processus cmd.exe pour obtenir l'adresse EPROCESS"
!process 0 0 cmd.exe
```

Puis regarder l'**offset** actuel de la valeur **Token** :
```txt title="Afficher la structure EPROCESS montrant l'offset du Token"
dt nt!_eprocess Token
```

Nous pouvons voir que son offset est `0x4b8` et son type est `_EX_FAST_REF`.
Récupérons l'adresse **Eprocess** du système depuis system :
```txt title="Énumérer le processus SYSTEM pour obtenir son adresse EPROCESS"
!process 0 0 system
```

Et obtenons la valeur Token de system en ajoutant l'offset :
```txt title="Lire la valeur du token SYSTEM à l'offset 0x4b8"
imagedt _EX_FAST_REF +0x4b8
```

Remplaçons le token du processus CMD par celui du system :
```txt title="Écrire le token SYSTEM dans l'EPROCESS de cmd.exe"
eq +0x4b8
```
Et vérifions le changement :
```txt title="Vérifier que la modification du token a réussi"
dt _EX_FAST_REF +0x4b8
```

Continuons l'exécution du noyau :

## Moteur de Base de Données du Registre
```mermaid
graph TB
HKLM[HKEY_LOCAL_MACHINE] --> SYS[SYSTEM]
HKLM --> SW[SOFTWARE]
HKLM --> HWR[HARDWARE]
HKU[HKEY_USERS] --> KSUID[SID]
HCC[HKEY_CURRENT_CONFIG] --> CCS[System CurrentControlSet]
```
Le Moteur de Base de Données du Registre sert de dépôt de configuration hiérarchique centralisé pour Windows, stockant les paramètres pour le système d'exploitation, les applications, les utilisateurs et le matériel. Il organise les données dans une structure arborescente avec des clés racines majeures incluant **HKEY_LOCAL_MACHINE** (HKLM), qui stocke les paramètres à l'échelle du système dans des ruches telles que SYSTEM (configurations de démarrage et pilotes), SOFTWARE (paramètres d'application), et HARDWARE (informations de périphériques détectés). Les paramètres spécifiques aux utilisateurs résident dans **HKEY_USERS**, organisés par Identifiant de Sécurité (SID), tandis que **HKEY_CURRENT_CONFIG** fournit une vue dans le profil matériel actuel, référençant principalement les paramètres système du jeu de contrôle CurrentControlSet.
Physiquement, le Registre est stocké sur plusieurs fichiers de ruche, chacun correspondant à une section majeure de la hiérarchie du Registre. Ces fichiers résident principalement dans %SystemRoot%\System32\Config et dans les répertoires de profils utilisateur. Les fichiers de ruche emploient une structure sophistiquée de type base de données avec support de transactions atomiques pour maintenir l'intégrité des données. Lorsque des modifications du Registre se produisent, les changements sont d'abord écrits dans des fichiers journaux de transactions (log1/log2) plutôt que directement dans la ruche, créant un système de journalisation. Cette approche protège contre la corruption lors de plantages système ou pannes de courant, car les modifications incomplètes restent dans les journaux jusqu'à ce qu'elles puissent être validées ou annulées en toute sécurité lors du prochain démarrage système.
Internement, le Registre utilise un modèle de stockage basé sur les cellules où les **Cellules de Clés** contiennent des métadonnées sur les clés du Registre, incluant les horodatages, descripteurs de sécurité et références aux sous-clés et valeurs. Pour optimiser les performances, le Registre implémente l'**Optimisation Fast Leaf** utilisant des structures de données d'arbre B+ qui permettent des recherches de clés efficaces avec une complexité O(log n). Cette indexation est cruciale pour l'accès rapide aux données du Registre, surtout dans les ruches contenant des milliers d'entrées, comme la ruche SOFTWARE sur une installation typique.
Pour les développeurs et les produits de sécurité, Windows fournit un mécanisme de **Filtrage du Registre** via les API `CmRegisterCallback` et `CmRegisterCallbackEx`. Ces fonctions permettent aux pilotes en mode noyau d'intercepter les opérations du Registre avant leur exécution, permettant la surveillance, modification ou blocage de l'accès au Registre. Cette capacité forme la fondation de nombreux produits antivirus, solutions de contrôle d'application et outils de surveillance système qui doivent protéger ou auditer les opérations sensibles du Registre.
Le **Mécanisme de Récupération** du Registre s'active au démarrage du système lorsque Windows détecte des ruches "sales", celles qui n'ont pas été fermées proprement lors de la session précédente. Le système applique ou annule automatiquement les transactions en attente depuis les fichiers journaux pour restaurer la cohérence. En cas de corruption sévère, Windows peut également récupérer depuis les ruches de sauvegarde (fichiers *.bak) ou les points de restauration.
## Primitives de Synchronisation du Noyau
```mermaid
graph LR
IR1[Start] --> IR2[PASSIVE_LEVEL]
IR2 -->|APC Delivery| IR3[APC_LEVEL]
IR3 -->|DPC Request| IR4[DISPATCH_LEVEL]
IR4 -->|Hardware Interrupt| IR5[DIRQL]
IR5 -->|NMI/PMC| IR6[HIGH_LEVEL]
```
Les Primitives de Synchronisation du Noyau dans Windows établissent un cadre hiérarchique de gestion des interruptions et de coordination des threads essentiel pour la stabilité et les performances du système. Au cœur de cette architecture se trouve le mécanisme **IRQL (Interrupt Request Level)**, qui priorise l'exécution du code en fonction de l'urgence et de la criticité. Le diagramme illustre comment les IRQL forment une progression des niveaux inférieurs aux supérieurs, chaque transition représentant une priorité d'exécution croissante et un contexte d'exécution plus restrictif.
À **PASSIVE_LEVEL** (IRQL 0), la priorité la plus basse, le code en mode utilisateur et la plupart des opérations en mode noyau s'exécutent. Ce niveau permet les commutations de contexte de threads et les défauts de page, permettant aux opérations d'accès mémoire de se terminer de manière transparente. Lorsque le système doit délivrer des **Appels de Procédure Asynchrones (APC)** à un thread, il élève temporairement l'IRQL à **APC_LEVEL** (IRQL 1), empêchant d'autres APC d'interrompre pendant le traitement des APC actuels. Pour les opérations de planification et de traitement différé, le système s'élève à **DISPATCH_LEVEL** (IRQL 2), où le planificateur de threads s'exécute et les **Appels de Procédure Différés (DPC)** s'exécutent. À ce niveau, les commutations de contexte de threads et les défauts de page sont interdits, contraignant quelles opérations peuvent se produire en toute sécurité.
Les interruptions matérielles se produisent aux niveaux **Device IRQL (DIRQL)** au-dessus de DISPATCH_LEVEL, chaque périphérique se voyant attribuer un IRQL spécifique en fonction de sa priorité. Les cartes réseau, contrôleurs de disque et autres périphériques déclenchent des interruptions qui préemptent le code s'exécutant à des IRQL inférieurs. Au niveau le plus élevé, **HIGH_LEVEL (IRQL 31)**, les interruptions critiques comme les Interruptions Non-Masquables (NMI) et les dépassements de Compteur de Surveillance de Performance s'exécutent, préemptant toutes les autres activités système.
Windows fournit des primitives de synchronisation spécialisées optimisées pour différents scénarios à travers ces niveaux IRQL. Les **Spinlocks**, acquis via des fonctions comme `KeAcquireSpinLockAtDpcLevel`, fournissent une protection d'accès exclusif dans les environnements multiprocesseur en interrogeant continuellement (spinning) jusqu'à ce que le verrou devienne disponible. Contrairement aux objets de synchronisation en attente, les spinlocks ne renoncent pas au temps CPU, les rendant adaptés pour des sections critiques brèves à DISPATCH_LEVEL ou au-dessus. Pour des modèles de synchronisation plus complexes, les **Ressources Executive** implémentent une sémantique lecteur-écrivain via `ExAcquireResourceSharedLite` et `ExAcquireResourceExclusiveLite`, permettant plusieurs lecteurs concurrents mais un accès exclusif pour les écrivains.