sabato 28 settembre 2019

21 - Comunicazione Client / Driver: Minifilter

Nelle lezioni precedenti si sono esaminati alcuni tipi di supporto nella comunicazione tra client e driver (nessuna, buffered e direct I/O) ma tutte e tre sono praticamente comunicazioni monodirezionali dove il client interroga il driver inviando una richiesta senza nessuna possibilità da parte di quest'ultimo di poter comunicare qualcosa al client in maniera diretta. I minifilter contemplano invece questa possibilità fornendo un tipo di comunicazione bidirezionale tra client e minifilter. Se si è seguita la lezione precedente sul monitoraggio dei file attraverso i minifilter non si dovrebbero incontrare particolari difficoltà nell'affrontare questa lezione in quanto ripropone lo stesso codice aggiungendo giusto qualche istruzione in più. L'esempio presentato mostra un client in attesa di comunicazione da parte del minifilter riguardo le scritture avvenute su tutti i file nominati readme.txt. Si è deciso invece di non implementare nessun tipo di comunicazione da parte del client al driver (benché contemplata) in quanto un minifilter è pur sempre un normale driver e per le comunicazioni dal client al server si possono usare le normali richieste usate finora: mettere a disposizione, da parte del driver, ulteriore codice da eseguire, al servizio del client, è qualcosa che dovrebbe essere fatta solo se realmente necessario.




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_INSENSITIVENULL, 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_PARAMETERFlags );
 
    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 ClientPortPVOID ServerPortCookiePVOID ConnectionContextULONG SizeOfContextPVOIDConnectionPortCookie) 
{
 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 PortCookiePVOID InputBufferULONG InputBufferLengthPVOID OutputBufferULONG OutputBufferLengthPULONG 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,
        NULLNULL, &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 BYTEbuffer) {
 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