Доброго дня, уважаемые читатели!
Во второй статье продолжим разговор, рассмотрев вопросы восстановления удалённых из реестра элементов (ключи, значения), а также некоторые особенности создания и хранения элементов реестра. В экспериментах используется виртуальная машина VirtualBox с Windows 11 23H2. Первую часть о форенсике реестра Windows и некоторых интересных сведениях из него можно прочитать здесь.
Как сказано ранее, все ветки реестра (Hives) представлены определёнными файлами на системном диске. На логическом уровне каждый файл ветки можно разделить на две части - заголовочную (Header) и N “контейнеров” (hbin). Заголовок имеет фиксированную длину 4096 байт и содержит:
Cигнатуру regf, в виде ASCII строки, первые четыре байта от смещения 0x0.
Два числа sequence numbers, Little-endian по 4 байта от смещений 0x04 и 0x08.
Windows FILETIME записи куста (число Little-endian), 8 байт от смещения 0x0C.
Смещение до корневого ключа (число Little-endian), 4 байта от смещения 0x24.
Более подробную спецификация формата можно найти здесь. Из этих данных для нас в вопросах восстановления особенно важны sequence numbers. Если они не совпадают, то это значит, что Windows не записала все последние изменения в файл куста, и такой файл именуют “Dirty Hive”. Когда это происходит недостающие недозаписанные данные с определённой вероятностью можно найти в логах транзакции (файлы .log, .log1 и .log2) при условии, что не изменены сами логи, т.к. они работают по принципу кольцевого буфера.
“Контейнеры” hbin это своего рода “матрёшка”, которая имеет внутри:
Cells - хранилища для записей Key Node (NK - структура описывающая ключ реестра), записей Key Value (VK - структура описывающая значение ключа реестра), либо же Key Security (SK - структура описывающая дескриптор безопасности).
Data - хранилища для значений ключей, размер которых больше 4х байт. Если размер значения ключа меньше или равен 4м байтам, то оно хранится в Key Value (очень напоминает резидентные файловые записи в MFT NTFS).
Lists - условно говоря это проводник связывающий между собой “ссылками” Cells и Data хранилища.
С верхнего плана абстракции весь файл куста можно графически представить так:
Следующая иллюстрация демонстрирует получения ключа со значением Foo из файла куста (цепочка Key Node -> List -> Key Value -> Data):
Когда мы попытаемся внести какие-либо изменения в реестр вручную или программно, мы можем заметить, что время модификации файлов веток на диске не изменяется сразу же, а изменения в самом реестре (через тот же regedit) видны. Посмотрим это на практическом эксперименте. Для этого создадим новый вложенный ключ с именем “TestPurposes1” в HKEY_CURRENT_USER\SOFTWARE и пять значений в нём, а скуку ручного труда разбавим “тайными знаниями змеиного языка”, замагичем как Гарри Поттер, шучу! :
import winreg
hive = winreg.HKEY_CURRENT_USER
with winreg.OpenKeyEx(hive, "SOFTWARE") as key:
subkey = winreg.CreateKey(key, "TestPurposes1")
for i in range(0, 5):
winreg.SetValueEx(subkey,
f"value {i}", 0,
winreg.REG_SZ,
f"data {i}")
Выполненный скрипт ожидаемо создаст в реестре требуемое по заданному “пути”, сам ключ и в нём значения строкового типа (REG_SZ) с их данными. Параллельно, перед выполнением скрипта запущен Process Monitor (утилита Sysinternals) с фильтрами “Process name is python.exe” и “Operation is RegCreateKey”. В его выводах видно точное время правок реестра. Сравним штампы времени для файла NTUSER.dat с временем из Process monitor:
Наглядно видно, что штамп времени модификации для NTUSER.dat не изменился, т.е. в данном случае не должен измениться сам файл, хотя мы явно внесли изменения скриптом. Попробуем открыть NTUSER.dat в live системе через Registry Explorer и в итоге увидим, что созданный ключ отображается:
Теперь выключим виртуальную машину по питанию, минуя штатные способы самой ОС:
Мы можем получить доступ к файлам на виртуальном жёстком диске .vdi используя 7-Zip, кроме того если сравнивать с файлами с “чистой” системы, то видно по их размерам, что были внесены изменения в файлы транзакций:
Откроем и попробуем поискать строку “TestPurposes1” в NTUSER.dat и сопутствующих файлах в hex редакторе HxD, лишний раз убедимся, что Key Node (NK) запись для созданного ключа в файле куста не создана, в файлах транзакций информация может присутствовать:
После повторого запуска виртуальной машины увидим, что созданный ключ и всё связанное с ним на месте. Однако, я повторял этот эскперимент несколько раз. В один из разов мне удалось поймать случай, когда в логах транзакции система не записала сведения о “TestPurposes1” или уже перезаписала их. Поэтому найти в них ничего не удавалось. Вся информация хранилась в оперативной памяти, поскольку в созданном через WinPmem raw дампе оперативки при монтировании через утилиту MemProcFS были видны созданные параметры и ключ “TestPurposes1”, а после выключения машины по питанию данные были потеряны и не отображались при повторном старте Windows. Восстановить потерянные из ОЗУ данные реестра по понятным причинам невозможно. Таким образом для анализа реестра важны не только файлы на диске, но и дамп оперативной памяти.
Windows какое-то время не записывает изменения в файлы веток на диск, а хранит их в оперативной памяти, либо записывает их в первую очередь в логи транзакций (файлы .log, .log1 и .log2). Запись в файлы самих веток реестра производится через определённый самой системой интервал времени, либо гарантированно при полном выключении системы. Этот механизм называется “lazy flush” (см. далее по ссылке про RegFlushKey). По данным из разных источников в сети, это необходимо из соображений производительности, т.к. “flush” крайне ресурсоёмкая операция, перезаписывается весь файл куста, а не какие-то его части. Для принудительной записи всех изменений в версиях до Windows 8.1 требовалось программно вызвать функцию Windows API - RegFlushKey, начиная с 8.1 эта функция не работает, её замена недокументированная - ZwSetSystemInformation (упомянуто здесь). В Windows 10-11 принудительный “flush” запустить не удалось ни с одной из этих функций. Пример скрипта python, который для этого использовался:
import ctypes
ZwSetSystemInformation = ctypes.windll.ntdll.ZwSetSystemInformation
SystemRegistryReconciliationInformation = 0x9b
ZwSetSystemInformation(SystemRegistryReconciliationInformation, None, 0)
Во втором эксперименте попробуем посмотреть подробнее на работу .log файлов транзакций когда значения параметров реестра и сами имена значений были перезаписаны. Ход эксперимента практически повторяет эти эксперименты - раз и два, разница только в версии Windows и в длительности имитации пользовательской активности:
Откат виртуальной машины до “чистого” состояния.
Cоздание ключа “TestPurposes2” аналогично “TestPurposes1” и его параметров.
Имитация пользовательской активности 25 минут.
Перезапись значений в ключе на “erased”:
Имитация пользовательской активности 25 минут.
Выключение виртуальной машины через меню Пуск.
По результату ожидается, что в логах транзакций мы должны увидеть наши изначальные значения параметров “TestPurposes2” вместо “erased”. Заберём их из .vdi и откроем в HxD:
Файл NTUSER.dat отображает только перезаписанные значения, т.к. система записала их на диск при выключении, а в файлах транзакции ntuser.dat.LOG1 и ntuser.dat.LOG2 значений не найдено вовсе, файл ntuser.dat.LOG не был создан изначально:
В первом эксперименте Windows записала сведения в логи транзакции и время, которое прошло между созданием ключа реестра и выключением машины по питанию, было приблизительно не больше 25 минут. В данном эксперименте запись не была произведена, т.е. либо система каждый раз выполняет запись в файлы транзакций через разные промежутки времени, либо в конкретно этом случае она выполнила запись, но данные уже после были перезаписаны.
Второй эксперимент аналогично первому был проведён несколько раз, и в один из повторов снят дамп оперативной памяти. Он после монтирования через MemProcFS следов прошлых значений параметров “TestPurposes” не показал:
Значит в оперативной памяти, с большей вероятностью, загружается и хранится только “актуальное” состояние реестра, а не то, что могло быть перезаписано или удалено. Тем не менее дамп памяти это хороший вспомогательный источник для сравнения при анализе реестра в дополнение к файлам кустов и логам транзакций.
В третьем эксперименте мы посмотрим на удаление из реестра с помощью Reg Delete PowerShell с ключами /VA /F и возможности восстановления после. Данные ключи необходимы для удаления всех параметров и подключей с их параметрами в указанном “пути” реестра рекурсивно, а не только тех, которые явно указаны в команде, а также для удаления даже если удаляемые элементы защищены. Изначальное состояние реестра:
После выполнения команды Reg Delete при попытке live анализа системы через Registry Explorer видно, что автоматизированному восстановлению подлежит только часть данных, к тому же, часть повреждена, нельзя сказать к какому изначально ключу и “пути” в реестре относятся восстановленные параметры (Unassociated deleted values):
В дампе оперативной памяти “актуальное” состояние реестра, уже после удаления. Вопреки этому в файле куста мы можем найти все удалённые Key Value, но без резидентных значений внутри. Это говорит о сложности даже ручного восстановления удалённых элементов:
Тем не менее всегда опираться только на автоматизированные инструменты не представляется возможным, доверяй, но проверяй. Продолжение следует...
msuhanov
Это не так. Flush — это запись в файл транзакции, все.
NtFreezeRegistry. Или создание теневой копии, под капотом там такой же вызов.
https://github.com/msuhanov/regf/blob/master/Windows registry file format specification.md