Kernel Mode Drivers

Part 10: Basic Technique: The Registry


10.1 Registry structure

The Registry is a centralized database that plays a key role in configuring and managing the system. The structure of the registry is similar to the structure of a logical disk, but the contents of the registry are not a static collection of data stored on a hard disk, but dynamically changes during the operation of the system. Registry consists of sections (keys), disk-like directories. The top-level partitions are called root keys. Sections are container comprising the other sections, called subsections (subkeys), and / or parameters (values), which can be compared with the files on the disk. Parameters store the actual data. Responsible for the implementation and management of the registryConfiguration Manager (Configuration Manager).

There are six root partitions:

HKEY_USER

- contains information about all accounts;

HKEY_CURRENT_USER

- stores the profile of the currently registered user;

HKEY_LOCAL_MACHINE

- stores information about the system configuration: hardware description, security policies, user passwords, application settings, as well as the configuration of services and drivers;

HKEY_CURRENT_CONFIG

- contains the current equipment profile;

HKEY_CLASSES_ROOT

- Stores file extension associations and registration data of COM classes;

HKEY_PERFORMANCE_DATA

- contains the values ​​of the performance counters.

HKEY_PERFORMANCE_DATA is a special section that you hardly need to access directly. In user mode, information about performance counters is available through the Performance Data Helper library, implemented in the pdh.dll module. The standard Performance Monitor snap-in uses this library. In addition to performance counters, this section contains a ton of additional information. For example, the Process Status functions (implemented in psapi.dll) that list processes, threads, modules, etc., get their information from the HKEY_PERFORMANCE_DATA section. The registry editors Regedit and Regedt32 do not show the contents of this section, because there is no point in editing it.

The root sections HKEY_CURRENT_USER, HKEY_CURRENT_CONFIG, and HKEY_CLASSES_ROOT do not contain any information. In fact, these are links to other registry subkeys.

HKEY_CURRENT_USER

- reference to the subkey HKEY_USER \ <Current_user_SID_> corresponding to the current user registered in the system.

HKEY_CURRENT_CONFIG

- reference to the subkey HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Hardware Profiles \ Current.

HKEY_CLASSES_ROOT

- reference to subsections HKEY_LOCAL_MACHINE \ SOFTWARE \ Classes and HKEY_CURRENT_USER \ SOFTWARE \ Classes.



10.2 Accessing the registry from the driver

How do you get to the registry from kernel mode? The registry is accessed in the same way as any other named object, i.e. through the object manager namespace (see Part 3 for details). To integrate the registry namespace with the object manager, the Configuration Manager creates a key object named "Registry" and places it in the root directory of the object manager namespace. For kernel-mode components, this is the registry entry point.

Rice. 10-1. The Registry key object in the Object Manager namespace

All kernel functions that access named objects receive their names as part of the OBJECT_ATRIBUTES structure - we have known this for a long time. For an object of type "registry key", this name must begin with "\ Registry". It remains to add the names of the root sections. I'll say right away that I don't know what name is used to open the horse section HKEY_PERFORMANCE_DATA and, in general, whether any name is used for this. Perhaps a different mechanism is used here. I have made some efforts in this direction, but they were in vain. If you know something on this issue, please share this knowledge with me. For the remaining two root partitions, everything is simple.

Chapter

Name

HKEY_USER

"\ Registry \ User"

HKEY_LOCAL_MACHINE

"\ Registry \ Machine"

Additional manipulations are required to access the three link sections. For example, to access the HKEY_CURRENT_CONFIG section, you need to know that it is a reference to the HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Hardware Profiles \ Current subkey and substitute the name of this root section. That. the name is \ Registry \ Machine \ SYSTEM \ CurrentControlSet \ Hardware Profiles \ Current. Unfortunately, it is possible to define such a long unicode string using macros CTW0, $ CTW0, etc. will fail, because this string exceeds the 47 character limit. You can use any method available to you.



10.3 RegistryWorks Driver Source Code

Now that we know the names of the root partitions, it shouldn't be too hard to get to them.


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

The driver code consists of several stand-alone procedures: CreateKey , SetValueKey , QueryValueKey , DeleteKey, and EnumerateKey , each of which works with the registry from scratch - this is clearer for the tutorial example.



10.3.1 Create and open a registry subkey

In the CreateKey procedure , by calling the ZwCreateKey function , create a new subkey Registry \ Machine \ Software \ CoolApp.


     invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, REG_OPTION_VOLATILE, addr dwDisposition

The REG_OPTION_VOLATILE flag prevents the newly created subkey from being written to the registry hive (hiev), one of the registry files on disk. Those. this subsection will only exist until the next system reboot. In this case, it is not necessary to use this flag, since we will delete the entire subsection anyway. If you want to register a subsection in the register for permanent residence, then you should not use this flag.


     .if eax == STATUS_SUCCESS
         .if dwDisposition == REG_CREATED_NEW_KEY
         .elseif dwDisposition == REG_OPENED_EXISTING_KEY
         .endif

After a successful call to ZwCreateKey , the value of the dwDisposition variable can be used to determine whether a new subkey was created (REG_CREATED_NEW_KEY) or whether such a subkey already existed in the registry (REG_OPENED_EXISTING_KEY) and was therefore opened.


     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

In the rest of the procedures, by calling the ZwOpenKey function, we open the existing section, requesting only the access rights required at the moment.



10.3.2 Create a Registry Parameter

Now, let's create in our subsection a string parameter named "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

The REG_SZ constant defines the type of the created parameter: a null-terminated unicode string. There are many other types - all of which are described in the DDK.



10.3.3 Get the value of the registry parameter

Let's get the value of our SomeData parameter.


         invoke ZwQueryValueKey, hKey, addr g_usValueName, \
                                 KeyValuePartialInformation, NULL, 0, addr cb

The third parameter of the ZwQueryValueKey function determines the type of information requested. The DDK defines three values: KeyValueBasicInformation , KeyValueFullInformation, and KeyValuePartialInformation , each with its own structure: KEY_VALUE_BASIC_INFORMATION, KEY_VALUE_FULL_INFORMATION, and KEY_VALUE_PARTIAL_INFORMATION. In this case, we want to get the content of the parameter. KeyValuePartialInformation is fine for this .

Because We do not know the amount of information in advance, we call ZwQueryValueKey , passing NULL in the fourth parameter (pointer to the buffer) and 0 in the fifth (buffer size). The ZwQueryValueKey function will calculate the required buffer size and return this value in the cb variable (many, but not all, Zw * functions work this way).


         .if cb != 0
             invoke ExAllocatePool, PagedPool, cb
             .if eax != NULL
                 mov ppi, eax

                 invoke ZwQueryValueKey, hKey, addr g_usValueName, \
                                     KeyValuePartialInformation, ppi, cb, addr cb

Having allocated the required memory space, we call ZwQueryValueKey again - now with a pointer to the buffer.


                 .if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
                     mov eax, ppi
                     .if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZ

Just in case, we check the type of the parameter.


                         lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).Data

If the parameter type is REG_SZ, then the Data field of the KEY_VALUE_PARTIAL_INFORMATION structure contains a null-terminated unicode string. We need to display this line in a debug message, so we need to convert it to an ansi string.


                         invoke RtlInitUnicodeString, addr us, eax

The RtlInitUnicodeString function measures the unicode string, the address of which is passed in the second parameter and fills in the UNICODE_STRING structure, the address of which is passed in the first parameter.


                         invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE

The RtlUnicodeStringToAnsiString function converts a unicode string to ansi. If the last parameter is set to TRUE, the function will allocate the buffer itself, write the converted string there and fill the ANSI_STRING structure (the as variable, in our case). The Buffer field of the ANSI_STRING structure will point to the allocated buffer with the converted ansi string. If the last parameter is set to FALSE, then the buffer for the ansi-line must be allocated in advance and a pointer to it must be placed in the Buffer field of the ANSI_STRING structure. In this case, we are asking the RtlUnicodeStringToAnsiString function to allocate the buffer for us.


                         .if eax == STATUS_SUCCESS
                             invoke DbgPrint, \
                                 $CTA0("RegistryWorks: Registry key value is: \=%s\=\n"), as.Buffer
                             invoke RtlFreeAnsiString, addr as
                         .endif

In the DDK (at least in the 2000 DDK), the description of the RtlUnicodeStringToAnsiString function is rather vague, I would even say it ambiguously. The IFS DDK describes how this function works much better. To dot the i's, consider a simple example.


 wsz db 'a', 0, 'b', 0, 'c', 0, 0, 0
 us  UNICODE_STRING <>
 as  ANSI_STRING    <>

Initially, the variables us and as are undefined. wsz is a unicode string to be translated to ANSI format.


 us._Length        = ?
 us.MaximumLength  = ?
 us.Buffer         = ?

 as._Length        = ?
 as.MaximumLength  = ?
 as.Buffer         = ?

RtlInitUnicodeString populates the us variable based on the size of the wsz string.


 invoke RtlInitUnicodeString, addr us, addr wsz

 us._Length        = 6
 us.MaximumLength  = 8
 us.Buffer         = offset wsz

 as._Length        = ?
 as.MaximumLength  = ?
 as.Buffer         = ?

By the last argument, RtlUnicodeStringToAnsiString sees that it should allocate a buffer of size us.MaximumLength / sizeof WCHAR. Allocating the buffer and placing a pointer to it in the as.Buffer field, the RtlUnicodeStringToAnsiString function starts converting the string itself. If this operation succeeds, the as variable will contain the full description of the converted string.


 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.

After use, the buffer allocated by the RtlUnicodeStringToAnsiString function must be freed by calling RtlFreeAnsiString , and as an argument it is passed not a pointer to the buffer itself, but a pointer to an ANSI_STRING structure. RtlFreeAnsiString frees the as.Buffer buffer and resets the entire as variable.


 invoke RtlFreeAnsiString, addr as

 us._Length        = 6
 us.MaximumLength  = 8
 us.Buffer         = offset wsz

 as._Length        = 0
 as.MaximumLength  = 0
 as.Buffer         = NULL

I hope everything is clear now.



10.3.4 Delete the registry subkey

I think you can figure it out without me.



10.3.5 We iterate over the contents of the registry subkey

Now let's see what's in the \ Registry \ User section.


         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

To organize the enumeration of the contents of a section, you must first find out the number of subsections / parameters that it contains. Let's use the KeyFullInformation information class for this . Allocating the required amount of memory and passing it to the ZwQueryKey function , we get the required number of subsections / parameters in the dwSubKeys variable.


                     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 )

Using the ZwEnumerateKey function with the KeyBasicInformation information class , we organize an iteration cycle. As in the previous cases, we call it two times: the first time in order to find out the size of the information, the second - in order to get this information.


                                     mov eax, pbi
                                     mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength
                                     add eax, sizeof WCHAR
                                     mov cb, eax
                                     invoke ExAllocatePool, PagedPool, cb

In the _Name field of the KEY_BASIC_INFORMATION structure, the subkey / parameter name will be returned as a unicode string, but this string is not null terminated. For the subsequent transformation of it into an ansi-string (for output in a debug message), we need a null-terminated string - we will allocate a temporary buffer for it.


                                     .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
 

Let's copy the name of the subsection / parameter into a temporary buffer, having previously zeroed it out.


                                         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

We make the necessary transformations, discussed in detail above, and display the name of the subsection / parameter in the debug message.


                                         invoke ExFreePool, pwszKeyName
                                     .endif
                                 .endif
                                 invoke ExFreePool, pbi
                             .endif
                         .endif
                         inc ebx
                     .endw
                     pop ebx
                 .endif
                 invoke ExFreePool, pfi
             .endif
         .endif

We carry out the necessary cleaning of resources.

The source code of the driver in the archive .