giovedì 15 agosto 2019

10 - Kernel API: Stringhe

In kernel mode si usano prevalentemente stringhe unicode, che richiedono due byte per ogni carattere (tecnicamente tale affermazione non sarebbe corretta ma grazie a questa semplificazione si rende più comprensibile il resto della lezione). A volte, però, capita di avere a che fare con qualche stringa ANSI, che richiede un solo byte (anche questa è una semplificazione di comodo), perché magari è stata passata con questa codifica dal client al driver. Fortunatamente, l'API del kernel fornisce quasi sempre funzioni aggiuntive in grado di accettare stringhe ANSI come parametro.
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 DriverObjectIN 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(PagedPoolBUFFER_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(PagedPoolBUFFER_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(PagedPoolBUFFER_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