martedì 3 settembre 2019

14 - Kernel API: Altre funzioni (Driver e Thread di sistema)

In modo molto simile a quanto mostrato nella precedente lezione (si veda [1]), si vedrà adesso come elencare i driver caricati ed avviati nel sistema. Inoltre si creerà un thread di sistema per svolgere un compito banale che, però, mostrerà come usare gli eventi per mettersi in attesa fino a che tale thread non termina il suo lavoro. Il codice presentato in questa lezione è tratto da [2].




Seguita la lezione precedente, non dovrebbe essere troppo difficile riuscire a capire il seguente codice per enumerare i driver, pertanto si eviteranno inutili lungaggini o ripetizioni fornendo semplicemente il codice commentato.

// Simile a struttura LDR_DATA_TABLE_ENTRY vista in lezione precedente (si veda [1]) ma 
// presenta info su moduli caricati in kernel space (driver, DLL speciali e Ntsokrnl.exe naturalmente)
typedef struct _KLDR_DATA_TABLE_ENTRY
{
 LIST_ENTRY InLoadOrderLinks;
 PVOID ExceptionTable;
 ULONG ExceptionTableSize;
 ULONG pad1;
 PVOID GpValue;
 PVOID NonPagedDebugInfo;
 PVOID DllBase;
 PVOID EntryPoint;
 ULONG SizeOfImage;
 ULONG pad2;
 UNICODE_STRING FullDllName;
 UNICODE_STRING BaseDllName;
 ULONG   Flags;
 USHORT  LoadCount;
 USHORT  pad3;
 PVOID   SectionPointer;
 ULONG   CheckSum;
 ULONG   CoverageSectionSize;
 PVOID CoverageSection;
 PVOID LoadedImports;
 PVOID Spare;
 ULONG SizeOfImageNotRounded;
 ULONG TimeDateStamp;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
 
VOID EnumDriver(PDRIVER_OBJECT pDriverObject)
{
 // Campo DriverSection di DRIVER_OBJECT è un puntatore ad una Head di una
 // lista doppiamente collegata i cui elementi sono di tipo KLDR_DATA_TABLE_ENTRY.
 // Simile a caso visto in lezione precedente: si veda [1]
 PKLDR_DATA_TABLE_ENTRY entry = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
 PKLDR_DATA_TABLE_ENTRY firstentry;
 firstentry = entry;
 
 while ((PKLDR_DATA_TABLE_ENTRY)entry->InLoadOrderLinks.Flink != firstentry)
 {
  DbgPrint("BASE=%p\tPATH=%wZ", entry->DllBase, entry->FullDllName);
  entry = (PKLDR_DATA_TABLE_ENTRY)entry->InLoadOrderLinks.Flink;
 }
}

Per quanto riguarda la creazione del thread di sistema e l'impostazione della routine da fargli eseguire, anche in questo caso la cosa non presenta particolari difficoltà.

// Valori negativi per tempo relativo (rispetto a timer di sistema), 
// considerati come numero di intervalli di 100 nanosecondi.
// Valori positivi per tempo assoluto (sempre considerando timer di sistema ed intervalli di 100 nanosecondi).
#define DELAY_ONE_MICROSECOND  (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
 
// Evento su cui mettersi in attesa
KEVENT kEvent;
 
 
VOID CreateThreadTest()
{
 HANDLE     hThread;
 UNICODE_STRING ustrTest = RTL_CONSTANT_STRING(L"This is a string for test!");
 NTSTATUS status;
 
 // KeInitializeEvent inizializza un oggetto evento impostandone il tipo e lo stato iniziale.
 // Per il tipo le opzioni sono due:
 // - NotificationEvent: Usato per risvegliare più thread in attesa sullo stesso oggetto
 //   evento. Una volta segnalato l'oggetto evento resta in questo stato fino a non si chiama 
 //   esplicitamente KeResetEvent o KeClearEvent su tale oggetto evento.
 // - SynchronizationEvent: Usato per risvegliare un singolo thread in attesa sull'oggetto
 //   evento. Tale oggetto viene riportato automaticamente allo stato non segnalato una volta 
 //   che l'attesa è soddisfatta.
 // Per lo stato iniziale: FALSE indica non segnalato.
 KeInitializeEvent(&kEvent, SynchronizationEventFALSE);
 
 // PsCreateSystemThread crea un thread di sistema che eseguira la funzione il cui indirizzo
 // è passato come penultimo parametro passandogli un argomento il cui indirizzo è passato
 // come ultimo parametro.
 // Il primo parametro è un puntatore che riceverà l'indirizzo dell'handle al thread di sistema creato.
 status = PsCreateSystemThread(&hThread, 0, NULLNULLNULL, MyThreadFunc, (PVOID)&ustrTest);
 if (!NT_SUCCESS(status))
 {
  DbgPrint("PsCreateSystemThread failed!");
  return;
 }
 
 // Chiude l'handle al thread di sistema poichè non serve in questo caso.
 // Nota: Tale operazione non termina il thread. Decrementa solo il 
 // numero di riferimenti al thread.
 ZwClose(hThread);
 
 // Resta in attesa sull'oggetto evento finché questo non viene segnalato.
 // Si veda la documentazione ufficiale per maggiori dettagli sugli altri parametri (non rilevanti al momento).
 KeWaitForSingleObject(&kEvent, ExecutiveKernelModeFALSENULL);
 DbgPrint("CreateThreadTest OVER!\n");
}

VOID MyThreadFunc(IN PVOID context)
{
 PUNICODE_STRING str = (PUNICODE_STRING)context;
 DbgPrint("Kernel thread running: %wZ\n", str);
 DbgPrint("Wait 3s!\n");
 MySleep(3000);
 DbgPrint("Kernel thread exit!\n");
 
 // KeSetEvent imposta allo stato segnalato l'oggetto evento passato come primo parametro.
 // Gli altri due parametri non sono importanti al momento; si veda la documentazione ufficiale per maggiori dettagli.
 KeSetEvent(&kEvent, 0, TRUE);
 
 // PsTerminateSystemThread termina il thread di sistema corrente passando lo stato con cui
 // si vuole terminare tale thread come primo parametro.
 PsTerminateSystemThread(STATUS_SUCCESS);
}

VOID MySleep(LONG msec)
{
 LARGE_INTEGER my_interval;
 my_interval.QuadPart = DELAY_ONE_MILLISECOND;
 my_interval.QuadPart *= msec;
 
 // KeDelayExecutionThread mette in attesa il thread corrente per il tempo passato come ultimo parametro.
 // In kernel mode primo e secondo parametro normalemente KernelMode e 0, rispettivamente.
 KeDelayExecutionThread(KernelMode, 0, &my_interval);
}

Insieme a DriverEntry ed il codice di supporto viene fornita anche una funzione che richiede il tempo di sistema e converte il risultato stampandolo in un formato adatto ad essere letto. Verificare, confrontare o semplicemente salvare il tempo di sistema può rivelarsi un'operazione utile in più di una occasione.

/*typedef struct TIME_FIELDS
{
 CSHORT Year;
 CSHORT Month;
 CSHORT Day;
 CSHORT Hour;
 CSHORT Minute;
 CSHORT Second;
 CSHORT Milliseconds;
 CSHORT Weekday;
} TIME_FIELDS;*/
 
VOID MyGetCurrentTime()
{
 LARGE_INTEGER CurrentTime;
 LARGE_INTEGER LocalTime;
 TIME_FIELDS   TimeFiled;
 // Quello che si ottiene qui è in realtà Greenwich Mean Time (GMT+0).
 KeQuerySystemTime(&CurrentTime);
 // Converte in tempo locale
 ExSystemTimeToLocalTime(&CurrentTime, &LocalTime);
 // Converte in un formato facile da utilizzare
 RtlTimeToTimeFields(&LocalTime, &TimeFiled);
 DbgPrint("[TimeTest] NowTime : %4d-%2d-%2d %2d:%2d:%2d",
  TimeFiled.Year, TimeFiled.Month, TimeFiled.Day,
  TimeFiled.Hour, TimeFiled.Minute, TimeFiled.Second);
}
 
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{
 UNREFERENCED_PARAMETER(pDriverObj);
}
 
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObjPUNICODE_STRING pRegistryString)
{
 UNREFERENCED_PARAMETER(pRegistryString);
 
 pDriverObj->DriverUnload = DriverUnload;
 
 EnumDriver(pDriverObj);
 MyGetCurrentTime();
 CreateThreadTest();
 
 return STATUS_SUCCESS;
}




Codice sorgente:
EnumDriversDriver.zip



Riferimenti:
[1] 13 - Kernel API: Altre funzioni (Processi, Thread e DLL)
[2] https://github.com/andylau004/LookDrvCode/blob/master/WIN64驱动编程基础教程/代码/%5B2-8%5DOtherFunction/MyDriver.h

Nessun commento:

Posta un commento