[Previous] [Next]

Функции для работы с Системным Реестром

Системный Реестр является системной базой данных для хранения разнообразных параметров, используемых приложениями пользовательского режима и программными модулями, работающими в режиме ядра. В операционных системах от Windows 98 до Windows Server 2003 существует набор системных вызовов, которыми можно воспользоваться в режиме ядра для доступа к существующим записям в Системном Реестре, для их чтения, редактирования, удаления, а также — для создания новых.

Перед тем, как перейти к рассмотрению собственно функций доступа к Системному Реестру из кода режима ядра, еще раз повторим, что Реестр разбит на основные разделы, среди которых основным является раздел HKEY_LOCAL_MACHINE (в литературе часто используется сокращение HKLM). Разделы могут быть разбиты на подразделы произвольной глубины вложения. В англоязычной литературе подразделы могут называться словом "path", однако, чаще употребляется "key" или "registry key". B подразделе (даже если он содержит вложенные подразделы) могут содержаться параметры (values), которые имеют имя (обычно, в литературе называемые ValueName) и значение.

Наиболее точно способ доступа к параметрам отражает следующее представление. Каждый подраздел можно считать файлом (в начале работы его следует открыть и получить соответствующий дескриптор), после чего можно работать с вложенной в него информацией — собственными параметрами, вложенными подразделами и их параметрами.

Функции доступа к Системному Реестру, предоставляемые Диспетчером ввода/вывода

Функции доступа к Реестру, предоставляемые Диспетчером ввода/вывода, предоставляют доступ по указателю на объект устройства, либо по имени зарегистрированного интерфейса (аналог символьной ссылки). На практике, программисты выбирают в качестве имени интерфейса глобальный идентификатор GUID в строковом представлении (генерируется программой GuidGen).

Документация DDK рекомендует применять функции, предоставляемые для доступа к Реестру Диспетчером ввода/вывода, вместо функций прямого доступа — для облегчения переноса на другие процессорные платформы и в качестве защиты от изменений в структуре Реестра в будущем.

IoRegisterDeviceInterface регистрирует интерфейс устройства (аналог регистрации символьной ссылки), что делает возможным доступ к устройству из приложений пользовательского режима и других системных компонентов. Диспетчер ввода/вывода создает подраздел Реестра для зарегистрированного интерфейса. Драйвер может хранить в этом подразделе собственные параметры, получая доступ к нему вызовом IoOpenDeviceInterfaceRegistryKey.

IoGetDeviceProperty запрашивает из Системного Реестра установочную информацию об устройстве.

IoOpenDeviceInterfaceRegistryKey возвращает дескриптор доступа к подразделу реестра для зарегистрированного интерфейса устройства (способ регистрации устройства в системе при помощи вызова IoRegisterDeviceInterface, вместо символьной ссылки). Полученный таким образом дескриптор должен по окончании использования быть закрыт вызовом ZwClose.

IoOpenDeviceRegistryKey возвращает дескриптор доступа к подразделу Системного Реестра для драйвера или устройства по указателю на объект устройства. Полученный таким образом дескриптор должен по окончании использования быть закрыт вызовом ZwClose.

IoSetDeviceInterfaceState разрешает или запрещает доступ к зарегистрированному ранее интерфейсу устройства. Приложения пользовательского режима и другие компоненты системы могут получать доступ только к незапрещенным интерфейсам.

Функции RtlXxx прямого доступа к Системному Реестру

RtlCheckRegistryKey возвращает STATUS_SUCCESS в том случае, если указанный вложенный подраздел существует внутри подраздела, описанного первым параметром вызова. Первый параметр указывается как одно из значений RTL_REGISTRY_Xxx. Например, раздел \Registry\Machine\System\CurrentControlSet\Services описывается значением RTL_REGISTRY_SERVICES, а раздел \Registry\User\CurrentUser описывается значением RTL_REGISTRY_USER (см. заголовочные файлы ntddk.h или wdm.h).

RtlCreateRegistryKey создает подраздел внутри подраздела Реестра, указанного вторым параметром, который работает так же, как и в описанной выше функции RtlCheckRegistryKey.

RtlQueryRegistryValues позволяет одним вызовом получить значения нескольких параметров из всего поддерева указанного подраздела Реестра. При обнаружении требуемого параметра происходит вызов callback процедуры QueryRoutine, предоставляемой драйвером. Тем не менее, во всем многообразии примеров DDK нет ни одного случая использования этого вызова с callback процедурой QueryRoutine. На практике, вызов RtlQueryRegistryValues выполняется с первым параметром, имеющим в своем составе флаг RTL_QUERY_REGISTRY_DIRECT, при котором callback процедура не используется.

RtlWriteRegistryValue производит запись значения параметра Системного Реестра. Попутно может быть создан один вложенный подраздел, см. пример ниже.

UNICODE_STRING newParameterUnicodeTextValue;
RtlInitUnicodeString( &newParameterUnicodeTextValue,
                      L"Example text parameter 1");

NTSTATUS status =
RtlWriteRegistryValue( RTL_REGISTRY_SERVICES,
	L"Example1\\InnerKey",
	L"NewParameter",    // можно даже L"Вложенный Раздел"
	REG_SZ,
	newParameterUnicodeTextValue.Buffer,
	sizeof(WCHAR)*( wcslen(newParameterUnicodeTextValue.Buffer)+1)
	);
if ( !NT_SUCCESS( status ) )
{
	#if DBG
	DbgPrint("RtlWriteRegistryValue call is unsuccessful.");
	#endif
} 

Первый параметр RTL_REGISTRY_SERVICES указывает на то, что действия будут выполняться в разделе \Registry\Machine\System\CurrentControlSet\Services, или, в общепринятых терминах, HKLM\SYSTEM\CurrentControlSet\Services. В нем должен существовать вложенный подраздел \Example1. Тогда возможны два варианта: в подраздел \Example1 вложен существующий подраздел \InnerKey, либо раздел \InnerKey не существует (и тогда он будет создан). В результате вызова получится подраздел HKLM\SYSTEM\CurrentControlSet\Services\Example1\InnerKey, в котором будет присутствовать строковый параметр NewParameter со значением "Example text parameter 1".

В том случае, если не существует не только подраздел \InnerKey, но и подраздел \Example1, вызов RtlWriteRegistryValue завершится неудачей.

В том случае, если все указанные подразделы присутствуют, и параметр NewParameter уже имеет какое-то значение, то в результате последовательности действий, как в описанном выше примере, в Системный Реестр будет записано новое значение, то есть "Example text parameter 1" .

RtlDeleteRegistryVaIue удаляет параметр из указанного подраздела, например:

status = RtlDeleteRegistryValue( RTL_REGISTRY_SERVICES,
                                 L"Example1\\InnerKey ",
                                 L"NewParameter" );
if( !NT_SUCCESS(x_status ) )
{
	#if DBG
	DbgPrint("RtlDeleteRegistryValue call is unsuccessful.");
	#endif
} 

В результате, в HKLM\SYSTEM\CurrentControlSet\Services\Example1\InnerKey исчезнет параметр NewParameter.

Работа с Системным Реестром через вызовы ZwXxx

Все-таки наиболее богатым и основательным является набор функций для работы с Системным Реестром ZwXxx.

ZwCreateKey открывает доступ к существующему подразделу Системного Реестра или создает новый. Возвращает дескриптор открытого объекта.

ZwOpenKey открывает доступ к существующему разделу Системного Реестра и возвращает дескриптор открытого объекта (см.пример ниже).

ZwQueryKey возвращает информацию о подразделе — его класс, число и размер вложенных подразделов. Инициатор вызова должен предоставить достаточного размера буфер для принимаемой информации, иначе вызов будет завершен с кодом ошибки STATUS_BUFFER_TOO_SMALL или STATUS_BUFFER_OVERFLOW.

ZwEnumerateKey возвращает информацию о вложенных подразделах предварительно открытого подраздела Системного Реестра.

ZwEnumerateValueKey возвращает информацию о параметрах и их значениях для предварительно открытого подраздела Системного Реестра.

ZwQueryValueKey возвращает информацию о значении параметра, присутствующего в данном предварительно открытом разделе Системного Реестра. Характер возвращаемой информации определяет третий аргумент вызова, который принимает одно из значений KeyValueBasicInformation, KeyValueFullInformation или KeyValuePartialInformation. Пример применения данной функции приводится ниже.

ZwSetValueKey создает или изменяет значение параметра в открытом подразделе Системного Реестра. Для возможности применения этой функции, дескриптор подраздела при открытии должен быть получен с применением маски DesiredAccess, содержащей флаг KEY_SET_VALUE

ZwFlushKey форсирует фиксацию изменений, сделанных в открытом подразделе вызовами ZwCreateKey или ZwSetValueKey, на диске.

ZwDeleteKey удаляет открытый подраздел из Системного Реестра.

ZwClose закрывает дескриптор открытого ранее подраздела Системного Реестра, фиксирует произведенные изменения на жестком диске.

Ниже приводится пример программного кода, выполняющий операции по получении значения параметра ErrorControl из раздела Системного Реестра, который был создан для описания драйвера и поступил в процедуру DriverEntry в аргументе RegistryPath. Для случая драйвера Example.sys этот подраздел называется HKLM\SYSTEM\ControlSet001\Services\Example, а собственно строка RegistryPath хранит значение:

L"\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet001\\Services\\Example"

Вся работа с Системным Реестром вынесена в отдельную функцию GetRegValueDword.

// Сначала объявляем прототип GetRegValueDword:
int GetRegValueDword(PCWSTR RegPath,PCWSTR ValueName,PULONG pValue);
extern "C"
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,
                      IN PUNICODE_STRING RegistryPath )
{
 . . .

if(!GetRegValueDword( RegistryPath->Buffer,
                      L"ErrorControl",
                      &ulValue))
{
	#if DBG
	DbgPrint("Error in GetRegValueDword.");
	#endif
}
else
{
	#if DBG
	DbgPrint("RegistryPath\\ErrorControl = %x.", ulValue);
	#endif
}

int GetRegValueDword(PCWSTR RegPath, PCWSTR ValueName, PULONG pValue)
{
int                            ReturnValue = 0;
NTSTATUS                       status;
OBJECT_ATTRIBUTES              ObjectAttributes;
HANDLE                         KeyHandle;
KEY_VALUE_PARTIAL_INFORMATION  *pInformation;
ULONG                          uInformationSize;
UNICODE_STRING	                UnicodeRegPath;
UNICODE_STRING	                UnicodeValueName;
// Инициализация UNICODE_STRING полученными
// не-счетными строками двухбайтных символов
RtlInitUnicodeString(&UnicodeRegPath,   RegPath);
RtlInitUnicodeString(&UnicodeValueName, ValueName);

// Описание атрибутов, в частности, полного имени подраздела
InitializeObjectAttributes(&ObjectAttributes,
                           &UnicodeRegPath,
                           0,     // Flags
                           NULL,  // Root directory
                           NULL); // Security descriptor

status = ZwOpenKey( &KeyHandle,
                    KEY_QUERY_VALUE,
                    &ObjectAttributes );

if( !NT_SUCCESS(status) )  // Если не получен доступ к подразделу:
{
	#if DBG
	DbgPrint("=Example= Can not open reg path %ws .",
		UnicodeRegPath.Buffer);
	DbgPrint("=Example= Status = %x.",status);
	if (Status==STATUS_INVALID_HANDLE)
		DbgPrint("=Example= STATUS_INVALID_HANDLE.");
	if(Status==STATUS_ACCESS_DENIED)
		DbgPrint ("=Example= STATUS_ACCESS_DENIED.");
	#endif
return 0;
}

// Вычисляем размер буфера для получения информации о параметре:
uInformationSize =
		sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(ULONG);

// Выделение области в страничной памяти.
// Область будет помечена тегом 'EXaM'	
pInformation =
	(KEY_VALUE_PARTIAL_INFORMATION*)
	ExAllocatePoolWithTag(PagedPool, uInformationSize,'EXaM');

if( pInformation == NULL )  // Не выделена память
{
	ZwClose(KeyHandle);
	return 0;
}

// Получить описание типа KeyValuePartialInformation:
//
status = ZwQueryValueKey(KeyHandle,
                         &UnicodeValueName,
                         KeyValuePartialInformation,
                         pInformation,
                         uInformationSize,
                         &uInformationSize );

if( !NT_SUCCESS(status) )
{
	#if DBG
	DbgPrint("=Example= ZwQueryValueKey not successful.");
	#endif
}
else
{
	if( pInformation->Type == REG_DWORD &&
		pInformation->DataLength == sizeof(ULONG) )
	{
		RtlCopyMemory(pValue, pInformation->Data, sizeof(ULONG));
		ReturnValue = 1;
	}
}

// Завершение работы
ExFreePool(Information);
ZwClose(KeyHandle);

return ReturnValue;
} 

Практически все функции для работы с Системным Реестром должны вызываться из кода, работающего на уровне IRQL равном PASSIVE_LEVEL