Innanzitutto è importante sapere che per usufruire di queste funzionalità da parte del sistema è necessario forzare i controlli di integrità sull'immagine del nostro driver. Anche se lavoriamo in Test Mode, e quindi con il DSE (Driver Signature Enforcement) disabilitato, si è comunque obbligati a rispettare tale restrizione durante la compilazione del nostro driver. A tale scopo si può passare l'opzione /integritycheck da riga di comando al linker attraverso le proprietà del progetto.
Per monitorare la creazione dei processi e la loro distruzione è possibile registrare la propria funzione di callback che verrà chiamata non appena si verificherà uno degli eventi appena citati (è un po' come ricevere una notifica dal sistema quando si verifica un particolare evento). Per i processi creati, il thread che eseguirà la funzione di callback è lo stesso che sta cercando di creare il processo. Per i processi distrutti, invece, il thread che eseguirà la funzione di callback è l'ultimo ad uscire prima che il processo venga distrutto. La registrazione avviene tramite la seguente funzione della Kernel API
NTSTATUS PsSetCreateProcessNotifyRoutineEx( PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, BOOLEAN Remove );
Remove è un valore booleano che permette di indicare se vogliamo registrare una funzione di callback (FALSE) o annullare una precedente registrazione (TRUE).
NotifyRoutine è 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 PCREATE_PROCESS_NOTIFY_ROUTINE_EX (deve essere quindi una funzione che non restituisce e che accetta 3 argomenti che possono poi essere utilizzati e analizzati per raccogliere le informazioni necessarie)
VOID(*PCREATE_PROCESS_NOTIFY_ROUTINE_EX) ( _Inout_ PEPROCESS Process, _In_ HANDLE ProcessId, _Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo );
Process è un puntatore alla struttura EPROCESS collegata al processo che si sta creando o distruggendo.
ProcessId è l'ID del processo (PID) che si sta creando o distruggendo.
CreateInfo è un puntatore ad una struttura di tipo PS_CREATE_NOTIFY_INFO che contiene ulteriori informazioni se il processo è in creazione (NULL se è in distruzione)
typedef struct _PS_CREATE_NOTIFY_INFO { SIZE_T Size; union { ULONG Flags; struct { ULONG FileOpenNameAvailable : 1; ULONG Reserved : 31; }; }; HANDLE ParentProcessId; CLIENT_ID CreatingThreadId; struct _FILE_OBJECT *FileObject; PCUNICODE_STRING ImageFileName; PCUNICODE_STRING CommandLine; NTSTATUS CreationStatus; } PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO; /* typedef struct _CLIENT_ID { PVOID UniqueProcess; PVOID UniqueThread; } CLIENT_ID, *PCLIENT_ID; */
FileOpenNameAvailable è un valore booleano che indica se il campo ImageFileName conterrà il nome esatto dell'eseguibile (TRUE) o solo un nome parziale (FALSE).
ParentProcessId è il PID del processo padre del processo che si sta creando.
CreatingThreadId è una struttura che contiene il PID (CreatingThreadId->UniqueProcess) del processo che si sta creando e il TID (CreatingThreadId->UniqueThread) del thread incaricato alla creazione del processo.
ImageFileName contiene il nome dell'eseguibile in formato esatto o parziale (in base al valore del campo FileOpenNameAvailable ).
CommandLine contiene la stringa con il comando usato per la creazione del processo. NULL se non è usato alcun comando.
CreationStatus è lo stato da passare all'operazione di creazione del processo. Passare il valore di uno stato di errore permette di far fallire la creazione del processo.
Di seguito è presentato il codice di una funzione di callback di esempio che, una volta registrata, verrà invocata durante la creazione o la distruzione di un processo. Inoltre, controlla se il processo in fase di creazione è Calculator.exe e, in caso affermativo, impedisce che tale operazione venga portata a termine con successo.
VOID MyCreateProcessNotifyEx ( __inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo ) { UNREFERENCED_PARAMETER(ProcessId); char xxx[16] = { 0 }; if (CreateInfo != NULL) { PWCHAR wc = wcsrchr(CreateInfo->ImageFileName->Buffer, L'\\'); DbgPrint("[PTM_Monitor][%ld]%s is creating Process: %ls", CreateInfo->ParentProcessId, GetProcessNameByProcessId(CreateInfo->ParentProcessId), ++wc); strcpy(xxx, PsGetProcessImageFileName(Process)); if (!_stricmp(xxx, "Calculator.exe")) { DbgPrint("[PTM_Monitor]Try running Calculator.exe"); CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL; } } else { DbgPrint("[PTM_Monitor]Process Exit: %s", PsGetProcessImageFileName(Process)); } }
PCHAR GetProcessNameByProcessId(HANDLE ProcessId) { NTSTATUS st = STATUS_UNSUCCESSFUL; PEPROCESS ProcessObj = NULL; PCHAR string = NULL; st = PsLookupProcessByProcessId(ProcessId, &ProcessObj); if (NT_SUCCESS(st)) { string = PsGetProcessImageFileName(ProcessObj); ObfDereferenceObject(ProcessObj); } return string; }
Per monitorare la creazione e distruzione dei thread, invece, è possibile registrare la propria funzione di callback o annullare una registrazione esistente tramite le seguenti funzioni della Kernel API. Il thread che eseguirà tale funzione di callback sarà quello che ha creato o distrutto il thread.
NTSTATUS PsSetCreateThreadNotifyRoutine( PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine );
NTSTATUS PsRemoveCreateThreadNotifyRoutine( PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine );
NotifyRoutine è l'indirizzo della funzione di callback da registrare o di cui si vuole annullare la registrazione e che deve essere compatibile con la definizione del puntatore a funzione PCREATE_THREAD_NOTIFY_ROUTINE
typedef VOID(*PCREATE_THREAD_NOTIFY_ROUTINE) ( IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create );
ProcessId è il PID del processo collegato al thread che si è creato o distrutto
ThreadId è il TID del thread che si è creato o distrutto.
Create è un valore booleano che è TRUE se il thread è stato creato, FALSE se è stato distrutto.
Il codice seguente mostra una funzione di callback di esempio che, una volta registrata, verrà invocata dopo che un thread è stato creato o distrutto.
VOID MyCreateThreadNotify ( IN HANDLE ProcessId, IN HANDLE ThreadId, IN BOOLEAN Create ) { if (Create) DbgPrint("[PTM_Monitor]Thread Created! PID=%ld;TID=%ld", ProcessId, ThreadId); else DbgPrint("[PTM_Monitor]Thread Exit! PID=%ld;TID=%ld", ProcessId, ThreadId); }
Per quanto riguarda il monitoraggio del caricamento dei moduli in memoria, è possibile registrare la propria funzione di callback o annullare una registrazione esistente tramite le seguenti funzioni della Kernel API
NTSTATUS PsSetLoadImageNotifyRoutine( PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine );
NTSTATUS PsRemoveLoadImageNotifyRoutine( PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine );
NotifyRoutine è l'indirizzo della funzione di callback da registrare o di cui si vuole annullare la registrazione e che deve essere compatibile con la definizione del puntatore a funzione PLOAD_IMAGE_NOTIFY_ROUTINE
typedef VOID(*PLOAD_IMAGE_NOTIFY_ROUTINE)( _In_opt_ PUNICODE_STRING FullImageName, _In_ HANDLE ProcessId, _In_ PIMAGE_INFO ImageInfo );
FullImageName contiene il nome completo del modulo. A volte il sistema non è in grado di recuperarlo e passa NULL come argomento per questo parametro.
ProcessId è il PID del processo in cui l'immagine del modulo viene mappata. Il sistema passa zero come argomento per questo parametro se il modulo è un driver.
ImageInfo è un puntatore ad una struttura di tipo IMAGE_INFO che contiene altre info utili sul modulo
typedef struct _IMAGE_INFO { union { ULONG Properties; struct { ULONG ImageAddressingMode : 8; ULONG SystemModeImage : 1; ULONG ImageMappedToAllPids : 1; ULONG ExtendedInfoPresent : 1; ULONG MachineTypeMismatch : 1; ULONG ImageSignatureLevel : 4; ULONG ImageSignatureType : 3; ULONG ImagePartialMap : 1; ULONG Reserved : 12; }; }; PVOID ImageBase; ULONG ImageSelector; SIZE_T ImageSize; ULONG ImageSectionNumber; } IMAGE_INFO, *PIMAGE_INFO;
Il codice seguente mostra una funzione di callback di esempio che, una volta registrata, verrà invocata poco prima che un driver sia caricato in memoria. Diversamente dalle notifiche che riguardano la creazione dei processi, però, in questo caso non esiste un modo canonico e documentato per impedire il caricamento di un modulo.
VOID LoadImageNotifyRoutine ( __in_opt PUNICODE_STRING FullImageName, __in HANDLE ProcessId, __in PIMAGE_INFO ImageInfo ) { PVOID pDrvEntry; if (FullImageName != NULL && MmIsAddressValid(FullImageName)) { if (ProcessId == 0) { DbgPrint("[PTM_Monitor]Module Loaded: %wZ\n", FullImageName); pDrvEntry = GetDriverEntryByImageBase(ImageInfo->ImageBase); DbgPrint("[PTM_Monitor]Module Entrypoint: %p\n", pDrvEntry); } } }
PVOID GetDriverEntryByImageBase(PVOID ImageBase) { PIMAGE_DOS_HEADER pDOSHeader; PIMAGE_NT_HEADERS pNTHeader; PVOID pEntryPoint; pDOSHeader = (PIMAGE_DOS_HEADER)ImageBase; pNTHeader = (PIMAGE_NT_HEADERS)(PtrToUlong(ImageBase) + pDOSHeader->e_lfanew); pEntryPoint = (PVOID)(PtrToUlong(ImageBase) + pNTHeader->OptionalHeader.AddressOfEntryPoint); return pEntryPoint; }
Infine il codice di DriverEntry, dove avviene la registrazione delle funzioni di callback, e di DriverUnload, dove avviene l'annullamento alle registrazioni corrispondenti.
VOID DriverUnload(PDRIVER_OBJECT pDriverObj) { UNREFERENCED_PARAMETER(pDriverObj); // rimuove registrazione alla notificazione per creazione/distruzione di processi e thread PsSetCreateProcessNotifyRoutineEx(MyCreateProcessNotifyEx, TRUE); PsRemoveCreateThreadNotifyRoutine(MyCreateThreadNotify); // rimuove registrazione alla notificazione per caricamento di moduli PsRemoveLoadImageNotifyRoutine(LoadImageNotifyRoutine); } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString) { UNREFERENCED_PARAMETER(pRegistryString); NTSTATUS st = 0; pDriverObj->DriverUnload = DriverUnload; // registra funzioni di callback per la notifica di creazione/distruzione di processi e thread st = PsSetCreateProcessNotifyRoutineEx(MyCreateProcessNotifyEx, FALSE); DbgPrint("PsSetCreateProcessNotifyRoutineEx return: %x", st); st = PsSetCreateThreadNotifyRoutine(MyCreateThreadNotify); DbgPrint("PsSetCreateThreadNotifyRoutine return: %x", st); // registra funzione di callback per la notifica di caricamento dei moduli st = PsSetLoadImageNotifyRoutine(LoadImageNotifyRoutine); DbgPrint("PsSetLoadImageNotifyRoutine return: %x", st); return STATUS_SUCCESS; }
Codice sorgente:
MonitorProcessThreadModuleDriver.zip
Riferimenti:
[1] https://github.com/andylau004/LookDrvCode/tree/master/WIN64驱动编程基础教程/代码/%5B4-1%5DMonitorCreateExitProcessThread/src
[2] https://github.com/andylau004/LookDrvCode/tree/master/WIN64驱动编程基础教程/代码/%5B4-2%5DMonitorLoadUnloadDllDriver/src
Nessun commento:
Posta un commento