Пожалуй, самый распространенный способ открытия ключа реестра это вызов функции advapi32!RegOpenKeyEx(...). В конце MSDN’новской статьи (в секции Remarks), посвященной этой функции можно увидеть следующие строки:
Далее пойдет речь о том, почему нужно с осторожностью использовать 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. На выходе получим следующий результат:
А теперь повторим вызов, изменив логику работы, указав 2-ой опциональный параметр:
Как видно из вышеприведенных результатов, описатель, полученный вызовом 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 с исходным кодом.
ΞρεΤΙκ