venerdì 12 luglio 2019

02 - Hello Kernel !!!

Come da tradizione, si parte con un codice che, di fatto, non fa nulla di eclatante ma che mette in evidenza la struttura di base di qualsiasi driver e permette di prendere confidenza con le operazioni preliminari in Visual Studio. Si crei dunque un nuovo progetto.




Selezionare il template Empty WDM Driver (è sempre un po' nascosto ma alla fine salta fuori) e impostare nome e percorso desiderati.




Se nella cartella Driver Files del progetto è presente un file con estensione .inf eliminatelo (la sua presenza potrebbe compromettere la compilazione per il tipo di driver che andremo a scrivere).




Aggiungere un file con estensione .c alla cartella Source Files






Impostare la modalità a 64-bit (se non già impostata di default).




Scrivere il seguente codice nel file con estensione .c aggiunto al progetto e cercare di capirne il contenuto: sono stati aggiunti abbondanti commenti a supporto.

#include <ntddk.h>
 
#define DRIVER_TAG 'dcba'
 
/*
typedef struct _UNICODE_STRING
{
 USHORT Length;
 USHORT MaximumLength;
 PWCH   Buffer;
} UNICODE_STRING;
*/
UNICODE_STRING g_RegistryPath;
 
// Funzione invocata alla rimozione del driver
void DriverUnload(_In_ PDRIVER_OBJECT DriverObject)
{
 // macro che sopprime avvisi trattati come errori
 UNREFERENCED_PARAMETER(DriverObject);
 
 // libera memoria allocata
 ExFreePool(g_RegistryPath.Buffer);
 KdPrint(("Hello driver Unload called\n"));
}
 
// DriverEntry è l'entrypoint del driver chiamato da un thread di sistema quando il driver viene caricato e avviato.
// DriverObject è l'oggetto che rappresenta questo stesso driver e che viene passato a DriverEntry per 
// essere inizializzato con vari puntatori a funzioni.
// RegistryPath punta ad una stringa che rappresenta la chiave nel registro di sistema che il driver può 
// usare per salvare/leggere dati.
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject_In_ PUNICODE_STRING RegistryPath)
{
 // Memory pool evita frammentazione ed inefficienze allocando solo blocchi di dimensione multipla 
 // di una pagina (in realtà è più complesso di così ma poco importa a livello pratico; la memoria 
 // ritornata da ExAllocatePoolWithTag verrà gestita in modo simile alla normale memoria allocata su heap).
 // Tag usato per facilitare compito di debugger e altri strumenti di diagnostica (max 4 caratteri tra
 // apici singoli; spesso scritti al contrario per il fatto che su macchine little-endian il loro ordine è rovesciato).
 g_RegistryPath.Buffer = (WCHAR*)ExAllocatePoolWithTag(PagedPoolRegistryPath->Length, DRIVER_TAG);
 
 // KdPrint macro simile a funzione printf in quanto permette di usare stringhe di formato e specificatori.
 // Compilando in modalità Debug è uguale a DbgPrint mentre in modalità Release non ha alcun effetto, al contrario
 // di DbgPrint che ha sempre effetto a prescindere dalla modalità di compilazione.
 if (g_RegistryPath.Buffer == NULL) {
  KdPrint(("Failed to allocate memory\n"));
  return STATUS_INSUFFICIENT_RESOURCES;
 }
 
 // RtlCopyUnicodeString(Destination, Source) copia il minor numero di caratteri tra 
 // Source.Length e Destination.MaximumLength da Source a Destination.
 // RtlCopyUnicodeString modifica solo il campo Length di Destination,
 // MaximumLength deve quindi essere impostato esplicitamente.
 g_RegistryPath.MaximumLength = RegistryPath->Length;
 RtlCopyUnicodeString(&g_RegistryPath, (PCUNICODE_STRING)RegistryPath);
 
 // Specificatore %wZ indica UNICODE_STRING
 KdPrint(("Copied registry path: %wZ\n", &g_RegistryPath));
 
 // Imposta metodo da invocare quando driver viene rimosso
 DriverObject->DriverUnload = DriverUnload;
 
 // Raccoglie info su OS
 RTL_OSVERSIONINFOW info = { sizeof(info) };
 RtlGetVersion(&info);
 KdPrint(("Windows Version: %d.%d.%d\n", info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber));
 
 KdPrint(("Hello driver initialized successfully\n"));
 
 return STATUS_SUCCESS;
}


Compilare e copiare il file con estensione .sys nel Guest. A questo punto, sempre nel Guest, possiamo usare una console dei comandi aperta come amministratore ed il tool sc.exe per installare, il driver attraverso il comando (stando attenti agli spazi):

sc create nome_driver type= kernel binPath= percorso_file_sys


Si può, sempre attraverso sc.exe, avviare, stoppare e rimuovere il driver attraverso i comandi:

sc start nome_driver

sc stop nome_driver

sc delete nome_driver

Se durante queste operazioni DebugView è aperto (come amministratore) si possono leggere le varie stringhe passate a KdPrint (controllare che l'opzione Capture -> Capture Kernel sia selezionata in DebugView). Notare che la funzione DriverUnload è chiamata allo stop del driver, non alla rimozione completa dalla memoria e dal registro.



Usare sc.exe non è il massimo della comodità. Per rendere le cose un po' più sbrigative si può scrivere un proprio tool sfruttando l'API relativa (si veda [3]) oppure si possono usare tool sviluppati da terzi come OSR Driver Loader o KdManager (si veda [2]; in questo corso userò KdManager). Avviare KdManager come amministratore, selezionare il file con estensione .sys, premere Register per installare il driver, Run per avviarlo, Stop per fermarlo e Unregister per rimuoverlo





Codice Sorgente:

HelloKernel.zip




Riferimenti:

[1] https://github.com/zodiacon/windowskernelprogrammingbook
[2] https://github.com/wanttobeno/Win64DriverStudy_Src/blob/master/%5B1-2%5DKrnlHW64/sys/objfre_win7_amd64/amd64/KmdManager.exe
[3] https://docs.microsoft.com/en-us/windows/win32/services/the-complete-service-sample

Nessun commento:

Posta un commento