EreTIk's Box » Cтатьи, исходники » Открытие ключа реестра текущего пользователя: RegOpenKeyEx(HKEY_CURRENT_USER, ...) или RegOpenCurrentUser()


Пожалуй, самый распространенный способ открытия ключа реестра это вызов функции advapi32!RegOpenKeyEx(...). В конце MSDN’новской статьи (в секции Remarks), посвященной этой функции можно увидеть следующие строки:


If your service or application impersonates different users, do not use this function with HKEY_CURRENT_USER. Instead, call the RegOpenCurrentUser function.

Далее пойдет речь о том, почему нужно с осторожностью использовать HKEY_CURRENT_USER в процессах, работающих с олицетворением пользователей. Но в начале немного прописных истин.


На NT-уровне работа с реестром происходит в общем пространстве имен дерева объектов ядра. Но почему-то Win32 Reg-функции библиотеки advapi32.dll вводят дополнительный уровень абстракции: предопределенные корневые ключи реестра HKEY_XXX_XXX. Можно построить упрощенную таблицу предопределенных ключей:

HKEY_CLASSES_ROOT "\REGISTRY\MACHINE\SOFTWARE\CLASSES"
HKEY_CURRENT_USER "\REGISTRY\USER\<USER_SID>"
HKEY_LOCAL_MACHINE "\REGISTRY\MACHINE"
HKEY_USERS "\REGISTRY\USER"
HKEY_CURRENT_CONFIG "\REGISTRY\MACHINE\SYSTEM\CURRENTCONTROLSET\HARDWARE PROFILES\CURRENT"

где <USER_SID> - SID текущего пользователя.


Как и следовало бы ожидать, Win32-подсистема не открывает эти ключи, каждый раз, когда процесс вызывает открытия/создание ключа Reg-функцией. Что бы избежать накладного вызова системного сервиса, Win32-подсистема кэширует первое успешное открытие предопределенного ключа для всего процесса. Для этого используется массив PredefinedHandleTable (не экспортируемый символ). Это массив структур, содержащих 3-и поля: закэшированный описатель ключа, адрес функции открытия этого ключа и BOOLEAN-флаг запрета кэширования. По-молчанию все предопределенные ключи разрешено кэшировать.


Для того что бы избежать проблем при открытии ключа пользователя, MSDN рекомендует вместо вызова advapi32!RegOpenKeyEx(HKEY_CURRENT_USER, ...) использовать функцию advapi32!RegOpenCurrentUser(...). Она не кэширует свой результат и всегда приводит к вызову ntdll!RtlOpenCurrentUser(...), который в свою очередь вызывает функцию ядра nt!NtOpenKey(...). Фактически, первый вызов advapi32!RegOpenKeyEx(HKEY_CURRENT_USER, ...) тоже приводит к исполнению функции ntdll!RtlOpenCurrentUser(...). Именно эта функция (ntdll!RtlOpenCurrentUser(...)), если не удается открыть ключ "\REGISTRY\USER\<USER_SID>", пытается открыть "\REGISTRY\USER\.DEFAULT".


Что бы продемонстрировать подводные камни использования предопределенного ключа HKEY_CURRENT_USER, я набросал небольшую программку RgImpUsr. Запускать ее нужно, указав в командной строке PID процесса, работающего под другим пользователем. Возможен опциональный второй параметр. Суть утилиты сведена к функции OpenAndPrintUserSoftwareKey(). Эта функция открывает под-ключ Software ключа пользователя 2-мя способами: используя преопределенный ключ HKEY_CURRENT_USER и функцией advapi32!RegOpenCurrentUser(). После каждого успешного открытия функция печатает NT-имя открытого объекта ключа реестра, полученное вызовом ntdll!NtQueryObject(..., ObjectNameInformation, ...).


Общая логика ее работы довольно проста:

  • если указан второй опциональный параметр (важен факт его наличия, его значение не анализируется), то вызываем OpenAndPrintUserSoftwareKey()
  • олицетворяем текущую нить токеном указанного в командной строке процесса, вызовом advapi32!ImpersonateLoggedOnUser(...)
  • вызываем OpenAndPrintUserSoftwareKey() в олицетворенной нити
  • возвращаемся к исходному пользователю процесса вызовом advapi32!RevertToSelf()
  • вызываем OpenAndPrintUserSoftwareKey() в не-олицетворенной нити

А теперь запустим утилиту от лица SYSTEM (использует реестр .DEFAULT) и укажем в качестве параметра процесс, работающий от лица пользователя S-1-5-21-XXX. На выходе получим следующий результат:


*** Impersonated HKEY_CURRENT_USER\Software is: \REGISTRY\USER\S-1-5-21-XXX\Software HKEY_CURRENT_USER\Software is: \REGISTRY\USER\S-1-5-21-XXX\Software *** Original HKEY_CURRENT_USER\Software is: \REGISTRY\USER\S-1-5-21-XXX\Software HKEY_CURRENT_USER\Software is: \REGISTRY\USER\.DEFAULT\Software

А теперь повторим вызов, изменив логику работы, указав 2-ой опциональный параметр:


HKEY_CURRENT_USER\Software is: \REGISTRY\USER\.DEFAULT\Software HKEY_CURRENT_USER\Software is: \REGISTRY\USER\.DEFAULT\Software *** Impersonated HKEY_CURRENT_USER\Software is: \REGISTRY\USER\.DEFAULT\Software HKEY_CURRENT_USER\Software is: \REGISTRY\USER\S-1-5-21-XXX\Software *** Original HKEY_CURRENT_USER\Software is: \REGISTRY\USER\.DEFAULT\Software HKEY_CURRENT_USER\Software is: \REGISTRY\USER\.DEFAULT\Software

Как видно из вышеприведенных результатов, описатель, полученный вызовом advapi32!RegOpenKeyEx(HKEY_CURRENT_USER, ...), застревает в кэше массива PredefinedHandleTable. Именно повторное использование HKEY_CURRENT_USER (до и после олицетворения нити) приводит к открытию ошибочного ключа. Это так же чревато тем, что открытый и закэшированный ключ другого пользователю может привести к тому, что при выходе пользователя из системы его реестр так и останется подгруженным в ветку "\REGISTRY\USER". Поэтому стоит быть очень осторожным при использовании HKEY_CURRENT_USER, если ваш процесс олицетворяет свои нити токенами других пользователей.


Кроме использования функции advapi32!RegOpenCurrentUser(...) так же предоставляет 2-е дополнительные Reg-функции:

  • advapi32!RegDisablePredefinedCache() - запрет кэширования ключа HKEY_CURRENT_USER. Функция устанавливает BOOLEAN-флаг запрета кэширования для ключа HKEY_CURRENT_USER в таблице PredefinedHandleTable равным TRUE. Если описатель был предварительно открыт, то он закрывается
  • advapi32!RegDisablePredefinedCacheEx() - Функция проводит теже действия, что и advapi32!RegDisablePredefinedCache(), но для всех предопределенных ключей. Функция присутствует на ОС Windows Vista и старше



Скачать demo-утилиту RgImpUsr с исходным кодом.


ΞρεΤΙκ