giovedì 19 settembre 2019

19 - Monitoraggio: Orologio di Sistema (Callback Object)

Probabilmente monitorare i cambiamenti apportati all'orologio di sistema non è propriamente tra le nozioni principali o i concetti prioritari per chi inizia a programmare in kernel mode ma sicuramente è il modo più semplice di introdurre i Callback Object. Finora si è visto come sia possibile essere notificati dal sistema quando si verifica un dato evento. E' bene sapere che è possibile non solo per il sistema ma anche per qualsiasi driver emettere delle notifiche. A questo scopo un driver deve creare un Callback Object che altro non è che un oggetto che conserva tutti i puntatori delle funzioni di callback da invocare quando si verifica un certo evento di interesse. A loro volta i driver interessati alla notifica di un evento non devono fare altro che registrare la propria funzione di callback nel Callback Object del driver che intende mandare le notifiche per quello stesso evento. Non si entrerà nel dettaglio su come creare un Callback Object per notificare un evento ad altri soggetti interessati (anche se è bene sapere che ciò è possibile), piuttosto si sfrutteranno i Callback Object predefiniti di sistema. Ogni Callback Object deve avere un nome per essere referenziato ed i nomi dei Callback Object predefiniti si possono osservare con WinObj




Per creare o aprire un Callback Object già esistente si usa la funzione ExCreateCallback che è molto simile a ZwCreateFile e IoCreateFile viste in una precedente lezione (si veda [2]). Si noti che una volta che il Callback Object (nel caso di creazione) o il riferimento ad esso (nel caso di apertura) non serve più è necessario usare ObDereferenceObject per eliminarlo o decrementare il contatore dei riferimenti.

NTSTATUS ExCreateCallback(
 PCALLBACK_OBJECT   *CallbackObject,
 POBJECT_ATTRIBUTES ObjectAttributes,
 BOOLEAN            Create,
 BOOLEAN            AllowMultipleCallbacks
);

CallbackObject è il puntatore che riceverà l'indirizzo del Callback Object da creare o aprire.

ObjectAttributes è un puntatore ad una istanza di tipo OBJECT_ATTRIBUTES che è una struttura che conterrà gli attributi del Callback Object (essenzialmente nome e metodo di comparazione di questi ultimi).

Create è un booleano che indica se eventualmente il Callback Object deve essere creato nel caso non fosse possibile aprirlo.

AllowMultipleCallbacks è un booleano che indica se possono essere aggiunte funzioni di callback multiple nel caso di creazione del Callback Object (non si applica nel caso di apertura).

Per registrare una funzione di callback presso il Callback Object appena creato o aperto si usa la funzione ExRegisterCallback mentre per rimuovere una registrazione esistente si usa ExUnregisterCallback

PVOID ExRegisterCallback(
 PCALLBACK_OBJECT   CallbackObject,
 PCALLBACK_FUNCTION CallbackFunction,
 PVOID              CallbackContext
);

void ExUnregisterCallback(
 PVOID CallbackRegistration
);

CallbackObject è il puntatore al Callback Object in cui registrare la funzione di callback.

CallbackContext è un puntatore ad una istanza di una struttura dati definita dal chiamante da passare alla funzione di callback (non rilevante al momento).

CallbackFunction è il puntatore alla funzione di callback da registrare e che verrà invocata dal sistema (per conto del driver che ha creato il Callback Object) quando si verifica un dato evento. E' importante notare che la funzione di callback verrà invocata dal sistema operativo allo stesso IRQL in cui era il driver quando ha creato il Callback Object. La funzione di callback deve essere conforme alla seguente definizione di PCALLBACK_FUNCTION

VOID
(*PCALLBACK_FUNCTION) (
 IN PVOID CallbackContext,
 IN PVOID Argument1,
 IN PVOID Argument2
 );

CallbackContext è il puntatore alla istanza di struttura dati vista in precedenza (non rilevante al momento).

Argument1 è un puntatore ad un argomento definito dal Callback Object (non rilevante al momento).

Argument2 è un puntatore ad un argomento definito dal Callback Object (non rilevante al momento).

ExRegisterCallback ritorna un puntatore che identifica la funzione di callback appena registrata e che è necessario passare a ExUnregisterCallback nel parametro CallbackRegistration per annullare la registrazione quando non si vogliono più ricevere notifiche.

Il codice di esempio è tratto da [1] ed è relativamente banale una volta capito come funzionano i Callback Object e le funzioni collegate appena descritte.

#include <ntddk.h>
 
// Esportata da Ntkrnl.exe ed esposta da Ntkrnl.lib (ma non negli header ufficiali)
NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process);
 
// Puntatore usato per rimuovere la registrazione della funzione di callback
PVOID g_CbRegistration;


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObjectPUNICODE_STRING pRegPath)
{
 UNREFERENCED_PARAMETER(pRegPath);
 DbgPrint("Enter DriverEntry\n");
 
 NTSTATUS status = STATUS_SUCCESS;
 pDriverObject->DriverUnload = DriverUnload;
 
 RegisterSetSystemTimeNotify();
 
 DbgPrint("Leave DriverEntry\n");
 return status;
}

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
 UNREFERENCED_PARAMETER(pDriverObject);
 
 UnRegisterSetSystemTimeNotify();
 
 DbgPrint("[MyDriver]Unloaded!\n");
}

NTSTATUS RegisterSetSystemTimeNotify()
{
 PCALLBACK_OBJECT pCallBackObj;
 OBJECT_ATTRIBUTES oa;
 UNICODE_STRING callbackname;
 NTSTATUS status;
 
 RtlInitUnicodeString(&callbackname, L"\\Callback\\SetSystemTime");
 InitializeObjectAttributes(&oa, &callbackname, OBJ_CASE_INSENSITIVE, 0, 0);
 
 status = ExCreateCallback(&pCallBackObj, &oa, TRUEFALSE);
 if (!NT_SUCCESS(status))
  return status;
 
 g_CbRegistration = ExRegisterCallback(pCallBackObj, SetSystemTimeNotify, NULL);
 if (g_CbRegistration == NULL)
  return STATUS_UNSUCCESSFUL;
 
 ObDereferenceObject(pCallBackObj);
 
 return STATUS_SUCCESS;
}

NTSTATUS UnRegisterSetSystemTimeNotify()
{
 if (g_CbRegistration == NULL)
  return STATUS_UNSUCCESSFUL;
 
 ExUnregisterCallback(g_CbRegistration);
 
 return STATUS_SUCCESS;
}

// Funzione di callback
VOID SetSystemTimeNotify
(
 IN PVOID CallbackContext,
 IN PVOID Argument1,
 IN PVOID Argument2
)
{
 UNREFERENCED_PARAMETER(CallbackContext);
 UNREFERENCED_PARAMETER(Argument1);
 UNREFERENCED_PARAMETER(Argument2);
 
 // Attenzione: si sta usando un Callback Object predefinito quindi questa funzione 
 // di callback sarà invocata ad IRQL = 2 con tutto ciò che questo comporta (si veda [3]).
 DbgPrint("[SetSystemTimeNotify]IRQL: %ld\n", KeGetCurrentIrql());
}




Riferimenti:
[1] https://github.com/andylau004/LookDrvCode/tree/master/WIN64驱动编程基础教程/代码/%5B4-8%5DTimeChangeCallback
[2] 11 - Kernel API: File
[3] 01 - Concetti preliminari

Nessun commento:

Posta un commento