martedì 27 agosto 2019

12 - Kernel API: Registro

In kernel mode a volte capita di dover interagire con il registro di sistema per configurare qualcosa o per salvare dei dati in modo permanente. La prima funzione che si prenderà in esame in questa lezione è ZwCreateKey, che restituisce un handle alla chiave di registro che si vuole creare o aprire. Una funzione molto simile è ZwOpenKey, che però permette solo di aprire una chiave già esistente.

NTSYSAPI NTSTATUS ZwCreateKey(
 PHANDLE            KeyHandle,
 ACCESS_MASK        DesiredAccess,
 POBJECT_ATTRIBUTES ObjectAttributes,
 ULONG              TitleIndex,
 PUNICODE_STRING    Class,
 ULONG              CreateOptions,
 PULONG             Disposition
);

NTSYSAPI NTSTATUS ZwOpenKey(
 PHANDLE            KeyHandle,
 ACCESS_MASK        DesiredAccess,
 POBJECT_ATTRIBUTES ObjectAttributes
);

KeyHandle è il puntatore che riceverà l'indirizzo dell'handle alla chiave di registro

DesiredAccess è un valore (o una combinazione di valori) che indica cosa si vuole fare della chiave tramite il relativo handle. Ad esempio, si può voler leggere o scrivere (o entrambe le cose) un valore della chiave.

ObjectAttributes è un puntatore ad una struttura di tipo OBJECT_ATTRIBUTES che deve essere inizializzata con vari attributi da associare all'handle. Tra i più importanti ci sono:
- il nome (spesso completamente qualificato) dell'oggetto kernel (chiave di registro) di cui si vuole ottenere l'handle.
- l'indicazione del fatto che l'handle sarà usato solo in kernel mode o meno.
- l'indicazione del fatto che la comparazione tra il nome indicato per l'oggetto kernel ed altri nomi di oggetti esistesti dello stesso tipo avvenga senza distinzione tra maiuscole o minuscole.
Per inizializzare tale struttura si può usare la macro InitializeObjectAttributes (si veda [2]).

TitleIndex e Class non sono rilevanti: impostarli a zero e NULL, rispettivamente.

CreateDisposition è un valore (o una combinazione di valori) che specifica le opzioni da applicare quando si crea o apre una chiave di registro. In particolare, si può indicare se la chiave deve essere cancellata (REG_OPTION_VOLATILE) o preservata (REG_OPTION_NON_VOLATILE) quando il sistema è riavviato

Disposition è un puntatore ad una variabile che riceve un valore che indica se una nuova chiave è stata creata (REG_CREATED_NEW_KEY) od una esistente è stata aperta (REG_OPENED_EXISTING_KEY).

Tutti gli esempi di codice presenti in questa lezione sono tratti da [1].

BOOLEAN MyCreateRegistryKey(UNICODE_STRING ustrRegistry)
{
 HANDLE hRegister = NULL;
 OBJECT_ATTRIBUTES objectAttributes = { 0 };
 ULONG ulResult = 0;
 NTSTATUS status = STATUS_SUCCESS;
 
 InitializeObjectAttributes(&objectAttributes, &ustrRegistryOBJ_CASE_INSENSITIVENULLNULL);
 
 status = ZwCreateKey(&hRegister,
  KEY_ALL_ACCESS,
  &objectAttributes,
  0,
  NULL,
  REG_OPTION_NON_VOLATILE,
  &ulResult);
 if (!NT_SUCCESS(status))
 {
  ShowError("ZwCreateKey", status);
  return FALSE;
 }
 if (ulResult == REG_CREATED_NEW_KEY)
 {
  DbgPrint("The register item is createed!\n");
 }
 else if (ulResult == REG_OPENED_EXISTING_KEY)
 {
  DbgPrint("The register item has been created, and now is opened!\n");
 }
 
 ZwClose(hRegister);
 return TRUE;
}

BOOLEAN MyOpenRegistryKey(UNICODE_STRING ustrRegistry)
{
 OBJECT_ATTRIBUTES objectAttributes = { 0 };
 HANDLE hRegister = NULL;
 NTSTATUS status = STATUS_SUCCESS;
 
 InitializeObjectAttributes(&objectAttributes, &ustrRegistryOBJ_CASE_INSENSITIVENULLNULL);
 
 status = ZwOpenKey(&hRegister, KEY_ALL_ACCESS, &objectAttributes);
 if (!NT_SUCCESS(status))
 {
  ShowError("ZwOpenKey", status);
  return FALSE;
 }
 DbgPrint("Open register successfully!\n");
 
 ZwClose(hRegister);
 return TRUE;
}

Un'altra funzione utile è ZwDeleteKey.

NTSYSAPI NTSTATUS ZwDeleteKey(
 HANDLE KeyHandle
);

KeyHandle è l'handle alla chiave che si vuole eliminare.

BOOLEAN MyDeleteRegistryKey(UNICODE_STRING ustrRegistry)
{
 HANDLE hRegister = NULL;
 OBJECT_ATTRIBUTES objectAttributes = { 0 };
 NTSTATUS status = STATUS_SUCCESS;
 
 InitializeObjectAttributes(&objectAttributes, &ustrRegistryOBJ_CASE_INSENSITIVENULLNULL);
 status = ZwOpenKey(&hRegister, KEY_ALL_ACCESS, &objectAttributes);
 if (!NT_SUCCESS(status))
 {
  ShowError("ZwOpenKey", status);
  return FALSE;
 }
 
 status = ZwDeleteKey(hRegister);
 if (!NT_SUCCESS(status))
 {
  ZwClose(hRegister);
  ShowError("ZwDeleteKey", status);
  return FALSE;
 }
 
 ZwClose(hRegister);
 return TRUE;
}

Se si vuole creare un valore per una chiava o si vuole impostare un suo valore già esistente si può usare la funzione ZwSetValueKey.

NTSYSAPI NTSTATUS ZwSetValueKey(
 HANDLE          KeyHandle,
 PUNICODE_STRING ValueName,
 ULONG           TitleIndex,
 ULONG           Type,
 PVOID           Data,
 ULONG           DataSize
);

KeyHandle è l'handle alla chiave di registro in cui si vuole creare o impostare il valore. In genere tale handle è ottenuto tramite ZwCreateKey o ZwOpenKey.

ValueName è un puntatore ad una struttura UNICODE_STRING che contiene il nome del valore che conterrà i dati che si vogliono scrivere. Se si fornisce il nome di un valore già esistente i dati che esso contiene verranno sovrascritti.

TitleIndex non è rilevante: impostarlo a zero.

Type è un valore che indica il tipo di dati da scrivere. Tra i valori più usati ci sono REG_BINARY (binario), REG_DWORD (numerico) e REG_SZ (stringa).

Data è un puntatore che contiene l'indirizzo del buffer con i dati da scrivere.

DataSize è un valore che indica la dimensione in byte del buffer specificato nel parametro Data.

BOOLEAN MySetRegistryKeyValue(
 UNICODE_STRING ustrRegistryUNICODE_STRING ustrKeyValueNameULONG ulKeyValueTypePVOID pKeyValueDataULONG ulKeyValueDataSize)
{
 HANDLE hRegister = NULL;
 OBJECT_ATTRIBUTES objectAttributes = { 0 };
 NTSTATUS status = STATUS_SUCCESS;
 
 InitializeObjectAttributes(&objectAttributes, &ustrRegistryOBJ_CASE_INSENSITIVENULLNULL);
 
 status = ZwOpenKey(&hRegister, KEY_ALL_ACCESS, &objectAttributes);
 if (!NT_SUCCESS(status))
 {
  ShowError("ZwOpenKey", status);
  return FALSE;
 }
 
 status = ZwSetValueKey(hRegister, &ustrKeyValueName, 0, ulKeyValueTypepKeyValueDataulKeyValueDataSize);
 if (!NT_SUCCESS(status))
 {
  ZwClose(hRegister);
  ShowError("ZwSetValueKey", status);
  return FALSE;
 }
 
 ZwClose(hRegister);
 return TRUE;
}

Se invece si vuole leggere il valore di una chiava si può usare la funzione ZwQueryValueKey.
NTSYSAPI NTSTATUS ZwQueryValueKey(
 HANDLE                      KeyHandle,
 PUNICODE_STRING             ValueName,
 KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
 PVOID                       KeyValueInformation,
 ULONG                       Length,
 PULONG                      ResultLength
);

KeyHandle è l'handle alla chiave di registro dove si trova il valore da leggere. In genere tale handle è ottenuto tramite ZwCreateKey o ZwOpenKey.

ValueName è un puntatore ad una struttura UNICODE_STRING che contiene il nome del valore che si vuole leggere.

KeyValueInformationClass è un valore che indica il tipo di informazioni che si vogliono leggere. Tale valore di solito è KeyValuePartialInformation quando si è interessati esclusivamente ai dati di un valore.

KeyValueInformation è un puntatore che contiene l'indirizzo del buffer che conterrà una istanza del tipo specificato nel parametro KeyValueInformationClass. Nel caso in esame questa sarà una istanza della struttura KEY_VALUE_PARTIAL_INFORMATION

Length è un valore che indica la dimensione in byte del buffer specificato nel parametro KeyValueInformation.

ResultLength è un puntatore ad una variabile. Se ZwQueryValueKey restituisce STATUS_SUCCESS tale variabile conterrà il numero di byte scritti nel buffer specificato nel membro KeyValueInformation. Se invece restituisce STATUS_BUFFER_OVERFLOW o STATUS_BUFFER_TOO_SMALL tale variabile conterrà la dimensione in byte necessari a contenere le informazioni richieste per il valore della chiave.

BOOLEAN MyQueryRegistryKeyValue(UNICODE_STRING ustrRegistryUNICODE_STRING ustrKeyValueName)
{
 HANDLE hRegister = NULL;
 OBJECT_ATTRIBUTES objectAttributes = { 0 };
 NTSTATUS status = STATUS_SUCCESS;
 ULONG ulBufferSize = 0;
 PKEY_VALUE_PARTIAL_INFORMATION pKeyValuePartialInfo = NULL;
 
 InitializeObjectAttributes(&objectAttributes, &ustrRegistryOBJ_CASE_INSENSITIVENULLNULL);
 
 status = ZwOpenKey(&hRegister, KEY_ALL_ACCESS, &objectAttributes);
 if (!NT_SUCCESS(status))
 {
  ShowError("ZwOpenKey", status);
  return FALSE;
 }
 
 // Passando zero come dimensione del buffer obbliga ZwQueryValueKey a restituire STATUS_BUFFER_TOO_SMALL
 // e passare la dimensione necessaria a contenere le informazioni richieste in ulBufferSize.
 // Passa NULL come puntatore al buffer perchè non necessario in 
 // questa fase: controllo su dimensioni avviene prima quindi il puntatore non viene mai usato.
 status = ZwQueryValueKey(hRegister, &ustrKeyValueNameKeyValuePartialInformationNULL, 0, &ulBufferSize);
 if (ulBufferSize == 0)
 {
  ZwClose(hRegister);
  ShowError("ZwQueryValueKey", status);
  return FALSE;
 }
 
 // Usa ulBufferSize per allocare un buffer della dimesione appropriata.
 pKeyValuePartialInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ExAllocatePool(NonPagedPool, ulBufferSize);
 
 status = ZwQueryValueKey(
  hRegister, 
  &ustrKeyValueNameKeyValuePartialInformation, 
  pKeyValuePartialInfo, 
  ulBufferSize, 
  &ulBufferSize);
 if (!NT_SUCCESS(status))
 {
  ExFreePool(pKeyValuePartialInfo);
  ZwClose(hRegister);
  ShowError("ZwQueryValueKey", status);
  return FALSE;
 }
 
 /*
 typedef struct _KEY_VALUE_PARTIAL_INFORMATION {
   ULONG TitleIndex;
   ULONG Type;
   ULONG DataLength;
   UCHAR Data[1];
 } KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION;
 */
 DbgPrint("KeyValueName=%wZ, KeyValueType=%d, KeyValueData=%S\n",
  &ustrKeyValueName, pKeyValuePartialInfo->Type, pKeyValuePartialInfo->Data);
 
 ExFreePool(pKeyValuePartialInfo);
 ZwClose(hRegister);
 return TRUE;
}

Per eliminare il valore di una chiave si può usare ZwDeleteValueKey.
NTSYSAPI NTSTATUS ZwDeleteValueKey(
 HANDLE          KeyHandle,
 PUNICODE_STRING ValueName
);

KeyHandle è l'handle alla chiave di registro dove si trova il valore da eliminare. In genere tale handle è ottenuto tramite ZwCreateKey o ZwOpenKey.

ValueName è un puntatore ad una struttura UNICODE_STRING che contiene il nome del valore che si vuole eliminare.

BOOLEAN MyDeleteRegistryKeyValue(UNICODE_STRING ustrRegistryUNICODE_STRING ustrKeyValueName)
{
 HANDLE hRegister = NULL;
 OBJECT_ATTRIBUTES objectAttributes = { 0 };
 NTSTATUS status = STATUS_SUCCESS;
 
 InitializeObjectAttributes(&objectAttributes, &ustrRegistryOBJ_CASE_INSENSITIVENULLNULL);
 
 status = ZwOpenKey(&hRegister, KEY_ALL_ACCESS, &objectAttributes);
 if (!NT_SUCCESS(status))
 {
  ShowError("ZwOpenKey", status);
  return FALSE;
 }
 
 status = ZwDeleteValueKey(hRegister, &ustrKeyValueName);
 if (!NT_SUCCESS(status))
 {
  ZwClose(hRegister);
  ShowError("ZwDeleteValueKey", status);
  return FALSE;
 }
 
 ZwClose(hRegister);
 return TRUE;
}

Esistono altre funzioni che permettono di interagire con il registro di sistema ma queste sono le principali. Infine, segue il codice di DriverEntry per testare le funzioni scritte finora.

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}
 
NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObjPIRP pIrp)
{
 NTSTATUS status = STATUS_SUCCESS;
 pIrp->IoStatus.Status = status;
 pIrp->IoStatus.Information = 0;
 IoCompleteRequest(pIrpIO_NO_INCREMENT);
 
 return status;
}
 
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObjectPUNICODE_STRING pRegPath)
{
 DbgPrint("Enter DriverEntry\n");
 
 NTSTATUS status = STATUS_SUCCESS;
 pDriverObject->DriverUnload = DriverUnload;
 for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
 {
  pDriverObject->MajorFunction[i] = DriverDefaultHandle;
 }
 
 UNICODE_STRING ustrRegistry;
 RtlInitUnicodeString(&ustrRegistry, L"\\Registry\\Machine\\Software\\TestReg");
 MyCreateRegistryKey(ustrRegistry);
 
 MyOpenRegistryKey(ustrRegistry);
 
 UNICODE_STRING ustrKeyValueName;
 WCHAR wstrKeyValueData[] = L"Stringa di prova";
 RtlInitUnicodeString(&ustrKeyValueName, L"Test");
 MySetRegistryKeyValue(ustrRegistry, ustrKeyValueName, REG_SZ, wstrKeyValueData, sizeof(wstrKeyValueData));
 
 MyQueryRegistryKeyValue(ustrRegistry, ustrKeyValueName);
 
 MyDeleteRegistryKeyValue(ustrRegistry, ustrKeyValueName);
 
 MyDeleteRegistryKey(ustrRegistry);
 
 DbgPrint("Leave DriverEntry\n");
 return status;
}




Riferimenti:
[1] https://github.com/DemonGan/Windows-Hack-Programming
[2] https://docs.microsoft.com/en-us/windows/win32/api/ntdef/nf-ntdef-initializeobjectattributes

Nessun commento:

Posta un commento