Добрый день, уважаемые читатели.

Реестр — это одна из самых заметных и значительных систем 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)


  1. Compiller
    29.06.2018 23:14
    +1

    Ключи с символом конца строки в имени утилита trashreg удаляла.
    А глюки файлов реестра из дос утилитой regview смотреть удобно.
    Более глобальные поломки файлов кустов реестра легко чинить утилитой rehive


  1. xi-tauw
    29.06.2018 23:19
    +2

    Для выполнения функции NtSetValueKey вам понадобятся права администратора.

    Весьма смелое, но очевидно неверное, утверждение. Только если вы имели в виду конкретный случай вашего кода — запись в HKLM-Run. Сама функция нормально работает в допустимых ветках хоть от AppContainer.


    1. OldMaster Автор
      29.06.2018 23:35
      +1

      Да, я имел в виду конкретный случай указанный в примере


      1. tendium
        30.06.2018 14:47

        И, смею предположить, HKLM-Run упомянуто не просто так. Какой-нить вирусописатель так записывал в ветку, чтобы нельзя было исправить штатными средствами. Я прав? ;)


  1. diversenok
    30.06.2018 16:47

    Насчёт первого пункта: на CodeProject есть редактор реестра на Native API, который позволяет работать с подобными именами.