
Driver Reverse & Exploitation
⚠️ Avvertenza : Questo progetto è strettamente educativo e dimostrativo. Non è destinato a essere utilizzato in un contesto dannoso. L'obiettivo è apprendere la metodologia del reverse engineering e le fasi di exploitation di un driver Windows.
Qui spiego l'approccio che ho seguito per risolvere l'esercizio proposto da d1rk(SaadAhla) https://github.com/SaadAhla, che consiste nell'effettuare reverse engineering ed exploitation su un driver legittimo, firmato e non presente nelle blocklist (HVCI, LOLBIN...). È disponibile un programma C in grado di terminare qualsiasi processo attivo sul sistema tramite questo driver in kernel-mode; ne descrivo il funzionamento più avanti.

📃 Uso : DriverKiller.exe <nome_processo.exe> [-d]
Opzione -d : consente di eliminare il servizio e il driver dal sistema dopo l'exploitation.
La modalità testsigning deve essere attivata sulla macchina target perché il certificato del driver è scaduto.
Parte 1 - Reverse engineering :
L'esercizio fornisce un file .sys, denominato con il suo hash SHA-256.
Il primo passo consiste nell'aprire il file con IDA.
IDA è disponibile gratuitamente. Basta andare sul sito di Hex-Rays per generare una licenza e scaricare il software.
Si inizia elencando la IAT (Import Address Table) del driver e cercando la chiamata all'API che ci interessa : ZwTerminateProcess.
Facendo doppio clic su ZwTerminateProcess, IDA ci reindirizza al codice compilato di questa funzione. Selezionando la voce e visualizzando i cross-reference, si ottiene l'elenco delle funzioni del driver che la chiamano.
Si osserva che è la funzione sub_12EF4, all'offset 1CE, a utilizzare ZwTerminateProcess. Dopo un doppio clic, IDA mostra il suo codice compilato.
Il codice decompilato rivela le chiamate a ZwOpenProcess (che apre un handle verso il processo target) e a ZwTerminateProcess (che termina il processo tramite tale handle).
Consultando la documentazione di ZwOpenProcess (https://learn.microsoft.com/fr-fr/windows-hardware/drivers/ddi/ntddk/nf-ntddk-zwopenprocess), si nota che il parametro ClientID corrisponde a un puntatore che indica il PID del processo interessato.
Sulla riga sopra, ClientId.UniqueProcess viene inizializzato con la variabile v22. Quest'ultima è definita poco sopra :
v22 = (void )((_QWORD *)i + 10);
Per comprendere questa assegnazione, bisogna identificare la variabile i e il campo +10.
Più sopra in questa funzione, si osserva una chiamata a ZwQuerySystemInformation con il parametro SYSTEM_PROCESS_INFORMATION. Si comprende inoltre che i è l'iteratore sulle voci di questa struttura insieme alla variabile v6.
Secondo la documentazione di ZwQuerySystemInformation : (https://learn.microsoft.com/en-us/windows/win32/sysinfo/zwquerysysteminformation), questa funzione restituisce un array contenente una voce per ogni processo attivo sul sistema.
La struttura SYSTEM_PROCESS_INFORMATION è descritta qui : https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
PVOID Reserved2;
ULONG HandleCount;
ULONG SessionId;
PVOID Reserved3;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG Reserved4;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
PVOID Reserved5;
SIZE_T QuotaPagedPoolUsage;
PVOID Reserved6;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;
Promemoria : dimensioni di alcuni tipi su Windows x64
typedef struct _UNICODE_STRING {
USHORT Length; -> 2
USHORT MaximumLength; -> + 2 = 4
PWSTR Buffer; -> + 8 = 12 (12 non è un multiplo di 8 quindi padding di 4 aggiunto prima di Buffer) = 16
} UNICODE_STRING;Calcolo dell'offset di UniqueProcessId :
ULONG NextEntryOffset; -> 4
ULONG NumberOfThreads; -> + 4 = 8
BYTE Reserved1[48]; -> + 48 = 56
UNICODE_STRING ImageName; -> + 16 = 72
KPRIORITY BasePriority; -> + 4 = 76 (76 non è un multiplo di 8 quindi padding di 4 aggiunto) = 80
HANDLE UniqueProcessId; -> + 8 = 88
Il membro UniqueProcessId si trova quindi all'offset 0x50 (80 decimale).
Osservando l'assegnazione della nostra variabile v22, si nota che i viene convertito (cast) in un puntatore QWORD (8 byte)
v22 = (void )((_QWORD *)i + 10);
v22 corrisponde all'indirizzo di i + 10 * 8 = 80 byte. Questa variabile contiene quindi il PID recuperato dalla struttura SYSTEM_PROCESS_INFORMATION.
Per sapere quale PID verrà passato a ZwTerminateProcess, bisogna analizzare la condizione che circonda questa assegnazione.
Si nota che il nome dell'immagine del processo viene prima recuperato :
v9 = (wchar_t )((_QWORD *)i + 8);
v9 = indirizzo di i + 8 × 8 = 64 byte. Ciò corrisponde al Buffer del membro ImageName, dato che tale membro si trova all'offset 56 + 2 (USHORT) + 2 (USHORT) + 4 (padding) = 64
Viste le manipolazioni e i loop qui sotto, si può ipotizzare che venga effettuato un confronto tra il nome del processo passato come argomento (a2) e i processi attivi sul sistema v9/String.
sub_1C078(String, v9, (int)v13); v17 = strupr(a2); v18 = strupr(String);
È quindi il parametro a2 a dover contenere il nome del processo da terminare tramite ZwTerminateProcess.
Si nota che a2 è un parametro della funzione sub_12EF4. Per andare oltre, bisogna esaminare i riferimenti di questa funzione (l'ho rinominata ZwTerminateProcessCaller per una migliore leggibilità).
Si constata che ZwTerminateProcessCaller viene chiamata dalla funzione sub_13624 all'offset 61A.
Prima di analizzare questo codice decompilato, vado a cercare i riferimenti della funzione sub_13624 (rinominata ZwTerminateProcessCallerCaller) per assicurarmi che questo codice venga effettivamente utilizzato dopo una chiamata API a DeviceIoControl dal UserMode.
Si constata che ZwTerminateProcessCallerCaller viene chiamata dalla funzione sub_14130 (rinominata ZwTerminateProcessCallerCallerCaller ...fortunatamente per noi, è l'ultima prima del punto di ingresso 😅).
Si constata che ZwTerminateProcessCallerCallerCaller viene chiamata dalla funzione sub_1A4A8 all'offset 306.
Si trova l'assegnazione della funzione ZwTerminateProcessCallerCallerCaller :
memset64(DriverObject->MajorFunction, (unsigned __int64)ZwTerminateProcessCallerCallerCaller, 0x1Cu);
Prima di tornare alla funzione sub_13624 (alias ZwTerminateProcessCallerCaller), si recuperano il Symbolic Name e il Device Name (identici qui) : Viragtlt.
Tornando su ZwTerminateProcessCallerCaller, si nota che il suo secondo parametro (quindi a2) corrisponde a MasterIrp->AssociatedIrp.SystemBuffer.
Subito sopra la chiamata a ZwTerminateProcessCaller si trova il codice IOCTL : -2106392528 (in esadecimale : 0x82730030).
Grazie a queste informazioni, si può dedurre che per sfruttare questo driver è necessario inviare una chiamata API DeviceIoControl al driver con il nome del processo da terminare nel SystemBuffer.
🔷 Informazioni recuperate tramite il reverse engineering :
0x82730030ViragtltViragtltParte 2 - Exploitation
Per sfruttare questo driver (se installato e attivo sulla macchina target), è necessario aprire un handle verso di esso, quindi effettuare una chiamata API DeviceIoControl con un Buffer contenente il nome del processo che si desidera terminare.
Per questo esercizio, ho sviluppato un progetto C che :
Ho anche aggiunto un'opzione -d che consente di eliminare il servizio e il driver dal sistema dopo l'exploitation.
Ecco il comportamento del programma C nel suo ciclo di esecuzione completo :
Evasione AV/EDR
In questo caso, DriverKiller.exe non viene rilevato da Microsoft Defender, né in statico né in dinamico. L'evasione non ha davvero senso qui perché il driver sfruttato ha un certificato scaduto, quindi il suo utilizzo in condizioni reali è difficilmente ipotizzabile. Ma per una migliore furtività, si sarebbe potuto implementare :
Rilevamento sul driver al 29/08/2025 (risultato già esistente, non ho inviato nulla su VirusTotal per ovvie ragioni) :
⚠️ Questo progetto è realizzato in un contesto di apprendimento. Può contenere imprecisioni o errori. Ogni suggerimento, correzione o discussione è il benvenuto ! 😃 Grazie a d1rk(SaadAhla) : https://github.com/SaadAhla !