Object Manager et Lifetime des objets Kernel

Ce cours dédiée au développement de drivers en kernel mode sans Framework

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 144
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Object Manager et Lifetime des objets Kernel

Post by Hydraxx »

Object Manager et Lifetime des objets Kernel

Windows repose sur un modèle objet centralisé. Processus, threads, fichiers, événements, mutex, sections, devices et plusieurs autres ressources sont représentés par des objets kernel.

Ce cours regroupe deux notions étroitement liées : l'Object Manager et la durée de vie des objets.


Sommaire
  • 1. Rôle de l'Object Manager
  • 2. Objet kernel et handle
  • 3. Tables de handles
  • 4. Namespace NT
  • 5. OBJECT_ATTRIBUTES
  • 6. UNICODE_STRING
  • 7. DRIVER_OBJECT et DEVICE_OBJECT
  • 8. Handles kernel
  • 9. ObReferenceObjectByHandle
  • 10. ObReferenceObject et ObDereferenceObject
  • 11. Handle Count et Pointer Count
  • 12. Pourquoi le lifetime est critique
  • 13. IRP_MJ_CLEANUP et IRP_MJ_CLOSE
  • 14. Rundown Protection
  • 15. Fuites de références
  • 16. Use-after-free
  • 17. Symétrie des ressources
  • 18. Sécurité des objets
  • 19. Bonnes pratiques

1. Rôle de l'Object Manager

L'Object Manager fournit une infrastructure commune pour :
  • créer les objets
  • les nommer
  • les stocker dans le namespace
  • gérer leurs handles
  • appliquer la sécurité
  • gérer les références
  • déterminer leur durée de vie

2. Objet kernel et handle

Un handle n'est pas un pointeur direct vers l'objet.

Conceptuellement :

Code: Select all

HANDLE
   |
   v
Handle Table Entry
   |
   v
Kernel Object
Plusieurs handles peuvent référencer le même objet.


3. Tables de handles

Chaque processus possède une table de handles.

Un handle utilisateur n'est donc pas une adresse kernel exploitable directement.

Le kernel doit utiliser les API prévues pour résoudre ou référencer l'objet.


4. Namespace NT

Les objets nommés apparaissent dans un namespace hiérarchique.

Exemples :

Code: Select all

\Device
\Driver
\KnownDlls
\BaseNamedObjects
\Sessions
\GLOBAL??
WinObj permet d'explorer une grande partie de ce namespace.


5. OBJECT_ATTRIBUTES

De nombreuses API natives prennent :

Code: Select all

OBJECT_ATTRIBUTES
Initialisation courante :

Code: Select all

InitializeObjectAttributes(
    &oa,
    &name,
    OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
    nullptr,
    nullptr
);
Champs importants :
  • ObjectName
  • RootDirectory
  • Attributes
  • SecurityDescriptor

6. UNICODE_STRING

Les noms kernel utilisent souvent :

Code: Select all

UNICODE_STRING
Exemple :

Code: Select all

UNICODE_STRING name;
RtlInitUnicodeString(&name, L"\\Device\\MyDevice");
Attention :
  • Length est en octets
  • MaximumLength est en octets
  • la chaîne n'est pas toujours null-terminated

7. DRIVER_OBJECT et DEVICE_OBJECT

Un driver possède un :

Code: Select all

DRIVER_OBJECT
et peut créer plusieurs :

Code: Select all

DEVICE_OBJECT
Exemple :

Code: Select all

IoCreateDevice(
    DriverObject,
    0,
    &DeviceName,
    FILE_DEVICE_UNKNOWN,
    0,
    FALSE,
    &DeviceObject
);
L'objet peut ensuite être exposé via un symbolic link.


8. Handles kernel

Lorsqu'un driver ouvre un objet via une API Zw*, il peut utiliser :

Code: Select all

OBJ_KERNEL_HANDLE
Cela place le handle dans un contexte réservé au kernel et évite qu'il soit directement exploitable par du code user mode.


9. ObReferenceObjectByHandle

Cette fonction permet d'obtenir un pointeur d'objet à partir d'un handle.

Exemple conceptuel :

Code: Select all

PVOID object = nullptr;

status = ObReferenceObjectByHandle(
    Handle,
    DesiredAccess,
    ObjectType,
    KernelMode,
    &object,
    nullptr
);
En cas de succès, une référence a été prise.


10. ObReferenceObject et ObDereferenceObject

Une référence explicite garantit que l'objet reste vivant.

Code: Select all

ObReferenceObject(Object);

// utilisation

ObDereferenceObject(Object);
Chaque acquisition doit être équilibrée.


11. Handle Count et Pointer Count

Un objet peut avoir :
  • des handles ouverts
  • des références kernel
Le nombre de handles et le nombre de références ne sont donc pas forcément identiques.

Fermer le dernier handle ne signifie pas nécessairement que l'objet est immédiatement détruit.


12. Pourquoi le lifetime est critique

Un driver peut garder un pointeur pendant qu'un autre thread détruit l'objet.

Le problème classique :

Code: Select all

if (GlobalObject != nullptr)
    Use(GlobalObject);
Entre le test et Use(), un autre thread peut libérer l'objet.

Il faut donc une synchronisation correcte et/ou une référence.


13. IRP_MJ_CLEANUP et IRP_MJ_CLOSE

Pour les objets fichier :

CLEANUP correspond généralement à la fin de l'utilisation des handles associés à l'ouverture.

CLOSE arrive lorsque le FILE_OBJECT peut réellement être détruit.

Cette distinction est directement liée au lifetime.


14. Rundown Protection

La rundown protection permet de garantir qu'une structure reste valide pendant qu'un traitement l'utilise.

Principe :

Code: Select all

Acquire
   |
   v
Use object
   |
   v
Release
Lors de la destruction :

Code: Select all

bloquer les nouvelles acquisitions
attendre les utilisations en cours
détruire
Ce modèle est utile pour les ressources globales partagées.


15. Fuites de références

Une référence oubliée peut empêcher la destruction.

Symptômes possibles :
  • driver qui ne s'unload pas correctement
  • objet qui reste vivant
  • mémoire non libérée
  • FILE_OBJECT ou DEVICE_OBJECT conservé trop longtemps

16. Use-after-free

L'erreur inverse est encore plus grave : utiliser un objet après libération.

Exemple conceptuel :

Code: Select all

ObDereferenceObject(Object);

// Object peut maintenant être détruit

Use(Object); // DANGER
Dans le kernel, ce type d'erreur peut provoquer immédiatement un BSOD.


17. Symétrie des ressources

Bonne règle :

Code: Select all

ObReferenceObject      <-> ObDereferenceObject
ZwOpen...              <-> ZwClose
ExAllocatePool2        <-> ExFreePool
IoCreateDevice         <-> IoDeleteDevice
IoCreateSymbolicLink   <-> IoDeleteSymbolicLink
Pour chaque acquisition, savoir immédiatement où aura lieu la libération.


18. Sécurité des objets

Les objets nommés peuvent avoir un Security Descriptor.

Le simple fait qu'un objet soit visible sous :

Code: Select all

\Device
\BaseNamedObjects
ne signifie pas que tout processus peut l'ouvrir.

Les droits sont évalués lors de la création ou de l'ouverture du handle.


19. Bonnes pratiques
  • Ne jamais conserver un pointeur sans comprendre son lifetime
  • Prendre une référence si l'objet doit survivre à l'appel courant
  • Libérer exactement une fois chaque référence acquise
  • Synchroniser la destruction des structures globales
  • Distinguer handles et pointeurs d'objets
  • Utiliser Driver Verifier pendant les tests

Conclusion

L'Object Manager et le lifetime sont inséparables. Les handles permettent d'accéder aux objets, les références garantissent leur survie et le namespace permet de les retrouver. Un driver robuste doit savoir exactement qui possède chaque ressource et quand sa destruction devient possible.


Exercices
  • Créer un Device Object nommé et le retrouver avec WinObj.
  • Utiliser ObReferenceObjectByHandle sur un handle obtenu dans un driver de test.
  • Tracer toutes les acquisitions/libérations d'un objet pour vérifier qu'elles sont équilibrées.

Who is online

Users browsing this forum: No registered users and 1 guest