Драйверы режима ядра: Часть 10: Базовая техника: Реестр — Архив WASM.RU

Все статьи

Драйверы режима ядра: Часть 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

Теперь, когда нам известны имена корневых разделов - добраться до них не составит большого труда.


 ;@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

Проводим необходимую очистку ресурсов.

Исходный код драйвера в архиве.

2002-2013 (c) wasm.ru