Реестр (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. |
Как же добраться до реестра из режима ядра? Доступ к реестру осуществляется точно также, как и к любому другому именованному объекту, т.е. через пространство имен диспетчера объектов (подробнее см. 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 символов. Можете воспользоваться любым доступным вам способом.
Теперь, когда нам известны имена корневых разделов - добраться до них не составит большого труда.
;@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.
invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, REG_OPTION_VOLATILE, addr dwDisposition
Флаг REG_OPTION_VOLATILE запрещает запись созданного подраздела в куст реестра (hiev)- один из файлов реестра на диске. Т.е. этот подраздел будет существовать только до следующей перезагрузки системы. В данном случае, использовать этот флаг не обязательно, т.к. мы всё равно удалим весь подраздел. Если вы захотите прописать подраздел в реестр на постоянное место жительства, то использовать этот флаг не следует.
.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) и поэтому был открыт.
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".
. . .
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.
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* функции работают подобным образом).
.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 ещё раз - теперь уже с указателем на буфер.
.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
Если тип параметра REG_SZ, то поле Data структуры KEY_VALUE_PARTIAL_INFORMATION содержит завершенную нулем unicode-строку. Нам надо вывести эту строку в отладочном сообщении, поэтому необходимо преобразовать её в ansi-строку.
invoke RtlInitUnicodeString, addr us, eax
Функция RtlInitUnicodeString измеряет unicode-строку, адрес которой передан во втором параметре и заполняет структуру UNICODE_STRING, адрес которой передан в первом параметре.
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
Функция RtlUnicodeStringToAnsiString конвертирует unicode-строку в ansi. Если последний параметр установлен в TRUE, функция сама выделит буфер, запишет туда преобразованную строку и заполнит структуру ANSI_STRING (переменную as, в нашем случае). Поле Buffer структуры ANSI_STRING будет указывать на выделенный буфер с преобразованной ansi-строкой. Если последний параметр установить в FALSE, то буфер для ansi-строки надо выделить заранее и поместить указатель на него в поле Buffer структуры ANSI_STRING. В данном случае, мы просим функцию RtlUnicodeStringToAnsiString выделить буфер за нас.
.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» рассмотрим простой пример.
wsz db 'a', 0, 'b', 0, 'c', 0, 0, 0
us UNICODE_STRING <>
as ANSI_STRING <>
Изначально переменные us и as неопределены. wsz - unicode-строка, подлежащая переводу в формат ANSI.
us._Length = ?
us.MaximumLength = ?
us.Buffer = ?
as._Length = ?
as.MaximumLength = ?
as.Buffer = ?
RtlInitUnicodeString заполняет переменную us на основании размера строки wsz.
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 будет содержать полное описание конвертированной строки.
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.
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.
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 искомое количество подразделов/параметров.
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, организуем цикл перебора. Как и в предыдущих случаях, вызываем её два раза: первый раз для того, чтобы узнать размер информации, второй - для того, чтобы эту информацию получить.
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-строку (для вывода в отладочном сообщении) нам нужна завершенная нулем строка - выделим для неё временный буфер.
.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
.endif
invoke ExFreePool, pbi
.endif
.endif
inc ebx
.endw
pop ebx
.endif
invoke ExFreePool, pfi
.endif
.endif
Проводим необходимую очистку ресурсов.
Исходный код драйвера в архиве.
[C] Four-F