Le prime modifiche da effettuare riguardano DriverEntry ma prima si dichiara una struttura che conterrà i dati da passare dal minifilter al client. In questo caso la struttura FileBackupPortMessage contiene la lunghezza del nome del file e, a seguire, la stringa con il nome stesso (lo spazio per tale stringa deve essere allocato dinamicamente). In questo l'esempio risulta alquanto artificioso in quanto il nome e la lunghezza saranno sempre le stesse ma il codice resta valido in contesti dove questo non è necessariamente vero.
#define DRIVER_TAG 'bF' PFLT_PORT FilterPort; PFLT_PORT SendClientPort; typedef struct _FileBackupPortMessage { USHORT FileNameLength; WCHAR FileName[1]; }FileBackupPortMessage; NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath ) { status = FltRegisterFilter(DriverObject, &FilterRegistration, &gFilterHandle); FLT_ASSERT(NT_SUCCESS(status)); if (NT_SUCCESS(status)) { do { UNICODE_STRING name = RTL_CONSTANT_STRING(L"\\FileBackupPort"); PSECURITY_DESCRIPTOR sd; status = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS); if (!NT_SUCCESS(status)) break; OBJECT_ATTRIBUTES attr; InitializeObjectAttributes(&attr, &name, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, sd); status = FltCreateCommunicationPort(gFilterHandle, &FilterPort, &attr, NULL, PortConnectNotify, PortDisconnectNotify, PortMessageNotify, 1); FltFreeSecurityDescriptor(sd); if (!NT_SUCCESS(status)) break; // Start filtering i/o status = FltStartFiltering(gFilterHandle); } while (FALSE); if (!NT_SUCCESS(status)) { KdPrint(("Communication ERROR")); FltUnregisterFilter(gFilterHandle); } } return status; }
FltCreateCommunicationPort crea una porta di comunicazione su cui il minifilter può ricevere richieste di connessione da parte dei client.
NTSTATUS FLTAPI FltCreateCommunicationPort( PFLT_FILTER Filter, PFLT_PORT *ServerPort, POBJECT_ATTRIBUTES ObjectAttributes, PVOID ServerPortCookie, PFLT_CONNECT_NOTIFY ConnectNotifyCallback, PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback, PFLT_MESSAGE_NOTIFY MessageNotifyCallback, LONG MaxConnections );
Filter è il puntatore inizializzato da FltRegisterFilter
ServerPort è un puntatore ad una variabile allocata dal minifilter che serve come handle alla porta di comunicazione del minifilter. Quest'ultimo resta in attesa su questo handle per ricevere le richieste di connessione da parte dei client.
ObjectAttributes è un puntatore ad una struttura di tipo OBJECT_ATTRIBUTES che deve essere inizializzata con vari attributi da associare all'handle della porta di comunicazione del minifilter. Tra i più importanti ci sono:
- il nome dell'oggetto kernel che rappresenta l'handle.
- l'indicazione del fatto che tale oggetto sarà usato solo in kernel mode o meno. In questo caso deve essere sempre vera la prima ipotesi in quanto questo handle verrà usato solo dal minifilter.
- l'indicazione del fatto che la comparazione tra il nome indicato per l'oggetto kernel ed altri nomi di oggetti esistesti dello stesso tipo avvenga senza distinzione tra maiuscole o minuscole.
- un descrittore di sicurezza da applicare all'oggetto kernel. Solitamente ne viene creato uno con la funzione FltBuildDefaultSecurityDescriptor, cosa che comporta che il client debba essere eseguito come amministratore per connettersi.
Per inizializzare tale struttura si può usare la macro InitializeObjectAttributes.
ConnectNotifyCallback è un puntatore ad una funzione di callback invocata quando un client invia una richiesta di connessione al minifilter.
DisconnectNotifyCallback è un puntatore ad una funzione di callback invocata quando il contatore dell'handle della porta di comunicazione del client si azzera (si tratta di un handle allocato e inizializzato dal client e che serve poi al minifilter per distinguere il client a cui inviare i propri messaggi; da non confondere con quello di cui abbiamo parlato finora e che serve invece al minifilter per accettare le richieste di connessione dei client). Viene inoltre invocata anche prima che il minifilter venga scaricato dalla memoria.
MessageNotifyCallback è un puntatore ad una funzione di callback invocata quando il client invoca FltSendMessage per inviare un messaggio al minifilter. Tale funzione di callback è eseguita a IRQL = PASSIVE. Può essere NULL se non è prevista comunicazione dal client al minifilter ed in questo caso il client riceverà un errore se tenterà di inviare comunque un messaggio.
MaxConnections è un valore numerico che indica il numero di client che possono connettersi simultaneamente alla porta del minifilter.
NTSTATUS MonitorFileMiniFilterUnload ( _In_ FLT_FILTER_UNLOAD_FLAGS Flags ) { UNREFERENCED_PARAMETER( Flags ); FltCloseCommunicationPort(FilterPort); FltUnregisterFilter( gFilterHandle ); return STATUS_SUCCESS; }
Nella funzione di unload tutto quello che è necessario aggiungere è la chiamata a FltCloseCommunication che chiude la porta di comunicazione del minifilter. Parliamo adesso brevemente delle funzioni di callback.
_Use_decl_annotations_ NTSTATUS PortConnectNotify( PFLT_PORT ClientPort, PVOID ServerPortCookie, PVOID ConnectionContext, ULONG SizeOfContext, PVOID* ConnectionPortCookie) { UNREFERENCED_PARAMETER(ServerPortCookie); UNREFERENCED_PARAMETER(ConnectionContext); UNREFERENCED_PARAMETER(SizeOfContext); UNREFERENCED_PARAMETER(ConnectionPortCookie); SendClientPort = ClientPort; KdPrint(("Communication established!")); return STATUS_SUCCESS; }
ClientPort è il puntatore all'handle della porta di comunicazione del client e che deve essere usato dal minifilter per distinguere tale client quando gli invia un messaggio. Per questo motivo è bene salvare tale puntatore in una variabile globale.
void PortDisconnectNotify(PVOID ConnectionCookie) { UNREFERENCED_PARAMETER(ConnectionCookie); FltCloseClientPort(gFilterHandle, &SendClientPort); SendClientPort = NULL; KdPrint(("Communication terminated!")); }
Quando un client si disconnette l'handle alla sua porta di comunicazione deve essere chiuso qui nel minifilter.
NTSTATUS PortMessageNotify( PVOID PortCookie, PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, PULONG ReturnOutputBufferLength) { UNREFERENCED_PARAMETER(PortCookie); UNREFERENCED_PARAMETER(InputBuffer); UNREFERENCED_PARAMETER(InputBufferLength); UNREFERENCED_PARAMETER(OutputBuffer); UNREFERENCED_PARAMETER(OutputBufferLength); UNREFERENCED_PARAMETER(ReturnOutputBufferLength); return STATUS_SUCCESS; }
L'esempio di codice di questa lezione non prevedere nessun tipo di comunicazione dal minifilter al client per questo motivo PortMessageNotify restituisce semplicemente STATUS_SUCCESS.
// WRITE else if (MajorFunction == IRP_MJ_WRITE) { if (IsProtectionFile(lpNameInfo)) { KdPrint(("[IRP_MJ_WRITE]%wZ", &lpNameInfo->Name)); if (SendClientPort) { USHORT nameLen = lpNameInfo->Name.Length; USHORT len = sizeof(FileBackupPortMessage) + nameLen; FileBackupPortMessage *msg = (FileBackupPortMessage*)ExAllocatePoolWithTag(PagedPool, len, DRIVER_TAG); if (msg) { msg->FileNameLength = nameLen / sizeof(WCHAR); RtlCopyMemory(msg->FileName, lpNameInfo->Name.Buffer, nameLen); LARGE_INTEGER timeout; timeout.QuadPart = -10000 * 100; // 100msec FltSendMessage(gFilterHandle, &SendClientPort, msg, len, NULL, NULL, &timeout); ExFreePool(msg); } }
Nella funzione di callback di PreOperation (CommunicationMiniFilterPreOperation) si invia un messaggio al client tramite la funzione FltSendMessage indicando l'handle della porta di comunicazione del client, il messaggio da inviare, la dimensione in byte dello stesso ed il tempo massimo che il minifilter deve restare in attesa affinché il client riceva tale messaggio.
Il client chiama FilterCommunicationPort per richiedere la connessione al minifilter e ricevere l'handle alla porta di comunicazione da usare con la funzione FltGetMessage che si mette in attesa dei messaggi da parte del minifilter. Ogni messaggio ricevuto è preceduto da un header di tipo FILTER_MESSAGE_HEADER e per questo motivo il client incrementa il puntatore di sizeof(FILTER_MESSAGE_HEADER) byte per recuperare i dati desiderati (di tipo FileBackupMessage).
#include <Windows.h> #include <fltUser.h> #include <stdio.h> #include <string> typedef struct _FileBackupPortMessage { USHORT FileNameLength; WCHAR FileName[1]; }FileBackupPortMessage; #pragma comment(lib, "fltlib") void HandleMessage(const BYTE* buffer) { auto msg = (FileBackupPortMessage*)buffer; std::wstring filename(msg->FileName, msg->FileNameLength); printf("file written: %ws\n", filename.c_str()); } int main() { HANDLE hPort; printf("connecting...\n"); auto hr = ::FilterConnectCommunicationPort(L"\\FileBackupPort", 0, nullptr, 0, nullptr, &hPort); if (FAILED(hr)) { printf("Error connecting to port (HR=0x%08X)\n", hr); return 1; } else printf("Connection Established!\n"); BYTE buffer[1 << 12]; // 4 KB auto message = (FILTER_MESSAGE_HEADER*)buffer; for (;;) { hr = ::FilterGetMessage(hPort, message, sizeof(buffer), nullptr); if (FAILED(hr)) { printf("Error receiving message (0x%08X)\n", hr); break; } HandleMessage(buffer + sizeof(FILTER_MESSAGE_HEADER)); } ::CloseHandle(hPort); return 0; }
Codice sorgente:
CommunicationMiniFilter.zip
Riferimenti:
[1] https://github.com/zodiacon/windowskernelprogrammingbook/blob/master/chapter10/FileBackup/FileBackup.cpp
[2] https://github.com/zodiacon/windowskernelprogrammingbook/blob/master/chapter10/FileBackupMon/FileBackupMon.cpp
Nessun commento:
Posta un commento