NTSTATUS CmRegisterCallback( PEX_CALLBACK_FUNCTION Function, PVOID Context, PLARGE_INTEGER Cookie );
NTSTATUS CmUnRegisterCallback( LARGE_INTEGER Cookie );
Context è un puntatore ad un valore definito nel driver che verrà passato alla funzione di callback specificata dal primo parametro (Function).
Cookie è un puntatore ad un intero che identifica univocamente la funzione di callback da registrare passata come primo parametro (Function) e che servirà poi ad annullare la registrazione di tale funzione di callback con CmUnRegisterCallback. Per questo motivo è necessario che l'argomento passato a questo parametro sia una variabile globale.
Function è l'indirizzo della funzione di callback da registrare o di cui si vuole annullare una registrazione esistente e che deve essere compatibile con la definizione del puntatore a funzione PEX_CALLBACK_FUNCTION.
NTSTATUS(*PEX_CALLBACK_FUNCTION)(IN PVOID CallbackContext, IN PVOID Argument1, IN PVOID Argument2);
CallbackContext è il puntatore al valore passato a CmRegisterCallback nel parametro Context.
Argument1 è un valore di tipo REG_NOTIFY_CLASS (non è un puntatore come il tipo PVOID associato ad esso potrebbe far pensare) che identifica il tipo di operazione sul registro e se la funzione di callback è stata invocata prima o dopo il completamento di tale operazione. Si vedano [2] e [3] per maggiori informazioni su REG_NOTIFY_CLASS.
Argument2 è un puntatore ad una struttura che contiene informazioni sull'operazione per cui è stata invocata la funzione di callback. Il tipo di questa struttura dipende naturalmente dal valore di tipo REG_NOTIFY_CLASS contenuto nel parametro Argument1.
Se la funzione di callback viene invocata prima del completamento dell'operazione sul registro è possibile fare in modo che tale operazione non venga portata a termine. A tale scopo è sufficiente far restituire alla funzione di callback un valore che indica uno stato di errore. Al contrario, restituendo STATUS_SUCCESS l'operazione sarà completata normalmente. Il seguente codice è tratto da [1] e mostra una funzione di callback di esempio che verrà invocata poco prima della creazione o della cancellazione di una chiave o di un valore tramite regedit.exe e restituisce STATUS_ACCESS_DENIED impedendo così il completamento dell'operazione.
NTSTATUS RegistryCallback ( IN PVOID CallbackContext, IN PVOID Argument1, IN PVOID Argument2 ) { UNREFERENCED_PARAMETER(CallbackContext); long type; NTSTATUS CallbackStatus = STATUS_SUCCESS; UNICODE_STRING registryPath; registryPath.Length = 0; registryPath.MaximumLength = 2048 * sizeof(WCHAR); registryPath.Buffer = ExAllocatePoolWithTag(NonPagedPool, registryPath.MaximumLength, REGISTRY_POOL_TAG); if (registryPath.Buffer == NULL) return STATUS_SUCCESS; type = (REG_NOTIFY_CLASS)PtrToInt(Argument1); switch (type) { case RegNtPreCreateKeyEx: // Un thread sta cercando di creare una chiave { // PsGetCurrentProcess ritorna un puntatore all'EPROCESS del processo collegato al thread // che sta eseguendo la funzione di callback. Come già detto all'inizio della lezione // il thread che esegue la funzione di callback è quello che sta effettuando o ha effettuato // un operazione sul registro. if (IsProcessName("regedit.exe", PsGetCurrentProcess())) { GetRegistryObjectCompleteName(®istryPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject); DbgPrint("[RegNtPreCreateKeyEx]KeyPath: %wZ", ®istryPath); DbgPrint("[RegNtPreCreateKeyEx]KeyName: %wZ", ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName); // Restituisce un valore di stato erroneo impedendo il completamento dell'operazione sul registro. CallbackStatus = STATUS_ACCESS_DENIED; } break; } case RegNtPreDeleteKey: // Un thread sta cercando di eliminare una chiave { if (IsProcessName("regedit.exe", PsGetCurrentProcess())) { GetRegistryObjectCompleteName(®istryPath, ((PREG_DELETE_KEY_INFORMATION)Argument2)->Object); DbgPrint("[RegNtPreDeleteKey]%wZ", ®istryPath); CallbackStatus = STATUS_ACCESS_DENIED; } break; } case RegNtPreSetValueKey: // Un thread sta cercando di creare una valore { if (IsProcessName("regedit.exe", PsGetCurrentProcess())) { GetRegistryObjectCompleteName(®istryPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object); DbgPrint("[RegNtPreSetValueKey]KeyPath: %wZ", ®istryPath); DbgPrint("[RegNtPreSetValueKey]ValName: %wZ", ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName); CallbackStatus = STATUS_ACCESS_DENIED; } break; } case RegNtPreDeleteValueKey: // Un thread sta cercando di eliminare un valore { if (IsProcessName("regedit.exe", PsGetCurrentProcess())) { GetRegistryObjectCompleteName(®istryPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object); DbgPrint("[RegNtPreDeleteValueKey]KeyPath: %wZ", ®istryPath); DbgPrint("[RegNtPreDeleteValueKey]ValName: %wZ", ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName); CallbackStatus = STATUS_ACCESS_DENIED; } break; } case RegNtPreRenameKey: // Un thread sta cercando di rinominare una chiave { if (IsProcessName("regedit.exe", PsGetCurrentProcess())) { GetRegistryObjectCompleteName(®istryPath, ((PREG_RENAME_KEY_INFORMATION)Argument2)->Object); DbgPrint("[RegNtPreRenameKey]KeyPath: %wZ", ®istryPath); DbgPrint("[RegNtPreRenameKey]NewName: %wZ", ((PREG_RENAME_KEY_INFORMATION)Argument2)->NewName); CallbackStatus = STATUS_ACCESS_DENIED; } break; } default: break; } if (registryPath.Buffer != NULL) ExFreePoolWithTag(registryPath.Buffer, REGISTRY_POOL_TAG); return CallbackStatus; }
BOOLEAN IsProcessName(char *string, PEPROCESS eprocess) { char xx[260] = { 0 }; strcpy(xx, (PCCHAR)PsGetProcessImageFileName(eprocess)); if (!_stricmp(xx, string)) return TRUE; else return FALSE; }
BOOLEAN GetRegistryObjectCompleteName(PUNICODE_STRING pRegistryPath, PVOID pRegistryObject) { BOOLEAN foundCompleteName = FALSE; if (!foundCompleteName) { NTSTATUS status; ULONG returnedLength; PUNICODE_STRING pObjectName = NULL; // ObQueryNameString restituisce nel secondo parametro il nome dell'oggetto passato come primo argomento. // Se l'oggetto in questione è un oggetto senza nome verrà restituito NULL nel secondo parametro. // OBJECT_NAME_INFORMATION è praticamente solo una wrapper di UNICODE_STRING. // Il terzo parametro indica la dimensione in byte del buffer specificato dal secondo parametro // che conterrà il nome del'oggetto. Se si passa zero come argomento per il terzo parametro il // quarto parametro riceverà la dimesione in byte necessaria a contenere il nome dell'oggetto. // Infine, se il terzo parametro è zero o un valore più piccolo della dimensione necessaria a contenere // il nome dell'oggetto ObQueryNameString restituisce STATUS_INFO_LENGTH_MISMATCH. status = ObQueryNameString(pRegistryObject, (POBJECT_NAME_INFORMATION)pObjectName, 0, &returnedLength); if (status == STATUS_INFO_LENGTH_MISMATCH) { pObjectName = ExAllocatePoolWithTag(NonPagedPool, returnedLength, REGISTRY_POOL_TAG); status = ObQueryNameString(pRegistryObject, (POBJECT_NAME_INFORMATION)pObjectName, returnedLength, &returnedLength); if (NT_SUCCESS(status)) { RtlCopyUnicodeString(pRegistryPath, pObjectName); foundCompleteName = TRUE; } ExFreePoolWithTag(pObjectName, REGISTRY_POOL_TAG); } } return foundCompleteName; }
Insieme all'entrypoint vengono anche dichiarate le variabili globali e le funzioni esterne necessarie. Infine, è bene notare che l'opzione /integritycheck da passare al linker è necessaria anche in questo caso.
#include <ntddk.h> #define REGISTRY_POOL_TAG 'pRE' // Esportata da NtosKrnl.exe e esposta da NtosKrnl.lib. // Dichiarata in alcuni header ufficiali ma di tali header serve solo questa funzione // quindi è più semplice dichiararla ed usarla in questo modo. NTKERNELAPI NTSTATUS ObQueryNameString ( IN PVOID Object, OUT POBJECT_NAME_INFORMATION ObjectNameInfo, IN ULONG Length, OUT PULONG ReturnLength ); // Esportata da NtosKrnl.exe e esposta da NtosKrnl.lib. NTKERNELAPI UCHAR* PsGetProcessImageFileName(PEPROCESS Process); // Variabili globali LARGE_INTEGER CmHandle; NTSTATUS CmSt; VOID DriverUnload(PDRIVER_OBJECT pDriverObj) { UNREFERENCED_PARAMETER(pDriverObj); // Cancella registrazione di callback usando variabile globale CmHandle che // identifica la callback registrata in DriverEntry. if (NT_SUCCESS(CmSt)) CmUnRegisterCallback(CmHandle); } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString) { UNREFERENCED_PARAMETER(pRegistryString); pDriverObj->DriverUnload = DriverUnload; // Registra funzione di callback ed usa variabile globale CmHandle per // identificarla univocamente. CmSt = CmRegisterCallback(RegistryCallback, NULL, &CmHandle); if (NT_SUCCESS(CmSt)) DbgPrint("CmRegisterCallback SUCCESS!"); else DbgPrint("CmRegisterCallback Failed!"); return STATUS_SUCCESS; }
Codice sorgente:
MonitorRegistryDriver.zip
Riferimenti:
[1] https://github.com/andylau004/LookDrvCode/tree/master/WIN64驱动编程基础教程/代码/%5B4-1%5DMonitorCreateExitProcessThread/src
[2] https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-cmregistercallback
[3] https://docs.microsoft.com/it-it/windows-hardware/drivers/ddi/content/wdm/nc-wdm-ex_callback_function
Nessun commento:
Posta un commento