Inoltre, in kernel mode usare direttamente stringhe di tipo char* o wchar* che terminano con il carattere nullo è un po' rischioso in quanto basterebbe sovrascrivere quest'ultimo carattere per portare a risultati devastanti. Per questo motivo in kernel mode si usano i wrapper UNICODE_STRING e ANSI_STRING
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef struct _ANSI_STRING { USHORT Length; USHORT MaximumLength; PCHAR Buffer; } ANSI_STRING, *PANSI_STRING;
Lenght è lunghezza in byte della stringa in Buffer (escluso eventuale carattere nullo terminatore)
MaximumLenght è lunghezza in byte del Buffer
Buffer è un puntatore ad un buffer che contiene la stringa
Quasi tutte le funzioni dell'API del kernel hanno a che fare con questi wrapper. In particolare, se si ha una stringa di tipo char* o wchar* e si vuole inizializzare una UNICODE_STRING o ANSI_STRING si usano le funzioni RtlInitUnicodeString e RtlInitAnsiString
NTSYSAPI VOID RtlInitUnicodeString( PUNICODE_STRING DestinationString, PCWSTR SourceString ); NTSYSAPI VOID RtlInitAnsiString( PANSI_STRING DestinationString, PCSZ SourceString );
Entrambe inizializzano il puntatore Buffer della struttura UNICODE_STRING o ANSI_STRING, passata come primo parametro, con l'indirizzo della stringa passata come secondo parametro (quindi se si vuole evitare che modifiche alla stringa si riflettano sul wrapper relativo è necessario allocare dello spazio, assegnare l'indirizzo di tale spazio al membro Buffer, copiare la stringa in tale spazio e ricordarsi di liberare lo spazio allocato alla fine delle operazioni con ExFreePool, RtlFreeUnicodeString o RtlFreeAnsiString. Per un esempio pratico si veda il codice alla fine della lezione). I membri Lenght e MaximumLenght vengono inizializzati a runtime. Lenght avrà la dimensione, in byte, della stringa passata come secondo parametro, escluso il carattere terminatore. MaximumLenght, invece, avrà la dimensione, in byte, della stringa passata come secondo parametro, incluso il carattere terminatore. Di particolare interesse è la macro RTL_CONSTANT_STRING che ha lo stesso scopo delle due funzioni suddette ma riesce a gestire sia stringhe UNICODE che ANSI e calcola la lunghezza della stringa passata come secondo parametro a compile-time, con tutti i vantaggi in efficienza che questo comporta (nel codice seguente STRING è simile ad ANSI_STRING).
STRING RTL_CONSTANT_STRING( [in] PCSZ SourceString ); UNICODE_STRING RTL_CONSTANT_STRING( [in] PCWSTR SourceString );
Per le operazioni di copia esistono RtlCopyUnicodeString e RtlCopyString che copiano il minor numero di caratteri tra SourceString.Length e DestinationString.MaximumLength da SourceString a DestinationString. Solo il campo Length di DestinationString viene impostato da RtlCopyUnicodeString e RtlCopyString. MaximumLength e Buffer devono quindi essere inizializzati esplicitamente prima della chiamata a tali funzioni.
NTSYSAPI VOID RtlCopyUnicodeString( PUNICODE_STRING DestinationString, PCUNICODE_STRING SourceString ); NTSYSAPI VOID RtlCopyString( PSTRING DestinationString, const STRING *SourceString );
Per la comparazione si possono usare RtlEqualUnicodeString e RtlEqualString
NTSYSAPI BOOLEAN RtlEqualUnicodeString( PCUNICODE_STRING String1, PCUNICODE_STRING String2, BOOLEAN CaseInSensitive ); NTSYSAPI BOOLEAN RtlEqualString( const STRING *String1, const STRING *String2, BOOLEAN CaseInSensitive );
Per convertire in maiuscolo esistono RtlUpcaseUnicodeString e RtlUpperString: AllocateDestinationString indica se si vuol dare a RtlUpcaseUnicodeString la responsabilità di allocare spazio per DestinationString. In caso affermativo è poi necessario usare RtlFreeUnicodeString per liberare tale spazio.
NTSYSAPI NTSTATUS RtlUpcaseUnicodeString( PUNICODE_STRING DestinationString, PCUNICODE_STRING SourceString, BOOLEAN AllocateDestinationString ); NTSYSAPI VOID RtlUpperString( PSTRING DestinationString, const STRING *SourceString );
Per convertire da intero a stringa UNICODE e viceversa si possono usare RtlUnicodeStringToInteger e RtlIntegerToUnicodeString
NTSYSAPI NTSTATUS RtlUnicodeStringToInteger( PCUNICODE_STRING String, ULONG Base, PULONG Value ); NTSYSAPI NTSTATUS RtlIntegerToUnicodeString( ULONG Value, ULONG Base, PUNICODE_STRING String );
Per convertire da stringa UNICODE ad ANSI e viceversa esistono le funzioni RtlUnicodeStringToAnsiString e RtlAnsiStringToUnicodeString: AllocateDestinationString indica se si vuol dare alle funzioni stesse la responsabilità di allocare spazio per DestinationString. In caso affermativo è poi necessario usare RtlFreeUnicodeString o RtlFreeAnsiString per liberare tale spazio.
NTSYSAPI NTSTATUS RtlUnicodeStringToAnsiString( PANSI_STRING DestinationString, PCUNICODE_STRING SourceString, BOOLEAN AllocateDestinationString ); NTSYSAPI NTSTATUS RtlAnsiStringToUnicodeString( PUNICODE_STRING DestinationString, PCANSI_STRING SourceString, BOOLEAN AllocateDestinationString );
Infine, è utile sapere che lo specificatore %wZ, nelle stringhe di formato passate a DbgPrint e KdPrint, indica UNICODE_STRING.
Le funzioni RtlXXX non sono altro che wrapper a funzioni implementate nel modulo NtosKrnl.exe (da usare in kernel mode) e/o nel mudulo Ntdll.dll (da usare in user mode). La documentazione Microsoft consiglia di usare le versioni RtlXXX poiché si tratta di interfacce non soggette a modifiche. Segue un esempio di codice tratto da uno dei tutorial scritti da Tesla.Angela (si veda [1]).
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject) { DbgPrint("[MyDriver]Unloaded...\n"); return; } NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { DriverObject->DriverUnload = DriverUnload; DbgPrint("[MyDriver]Loaded...\n"); StringInitTest(); StringCopyTest(); StringCompareTest(); StringToUpperTest(); StringToIntegerTest(); StringConverTest(); return STATUS_SUCCESS; }
VOID StringInitTest() { ANSI_STRING AnsiString1; CHAR *string1 = "hello"; RtlInitAnsiString(&AnsiString1, string1); DbgPrint("AnsiString1:%Z\n", &AnsiString1); string1[0] = 'H'; string1[1] = 'E'; string1[2] = 'L'; string1[3] = 'L'; string1[4] = 'O'; DbgPrint("AnsiString1:%Z\n", &AnsiString1); #define BUFFER_SIZE 1024 UNICODE_STRING UnicodeString1 = { 0 }; WCHAR* wideString = L"hello"; UnicodeString1.MaximumLength = BUFFER_SIZE; UnicodeString1.Buffer = (PWSTR)ExAllocatePool(PagedPool, BUFFER_SIZE); UnicodeString1.Length = 2 * wcslen(wideString); ASSERT(UnicodeString1.MaximumLength >= UnicodeString1.Length); RtlCopyMemory(UnicodeString1.Buffer, wideString, UnicodeString1.Length); UnicodeString1.Length = 2 * wcslen(wideString); DbgPrint("UnicodeString:%wZ\n", &UnicodeString1); ExFreePool(UnicodeString1.Buffer); UnicodeString1.Buffer = NULL; UnicodeString1.Length = UnicodeString1.MaximumLength = 0; }
VOID StringCopyTest() { UNICODE_STRING UnicodeString1; UNICODE_STRING UnicodeString2 = { 0 }; RtlInitUnicodeString(&UnicodeString1, L"Hello World"); UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool, BUFFER_SIZE); UnicodeString2.MaximumLength = BUFFER_SIZE; RtlCopyUnicodeString(&UnicodeString2, &UnicodeString1); DbgPrint("UnicodeString1:%wZ\n", &UnicodeString1); DbgPrint("UnicodeString2:%wZ\n", &UnicodeString2); RtlFreeUnicodeString(&UnicodeString2); }
VOID StringCompareTest() { UNICODE_STRING UnicodeString1; UNICODE_STRING UnicodeString2; RtlInitUnicodeString(&UnicodeString1, L"Hello World"); RtlInitUnicodeString(&UnicodeString1, L"Hello"); if (RtlEqualUnicodeString(&UnicodeString1, &UnicodeString2, TRUE)) DbgPrint("UnicodeString1 and UnicodeString2 are equal\n"); else DbgPrint("UnicodeString1 and UnicodeString2 are NOT equal\n"); }
VOID StringToUpperTest() { UNICODE_STRING UnicodeString1; UNICODE_STRING UnicodeString2; RtlInitUnicodeString(&UnicodeString1, L"Hello World"); DbgPrint("UnicodeString1:%wZ\n", &UnicodeString1); RtlUpcaseUnicodeString(&UnicodeString2, &UnicodeString1, TRUE); DbgPrint("UnicodeString2:%wZ\n", &UnicodeString2); RtlFreeUnicodeString(&UnicodeString2); }
VOID StringToIntegerTest() { UNICODE_STRING UnicodeString1; ULONG lNumber; NTSTATUS nStatus; RtlInitUnicodeString(&UnicodeString1, L"-100"); nStatus = RtlUnicodeStringToInteger(&UnicodeString1, 10, &lNumber); if (NT_SUCCESS(nStatus)) { DbgPrint("Conver to integer succussfully!\n"); DbgPrint("Result:%d\n", lNumber); } else { DbgPrint("Conver to integer unsuccessfully!\n"); } NTSTATUS nStatus; UNICODE_STRING UnicodeString2 = { 0 }; UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool, BUFFER_SIZE); UnicodeString2.MaximumLength = BUFFER_SIZE; nStatus = RtlIntegerToUnicodeString(200, 10, &UnicodeString2); if (NT_SUCCESS(nStatus)) { DbgPrint("Conver to string succussfully!\n"); DbgPrint("Result:%wZ\n", &UnicodeString2); } else { DbgPrint("Conver to string unsuccessfully!\n"); } RtlFreeUnicodeString(&UnicodeString2); }
VOID StringConverTest() { UNICODE_STRING UnicodeString1; ANSI_STRING AnsiString1; NTSTATUS nStatus; RtlInitUnicodeString(&UnicodeString1, L"Hello World"); nStatus = RtlUnicodeStringToAnsiString(&AnsiString1, &UnicodeString1, TRUE); if (NT_SUCCESS(nStatus)) { DbgPrint("Conver succussfully!\n"); DbgPrint("Result:%Z\n", &AnsiString1); } else { DbgPrint("Conver unsuccessfully!\n"); } RtlFreeAnsiString(&AnsiString1); ANSI_STRING AnsiString2; UNICODE_STRING UnicodeString2; NTSTATUS nStatus; RtlInitString(&AnsiString2, "Hello World"); nStatus = RtlAnsiStringToUnicodeString(&UnicodeString2, &AnsiString2, TRUE); if (NT_SUCCESS(nStatus)) { DbgPrint("Conver succussfully!\n"); DbgPrint("Result:%wZ\n", &UnicodeString2); } else { DbgPrint("Conver unsuccessfully!\n"); } RtlFreeUnicodeString(&UnicodeString2); }
Riferimenti:
[1] https://github.com/andylau004/LookDrvCode/blob/master/WIN64驱动编程基础教程/代码/%5B2-4%5DStringOperationTest/main.c
Nessun commento:
Posta un commento