Добрый день, уважаемые читатели.
Реестр — это одна из самых заметных и значительных систем Windows. Вряд ли найдется человек, который не слышал о нем. Занимаясь программированием под Windows уже около 20 лет, я думал, что знаю о нем все. Но время от времени появляется что-то новое, что показывает мне, как я был неправ. Поэтому сегодня я хочу рассказать вам о необычных способах работы с реестром, которые я встречал, исследуя руткиты, и которые удивили меня.
Все мы знаем, что в Windows существуют некоторые правила именования объектов, будь то файлы, каталоги или ключи реестра. Имена файлов не могут содержать символ “\”. Имена не могут быть пустыми. У имен есть некоторые ограничения по длине и т. д.
Невольно мы распространяем эти ограничения на все системы Windows и соблюдаем их при работе с реестром. И тут заключается наша ошибка. В реестре ограничений при создании имен на удивление мало. Например, в имени значений можно использовать символ “\”
Удивлены? Нет? Тогда что вы скажете, если я покажу вам, что в имени значения можно использовать символ “\0”? Да-да, именно нулевой символ. Тот самый, который традиционно используется для указания конца строки.
Для этого нам понадобится функция NtSetValueKey, экспортируемая из ntdll.dll
Для выполнения функции NtSetValueKey вам понадобятся права администратора. В результате у вас в реестре появится значение с именем Test\0Zero.
Некоторые разработчики Microsoft тоже будут удивлены, т. к. стандартный редактор реестра не может отобразить такое необычное значение реестра.
Вторая история, которую я вам сегодня расскажу, произошла в 2013 году.
Вначале небольшое отступление. В «Лаборатории Касперского» я являюсь членом команды, которая, помимо всего прочего, создает Kaspersky Rescue Disk. Для лечения Windows из-под Linux нам необходимо самостоятельно разбирать файлы реестра. И для проверки правильности работы этого механизма мы используем множество тестов. Среди них есть один достаточно простой:
И вот в один прекрасный день мы обновили на тестовом стенде Windows до версии 8.1, и тест перестал удалять проверочные значения. Как же так, удивился я. Скопировал файл с кустом реестра к себе на рабочий компьютер — нет значений! Моя первая мысль: нужно добавить в тест Flush измененных ключей. Добавил вызов RegFlushKey, перезапустил тест — нет значений!
Неужели RegFlushKey не работает, задумался я. Но, как оказалось, прав я был лишь частично.
Фокус оказался в том, что в Windows 8.1 компания Microsoft изменила механизм сохранения изменений в реестр. Раньше все изменения реестра накапливались в памяти, а потом, при закрытии ключа, при выполнении RegFlushKey или по истечении некоторого времени система сохраняла изменения в файл куста реестра. В Windows 8.1 изменения вместо файла куста реестра сохраняются в одноименные файлы с расширениями .LOG, .LOG1 и .LOG2, а мой код эти файлы в те времена игнорировал.
В этих файлах изменения накапливаются около часа. И лишь после этого Windows начинает задачу интеграции изменений в основной файл. Эта задача называется Reconciliation, и она запускается либо раз в 40 минут, либо при завершении работы Windows. Вызов функции RegFlushKey к запуску задачи Reconciliation не приводит. Для принудительного запуска задачи интеграции изменений нужно вызвать ZwSetSystemInformation с недокументированным аргументом SystemRegistryReconciliationInformation.
Для выполнения функции ZwSetSystemInformation вам понадобятся права администратора. А архитектура исполняемого файла должна совпадать с архитектурой системы. Вызвать эту функцию из 32-битной программы в 64-битной Windows не получится.
Некоторое время назад мы обнаружили руткит, который прописывал в реестре запуск своего драйвера. Наши продукты удаляли соответствующие ключи реестра, но после перезагрузки ключи оказывались на своих местах. Похоже, он ставит свои callback-функции на изменения реестра и после наших изменений восстанавливает свои ключи, подумал я. Но оказалось, что нет. Точнее — да. Callback-функции руткит ставил, но к задаче восстановления ключей они не имели никакого отношения. Все было сделано проще и изящнее.
Драйвер руткита при запуске переименовывал файл куста реестра SYSTEM в HARDWARE. Создавал свой файл SYSTEM и периодически сохранял в него с помощью функции RegSaveKey ветку HKLM/System. При сохранении он восстанавливал свои ключи. При перезагрузке Windows система загружала файл SYSTEM и запускала драйвер руткита. Красиво? Красиво.
P.S. У нас тут ищут разработчика-исследователя в команду, которая пилит анти-спамовый движок, а еще нужен инженер по тестированию.
Реестр — это одна из самых заметных и значительных систем Windows. Вряд ли найдется человек, который не слышал о нем. Занимаясь программированием под Windows уже около 20 лет, я думал, что знаю о нем все. Но время от времени появляется что-то новое, что показывает мне, как я был неправ. Поэтому сегодня я хочу рассказать вам о необычных способах работы с реестром, которые я встречал, исследуя руткиты, и которые удивили меня.
История первая. Имена значений и ключей реестра
Все мы знаем, что в Windows существуют некоторые правила именования объектов, будь то файлы, каталоги или ключи реестра. Имена файлов не могут содержать символ “\”. Имена не могут быть пустыми. У имен есть некоторые ограничения по длине и т. д.
Невольно мы распространяем эти ограничения на все системы Windows и соблюдаем их при работе с реестром. И тут заключается наша ошибка. В реестре ограничений при создании имен на удивление мало. Например, в имени значений можно использовать символ “\”
Удивлены? Нет? Тогда что вы скажете, если я покажу вам, что в имени значения можно использовать символ “\0”? Да-да, именно нулевой символ. Тот самый, который традиционно используется для указания конца строки.
Для этого нам понадобится функция NtSetValueKey, экспортируемая из ntdll.dll
HKEY hKey = 0;
RegOpenKeyA(
HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
&hKey);
UNICODE_STRING uName;
uName.Buffer = L"Test\0Zero";
uName.MaxLen = uName.Length = 9 * sizeof(wchar_t);
NTSTATUS status = 0;
status = NtSetValueKey(
hKey,
&uName,
0,
REG_SZ,
(void*)lpData,
DataSize);
RegCloseKey(hKey);
Для выполнения функции NtSetValueKey вам понадобятся права администратора. В результате у вас в реестре появится значение с именем Test\0Zero.
Некоторые разработчики Microsoft тоже будут удивлены, т. к. стандартный редактор реестра не может отобразить такое необычное значение реестра.
История вторая
Вторая история, которую я вам сегодня расскажу, произошла в 2013 году.
Вначале небольшое отступление. В «Лаборатории Касперского» я являюсь членом команды, которая, помимо всего прочего, создает Kaspersky Rescue Disk. Для лечения Windows из-под Linux нам необходимо самостоятельно разбирать файлы реестра. И для проверки правильности работы этого механизма мы используем множество тестов. Среди них есть один достаточно простой:
- Под Windows записываем в реестре тестовые значения.
- Копируем файл куста реестра в тестовый каталог.
- Запускаем программу, выполняющую удаление тестовых значений.
- Загружаем модифицированный куст в реестр для проверки правильности удаления.
И вот в один прекрасный день мы обновили на тестовом стенде Windows до версии 8.1, и тест перестал удалять проверочные значения. Как же так, удивился я. Скопировал файл с кустом реестра к себе на рабочий компьютер — нет значений! Моя первая мысль: нужно добавить в тест Flush измененных ключей. Добавил вызов RegFlushKey, перезапустил тест — нет значений!
Неужели RegFlushKey не работает, задумался я. Но, как оказалось, прав я был лишь частично.
Фокус оказался в том, что в Windows 8.1 компания Microsoft изменила механизм сохранения изменений в реестр. Раньше все изменения реестра накапливались в памяти, а потом, при закрытии ключа, при выполнении RegFlushKey или по истечении некоторого времени система сохраняла изменения в файл куста реестра. В Windows 8.1 изменения вместо файла куста реестра сохраняются в одноименные файлы с расширениями .LOG, .LOG1 и .LOG2, а мой код эти файлы в те времена игнорировал.
В этих файлах изменения накапливаются около часа. И лишь после этого Windows начинает задачу интеграции изменений в основной файл. Эта задача называется Reconciliation, и она запускается либо раз в 40 минут, либо при завершении работы Windows. Вызов функции RegFlushKey к запуску задачи Reconciliation не приводит. Для принудительного запуска задачи интеграции изменений нужно вызвать ZwSetSystemInformation с недокументированным аргументом SystemRegistryReconciliationInformation.
ZwSetSystemInformation(
0x9b, //SystemRegistryReconciliationInformation
NULL,
0);
Для выполнения функции ZwSetSystemInformation вам понадобятся права администратора. А архитектура исполняемого файла должна совпадать с архитектурой системы. Вызвать эту функцию из 32-битной программы в 64-битной Windows не получится.
История третья
Некоторое время назад мы обнаружили руткит, который прописывал в реестре запуск своего драйвера. Наши продукты удаляли соответствующие ключи реестра, но после перезагрузки ключи оказывались на своих местах. Похоже, он ставит свои callback-функции на изменения реестра и после наших изменений восстанавливает свои ключи, подумал я. Но оказалось, что нет. Точнее — да. Callback-функции руткит ставил, но к задаче восстановления ключей они не имели никакого отношения. Все было сделано проще и изящнее.
Драйвер руткита при запуске переименовывал файл куста реестра SYSTEM в HARDWARE. Создавал свой файл SYSTEM и периодически сохранял в него с помощью функции RegSaveKey ветку HKLM/System. При сохранении он восстанавливал свои ключи. При перезагрузке Windows система загружала файл SYSTEM и запускала драйвер руткита. Красиво? Красиво.
Wanted
P.S. У нас тут ищут разработчика-исследователя в команду, которая пилит анти-спамовый движок, а еще нужен инженер по тестированию.
Комментарии (5)
xi-tauw
29.06.2018 23:19+2Для выполнения функции NtSetValueKey вам понадобятся права администратора.
Весьма смелое, но очевидно неверное, утверждение. Только если вы имели в виду конкретный случай вашего кода — запись в HKLM-Run. Сама функция нормально работает в допустимых ветках хоть от AppContainer.
diversenok
30.06.2018 16:47Насчёт первого пункта: на CodeProject есть редактор реестра на Native API, который позволяет работать с подобными именами.
Compiller
Ключи с символом конца строки в имени утилита trashreg удаляла.
А глюки файлов реестра из дос утилитой regview смотреть удобно.
Более глобальные поломки файлов кустов реестра легко чинить утилитой rehive