Драйверы режима ядра: Часть 10: Базовая техника: Реестр — Архив WASM.RU
10.1 Структура реестра
Реестр (Registry) - это централизованная база данных, играющая ключевую роль в конфигурировании и управлении системой. Структура реестра аналогична структуре логического диска, но содержимое реестра не является статической совокупностью хранящихся на жестком диске данных, а динамически изменяется в процессе работы системы. Реестр состоит из разделов (keys), напоминающих дисковые каталоги. Разделы самого верхнего уровня называются корневыми (root keys). Разделы представляют собой контейнер, содержащий другие разделы, называемые подразделами (subkeys), и/или параметры (values), которые можно сравнить с файлами на диске. Параметры хранят собственно данные. За реализацию и управление реестром отвечает Диспетчер конфигурации (Configuration Manager).
Существует шесть корневых разделов:
HKEY_USER
- содержит информацию обо всех учётных записях;
HKEY_CURRENT_USER
- хранит профиль текущего зарегистрированного пользователя;
HKEY_LOCAL_MACHINE
- хранит информацию о конфигурации системы: описание аппаратного обеспечения, политики безопасности, пароли пользователей, настройки приложений, а также конфигурацию сервисов и драйверов;
HKEY_CURRENT_CONFIG
- содержит текущий профиль оборудования;
HKEY_CLASSES_ROOT
- хранит сопоставления расширений файлов и регистрационные данные COM-классов;
HKEY_PERFORMANCE_DATA
- содержит значения счетчиков производительности.
HKEY_PERFORMANCE_DATA особый раздел, обращаться к которому напрямую вряд ли потребуется. В пользовательском режиме информация о счетчиках производительности доступна через библиотеку Performance Data Helper, реализованную в модуле pdh.dll. Стандартная оснастка "Производительность" (Performance Monitor) использует именно эту библиотеку. Помимо счетчиков производительности этот раздел содержит массу дополнительной информации. Например, функции Process Status (реализованы в psapi.dll), перечисляющие процессы, потоки, модули и т.п., черпают информацию именно из раздела HKEY_PERFORMANCE_DATA. Редакторы реестра Regedit и Regedt32 не показывают содержимое этого раздела, т.к. редактировать его нет смысла.
Корневые разделы HKEY_CURRENT_USER, HKEY_CURRENT_CONFIG и HKEY_CLASSES_ROOT не содержат никакой информации. На самом деле это ссылки на другие подразделы реестра.
HKEY_CURRENT_USER
- ссылка на подраздел HKEY_USER\<SID_текущего_пользователя>, соответствующий текущему зарегистрированному в системе пользователю.
HKEY_CURRENT_CONFIG
- ссылка на подраздел HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current.
HKEY_CLASSES_ROOT
- ссылка на подразделы HKEY_LOCAL_MACHINE\SOFTWARE\Classes и HKEY_CURRENT_USER\SOFTWARE\Classes.
10.2 Доступ к реестру из драйвера
Как же добраться до реестра из режима ядра? Доступ к реестру осуществляется точно также, как и к любому другому именованному объекту, т.е. через пространство имен диспетчера объектов (подробнее см. 3 часть). Для интеграции пространства имен реестра и диспетчера объектов Диспетчер конфигурации (Configuration Manager) создает объект типа "раздел реестра" (key object) под именем "Registry" и помещает его в корневой каталог пространства имен диспетчера объектов. Для компонентов режима ядра это и есть точка входа в реестр.
Рис. 10-1. Объект "раздел реестра" Registry в пространстве имен диспетчера объектов
Все функции ядра, осуществляющие доступ к именованным объектам получают их имена в составе структуры OBJECT_ATRIBUTES - это нам уже давно известно. В случае объекта типа "раздел реестра" это имя должно начинаться с "\Registry". Остаётся добавить имена корневых разделов. Сразу скажу, что я не знаю, какое имя используется для открытия коневого раздела HKEY_PERFORMANCE_DATA и вообще, используется ли для этого какое-то имя. Возможно, здесь применяется другой механизм. Я предпринял кое-какие усилия в этом направлении, но они оказались напрасны. Если вам что-то известно по этому вопросу - просьба поделиться со мной этими знаниями. Для оставшихся двух корневых разделов всё просто.
Раздел
Имя
HKEY_USER
"\Registry\User"
HKEY_LOCAL_MACHINE
"\Registry\Machine"
Для обращения к трём разделам-ссылкам потребуются дополнительные манипуляции. Например, чтобы обратиться к разделу HKEY_CURRENT_CONFIG, надо знать, что он является ссылкой на подраздел HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current и подставить имя этого корневого раздела. Т.о. получится такое имя \Registry\Machine\SYSTEM\CurrentControlSet\Hardware Profiles\Current. К сожалению, определить такую длинную unicode-строку, используя макросы CTW0, $CTW0 и т.п. не удастся, т.к. эта строка превышает лимит в 47 символов. Можете воспользоваться любым доступным вам способом.
10.3 Исходный текст драйвера RegistryWorks
Теперь, когда нам известны имена корневых разделов - добраться до них не составит большого труда.
Код (Text):
;@echo off ;goto make ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; ; RegistryWorks - Пример работы с реестром ; ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: .386 .model flat, stdcall option casemap:none ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; В К Л Ю Ч А Е М Ы Е Ф А Й Л Ы ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: include \masm32\include\w2k\ntstatus.inc include \masm32\include\w2k\ntddk.inc include \masm32\include\w2k\ntoskrnl.inc includelib \masm32\lib\w2k\ntoskrnl.lib include \masm32\Macros\Strings.mac ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; Н Е И З М Е Н Я Е М Ы Е Д А Н Н Ы Е ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: .const CCOUNTED_UNICODE_STRING "\\Registry\\Machine\\Software\\CoolApp", g_usMachineKeyName, 4 CCOUNTED_UNICODE_STRING "SomeData", g_usValueName, 4 CTW0 "It's just a string", g_wszStringData, 4 ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; К О Д ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: .code ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; CreateKey ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CreateKey proc local oa:OBJECT_ATTRIBUTES local hKey:HANDLE local dwDisposition:DWORD invoke DbgPrint, $CTA0("\nRegistryWorks: *** Creating registry key\n") lea ecx, oa InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, REG_OPTION_VOLATILE, addr dwDisposition .if eax == STATUS_SUCCESS .if dwDisposition == REG_CREATED_NEW_KEY invoke DbgPrint, $CTA0("RegistryWorks: Registry key \\Registry\\Machine\\Software\\CoolApp created\n") .elseif dwDisposition == REG_OPENED_EXISTING_KEY invoke DbgPrint, $CTA0("RegistryWorks: Registry key \\Registry\\Machine\\Software\\CoolApp opened\n") .endif invoke ZwClose, hKey invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n") .else invoke DbgPrint, $CTA0("RegistryWorks: Can't create registry key. Status: %08X\n"), eax .endif ret CreateKey endp ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; SetValueKey ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: SetValueKey proc local oa:OBJECT_ATTRIBUTES local hKey:HANDLE invoke DbgPrint, $CTA0("\nRegistryWorks: *** Opening registry key to set new value\n") lea ecx, oa InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL invoke ZwOpenKey, addr hKey, KEY_SET_VALUE, ecx .if eax == STATUS_SUCCESS invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded\n") invoke ZwSetValueKey, hKey, addr g_usValueName, 0, REG_SZ, \ addr g_wszStringData, sizeof g_wszStringData .if eax == STATUS_SUCCESS invoke DbgPrint, $CTA0("RegistryWorks: Registry key value added\n") .else invoke DbgPrint, \ $CTA0("RegistryWorks: Can't set registry key value. Status: %08X\n"), eax .endif invoke ZwClose, hKey invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n") .else invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X\n"), eax .endif ret SetValueKey endp ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; QueryValueKey ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: QueryValueKey proc local oa:OBJECT_ATTRIBUTES local hKey:HANDLE local cb:DWORD local ppi:PKEY_VALUE_PARTIAL_INFORMATION local as:ANSI_STRING local us:UNICODE_STRING invoke DbgPrint, $CTA0("\nRegistryWorks: *** Opening registry key to read value\n") lea ecx, oa InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL invoke ZwOpenKey, addr hKey, KEY_QUERY_VALUE, ecx .if eax == STATUS_SUCCESS invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded\n") invoke ZwQueryValueKey, hKey, addr g_usValueName, \ KeyValuePartialInformation, NULL, 0, addr cb .if cb != 0 invoke ExAllocatePool, PagedPool, cb .if eax != NULL mov ppi, eax invoke ZwQueryValueKey, hKey, addr g_usValueName, \ KeyValuePartialInformation, ppi, cb, addr cb .if ( eax == STATUS_SUCCESS ) && ( cb != 0 ) mov eax, ppi .if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZ lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).Data invoke RtlInitUnicodeString, addr us, eax invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE .if eax == STATUS_SUCCESS invoke DbgPrint, \ $CTA0("RegistryWorks: Registry key value is: \=%s\=\n"), as.Buffer invoke RtlFreeAnsiString, addr as .endif .endif .else invoke DbgPrint, \ $CTA0("RegistryWorks: Can't query registry key value. Status: %08X\n"), eax .endif invoke ExFreePool, ppi .else invoke DbgPrint, $CTA0("RegistryWorks: Can't allocate memory. Status: %08X\n"), eax .endif .else invoke DbgPrint, \ $CTA0("RegistryWorks: Can't get bytes count needed for key partial information. Status: %08X\n"), eax .endif invoke ZwClose, hKey invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n") .else invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X\n"), eax .endif ret QueryValueKey endp ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; DeleteKey ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: DeleteKey proc local oa:OBJECT_ATTRIBUTES local hKey:HANDLE invoke DbgPrint, $CTA0("\nRegistryWorks: *** Deleting registry key\n") lea ecx, oa InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL invoke ZwOpenKey, addr hKey, KEY_ALL_ACCESS, ecx .if eax == STATUS_SUCCESS invoke DbgPrint, $CTA0("RegistryWorks: Registry key opened\n") invoke ZwDeleteKey, hKey .if eax == STATUS_SUCCESS invoke DbgPrint, $CTA0("RegistryWorks: Registry key deleted\n") .else invoke DbgPrint, $CTA0("RegistryWorks: Can't delete registry key. Status: %08X\n"), eax .endif invoke ZwClose, hKey invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n") .else invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X\n"), eax .endif ret DeleteKey endp ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; EnumerateKey ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: EnumerateKey proc local oa:OBJECT_ATTRIBUTES local hKey:HANDLE local cb:DWORD local pbi:PKEY_BASIC_INFORMATION local pfi:PKEY_FULL_INFORMATION local as:ANSI_STRING local us:UNICODE_STRING local dwSubKeys:DWORD local pwszKeyName:PWCHAR invoke DbgPrint, $CTA0("\nRegistryWorks: *** Opening \\Registry\\User key to enumerate\n") CCOUNTED_UNICODE_STRING "\\Registry\\User", g_usUserKeyName, 4 lea ecx, oa InitializeObjectAttributes ecx, offset g_usUserKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL invoke ZwOpenKey, addr hKey, KEY_ENUMERATE_SUB_KEYS, ecx .if eax == STATUS_SUCCESS invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded\n") invoke ZwQueryKey, hKey, KeyFullInformation, NULL, 0, addr cb .if cb != 0 invoke ExAllocatePool, PagedPool, cb .if eax != NULL mov pfi, eax invoke ZwQueryKey, hKey, KeyFullInformation, pfi, cb, addr cb .if ( eax == STATUS_SUCCESS ) && ( cb != 0 ) mov eax, pfi push (KEY_FULL_INFORMATION PTR [eax]).SubKeys pop dwSubKeys invoke DbgPrint, \ $CTA0("RegistryWorks: ---------- Starting enumerate subkeys ----------\n") push ebx xor ebx, ebx .while ebx < dwSubKeys invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, NULL, 0, addr cb .if cb != 0 invoke ExAllocatePool, PagedPool, cb .if eax != NULL mov pbi, eax invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, pbi, cb, addr cb .if ( eax == STATUS_SUCCESS ) && ( cb != 0 ) mov eax, pbi mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength add eax, sizeof WCHAR mov cb, eax invoke ExAllocatePool, PagedPool, cb .if eax != NULL mov pwszKeyName, eax invoke memset, pwszKeyName, 0, cb mov ecx, pbi mov eax, (KEY_BASIC_INFORMATION PTR [ecx]).NameLength shr eax, 1 lea ecx, (KEY_BASIC_INFORMATION PTR [ecx])._Name invoke wcsncpy, pwszKeyName, ecx, eax invoke RtlInitUnicodeString, addr us, pwszKeyName invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE .if eax == STATUS_SUCCESS invoke DbgPrint, $CTA0("RegistryWorks: \=%s\=\n"), as.Buffer invoke RtlFreeAnsiString, addr as .endif invoke ExFreePool, pwszKeyName .endif .else invoke DbgPrint, \ $CTA0("RegistryWorks: Can't enumerate registry keys. Status: %08X\n"), eax .endif invoke ExFreePool, pbi .endif .endif inc ebx .endw pop ebx invoke DbgPrint, \ $CTA0("RegistryWorks: ------------------------------------------------\n") .else invoke DbgPrint, \ $CTA0("RegistryWorks: Can't query registry key information. Status: %08X\n"), eax .endif invoke ExFreePool, pfi .else invoke DbgPrint, $CTA0("RegistryWorks: Can't allocate memory. Status: %08X\n"), eax .endif .endif invoke ZwClose, hKey invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n") .else invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X\n"), eax .endif ret EnumerateKey endp ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; DriverEntry ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING invoke DbgPrint, $CTA0("\nRegistryWorks: Entering DriverEntry\n") ;::::::::::::::::::::::::::::::::::::::: ; Создаём новый подраздел ; ;::::::::::::::::::::::::::::::::::::::: invoke CreateKey ;::::::::::::::::::::::::::::::::::::::: ; Создаем в этом подразделе параметр ; ;::::::::::::::::::::::::::::::::::::::: invoke SetValueKey ;::::::::::::::::::::::::::::::::::::::: ; Получаем значение параметра ; ;::::::::::::::::::::::::::::::::::::::: invoke QueryValueKey ;::::::::::::::::::::::::::::::::::::::: ; Удаляем подраздел ; ;::::::::::::::::::::::::::::::::::::::: invoke DeleteKey ;::::::::::::::::::::::::::::::::::::::: ; Перечисляем содержимое раздела ; ;::::::::::::::::::::::::::::::::::::::: invoke EnumerateKey invoke DbgPrint, $CTA0("\nRegistryWorks: Leaving DriverEntry\n") mov eax, STATUS_DEVICE_CONFIGURATION_ERROR ret DriverEntry endp ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: end DriverEntry :make set drv=RegistryWorks \masm32\bin\ml /nologo /c /coff %drv%.bat \masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj del %drv%.obj echo. pauseКод драйвера состоит из нескольких автономных процедур: CreateKey, SetValueKey, QueryValueKey, DeleteKey и EnumerateKey, каждая из которых работает с реестром «с нуля» - для учебного примера так нагляднее.
10.3.1 Создаём и открываем подраздел реестра
В процедуре CreateKey, вызовом функции ZwCreateKey, создаем новый подраздел Registry\Machine\Software\CoolApp.
Код (Text):
invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, REG_OPTION_VOLATILE, addr dwDispositionФлаг REG_OPTION_VOLATILE запрещает запись созданного подраздела в куст реестра (hiev)- один из файлов реестра на диске. Т.е. этот подраздел будет существовать только до следующей перезагрузки системы. В данном случае, использовать этот флаг не обязательно, т.к. мы всё равно удалим весь подраздел. Если вы захотите прописать подраздел в реестр на постоянное место жительства, то использовать этот флаг не следует.
Код (Text):
.if eax == STATUS_SUCCESS .if dwDisposition == REG_CREATED_NEW_KEY .elseif dwDisposition == REG_OPENED_EXISTING_KEY .endifПосле успешного вызова ZwCreateKey по значению переменной dwDisposition можно определить, был ли создан новый подраздел (REG_CREATED_NEW_KEY) или такой подраздел уже существовал в реестре (REG_OPENED_EXISTING_KEY) и поэтому был открыт.
Код (Text):
invoke ZwOpenKey, addr hKey, KEY_SET_VALUE, ecx invoke ZwOpenKey, addr hKey, KEY_QUERY_VALUE, ecx invoke ZwOpenKey, addr hKey, KEY_ALL_ACCESS, ecx invoke ZwOpenKey, addr hKey, KEY_ENUMERATE_SUB_KEYS, ecxВ остальных процедурах вызовом функции ZwOpenKey открываем существующий раздел, запрашивая только необходимые в данный момент права доступа.
10.3.2 Создаём параметр реестра
Теперь, создадим в нашем подразделе строковый параметр с именем "SomeData".
Код (Text):
. . . CCOUNTED_UNICODE_STRING "SomeData", g_usValueName, 4 CTW0 "It's just a string", g_wszStringData, 4 . . . invoke ZwSetValueKey, hKey, addr g_usValueName, 0, REG_SZ, \ addr g_wszStringData, sizeof g_wszStringDataКонстанта REG_SZ определяет тип создаваемого параметра: unicode-строка завершающаяся нулём. Существует масса других типов - все они описаны в DDK.
10.3.3 Получаем значение параметра реестра
Получим значение нашего параметра SomeData.
Код (Text):
invoke ZwQueryValueKey, hKey, addr g_usValueName, \ KeyValuePartialInformation, NULL, 0, addr cbТретий параметр функции ZwQueryValueKey определяет тип запрашиваемой информации. В DDK определено три значения: KeyValueBasicInformation, KeyValueFullInformation и KeyValuePartialInformation, каждому из которых соответствует своя структура: KEY_VALUE_BASIC_INFORMATION, KEY_VALUE_FULL_INFORMATION и KEY_VALUE_PARTIAL_INFORMATION. В данном случае, мы хотим получить содержимое параметра. Для этого вполне годится KeyValuePartialInformation.
Т.к. объём информации нам заранее не известен, вызываем ZwQueryValueKey, передавая NULL в четвертом параметре (указатель на буфер) и 0 - в пятом (размер буфера). Функция ZwQueryValueKey подсчитает необходимый ей размер буфера и вернет это значение в переменной cb (многие, но не все, Zw* функции работают подобным образом).
Код (Text):
.if cb != 0 invoke ExAllocatePool, PagedPool, cb .if eax != NULL mov ppi, eax invoke ZwQueryValueKey, hKey, addr g_usValueName, \ KeyValuePartialInformation, ppi, cb, addr cbВыделив необходимое пространство памяти, вызываем ZwQueryValueKey ещё раз - теперь уже с указателем на буфер.
Код (Text):
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 ) mov eax, ppi .if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZНа всякий случай, проверяем тип параметра.
Код (Text):
lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).DataЕсли тип параметра REG_SZ, то поле Data структуры KEY_VALUE_PARTIAL_INFORMATION содержит завершенную нулем unicode-строку. Нам надо вывести эту строку в отладочном сообщении, поэтому необходимо преобразовать её в ansi-строку.
Код (Text):
invoke RtlInitUnicodeString, addr us, eaxФункция RtlInitUnicodeString измеряет unicode-строку, адрес которой передан во втором параметре и заполняет структуру UNICODE_STRING, адрес которой передан в первом параметре.
Код (Text):
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUEФункция RtlUnicodeStringToAnsiString конвертирует unicode-строку в ansi. Если последний параметр установлен в TRUE, функция сама выделит буфер, запишет туда преобразованную строку и заполнит структуру ANSI_STRING (переменную as, в нашем случае). Поле Buffer структуры ANSI_STRING будет указывать на выделенный буфер с преобразованной ansi-строкой. Если последний параметр установить в FALSE, то буфер для ansi-строки надо выделить заранее и поместить указатель на него в поле Buffer структуры ANSI_STRING. В данном случае, мы просим функцию RtlUnicodeStringToAnsiString выделить буфер за нас.
Код (Text):
.if eax == STATUS_SUCCESS invoke DbgPrint, \ $CTA0("RegistryWorks: Registry key value is: \=%s\=\n"), as.Buffer invoke RtlFreeAnsiString, addr as .endifВ DDK (по крайней мере, в 2000 DDK) описание функции RtlUnicodeStringToAnsiString, весьма туманно, я бы даже сказал двусмысленно. IFS DDK описывает работу этой функции значительно лучше. Чтобы расставить «точки над i» рассмотрим простой пример.
Код (Text):
wsz db 'a', 0, 'b', 0, 'c', 0, 0, 0 us UNICODE_STRING as ANSI_STRINGИзначально переменные us и as неопределены. wsz - unicode-строка, подлежащая переводу в формат ANSI.
Код (Text):
us._Length = ? us.MaximumLength = ? us.Buffer = ? as._Length = ? as.MaximumLength = ? as.Buffer = ?RtlInitUnicodeString заполняет переменную us на основании размера строки wsz.
Код (Text):
invoke RtlInitUnicodeString, addr us, addr wsz us._Length = 6 us.MaximumLength = 8 us.Buffer = offset wsz as._Length = ? as.MaximumLength = ? as.Buffer = ?По последнему аргументу RtlUnicodeStringToAnsiString видит, что она должна выделить буфер размера us.MaximumLength / sizeof WCHAR. Выделив буфер и поместив указатель на него в поле as.Buffer, функция RtlUnicodeStringToAnsiString приступает к собственно конвертации строки. Если эта операция проходит успешно, то переменная as будет содержать полное описание конвертированной строки.
Код (Text):
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE us._Length = 6 us.MaximumLength = 8 us.Buffer = offset wsz as._Length = 3 as.MaximumLength = 4 as.Buffer = -> 'a', 'b', 'c', 0 ; указатель на выделенный функцией RtlUnicodeStringToAnsiString,,,TRUE буфер, ; который содержит преобразованную строку wsz в формате ANSI.После использования, буфер, выделенные функцией RtlUnicodeStringToAnsiString, надо освободить, вызвав RtlFreeAnsiString, причем в качестве аргумента ей передается не указатель на сам буфер, а указатель на структуру ANSI_STRING. RtlFreeAnsiString освобождает буфер as.Buffer и обнуляет всю переменную as.
Код (Text):
invoke RtlFreeAnsiString, addr as us._Length = 6 us.MaximumLength = 8 us.Buffer = offset wsz as._Length = 0 as.MaximumLength = 0 as.Buffer = NULLНадеюсь, теперь всё понятно.
10.3.4 Удаляем подраздел реестра
Думаю, тут вы разберетесь и без меня.
10.3.5 Перебираем содержимое подраздела реестра
Теперь посмотрим, что лежит в разделе \Registry\User.
Код (Text):
invoke ZwQueryKey, hKey, KeyFullInformation, NULL, 0, addr cb .if cb != 0 invoke ExAllocatePool, PagedPool, cb .if eax != NULL mov pfi, eax invoke ZwQueryKey, hKey, KeyFullInformation, pfi, cb, addr cb .if ( eax == STATUS_SUCCESS ) && ( cb != 0 ) mov eax, pfi push (KEY_FULL_INFORMATION PTR [eax]).SubKeys pop dwSubKeysДля организации перебора содержимого раздела требуется сначала узнать количество подразделов/параметров, которое он содержит. Воспользуемся для этого информационным классом KeyFullInformation. Выделив необходимое количество памяти и передав её в функцию ZwQueryKey, получаем в переменной dwSubKeys искомое количество подразделов/параметров.
Код (Text):
push ebx xor ebx, ebx .while ebx < dwSubKeys invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, NULL, 0, addr cb .if cb != 0 invoke ExAllocatePool, PagedPool, cb .if eax != NULL mov pbi, eax invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, pbi, cb, addr cb .if ( eax == STATUS_SUCCESS ) && ( cb != 0 )Ползуясь функцией ZwEnumerateKey с информацмонным классом KeyBasicInformation, организуем цикл перебора. Как и в предыдущих случаях, вызываем её два раза: первый раз для того, чтобы узнать размер информации, второй - для того, чтобы эту информацию получить.
Код (Text):
mov eax, pbi mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength add eax, sizeof WCHAR mov cb, eax invoke ExAllocatePool, PagedPool, cbВ поле _Name структуры KEY_BASIC_INFORMATION вернётся имя подраздела/параметра в виде unicode-строки, но строка эта не завершена нулём. Для последующего преобразования её в ansi-строку (для вывода в отладочном сообщении) нам нужна завершенная нулем строка - выделим для неё временный буфер.
Код (Text):
.if eax != NULL mov pwszKeyName, eax invoke memset, pwszKeyName, 0, cb mov ecx, pbi mov eax, (KEY_BASIC_INFORMATION PTR [ecx]).NameLength shr eax, 1 lea ecx, (KEY_BASIC_INFORMATION PTR [ecx])._Name invoke wcsncpy, pwszKeyName, ecx, eaxСкопируем имя подраздела/параметра во временный буфер, предварительно обнулив его.
Код (Text):
invoke RtlInitUnicodeString, addr us, pwszKeyName invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE .if eax == STATUS_SUCCESS invoke DbgPrint, $CTA0("RegistryWorks: \=%s\=\n"), as.Buffer invoke RtlFreeAnsiString, addr as .endifДелаем необходимые преобразования, подробно рассмотренные выше и выводим имя подраздела/параметра в отладочном сообщении.
Код (Text):
invoke ExFreePool, pwszKeyName .endif .endif invoke ExFreePool, pbi .endif .endif inc ebx .endw pop ebx .endif invoke ExFreePool, pfi .endif .endifПроводим необходимую очистку ресурсов.
Исходный код драйвера в архиве.
© Four-F
Драйверы режима ядра: Часть 10: Базовая техника: Реестр
Дата публикации 23 дек 2003