Le registre Windows peut être utilisé depuis un driver kernel pour charger ou conserver des paramètres persistants. En kernel mode, on manipule principalement des
Code: Select all
UNICODE_STRINGCode: Select all
OBJECT_ATTRIBUTESCode: Select all
Zw*Code: Select all
Rtl*Code: Select all
Io*1. Namespace NT du registre
Les chemins kernel utilisent le namespace NT, par exemple :
Code: Select all
\Registry\Machine\System\CurrentControlSet\Services\MyDriver
Code: Select all
HKEY_LOCAL_MACHINE2. UNICODE_STRING et OBJECT_ATTRIBUTES
Les noms de clés et de valeurs sont généralement représentés avec
Code: Select all
UNICODE_STRINGCode: Select all
UNICODE_STRING keyName;
RtlInitUnicodeString(
&keyName,
L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\MyDriver"
);
Code: Select all
OBJECT_ATTRIBUTESCode: Select all
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(
&oa,
&keyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
nullptr,
nullptr
);
3. Droits d'accès
Quelques droits importants :
Code: Select all
KEY_READCode: Select all
KEY_WRITECode: Select all
KEY_QUERY_VALUECode: Select all
KEY_SET_VALUECode: Select all
KEY_ENUMERATE_SUB_KEYSCode: Select all
KEY_CREATE_SUB_KEYCode: Select all
DELETE
4. Ouvrir une clé avec ZwOpenKey
Code: Select all
HANDLE key = nullptr;
NTSTATUS status =
ZwOpenKey(
&key,
KEY_READ,
&oa
);
if (!NT_SUCCESS(status))
{
return status;
}
Code: Select all
ZwClose(key);
Code: Select all
ULONG disposition = 0;
status =
ZwCreateKey(
&key,
KEY_READ | KEY_WRITE,
&oa,
0,
nullptr,
REG_OPTION_NON_VOLATILE,
&disposition
);
Code: Select all
disposition6. Zw* et Nt*
Depuis un driver, on utilise généralement les variantes
Code: Select all
Zw*Code: Select all
Nt*Code: Select all
Zw*7. Clés associées aux périphériques
Pour un PDO, on peut utiliser :
Code: Select all
IoOpenDeviceRegistryKey
Code: Select all
PLUGPLAY_REGKEY_DEVICE
PLUGPLAY_REGKEY_DRIVER
Pour une interface de périphérique :
Code: Select all
IoOpenDeviceInterfaceRegistryKey
Le pattern le plus important est celui des deux appels :
Code: Select all
ULONG size = 0;
status =
ZwQueryValueKey(
key,
&valueName,
KeyValuePartialInformation,
nullptr,
0,
&size
);
9. STATUS_BUFFER_TOO_SMALL et STATUS_BUFFER_OVERFLOW
Lors du premier appel, ces statuts peuvent être attendus :
Code: Select all
STATUS_BUFFER_TOO_SMALL
STATUS_BUFFER_OVERFLOW
10. Allocation moderne du buffer
Code: Select all
PKEY_VALUE_PARTIAL_INFORMATION info =
(PKEY_VALUE_PARTIAL_INFORMATION)
ExAllocatePool2(
POOL_FLAG_PAGED,
size,
'geRK'
);
if (info == nullptr)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
Code: Select all
ExAllocatePoolCode: Select all
ExAllocatePoolWithTag11. Deuxième appel de ZwQueryValueKey
Code: Select all
status =
ZwQueryValueKey(
key,
&valueName,
KeyValuePartialInformation,
info,
size,
&size
);
Code: Select all
ExFreePool(info);
Cette structure contient notamment :
Code: Select all
Type
DataLength
Data
Code: Select all
TypeCode: Select all
DataLength13. Types de valeurs importants
Code: Select all
REG_DWORDCode: Select all
REG_QWORDCode: Select all
REG_SZCode: Select all
REG_EXPAND_SZCode: Select all
REG_MULTI_SZCode: Select all
REG_BINARY
14. Lire un REG_DWORD
Code: Select all
if (info->Type == REG_DWORD &&
info->DataLength == sizeof(ULONG))
{
ULONG value =
*(PULONG)info->Data;
}
Code: Select all
if (info->Type == REG_QWORD &&
info->DataLength == sizeof(ULONGLONG))
{
ULONGLONG value =
*(PULONGLONG)info->Data;
}
Code: Select all
REG_SZCode: Select all
REG_EXPAND_SZCode: Select all
REG_MULTI_SZExemple conceptuel :
Code: Select all
"One\0Two\0Three\0\0"
Code: Select all
REG_BINARY18. Valider les tailles
Une taille lue dans le registre ne doit pas être utilisée sans limite.
Code: Select all
if (size == 0 || size > MAX_ALLOWED_SIZE)
{
return STATUS_INVALID_BUFFER_SIZE;
}
19. Écrire une valeur avec ZwSetValueKey
Code: Select all
UNICODE_STRING valueName;
RtlInitUnicodeString(
&valueName,
L"MyValue"
);
ULONG value = 42;
status =
ZwSetValueKey(
key,
&valueName,
0,
REG_DWORD,
&value,
sizeof(value)
);
Code: Select all
KEY_SET_VALUE20. Supprimer une valeur
Code: Select all
ZwDeleteValueKey(
key,
&valueName
);
Code: Select all
ZwDeleteKey(key);
ZwClose(key);
22. Fonctions Rtl* du registre
On peut aussi rencontrer :
Code: Select all
RtlQueryRegistryValues
RtlWriteRegistryValue
RtlDeleteRegistryValue
Code: Select all
Zw*23. ZwQueryKey
Code: Select all
ZwQueryKey
Code: Select all
KeyFullInformation
On y trouve notamment :
- le nombre de sous-clés ;
- le nombre de valeurs ;
- la longueur maximale des noms ;
- la longueur maximale des données ;
- la date de dernière modification.
On utilise :
Code: Select all
ZwEnumerateKey
Exemple conceptuel :
Code: Select all
for (ULONG index = 0; ; ++index)
{
status =
ZwEnumerateKey(
key,
index,
KeyBasicInformation,
buffer,
bufferSize,
&resultLength
);
if (status == STATUS_NO_MORE_ENTRIES)
break;
if (!NT_SUCCESS(status))
break;
}
Cette structure contient notamment :
Code: Select all
LastWriteTime
NameLength
Name
Code: Select all
NameLength27. Énumérer les valeurs
On utilise :
Code: Select all
ZwEnumerateValueKey
Code: Select all
KeyValueBasicInformation
KeyValuePartialInformation
KeyValueFullInformation
Cette structure contient notamment :
Code: Select all
Type
NameLength
Name
29. ZwFlushKey
Code: Select all
ZwFlushKey
Il ne faut pas l'appeler systématiquement : Windows gère normalement lui-même l'écriture différée du registre.
30. IRQL
Les opérations de registre sont des opérations pouvant bloquer.
Elles sont généralement effectuées à :
Code: Select all
PASSIVE_LEVEL
31. RegistryPath dans DriverEntry
Code: Select all
extern "C"
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
)
Code: Select all
RegistryPathSi ce chemin doit être conservé après
Code: Select all
DriverEntry32. Registre et PnP
Il faut distinguer :
- configuration globale du driver ;
- configuration d'une instance de périphérique ;
- configuration d'une interface.
Code: Select all
IoOpenDeviceRegistryKeyCode: Select all
IoOpenDeviceInterfaceRegistryKey33. Gestion des erreurs
Statuts fréquents :
Code: Select all
STATUS_OBJECT_NAME_NOT_FOUND
STATUS_OBJECT_PATH_NOT_FOUND
STATUS_BUFFER_TOO_SMALL
STATUS_BUFFER_OVERFLOW
STATUS_ACCESS_DENIED
STATUS_NO_MORE_ENTRIES
Code: Select all
NT_SUCCESS(status)
34. Nettoyage
Lorsqu'une fonction possède plusieurs ressources :
Code: Select all
HANDLE key;
PVOID buffer;
Le modèle mental est :
Code: Select all
ouvrir
↓
allouer
↓
utiliser
↓
libérer
↓
fermer
Code: Select all
NTSTATUS ReadDword(
HANDLE Key,
PUNICODE_STRING ValueName,
PULONG Result
)
{
ULONG size = 0;
NTSTATUS status =
ZwQueryValueKey(
Key,
ValueName,
KeyValuePartialInformation,
nullptr,
0,
&size
);
if (status != STATUS_BUFFER_TOO_SMALL &&
status != STATUS_BUFFER_OVERFLOW)
{
return status;
}
if (size == 0 || size > 4096)
{
return STATUS_INVALID_BUFFER_SIZE;
}
PKEY_VALUE_PARTIAL_INFORMATION info =
(PKEY_VALUE_PARTIAL_INFORMATION)
ExAllocatePool2(
POOL_FLAG_PAGED,
size,
'geRK'
);
if (info == nullptr)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
status =
ZwQueryValueKey(
Key,
ValueName,
KeyValuePartialInformation,
info,
size,
&size
);
if (NT_SUCCESS(status))
{
if (info->Type != REG_DWORD ||
info->DataLength != sizeof(ULONG))
{
status = STATUS_OBJECT_TYPE_MISMATCH;
}
else
{
*Result =
*(PULONG)info->Data;
}
}
ExFreePool(info);
return status;
}
Code: Select all
NTSTATUS WriteDword(
HANDLE Key,
PCWSTR Name,
ULONG Value
)
{
UNICODE_STRING valueName;
RtlInitUnicodeString(
&valueName,
Name
);
return ZwSetValueKey(
Key,
&valueName,
0,
REG_DWORD,
&Value,
sizeof(Value)
);
}
- oublier ;
Code: Select all
ZwClose - demander sans nécessité ;
Code: Select all
KEY_ALL_ACCESS - oublier dans un contexte où il est nécessaire ;
Code: Select all
OBJ_KERNEL_HANDLE - ne pas vérifier ;
Code: Select all
NTSTATUS - ne pas vérifier le type ;
Code: Select all
REG_* - ne pas vérifier ;
Code: Select all
DataLength - faire confiance à une taille gigantesque ;
- confondre octets et caractères ;
- supposer que tous les noms retournés sont terminés par zéro ;
- accéder au registre à un IRQL trop élevé ;
- conserver sans gérer la durée de vie de son buffer.
Code: Select all
RegistryPath
Code: Select all
ZwOpenKey
ZwCreateKey
ZwQueryValueKey
ZwSetValueKey
ZwDeleteValueKey
ZwDeleteKey
ZwEnumerateKey
ZwEnumerateValueKey
ZwQueryKey
ZwClose
IoOpenDeviceRegistryKey
IoOpenDeviceInterfaceRegistryKey
Code: Select all
UNICODE_STRING
OBJECT_ATTRIBUTES
KEY_VALUE_PARTIAL_INFORMATION
KEY_VALUE_BASIC_INFORMATION
KEY_VALUE_FULL_INFORMATION
KEY_BASIC_INFORMATION
KEY_FULL_INFORMATION
Ouverture :
Code: Select all
UNICODE_STRING
↓
OBJECT_ATTRIBUTES
↓
ZwOpenKey
↓
HANDLE
Code: Select all
ZwQueryValueKey
↓
taille nécessaire
↓
ExAllocatePool2
↓
ZwQueryValueKey
↓
valider Type + DataLength
↓
traiter
↓
ExFreePool
Code: Select all
ZwClose
Le registre kernel repose principalement sur :
Code: Select all
UNICODE_STRING
OBJECT_ATTRIBUTES
HANDLE
Zw*
Code: Select all
initialiser le nom
↓
initialiser OBJECT_ATTRIBUTES
↓
ouvrir
↓
query / set / enumerate
↓
fermer
Enfin, le driver doit systématiquement valider :
- les tailles ;
- les types ;
- les droits ;
- les handles ;
- les allocations ;
- l'IRQL ;
- la durée de vie des buffers.
Code: Select all
ExAllocatePool2
Code: Select all
ExAllocatePool
ExAllocatePoolWithTag
