Objectif
Les callbacks permettent à un driver d'être appelé automatiquement lorsqu'un événement se produit. Selon la famille utilisée, ils servent à recevoir une notification, inspecter une opération, modifier certains paramètres, bloquer certaines opérations ou transmettre ensuite l'événement à un client user-mode.
1. Principe général
Un callback est une fonction fournie par le driver au système.
Code: Select all
DriverEntry
|
| RegisterCallback(...)
v
Windows mémorise la routine
|
| événement
v
Callback(...)
2. Callback vs thread système
Un thread système est créé volontairement par le driver avec une API comme :
Code: Select all
PsCreateSystemThread(...)
3. Cycle de vie
Un callback suit généralement :
Code: Select all
Register
|
v
Event
|
v
Callback
|
v
Unregister
4. Règles générales
- connaître l'IRQL ;
- savoir si l'attente est autorisée ;
- connaître la durée de vie des pointeurs reçus ;
- protéger les structures globales ;
- éviter le travail lourd ;
- copier les données qu'on veut conserver ;
- désenregistrer proprement au unload.
Les principales APIs sont :
Code: Select all
ExCreateCallback
ExRegisterCallback
ExNotifyCallback
ExUnregisterCallback
6. ExCreateCallback
`ExCreateCallback` crée ou ouvre un objet callback.
Code: Select all
Callback Object
|
+--> Driver A
+--> Driver B
+--> Driver C
7. ExRegisterCallback
Un driver peut s'enregistrer sur l'objet :
Code: Select all
registration = ExRegisterCallback(
callbackObject,
MyCallback,
context
);
Code: Select all
VOID MyCallback(
PVOID CallbackContext,
PVOID Argument1,
PVOID Argument2
);
Un composant déclenche la notification :
Code: Select all
ExNotifyCallback(
callbackObject,
argument1,
argument2
);
9. ExUnregisterCallback
Lorsqu'un driver ne veut plus recevoir les notifications :
Code: Select all
ExUnregisterCallback(registration);
10. Callbacks de processus
APIs importantes :
Code: Select all
PsSetCreateProcessNotifyRoutine
PsSetCreateProcessNotifyRoutineEx
11. Process callback
Prototype :
Code: Select all
VOID ProcessNotify(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo
);
Code: Select all
CreateInfo != NULL
Si :
Code: Select all
CreateInfo == NULL
12. PS_CREATE_NOTIFY_INFO
Cette structure permet notamment d'obtenir des informations sur :
- l'image ;
- la command line ;
- le parent ;
- le contexte de création ;
- le statut de création.
13. Callbacks de threads
API classique :
Code: Select all
PsSetCreateThreadNotifyRoutine
Code: Select all
HANDLE ProcessId
HANDLE ThreadId
BOOLEAN Create
14. Remote Thread Detection
En combinant contexte créateur et processus cible, on peut journaliser les créations de threads distants.
Attention : un remote thread n'est pas automatiquement malveillant. Des débogueurs, outils système ou logiciels de sécurité peuvent en produire.
15. Callbacks de chargement d'images
APIs :
Code: Select all
PsSetLoadImageNotifyRoutine
PsRemoveLoadImageNotifyRoutine
16. IMAGE_INFO
Le callback reçoit une structure `IMAGE_INFO` permettant notamment de connaître l'adresse de base et la taille de l'image.
En combinant Process + Thread + Image, on peut construire un petit système de monitoring.
17. Object Manager callbacks
API principale :
Code: Select all
ObRegisterCallbacks
18. OB_CALLBACK_REGISTRATION
La structure contient notamment :
- version ;
- nombre d'opérations ;
- altitude ;
- contexte ;
- tableau d'opérations.
19. OB_OPERATION_REGISTRATION
Cette structure décrit :
- le type d'objet ;
- les opérations surveillées ;
- le pre-callback ;
- le post-callback.
Code: Select all
PsProcessType
PsThreadType
Opérations importantes :
Code: Select all
OB_OPERATION_HANDLE_CREATE
OB_OPERATION_HANDLE_DUPLICATE
21. Pre-operation callback
Le pre-callback reçoit :
Code: Select all
POB_PRE_OPERATION_INFORMATION
22. DesiredAccess
Lors de l'ouverture d'un processus :
Code: Select all
PROCESS_QUERY_INFORMATION
PROCESS_VM_READ
PROCESS_TERMINATE
Un pre-callback peut réduire certains droits :
Code: Select all
DesiredAccess &= ~PROCESS_TERMINATE;
23. Post-operation callback
Le post-callback reçoit :
Code: Select all
POB_POST_OPERATION_INFORMATION
24. ObUnRegisterCallbacks
Au cleanup :
Code: Select all
ObUnRegisterCallbacks(RegistrationHandle);
Architecture :
Code: Select all
Application
|
| IOCTL_ADD_PID
v
Driver
|
| liste de PID protégés
|
| ObRegisterCallbacks
v
Ouverture handle processus
|
| PreCallback
v
retire PROCESS_TERMINATE
26. ERESOURCE
`ERESOURCE` est intéressant lorsque beaucoup de lectures ont lieu mais peu d'écritures.
Exemple :
- callbacks : lecture de la liste de PID ;
- IOCTL add/remove : écriture dans la liste.
Le Configuration Manager expose :
Code: Select all
CmRegisterCallbackEx
CmUnRegisterCallback
28. REG_NOTIFY_CLASS
Le callback reçoit une classe décrivant l'opération.
Exemples :
- création de clé ;
- ouverture ;
- suppression ;
- modification d'une valeur ;
- suppression d'une valeur ;
- renommage ;
- query ;
- pre-operation ;
- post-operation.
Avant l'opération, le driver peut :
- inspecter ;
- logger ;
- laisser continuer ;
- retourner une erreur ;
- dans certains cas modifier certaines données.
Après l'opération, le driver peut obtenir des informations telles que le statut final via des structures comme :
Code: Select all
REG_POST_OPERATION_INFORMATION
Classes utiles :
Code: Select all
RegNtPreSetValueKey
RegNtPostSetValueKey
32. Données de taille variable
Les noms et valeurs peuvent être de taille variable.
Une stratégie robuste consiste à allouer :
Code: Select all
sizeof(EVENT)
+ keyNameSize
+ valueNameSize
+ dataSize
33. Structure avec offsets
Exemple :
Code: Select all
struct EVENT
{
ULONG Type;
ULONG KeyNameOffset;
ULONG ValueNameOffset;
ULONG DataOffset;
ULONG DataSize;
};
Code: Select all
[EVENT][KeyName][ValueName][Data]
Cette API peut servir à obtenir une représentation exploitable du nom d'une clé à partir de l'objet Registry.
Il faut respecter la routine de libération documentée pour les données retournées.
35. Performance Registry
Le Registry est très actif.
Le callback doit donc :
- filtrer tôt ;
- éviter les gros traitements ;
- limiter les allocations ;
- différer le travail lourd.
Un driver de monitoring peut utiliser une structure :
Code: Select all
struct EVENT_HEADER
{
LIST_ENTRY Entry;
LARGE_INTEGER Time;
ULONG Type;
};
37. LIST_ENTRY
APIs classiques :
Code: Select all
InsertTailList
RemoveHeadList
IsListEmpty
38. Limiter la file
Si le producer est plus rapide que le consumer, la file peut grossir sans limite.
Il faut prévoir une stratégie :
- limite maximale ;
- drop ancien ;
- drop nouveau ;
- compteur de pertes.
Le client peut récupérer les événements via :
- `IRP_MJ_READ` ;
- IOCTL ;
- mécanisme asynchrone.
Code: Select all
Callbacks
|
v
Queue
|
v
IRP_MJ_READ
|
v
Client user-mode
Combiner :
Code: Select all
Process
Thread
Image
Registry
Code: Select all
[PROCESS] PID=1200 notepad.exe
[THREAD] PID=1200 TID=4200
[IMAGE] PID=1200 kernel32.dll
[REG] PID=1200 HKLM\...\Test = 42
Les callbacks peuvent s'exécuter en concurrence.
Selon le contexte :
- spin lock ;
- fast mutex ;
- ERESOURCE ;
- autres mécanismes adaptés.
42. Garder les sections critiques courtes
Mauvais :
Code: Select all
AcquireLock();
allocation complexe
formatage
attente
traitement lourd
ReleaseLock();
Code: Select all
AcquireLock();
modifier structure minimale
ReleaseLock();
traitement lourd ailleurs
Ne jamais supposer qu'un pointeur reçu dans un callback reste valide après le retour.
Mauvais :
Code: Select all
g_Data = CallbackData;
Code: Select all
AllocateOwnBuffer();
CopyData();
Ordre général :
Code: Select all
1. empêcher nouvelles opérations internes
2. désenregistrer les callbacks
3. attendre les travaux internes si nécessaire
4. vider les files
5. libérer la mémoire
6. supprimer les devices
7. unload
Les minifilters utilisent eux aussi un modèle callback :
- pre-create ;
- post-create ;
- pre-read ;
- post-read ;
- pre-write ;
- post-write.
46. WFP callbacks
Windows Filtering Platform utilise des callouts pour le réseau.
Conceptuellement :
Code: Select all
Network event
|
v
WFP
|
v
Callout
47. Comparaison des familles
Executive
Callbacks génériques entre composants kernel.
Ps*
Processus, threads, images.
Ob*
Handles et Object Manager.
Cm*
Registry.
Minifilter
Filesystem.
WFP
Réseau.
48. Notification vs interception
Certaines callbacks sont surtout informatives :
Code: Select all
Process create
Thread create
Image load
Code: Select all
Ob pre-operation
Registry pre-operation
Minifilter pre-operation
Callback Object entre deux drivers :
- Driver A crée l'objet callback ;
- Driver B s'enregistre ;
- Driver A appelle `ExNotifyCallback` ;
- Driver B reçoit la notification.
Process Monitor :
- création/terminaison ;
- PID ;
- parent PID ;
- image ;
- command line ;
- transfert user-mode.
Process Protector :
- liste de PID ;
- `ObRegisterCallbacks` ;
- interception des handles ;
- suppression de `PROCESS_TERMINATE`.
Registry Monitor :
- `CmRegisterCallbackEx` ;
- filtre sur writes ;
- nom de clé ;
- nom de valeur ;
- type ;
- taille ;
- données.
Mini SysMon :
Code: Select all
Process callback -----Thread callback -------Image callback ---------> Queue ---> User-mode client
Registry callback -----/
- oublier le désenregistrement ;
- conserver un pointeur temporaire ;
- faire trop de travail ;
- mauvais IRQL ;
- mauvais lock ;
- deadlock ;
- fuite mémoire ;
- file illimitée ;
- confusion pre/post ;
- filtrage trop large ;
- oublier la concurrence.
Executive
Code: Select all
ExCreateCallback
ExRegisterCallback
ExNotifyCallback
ExUnregisterCallback
Code: Select all
PsSetCreateProcessNotifyRoutineEx
PsSetCreateThreadNotifyRoutine
PsSetLoadImageNotifyRoutine
PsRemoveLoadImageNotifyRoutine
Code: Select all
ObRegisterCallbacks
ObUnRegisterCallbacks
Code: Select all
CmRegisterCallbackEx
CmUnRegisterCallback
CmCallbackGetKeyObjectIDEx
Les callbacks sont un mécanisme fondamental du kernel Windows.
Pour un développeur kernel orienté logiciel, les familles les plus importantes sont :
Code: Select all
Process
Thread
Image
Object Manager
Registry
Executive Callback Objects
Il faut surtout maîtriser :
- le contexte d'exécution ;
- l'IRQL ;
- le lifetime des données ;
- la concurrence ;
- le cleanup ;
- les performances ;
- les règles propres à chaque sous-système.
