Три коротких истории о реестре Windows

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

    Реестр — это одна из самых заметных и значительных систем 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. У нас тут ищут разработчика-исследователя в команду, которая пилит анти-спамовый движок, а еще нужен инженер по тестированию.
    • +36
    • 22,3k
    • 6

    «Лаборатория Касперского»

    368,00

    Ловим вирусы, исследуем угрозы, спасаем мир

    Поделиться публикацией
    Комментарии 6
      +1

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

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

        Весьма смелое, но очевидно неверное, утверждение. Только если вы имели в виду конкретный случай вашего кода — запись в HKLM-Run. Сама функция нормально работает в допустимых ветках хоть от AppContainer.
          +2
          Да, я имел в виду конкретный случай указанный в примере
            0
            И, смею предположить, HKLM-Run упомянуто не просто так. Какой-нить вирусописатель так записывал в ветку, чтобы нельзя было исправить штатными средствами. Я прав? ;)
          0
          Насчёт первого пункта: на CodeProject есть редактор реестра на Native API, который позволяет работать с подобными именами.
            0
            Как-то пропустил данную статью, поэтому будет некрокомментарий.

            Фокус оказался в том, что в Windows 8.1 компания Microsoft изменила механизм сохранения изменений в реестр.


            Об этом в Интернете было написано еще в 2016 году. Ну и на ZeroNights я рассказывал как раз про случай с KRD. ;-)

            При перезагрузке Windows система загружала файл SYSTEM и запускала драйвер руткита. Красиво? Красиво.


            Есть похожая атака, которая, однако, не требует драйвера. Достаточно прав администратора.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое