Composants internes de Windows
Publié le 12 min de lecture
Mis à jour le
Dans cette série32 min de lecture au total
- Composants internes de Windows
- Composants Internes des EDR
- Neutralisation d'EDR
Suivre une opération Windows de l’API user-mode jusqu’aux objets, gestionnaires et contraintes du noyau.
Windows Internals devient vite rébarbatif lorsqu’il est présenté comme une liste de structures. Le fil directeur de cet article est donc une opération : un thread d’application demande l’accès à une ressource, le système traduit cette demande, vérifie son contexte, fait circuler une requête et conserve l’état nécessaire à son exécution.
Ce parcours permet de répondre à trois questions à chaque couche :
- quel composant possède le contrat ;
- quelles données traversent la frontière ;
- quelles conclusions sont stables, et lesquelles dépendent du build observé.
Traverser la frontière user-mode et kernel-mode
Une application s’exécute en user-mode, dans un espace d’adressage isolé. Elle utilise des bibliothèques qui exposent des interfaces Win32 ou plus spécialisées. Ces interfaces peuvent valider des paramètres, adapter des formats et, lorsque l’opération requiert un service du noyau, rejoindre une routine Native API exportée par ntdll.dll.
La transition système change le niveau de privilège, mais ne transforme pas l’application en code kernel. ntoskrnl.exe reprend l’exécution dans un point d’entrée contrôlé, valide les arguments selon le service demandé puis mobilise le gestionnaire concerné : objets, processus, mémoire, entrées/sorties ou configuration.
Le Hardware Abstraction Layer (HAL), ou couche d’abstraction matérielle, isole certains détails de plateforme, notamment autour des interruptions et de l’accès au matériel. Les drivers s’intègrent quant à eux dans des piles de devices ou appellent des interfaces supportées du noyau. Leur présence en kernel-mode leur donne un impact élevé, mais pas le droit de contourner les contrats d’Interrupt Request Level (IRQL), de mémoire ou d’objets.
Cette architecture établit où s’effectue la transition de privilège. Elle ne décrit pas encore l’état durable sur lequel le noyau agit. Pour suivre une opération dans le temps, il faut distinguer les objets processus et thread des structures user-mode qui les accompagnent.
Processus et threads : séparer les rôles
EPROCESS est l’objet processus opaque utilisé par le noyau. Microsoft documente son rôle et les routines supportées qui acceptent un pointeur vers cet objet, mais pas un contrat permettant aux drivers d’en modifier librement les champs. Les offsets affichés par un débogueur sont donc des observations propres aux symboles et au build chargés.
Conceptuellement, EPROCESS porte l’état executive du processus et embarque un KPROCESS associé au dispatcher et à l’ordonnancement. Un thread est représenté par un objet ETHREAD, qui embarque un KTHREAD pour son état d’exécution kernel. Ces relations sont utiles pour comprendre le modèle ; leurs layouts ne constituent pas une ABI stable pour un driver.
Le Process Environment Block (PEB) et le Thread Environment Block (TEB) résident en user-mode. Le PEB expose notamment des informations relatives au processus et à son chargeur ; le TEB porte de l’état propre au thread. Un outil de sécurité peut les lire dans certains contextes, mais leur emplacement en user-mode implique qu’ils ne remplacent pas l’autorité des objets kernel.
Les handles relient le processus à des objets tels que fichiers, événements, sections ou clés de registre. Un handle n’est pas l’objet lui-même : c’est une entrée dans un espace de handles, associée à des droits accordés. Le jeton primaire du processus fournit par ailleurs le contexte de sécurité utilisé lors de nombreux contrôles d’accès.
Ces structures décrivent qui exécute et quels objets lui sont accessibles. Elles ne disent pas quand un thread obtient effectivement un processeur. L’étape suivante est donc le cycle d’ordonnancement.
Ordonnancement : du thread prêt au thread en attente
Windows utilise un ordonnanceur préemptif fondé sur les priorités. Un thread prêt est éligible à l’exécution. Lorsqu’il est distribué sur un processeur, il passe en cours d’exécution ; une préemption peut le rendre à nouveau prêt. Une attente sur une E/S, un objet dispatcher ou un délai le retire du processeur jusqu’à ce que la condition soit satisfaite.
La priorité d’un thread et l’IRQL courant sont deux notions différentes. La priorité aide l’ordonnanceur à choisir entre des threads prêts. L’IRQL décrit un niveau d’exécution par processeur qui masque certaines interruptions et contraint les opérations kernel autorisées. Un thread ordinaire s’exécute à PASSIVE_LEVEL, même si sa priorité d’ordonnancement est élevée.
L’ordonnanceur explique quand le code s’exécute, mais pas où résident ses instructions et ses données. Pour cela, Windows interpose un espace d’adressage virtuel et un gestionnaire de mémoire.
Mémoire virtuelle : traduire une adresse, retrouver son contenu
Chaque processus manipule des adresses virtuelles. Les tables de pages de l’architecture traduisent ces adresses vers des cadres de mémoire physique lorsque la page est résidente. La base de données Page Frame Number (PFN) suit l’état des cadres physiques ; l’ensemble de travail d’un processus décrit les pages qui lui sont actuellement résidentes selon les politiques du gestionnaire de mémoire.
Une entrée non valide ne signifie pas nécessairement que les données sont absentes. Un défaut de page peut être satisfait depuis une liste mémoire, par une page privée enregistrée dans le fichier d’échange ou par les octets d’un fichier mappé. La source exacte dépend du type de mappage et de l’état courant.
Le gestionnaire de mémoire établit donc comment un buffer devient accessible au CPU. Il ne transporte pas à lui seul une demande jusqu’au système de fichiers ou au matériel. Ce transport appartient au modèle d’entrées/sorties.
Entrées/sorties : la requête descend, la complétion remonte
Pour ouvrir un fichier, une application peut appeler CreateFileW. Cette API est exportée par Kernel32.dll, déclarée dans fileapi.h et liée avec Kernel32.lib; selon le build, l’export peut relayer son implémentation vers KernelBase.dll. La Native API correspondante, NtCreateFile, est exportée par ntdll.dll et déclarée dans winternl.h.
Après la transition système, le gestionnaire d’entrées/sorties de ntoskrnl.exe construit une requête. Dans le modèle Windows Driver Model (WDM), elle est généralement représentée par un I/O Request Packet (IRP). Une opération d’ouverture porte le code majeur IRP_MJ_CREATE. Chaque driver de la pile reçoit une vue de paramètres adaptée à son niveau, peut traiter la demande ou la transmettre vers le bas.
Un chemin fichier peut traverser des minifilters, le système de fichiers, des drivers de volume puis de stockage. Cette liste est conceptuelle : les composants réellement présents dépendent de la configuration, du type de device et de l’opération. La requête descend vers le device ; le statut, le nombre d’octets et les éventuelles données remontent par les routines de complétion.
Le Direct Memory Access (DMA), ou accès direct à la mémoire, permet à certains devices de transférer des données sans faire copier chaque octet par le CPU. Des Memory Descriptor Lists (MDL) et mécanismes de mappage décrivent les buffers selon le contrat du driver et de la plateforme.
Le modèle E/S explique comment une demande atteint son fournisseur. Il ne suffit pas à décider si l’appelant possède les droits requis. Avant de remettre un handle exploitable, Windows doit comparer le contexte de sécurité de l’appelant à la politique de l’objet.
Contrôle d’accès : calculer les droits du handle
Un jeton d’accès contient notamment les Security Identifiers (SID) de l’utilisateur et de ses groupes, ainsi que des privilèges. Un descripteur de sécurité identifie le propriétaire d’un objet sécurisable et peut contenir une Discretionary Access Control List (DACL), qui autorise ou refuse des droits, ainsi qu’une System Access Control List (SACL), qui configure l’audit.
Le contrôle compare le masque d’accès demandé au contexte de l’appelant et aux règles de l’objet. D’autres mécanismes peuvent intervenir, notamment les privilèges, le Mandatory Integrity Control et les vérifications propres au type d’objet. En cas de succès, le handle reçu porte les droits effectivement accordés ; il ne confère pas automatiquement tous les droits possibles sur l’objet.
Observation dans WinDbg : un champ interne n’est pas un contrat
La première vue établit le contexte général de la session WinDbg.
La commande !process 0 0 cmd.exe retrouve l’adresse de l’objet processus associé à cmd.exe.
dt nt!_EPROCESS Token interroge les symboles du build courant. L’offset visible dans la capture ne doit jamais être réutilisé comme constante universelle.
La même énumération retrouve l’objet du processus System, utilisé ici uniquement pour comparer deux champs.
Le champ est de type _EX_FAST_REF, c’est-à-dire une référence d’objet qui combine un pointeur aligné et des bits de comptage. Lire seulement l’adresse sans comprendre ces bits conduit à une interprétation incorrecte.
La capture suivante montre l’état après une écriture expérimentale. Elle démontre qu’un débogueur kernel peut modifier le layout observé, pas que ce layout est une interface supportée.
La reprise de l’exécution mesure ensuite les conséquences sur la cible.
Ce cas montre pourquoi un offset interne ne doit pas être confondu avec une API. Il manque encore un type de ressource central pour la configuration : le registre, dont les noms logiques sont reliés à des ruches et à des callbacks spécialisés.
Registre : des vues logiques aux ruches
Le registre organise clés et valeurs dans des vues hiérarchiques. Une ruche est un groupe logique de clés, sous-clés et valeurs associé à un ensemble de fichiers de support chargé en mémoire. Les ruches système principales utilisent notamment des fichiers sous %SystemRoot%\System32\Config; les profils utilisateurs possèdent leurs propres fichiers.
Les fichiers de journalisation participent à la cohérence et à la récupération. Les cellules, index et autres structures internes expliquent l’implémentation d’un build, mais une application doit utiliser les API documentées plutôt que dépendre de leur layout.
Les fonctions RegOpenKeyExW et RegSetValueExW sont exposées par Advapi32.dll, déclarées dans winreg.h et liées avec Advapi32.lib. Elles rejoignent les services Native API de ntdll.dll, puis le Configuration Manager de ntoskrnl.exe.
Un driver de filtrage peut appeler CmRegisterCallbackEx. Cette routine est exécutée par ntoskrnl.exe, déclarée dans wdm.h et liée avec NtosKrnl.lib. Elle inscrit une fonction fournie par le driver et une altitude. Le Configuration Manager livre ensuite des classes REG_NOTIFY_CLASS distinctes avant ou après les opérations. Les données disponibles et les modifications permises dépendent de la classe exacte.
Le registre ajoute une source de configuration et de télémétrie. Il ne supprime pas les contraintes du contexte kernel qui exécute le callback. Pour savoir quelles fonctions et quelles mémoires restent sûres à cet instant, il faut enfin comprendre l’IRQL.
IRQL et synchronisation : le contexte fait partie du contrat
L’Interrupt Request Level (IRQL) est un état par processeur. En élevant l’IRQL, Windows masque certaines interruptions de niveau inférieur ou égal et restreint les opérations sûres. Le niveau ne mesure pas la « puissance » d’un driver ; il exprime un contexte d’exécution.
À PASSIVE_LEVEL, une routine peut attendre et accéder à de la mémoire pageable si son propre contrat l’autorise. À partir de DISPATCH_LEVEL, elle ne doit pas effectuer une attente bloquante ni provoquer de défaut de page. Les Device IRQL (DIRQL) servent aux routines d’interruption de devices. Les valeurs exactes dépendent de l’architecture ; HIGH_LEVEL désigne symboliquement le niveau le plus élevé.
KeAcquireSpinLock est une macro déclarée dans wdm.h et liée selon le WDK avec Hal.lib. Elle élève le processeur à DISPATCH_LEVEL, acquiert le spinlock et rend l’ancien IRQL à l’appelant. La section protégée doit rester non pageable et très courte.
Une ressource executive répond à un autre besoin. ExAcquireResourceSharedLite et ExAcquireResourceExclusiveLite fournissent un verrou lecteur-écrivain susceptible d’attendre. Ces routines sont exécutées par ntoskrnl.exe, déclarées dans wdm.h et liées avec NtosKrnl.lib; leur contrat impose notamment un IRQL compatible et la gestion des APC kernel normaux. Elles ne sont pas interchangeables avec un spinlock à DISPATCH_LEVEL.
Conclusion
Le fonctionnement interne de Windows se lit comme une chaîne de contrats. Une API prépare une demande, la Native API traverse la frontière, un gestionnaire sélectionne des objets et des droits, les requêtes circulent dans des piles, puis l’ordonnanceur, la mémoire et l’IRQL bornent l’exécution.
Cette base rend les deux articles suivants plus lisibles sans répéter les mêmes définitions : l’analyse technique des internals EDR applique ces contrats aux capteurs, tandis que Neutralisation d’EDR étudie l’écart entre confiance de chargement et autorisation dans le protocole d’un driver.
Références
- Microsoft, Windows kernel opaque structures et Thread Priorities.
- Microsoft, Memory management for Windows drivers et Map Registers.
- Microsoft,
CreateFileW,NtCreateFileet Overview of the Windows I/O Model. - Microsoft, Parts of the Access Control Model.
- Microsoft, Registry Hives, Filtering Registry Calls et
CmRegisterCallbackEx. - Microsoft, Managing hardware priorities,
KeAcquireSpinLocketExAcquireResourceSharedLite. - Pavel Yosifovich, Mark Russinovich, David Solomon et Alex Ionescu, Windows Internals, 7e édition, Microsoft Press.






