Можно ли создать такой ключ реестра, который будет виден в Windows в составе активного (подключенного) реестра, но не будет виден программам, работающим с неактивным (отключенным) реестром? Оказывается, если у вас есть возможность изменить лишь одну переменную ядра (например, с помощью драйвера), то да, способ есть.
Сокрытие ключей реестра от программ, работающих с неактивным реестром, при сохранении возможности обычной работы с этими ключами стандартными средствами операционной системы Windows (в составе активного реестра) может быть использовано для достижения двух целей:
Реестр Windows состоит из двух частей: энергозависимая часть (ключи реестра и значения, которые будут потеряны после отключения куста из-за того, что они не сохраняются в файл; пример: ключ «CurrentControlSet» куста «SYSTEM»), энергонезависимая часть (синхронизируется с файлом куста реестра).
Поскольку во время записи энергонезависимой части в файл куста необходимо обеспечить целостность сохраняемых данных (например, в случае сбоя электропитания, прерывающего операции записи данных), ядро Windows использует журналирование реестра — записываемые данные сохраняются сначала в файл журнала (этот файл находится в одной директории вместе с основным файлом и имеет расширение «.LOG», «.LOG1» или «.LOG2») и только потом в основной файл куста (если запись в файл журнала не будет успешно завершена, то основной файл останется целым и нетронутым, а если запись в основной файл не будет успешно завершена, то его целостность можно восстановить с помощью данных из журнала, которые были успешно записаны до сбоя).
Предлагаемый способ сокрытия ключей (и их значений, а также других элементов) заключается в сохранении соответствующих данных только в журнал, но не в основной файл куста реестра. Сторонние программы, работающие с неактивным реестром, в подавляющем большинстве случаев игнорируют файл (файлы) журнала, а потому ключи реестра, хранимые в журнале, но не в основном файле, будут невидимыми для этих программ. Ядро Windows, с другой стороны, использует журнал для восстановления целостности куста при его подключении, а потому обсуждаемые ключи будут видимыми для ядра и, соответственно, других запущенных программ.
Для блокирования записи в основной файл куста можно использовать отладочный механизм, который появился в Windows Vista. Для понимания сути этого механизма необходимо рассмотреть появившуюся в Windows Vista схему журналирования.
В Windows XP и более ранних версиях Windows каждому энергонезависимому кусту реестра соответствует один основной файл и один файл журнала. Исключением из этого правила является куст SYSTEM в Windows 2000 и более ранних версиях Windows, который зеркалируется (в файл с именем «system.alt»), а не журналируется, чтобы упростить код загрузчика (который должен загрузить в память указанный куст) и не добавлять в него поддержку восстановления из журнала (под зеркалированием понимается поочередная запись данных в два основных файла, которые в результате будут иметь одинаковую логическую структуру ключей, значений и других элементов).
Журналирование происходит путем компактного (без выравнивания по смещениям) сохранения в файл журнала подлежащих записи в основной файл данных вместе со структурой — битовой картой секторов основного файла, которая позволяет определить, по каким смещениям нужно записать в основной файл блоки данных из файла журнала. Если при подключении куста будет установлено, что в его основной файл не была завершена запись данных, то будут прочитаны блоки из файла журнала, определены (с помощью битовой карты) смещения этих блоков в основном файле, а затем эти блоки будут записаны в основной файл, таким образом завершая ранее прерванную из-за сбоя запись.
Подобная схема имеет существенный недостаток — если во время записи в основной файл произойдет ошибка ввода-вывода (например, из-за попытки записи в сбойный сектор), то дальнейшие операции синхронизации куста с основным файлом окажутся невозможными до перезагрузки компьютера (даже в том случае, если сбойный сектор будет нейтрализован переназначением секторов на уровне драйвера файловой системы или хранилища). Это происходит из-за того, что журналирование каждый раз очищает файл журнала от старых данных, а значит ошибка записи в основной файл приведет к нарушению целостности этого файла, и новая попытка синхронизации куста потребует стирания данных из журнала, который остался единственным способом восстановить уже нарушенную целостность основного файла.
Следовательно, если такое стирание журнала допустить, возможно возникновение ситуации, когда из-за нового сбоя будет нарушена целостность единственного файла журнала, при этом целостность основного файла была нарушена предыдущим сбоем.
Для решения проблемы синхронизации куста с основным файлом в условиях повторяющихся сбоев была реализована схема двойного журналирования. В этой схеме каждому основному файлу соответствуют два файла журнала (с расширениями «.LOG1» и «.LOG2»). По умолчанию используется первый файл журнала («.LOG1»).
Если при записи в основной файл произошла ошибка, то происходит смена файла журнала (с «.LOG1» на «.LOG2» и наоборот). Таким подходом обеспечивается постоянное наличие корректного файла журнала, в котором есть данные от предыдущей попытки синхронизации. В результате сбой во время записи в файл журнала (после сбоя во время записи в основной файл) не приведет к неисправимому нарушению целостности куста реестра (кстати говоря, если такая ситуация все же возникнет, в ядре Windows есть механизмы самовосстановления, исправляющие явные ошибки в логической структуре куста).
Но такую схему журналирования нужно отладить, а потому в ядро Windows была внедрена переменная, позволяющая имитировать повторяющиеся ошибки записи в основные файлы всех кустов реестра, — CmpFailPrimarySave. По неизвестным причинам эта переменная есть и в обычных версиях ядра (а не только в отладочных версиях). Если в эту переменную записать некоторое значение, отличное от нуля, то функция записи данных в основной файл будет имитировать ошибку на разных этапах такой записи.
Следует отметить, что в процессе подключения куста реестра ядро должно выбрать, какой из двух файлов журнала использовать для восстановления, для чего реализуется относительно сложный алгоритм, определяющий, какой из файлов журнала сохранил целостность, какой из них содержит более позднюю версию записываемых данных и т. д. До Windows 8 этот алгоритм содержал серьезную ошибку, в результате которой почти во всех случаях, вне зависимости от конкретных деталей, выбирался первый файл журнала («.LOG1»). В частности, для Windows 7 соответствующие исправления алгоритма были выпущены лишь в марте 2016 года (следовательно, все это время двойное журналирование в Windows 7 предоставляло защиту целостности не лучше, чем Windows XP). Для преодоления описанной ошибки необходимо не только блокировать запись в основной файл куста, но и блокировать переход ко второму файлу журнала («.LOG2») в случае сбоя (чтобы первый файл журнала всегда содержал наиболее поздние данные, пусть даже и в ущерб целостности в случае сбоя; в противном случае при следующей загрузке системные кусты реестра могут быть восстановлены в состояние, неожиданно более раннее, чем при завершении штатного выключения компьютера). К счастью, следующее значение обсуждаемой переменной позволяет достигнуть желаемого эффекта без смены файла журнала — 3.
Эта же переменная будет работать так же и в более новых версиях Windows (8.1 и 10), где применяется другой способ журналирования (вне рамок данной статьи).
В качестве эксперимента создадим невидимые ключ и его значение в операционной системе Windows 7 (Service Pack 1). Для этого в запущенной операционной системе изменим (редактированием памяти) значение переменной ядра CmpFailPrimarySave с 0 на 3, а затем создадим ключ реестра «HKEY_LOCAL_MACHINE\SYSTEM\invisible_key» со значением с именем «invisible_value», содержащим строку «123456». Затем выключим операционную систему штатным способом и экспортируем файлы куста реестра SYSTEM.
После повторного включения операционной системы запустим редактор реестра и отметим, что искомые ключ и значение в нем видны (рис. 1).
Рис. 1: Редактор реестра Windows
В то же время в экспортированных файлах реестра искомые ключ и значение сторонние программы (например, Windows Registry Recovery и Registry Explorer) не отображают (рис. 2 и 3).
Рис. 2: Windows Registry Recovery
Рис. 3: Registry Explorer
Не следует чрезмерно полагаться на программы, работающие с неактивным реестром, во время расследования инцидента информационной безопасности, а также во время контроля целостности. В данной статье был продемонстрирован один из многих способов спрятать ключ реестра, его значения и другие элементы от подобных программ.
Зачем это нужно?
Сокрытие ключей реестра от программ, работающих с неактивным реестром, при сохранении возможности обычной работы с этими ключами стандартными средствами операционной системы Windows (в составе активного реестра) может быть использовано для достижения двух целей:
- сокрытие внесенных в реестр изменений от криминалистического исследования (например, сокрытие ключей определенного сервиса, которые будут корректно прочитаны и использованы операционной системой Windows в процессе загрузки, но не будут видны для сторонних программ, работающих с неактивным реестром, во время исследования накопителя);
- сокрытие внесенных в реестр изменений от предзагрузочного контроля целостности (например, внесение таких изменений в ключи реестра, которые не будут видны для модулей доверенной загрузки во время контроля целостности, но будут видны для самой операционной системы Windows).
Как это происходит?
Реестр Windows состоит из двух частей: энергозависимая часть (ключи реестра и значения, которые будут потеряны после отключения куста из-за того, что они не сохраняются в файл; пример: ключ «CurrentControlSet» куста «SYSTEM»), энергонезависимая часть (синхронизируется с файлом куста реестра).
Поскольку во время записи энергонезависимой части в файл куста необходимо обеспечить целостность сохраняемых данных (например, в случае сбоя электропитания, прерывающего операции записи данных), ядро Windows использует журналирование реестра — записываемые данные сохраняются сначала в файл журнала (этот файл находится в одной директории вместе с основным файлом и имеет расширение «.LOG», «.LOG1» или «.LOG2») и только потом в основной файл куста (если запись в файл журнала не будет успешно завершена, то основной файл останется целым и нетронутым, а если запись в основной файл не будет успешно завершена, то его целостность можно восстановить с помощью данных из журнала, которые были успешно записаны до сбоя).
Предлагаемый способ сокрытия ключей (и их значений, а также других элементов) заключается в сохранении соответствующих данных только в журнал, но не в основной файл куста реестра. Сторонние программы, работающие с неактивным реестром, в подавляющем большинстве случаев игнорируют файл (файлы) журнала, а потому ключи реестра, хранимые в журнале, но не в основном файле, будут невидимыми для этих программ. Ядро Windows, с другой стороны, использует журнал для восстановления целостности куста при его подключении, а потому обсуждаемые ключи будут видимыми для ядра и, соответственно, других запущенных программ.
Для блокирования записи в основной файл куста можно использовать отладочный механизм, который появился в Windows Vista. Для понимания сути этого механизма необходимо рассмотреть появившуюся в Windows Vista схему журналирования.
Журналирование до Windows Vista
В Windows XP и более ранних версиях Windows каждому энергонезависимому кусту реестра соответствует один основной файл и один файл журнала. Исключением из этого правила является куст SYSTEM в Windows 2000 и более ранних версиях Windows, который зеркалируется (в файл с именем «system.alt»), а не журналируется, чтобы упростить код загрузчика (который должен загрузить в память указанный куст) и не добавлять в него поддержку восстановления из журнала (под зеркалированием понимается поочередная запись данных в два основных файла, которые в результате будут иметь одинаковую логическую структуру ключей, значений и других элементов).
Журналирование происходит путем компактного (без выравнивания по смещениям) сохранения в файл журнала подлежащих записи в основной файл данных вместе со структурой — битовой картой секторов основного файла, которая позволяет определить, по каким смещениям нужно записать в основной файл блоки данных из файла журнала. Если при подключении куста будет установлено, что в его основной файл не была завершена запись данных, то будут прочитаны блоки из файла журнала, определены (с помощью битовой карты) смещения этих блоков в основном файле, а затем эти блоки будут записаны в основной файл, таким образом завершая ранее прерванную из-за сбоя запись.
Подобная схема имеет существенный недостаток — если во время записи в основной файл произойдет ошибка ввода-вывода (например, из-за попытки записи в сбойный сектор), то дальнейшие операции синхронизации куста с основным файлом окажутся невозможными до перезагрузки компьютера (даже в том случае, если сбойный сектор будет нейтрализован переназначением секторов на уровне драйвера файловой системы или хранилища). Это происходит из-за того, что журналирование каждый раз очищает файл журнала от старых данных, а значит ошибка записи в основной файл приведет к нарушению целостности этого файла, и новая попытка синхронизации куста потребует стирания данных из журнала, который остался единственным способом восстановить уже нарушенную целостность основного файла.
Следовательно, если такое стирание журнала допустить, возможно возникновение ситуации, когда из-за нового сбоя будет нарушена целостность единственного файла журнала, при этом целостность основного файла была нарушена предыдущим сбоем.
Журналирование начиная с Windows Vista (до Windows 8.1)
Для решения проблемы синхронизации куста с основным файлом в условиях повторяющихся сбоев была реализована схема двойного журналирования. В этой схеме каждому основному файлу соответствуют два файла журнала (с расширениями «.LOG1» и «.LOG2»). По умолчанию используется первый файл журнала («.LOG1»).
Если при записи в основной файл произошла ошибка, то происходит смена файла журнала (с «.LOG1» на «.LOG2» и наоборот). Таким подходом обеспечивается постоянное наличие корректного файла журнала, в котором есть данные от предыдущей попытки синхронизации. В результате сбой во время записи в файл журнала (после сбоя во время записи в основной файл) не приведет к неисправимому нарушению целостности куста реестра (кстати говоря, если такая ситуация все же возникнет, в ядре Windows есть механизмы самовосстановления, исправляющие явные ошибки в логической структуре куста).
Но такую схему журналирования нужно отладить, а потому в ядро Windows была внедрена переменная, позволяющая имитировать повторяющиеся ошибки записи в основные файлы всех кустов реестра, — CmpFailPrimarySave. По неизвестным причинам эта переменная есть и в обычных версиях ядра (а не только в отладочных версиях). Если в эту переменную записать некоторое значение, отличное от нуля, то функция записи данных в основной файл будет имитировать ошибку на разных этапах такой записи.
Следует отметить, что в процессе подключения куста реестра ядро должно выбрать, какой из двух файлов журнала использовать для восстановления, для чего реализуется относительно сложный алгоритм, определяющий, какой из файлов журнала сохранил целостность, какой из них содержит более позднюю версию записываемых данных и т. д. До Windows 8 этот алгоритм содержал серьезную ошибку, в результате которой почти во всех случаях, вне зависимости от конкретных деталей, выбирался первый файл журнала («.LOG1»). В частности, для Windows 7 соответствующие исправления алгоритма были выпущены лишь в марте 2016 года (следовательно, все это время двойное журналирование в Windows 7 предоставляло защиту целостности не лучше, чем Windows XP). Для преодоления описанной ошибки необходимо не только блокировать запись в основной файл куста, но и блокировать переход ко второму файлу журнала («.LOG2») в случае сбоя (чтобы первый файл журнала всегда содержал наиболее поздние данные, пусть даже и в ущерб целостности в случае сбоя; в противном случае при следующей загрузке системные кусты реестра могут быть восстановлены в состояние, неожиданно более раннее, чем при завершении штатного выключения компьютера). К счастью, следующее значение обсуждаемой переменной позволяет достигнуть желаемого эффекта без смены файла журнала — 3.
Эта же переменная будет работать так же и в более новых версиях Windows (8.1 и 10), где применяется другой способ журналирования (вне рамок данной статьи).
Эксперимент
В качестве эксперимента создадим невидимые ключ и его значение в операционной системе Windows 7 (Service Pack 1). Для этого в запущенной операционной системе изменим (редактированием памяти) значение переменной ядра CmpFailPrimarySave с 0 на 3, а затем создадим ключ реестра «HKEY_LOCAL_MACHINE\SYSTEM\invisible_key» со значением с именем «invisible_value», содержащим строку «123456». Затем выключим операционную систему штатным способом и экспортируем файлы куста реестра SYSTEM.
После повторного включения операционной системы запустим редактор реестра и отметим, что искомые ключ и значение в нем видны (рис. 1).
Рис. 1: Редактор реестра Windows
В то же время в экспортированных файлах реестра искомые ключ и значение сторонние программы (например, Windows Registry Recovery и Registry Explorer) не отображают (рис. 2 и 3).
Рис. 2: Windows Registry Recovery
Рис. 3: Registry Explorer
Вывод
Не следует чрезмерно полагаться на программы, работающие с неактивным реестром, во время расследования инцидента информационной безопасности, а также во время контроля целостности. В данной статье был продемонстрирован один из многих способов спрятать ключ реестра, его значения и другие элементы от подобных программ.