Статья Застать врасплох Guard Off Guard

  • Автор темы Автор темы ADMIN
  • Дата начала Дата начала

ADMIN

Administrator
ADMIN
Регистрация
18 Окт 1989
Сообщения
1,408
Реакции
93
Баллы
48
☑ ДЕПОЗИТ
0$
Вкратце: в связи с появлением новых функций безопасности в Windows и отсутствием соответствующих исследований мы решили найти способы извлечения учётных данных в полностью пропатченных средах Windows с включёнными современными средствами защиты. Это привело к появлению нового поколения методов дампа учётных данных.

[Изменено 29 октября 2025 г.] – Мы обновили раздел Credential Guard , чтобы лучше отразить внутреннюю работу системы безопасности на основе виртуализации ( VBS ). Благодарим Идо Вельцмана за указание на некоторые недостатки в нашем описании этой функции безопасности.

Сброс учётных данных остаётся одним из самых эффективных методов, используемых злоумышленниками и боевиками «Красной команды» для последующей эксплуатации уязвимостей и горизонтального продвижения в средах Windows. В связи с масштабом этой угрозы Microsoft представила Credential Guard в Windows 10 в 2015 году как способ защиты учётных данных и конфиденциальной информации путём их изоляции с помощью виртуализации.

Использование Credential Guard делает известные методы сброса учётных данных, такие как те, что использует известный инструмент Mimikatz , неэффективными. Несмотря на то, что Credential Guard существует уже почти десять лет, независимых исследований по сравнению с другими функциями безопасности Windows было проведено относительно мало. Насколько нам известно, в этой записи блога Оливера Ляка описывается единственный публично задокументированный метод взаимодействия с Credential Guard для извлечения секретных данных, хотя и шумный и зачастую практически нереализуемый.

Из-за этого пробела в исследованиях безопасности мы решили более подробно рассмотреть Credential Guard и изучить потенциальные методы извлечения секретов, стремясь оценить реальную осуществимость и влияние сброса учетных данных на современные средства защиты.

Все исследования, представленные в этом блоге, проводились в современной среде Windows, состоящей из полностью обновленных рабочих станций Windows 11 и систем Windows Server 2025.

Локальный орган безопасности (LSA) в Windows

В основе Windows лежит локальная служба безопасности (LSA), которая отвечает за управление аутентификацией пользователей и политиками безопасности. LSA реализована в исполняемом файле lsass.exe , и Microsoft описывает LSA следующим образом:

« Защищённая подсистема, которая аутентифицирует и регистрирует пользователей в локальной системе. LSA также хранит информацию обо всех аспектах локальной безопасности системы, совместно именуемую локальной политикой безопасности системы » .

Хотя LSA отвечает за аутентификацию пользователей в Windows, подсистема не реализует протоколы аутентификации напрямую, а делегирует потоки аутентификации пользователей поставщикам поддержки безопасности ( SSP ), которые представляют собой модульные компоненты, подключаемые к LSA для расширения поддержки различных протоколов и механизмов аутентификации через интерфейс поставщика поддержки безопасности (SSPI).

SSP обычно реализует набор пакетов безопасности ( SP ), которые обеспечивают фактическую реализацию протокола безопасности. Некоторые SSP также классифицируются как пакеты аутентификации ( AP ), если они реализуют логику аутентификации, используемую для определения разрешения пользователю на вход в систему.

По умолчанию Windows поставляется с несколькими поставщиками общих служб (SSP), каждый из которых зарегистрирован и управляется компонентом lsasrv.dll из LSA. Вот некоторые из наиболее важных пакетов безопасности, входящих в их состав:

  • Negotiate ( lsasrv.dll ) – брокер, который согласовывает, какой протокол безопасности использовать.
  • NTLM ( msv1_0.dll ) – устаревший пользовательский протокол.
  • Kerberos ( kerberos.dll ) – протокол домена по умолчанию.
  • Schannel ( schannel.dll ) – протокол SSL/TLS.
SSPI действует как стандартизированный уровень API, который позволяет приложениям использовать SSP для аутентификации пользователей, создания безопасных каналов и доступа к контекстно-зависимым криптографическим функциям для обеспечения конфиденциальности и целостности, без необходимости понимать, как работает протокол аутентификации.

Процесс аутентификации SSPI следует предопределенной последовательности:

  • Получите доступ к своим учетным данным.
    • И клиент (отправитель), и сервер (получатель) потока аутентификации должны вызвать AcquireCredentialsHandle , чтобы получить дескриптор, представляющий их учетные данные.
  • Создайте контекст безопасности.
    • Клиент должен вызвать InitializeSecurityContext для генерации запроса на согласование и/или обработки ответа на согласование.
    • Сервер должен вызвать AcceptSecurityContext для обработки запроса на согласование и/или генерации ответа на согласование.
    • Этот процесс повторяется до тех пор, пока обе стороны не создадут безопасный контекст.
  • Обменивайтесь сообщениями в частном порядке.
    • После установления безопасного контекста и клиент, и сервер могут использовать EncryptMessage для обеспечения конфиденциальности и целостности исходящих данных, а также DecryptMessage для проверки конфиденциальности и целостности входящих данных.
Следующая диаграмма иллюстрирует этот поток аутентификации и последующие взаимодействия.

image_9ff664.png

Приложения, которым необходимо напрямую взаимодействовать с пакетом аутентификации для реализации функциональности, не вписывающейся в поток аутентификации SSPI, должны делать это в следующей последовательности:

  • Получите идентификатор LSA.
    • Приложения должны вызывать либо LsaRegisterLogonProcess , либо LsaConnectUntrusted, чтобы получить дескриптор сервера LSA, предоставляющий функциональные возможности, предоставляемые пакетом аутентификации.
  • Найдите идентификатор пакета для пакета аутентификации.
    • Приложения должны вызывать LsaLookupAuthenticationPackage для получения внутреннего идентификатора для выбранного пакета аутентификации (NTLM, Kerberos и т. д.).
  • Обмен сообщениями с пакетом аутентификации.
    • Приложения должны вызывать LsaCallAuthenticationPackage для отправки и получения данных из выбранного пакета аутентификации, что позволяет реализовать функции аутентификации и связанные с ними функции безопасности.
  • Примечание : этот рабочий процесс можно смоделировать с помощью LSA Whisperer Эвана МакБрума , который использует эти функции для взаимодействия с предопределенными пакетами безопасности в LSA.
Хотя процесс аутентификации SSPI подходит для аутентификации приложений, для интерактивного входа используется другой процесс. Когда пользователь входит в локальную систему через интерфейс входа или в удалённую систему через клиент протокола удалённого рабочего стола (RDP), его учётные данные передаются в LSA, а затем делегируются ответственному пакету аутентификации.

Например, интерактивный вход в систему с локальной учетной записью будет делегирован пакету аутентификации Microsoft v1.0 (MSV1_0), который сравнивает NT-хэш предоставленного пароля с хешем, сохраненным для целевого пользователя в локальной базе данных диспетчера учетных записей безопасности (SAM).

Если попытка аутентификации завершается успешно, LSA создает сеанс входа в систему и сохраняет предоставленные учетные данные и производные секреты в глобальной структуре внутри lsasrv.dll, называемой LogonSessionList , что позволяет Windows предоставлять единый вход (SSO) для последующих запросов на аутентификацию.

Традиционный метод сброса учетных данных

Поскольку интерактивные входы в систему приводят к тому, что предоставленные учетные данные и производные ключи сохраняются в глобальной структуре LogonSessionList в lsasrv.dll , можно проанализировать эту структуру, прочитав оперативную память LSA (или дамп памяти LSA), чтобы извлечь конфиденциальную информацию, включая текстовые пароли и хэши NT.

Инструментом для реализации вышеупомянутой техники является Mimikatz Бенджамина Дельпи , который считается швейцарским армейским ножом для постэксплуатации Windows и сброса учетных данных.

Однако, поскольку Mimikatz проверяет энергозависимые структуры памяти и полагается исключительно на сканирование двоичных шаблонов для обнаружения структуры LogonSessionList , которые могут значительно меняться между версиями Windows и крупными обновлениями, для поддержания его эффективности требуются частые обновления. Кроме того, доступ к памяти привилегированного процесса, такого как lsass.exe , чрезвычайно шумный, и современные EDR выдают оповещения при попытке выгрузить учётные данные из LSA.

Для противодействия подобным методам Microsoft представила функцию безопасности Protected Process Light (PPL) для LSA. Когда lsass.exe запускается как процесс PPL, недоверенные приложения пользовательского режима больше не могут получать дескрипторы чтения его памяти, а пакеты безопасности, загружаемые LSA, должны быть подписаны доверенным сертификатом.

Хотя PPL остаётся эффективной мерой защиты, исследователи безопасности продемонстрировали несколько способов её обхода. К ним относятся атаки в пользовательском режиме, например, описанная в этой записи Джеймса Форшоу , которая позволяет внедряться в процессы, защищённые PPL, а также атаки в режиме ядра, например, используемая PPLKiller , которая снимает защиту PPL с процесса lsass.exe и восстанавливает доступ на чтение к его области памяти.

Охранник удостоверений

Чтобы устранить ограничения PPL и обеспечить более надежную защиту от сброса учетных данных, Microsoft представила Credential Guard — современную и мощную функцию безопасности, созданную на основе безопасности на основе виртуализации (VBS).

В стандартных системах Windows без VBS процесс загрузки загружает только ядро операционной системы при запуске. В этой конфигурации ядро имеет наивысший уровень привилегий в системе, что означает, что любой код, имеющий доступ к ядру, фактически получает неограниченный контроль над всеми системными ресурсами.

В системах Windows с включённым VBS процесс загрузки загружает дополнительный гипервизор, который управляет стандартным ядром операционной системы, а также дополнительными изолированными ядрами, представляющими собой микрооперационные системы, каждое из которых работает в отдельных областях физической памяти. Без эксплойта, позволяющего злоумышленнику обойти гипервизор, получение доступа к ядру Windows больше не даёт неограниченного контроля над более привилегированными системными ресурсами, включая более привилегированные изолированные ядра.

Единственное изолированное ядро, реализуемое в настоящее время гипервизором, — это безопасное ядро ( securekernel.exe ). Безопасное ядро работает на гостевом уровне гипервизора на уровне виртуального доверия 1 (VTL1), в то время как стандартное ядро операционной системы ( ntoskrnl.exe ) работает на уровне виртуального доверия 0 (VTL0). Такое разделение гарантирует, что даже в случае компрометации ядра операционной системы критически важные операции безопасности, выполняемые безопасным ядром, останутся защищенными.

Безопасное ядро (режим ядра) дополняется изолированным пользовательским режимом (режим пользователя), приложения которого называются трастлетами . Эти трастлеты предоставляют специализированные, критически важные для безопасности функции, и для обеспечения их целостности их можно загрузить только при наличии подписи с действительным сертификатом Microsoft.

Трастлет изолированного пользовательского режима ( IUM ), используемый Credential Guard, — это изолированный LSA ( lsaiso.exe ), который размещает несколько интерфейсов поддержки для аутентификации и может вызываться через расширенный локальный вызов процедур (ALPC) или удаленный вызов процедур (RPC).

Эта архитектура представлена на иллюстрации ниже.

image_9bdc68.png

Изолированный LSA ( lsaiso.exe ) считается критическим системным процессом, и попытка остановить или перезапустить его приведёт к сбою операционной системы. Кроме того, интерфейсы поддержки NTLM и Kerberos включают функции инициализации, возвращающие дескриптор контекста, который может быть вызван только один раз за время существования процесса изолированного LSA . Оба эти дескриптора вызываются стандартным LSA во время загрузки системы, что означает, что никакое другое приложение в системе не может получить эти дескрипторы контекста или взаимодействовать с этими интерфейсами из изолированного LSA .

Кроме того, изолированный LSA выполняет криптографические операции, вызывая системные вызовы EncryptData и DecryptData из защищенного ядра , оба из которых используют сеансовый ключ, который генерируется случайным образом при каждой инициализации защищенного ядра .

Функции поддержки Kerberos и NTLM в изолированном LSA называются KerberosIum и NtlmIum соответственно , где суффикс «Ium» означает «изолированный пользовательский режим ». Поскольку стандартный LSA теперь использует изолированный LSA для обработки многих функций протокола аутентификации, LSA больше не хранит учётные данные и производные ключи в виде открытого текста, а в зашифрованном виде, который может расшифровать только изолированный LSA , используя сеансовый ключ, управляемый безопасным ядром.

Если LSA необходимо сгенерировать аутентификатор AP-REQ для аутентификации Kerberos, он вызывает функцию KerbIumBuildApReqAuthenticator из интерфейса KerberosIum вместо того, чтобы создавать сам аутентификатор AP-REQ.

В результате больше невозможно получить идентификационный материал Kerberos или NTLM путем сброса процесса LSA, и для взаимодействия с этими изолированными секретами требуется более сложный подход.

Для взаимодействия с интерфейсами изолированного LSA нам потребуется внедрить код в lsass.exe или загрузить пользовательский пакет безопасности, а затем извлечь дескриптор контекста для нужного интерфейса, прежде чем мы сможем вызвать какую-либо функциональность через ALPC или RPC.

Однако, как уже упоминалось, доступ к памяти LSA чрезвычайно шумный, и современные EDR выдают предупреждения при попытке внедрения кода в LSA. Кроме того, если LSA работает в режиме PPL, внедрение кода в LSA становится более затруднительным, а загрузка пользовательского пакета безопасности потребует подписи библиотеки с доверенным сертификатом подписи кода, что ещё больше ограничивает возможность прямого взаимодействия с изолированным LSA .

Таким образом, ключевая проблема, которую нам необходимо решить, заключается в том, как взаимодействовать с интерфейсами Credential Guard чистым и ненавязчивым образом, не обнаруживаемым EDR и не блокируемым защитой PPL.

Удаленная защита учетных данных

Потенциальным решением нашей проблемы является Remote Credential Guard ( RCG ), функция безопасности для служб удаленных рабочих столов ( RDS ), ранее известных как службы терминалов Microsoft ( MSTS ).

В Windows 8.1 и Windows Server 2012 R2 компания Microsoft представила функцию безопасности под названием «Ограниченный режим администратора» для RDP-подключений, которая позволяла администраторам подключаться к удалённому узлу, не отправляя свои учётные данные, тем самым защищая учётную запись от атак с дампом учётных данных на удалённом узле. Хотя эта функция эффективно предотвращала дамп учётных данных с удалённого узла, она имела серьёзное ограничение: учётные данные не передавались на удалённый узел и, следовательно, не могли быть использованы для доступа к другим службам с этого узла.

Для решения этой проблемы, известной как «проблема двойного перехода Kerberos», Microsoft представила новую функцию безопасности Remote Credential Guard ( RCG ). Эта функция основана на двух протоколах Microsoft для защиты учётных данных во время сеансов RDP:

  • Протокол удаленного рабочего стола: протокол виртуального канала перенаправления аутентификации (MS-RDPEAR) позволяет использовать учетные данные по соединению RDP, не раскрывая эти учетные данные или их производные ключи удаленной системе.
  • « Протокол поставщика поддержки безопасности учетных данных » (MS-CSSP) позволяет клиенту безопасно делегировать учетные данные удаленному хосту.
На практике RCG использует MS-CSSP для передачи зашифрованных учётных данных от клиента на удалённый хост, но эти данные не могут быть расшифрованы самим удалённым хостом. Вместо этого MS-RDPEAR используется для перенаправления операций аутентификации обратно клиенту при необходимости.

Такая конструкция гарантирует, что открытые текстовые учетные данные и их производные ключи никогда не будут находиться на удаленном хосте, в то же время позволяя удаленному хосту проходить аутентификацию для других служб, поскольку он может запрашивать у клиента выполнение операций аутентификации от его имени.

Однако, с нашей точки зрения, это не обязательно является проблемой, поскольку мы ищем способ взаимодействия с интерфейсом Credential Guard на клиенте. Когда RCG требуется выполнить аутентификацию с использованием Kerberos или NTLM, он взаимодействует с клиентом через интерфейсы KerbCredIsoRemote или NtlmCredIsoRemote . Эти удалённые интерфейсы затем передают функции различным внутренним интерфейсам в зависимости от состояния Credential Guard.

Если Credential Guard включен, запросы направляются в интерфейс KerbCredIsoIum или интерфейс NtlmCredIsoIum , который является интерфейсом Credential Guard для соответствующих протоколов.

Если Credential Guard отключен, запросы направляются в интерфейс KerbCredIsoInProc или интерфейс NtlmCredIsoInProc , который является стандартным интерфейсом для соответствующих протоколов.

Как ни парадоксально, это означает, что RCG работает независимо от того, включен ли Credential Guard, но также и то, что мы можем напрямую взаимодействовать с интерфейсом Credential Guard через RCG, не имея привилегированного доступа на клиенте, с которого мы инициируем RDP-соединение.

Начало разговора с удаленным Credential Guard

На этом этапе мы решили создать контекст безопасности с нашим локальным LSA в небольшом концептуальном приложении, многократно вызывая InitializeSecurityContext и AcceptSecurityContext , чтобы мы могли попытаться связаться с интерфейсами Credential Guard.

Первая проблема, с которой мы столкнулись, заключалась в том, что перенаправленные учетные данные никогда не отправлялись через наш установленный контекст безопасности, поэтому мы решили глубже разобраться в том, что происходит в поставщике поддержки безопасности служб терминалов (TS SSP), который реализован в TsPkg.dll .

В LSA все вызовы API пользовательского режима, такие как InitializeSecurityContext или AcceptSecurityContext, делегируются обратным вызовам в ответственном SSP. В случае InitializeSecurityContext назначенный обратный вызов называется SpInitLsaModeContext . При анализе SpInitLsaModeContext мы обнаружили, что перенаправляемые учетные данные получаются клиентом (и отправляются на удаленный хост) только при установке определённого флага для дескриптора учетных данных.

image_6826f4.png

Кроме того, мы продолжали вводить этот оператор if, который переходит к LABEL_248 , фактически пропуская указанный выше код для получения перенаправляемых учетных данных.

image_450c7a.png

Оператор if вводится только в том случае, если определенный член нашего дескриптора контекста установлен на ненулевое значение, и если мы выполним обратную трассировку члена дескриптора контекста, то увидим, что он установлен, когда TSIsLoopback возвращает true.

image_e05611.png

На практике это означает, что мы не можем установить контекст безопасности с локальной LSA, поскольку в Terminal Services SSP есть механизм, который обнаруживает локальную аутентификацию и предотвращает делегирование учётных данных в таких случаях. Пакеты безопасности Kerberos и NTLM имеют схожие механизмы.

Одной из наших первоначальных мыслей было то, что мы могли бы просто исключить параметр pszTargetName , но, к сожалению, SSP не отправляет перенаправленные учетные данные, если клиент не аутентифицирован по протоколу Kerberos, а если мы исключим параметр pszTargetName , аутентификация вернется к NTLM.

Это привело к нашему первому важному требованию для возможности взаимодействия с Credential Guard, поскольку нам необходимо иметь привилегированный доступ к удаленному хосту и выполняться в контексте SYSTEM на этом же удаленном хосте, прежде чем наши вызовы AcceptSecurityContext сгенерируют допустимые токены, которые будут соответствовать цели, указанной в наших клиентских вызовах InitializeSecurityContext .

После прохождения ограничения обратной связи оператор if, содержащий функциональность для получения перенаправляемых учетных данных, все еще передавался, поэтому нам нужно было также выяснить, как заставить дескриптор учетных данных содержать правильный флаг.

Обратным вызовом для AcquireCredentialsHandle является SpAcquireCredentialsHandle , и при выполнении этой функции мы обнаружили, что поле флага в дескрипторе учетных данных было унаследовано от переменной, которую мы назвали Flags на изображении ниже.

image_796f70.png

Переменная Flags является результатом вызова TSCaptureSuppliedCreds .

image_873bf2.png

Внутри TSCaptureSuppliedCreds мы обнаружили, что параметр pAuthData , который мы передали функции AcquireCredentialsHandle, был проанализирован как объект CREDSSP_CRED , и если поле Type объекта было установлено равным 0x64 (100), что является CredsspCredEx , то Flags будет установлен при вызове TSUnpackCredEx .

image_783b02.png

После этого pAuthData будет установлен в поле Cred структуры CREDSSP_CRED_EX , которое является вложенным объектом CREDSSP_CRED , и анализ продолжится для других типов.

Поскольку структура CREDSSP_CRED_EX содержит поле Flags , можно предположить, что это тот флаг, который нам нужно заполнить для входа в нужную ветвь. Единственным допустимым флагом для этого поля объекта является CREDSSP_FLAG_REDIRECT , что вполне логично, учитывая, что мы пытаемся получить перенаправленные учетные данные.

Для вложенной структуры CREDSSP_CRED мы обнаружили, что если установить поле Type объекта равным CredsspSubmitBufferBoth (50) или CredsspSubmitBufferBothOld (51), нам не нужно будет предоставлять какие-либо учётные данные, и поставщик общих служб по умолчанию будет использовать наши основные учётные данные. Ниже показан корректный вызов AcquireCredentialsHandle , прошедший проверку в SpInitLsaModeContext .

image_5b0070.png

После разделения клиентской и серверной частей приложения-проверки концепции на отдельные приложения и отправки токенов согласования между двумя приложениями с использованием сетевых сокетов нам удалось установить контекст безопасности и, наконец, получить возможность взаимодействовать с интерфейсами Credential Guard на клиенте — или мы так думали...

Входные данные, которые необходимо передать в вызов RCG, окутаны несколькими уровнями абстракции, а уровень MS-CSSP требует шифрования с использованием установленного контекста безопасности. Это не должно быть проблемой, поскольку мы уже установили контекст безопасности и можем вызвать EncryptMessage, используя дескриптор контекста, верно?

К сожалению, нет. Оказывается, что поставщик общих служб терминалов управляет базовым контекстом безопасности Negotiate SSP, и что контекст безопасности, используемый для шифрования уровня MS-CSSP входных данных, — это контекст безопасности, установленный Negotiate SSP. Поскольку поставщик общих служб терминалов не предоставляет метода, с помощью которого мы можем получить доступ к базовому контексту безопасности Negotiate SSP, мы не можем зашифровать полезную нагрузку MS-CSSP.

На этом этапе мы были слишком заняты, поэтому решили повторно реализовать уровень Terminal Services SSP для сервера (т. е. реализовать обработчик обратного вызова для AcceptSecurityContext ), чтобы иметь возможность получить доступ к базовому дескриптору контекста безопасности Negotiate SSP.

Повторная реализация Terminal Services SSP

Процесс аутентификации Terminal Services SSP, который мы хотим перереализовать, показан в серверной части диаграммы ниже.

image_b30421.png

Из уважения к читателю и для краткости мы не будем подробно описывать процесс реверс-инжиниринга протокола, но общая идея, проиллюстрированная на диаграмме выше, заключается в том, что Terminal Services SSP передает информацию аутентификации между клиентом и сервером через структуру, называемую TSRequest .

Структура TSRequest имеет несколько полей, которые после заполнения должны быть обработаны сервером, а результат должен быть включен в ответ клиенту:

  • Версия – это поле содержит версию структуры TSRequest .
  • NegoTokens – это поле содержит токены, которые необходимо передать поставщику общих служб (SSP) для установления базового контекста безопасности. В зависимости от согласованного протокола, это могут быть запросы аутентификации Kerberos или NTLM.
  • PubKeyAuth – это поле зависит от поля Version. В современных версиях (5 или 6) это поле содержит хэш SHA256 открытого ключа сертификата, предоставленного сервером во время SSL/TLS-рукопожатия при установке первоначального RDP-подключения.
  • ClientNonce – это поле присутствует только в версии 5 и выше и содержит одноразовый код клиента, который был включён в хэш SHA256, используемый для генерации поля PubKeyAuth. Его также необходимо включить при генерации ответа на поле PubKeyAuth.
  • AuthInfo — это поле содержит учетные данные, делегированные клиентом, которые должны быть отправлены через MS-RDPEAR удаленным хостом всякий раз, когда клиенту требуется выполнить операции аутентификации.
Когда поле NegoTokens установлено, мы просто пересылаем их Negotiate SSP, контекст безопасности которого мы пытаемся установить, и если Negotiate SSP выдает выходные данные, мы прикрепляем их к TSRequest , который мы отправим обратно клиенту.

При установке поля PubKeyAuth сервер должен сначала расшифровать его, используя контекст безопасности, установленный с помощью Negotiate SSP, и сгенерировать хэш SHA256 своего открытого ключа, используя поле ClientNonce и статическую соль «CredSSP Client-To-Server Binding Hash». Сравнивая его с расшифрованным полем PubKeyAuth, сервер проверяет, что клиент знает свой открытый ключ и смог зашифровать хэш SHA256, используя установленный контекст безопасности Negotiate SSP.

Затем сервер генерирует ответный хэш SHA256 для клиента, используя поле ClientNonce и статическую соль «CredSSP Server-To-Client Binding Hash». Этот хэш отправляется обратно клиенту, чтобы тот мог убедиться, что сервер смог зашифровать хэш SHA256 с использованием установленного контекста безопасности Negotiate SSP.

После прохождения аутентификации по открытому ключу клиент отправляет финальный запрос, содержащий поле AuthInfo. Сервер должен расшифровать поле AuthInfo и декодировать его в структуру TSCredentials , которая может содержать пароли, смарт-карты или перенаправленные учётные данные.

После получения структуры AuthInfo аутентификация завершена, и у нас должен быть доступ как к контексту безопасности Negotiate SSP для шифрования или дешифрования сообщений между клиентом и сервером, так и к перенаправленным учетным данным пользователя.

Перенаправленные учетные данные содержат как основные учетные данные для входа в систему по согласованному протоколу (мы знаем, что это Kerberos), так и дополнительные учетные данные для пакетов безопасности, которые поддерживаются перенаправленными учетными данными, например, NTLM.

Имея их под рукой, мы можем приступить к вызову функций через интерфейсы KerbCredIsoRemote или NtlmCredIsoRemote для взаимодействия с Credential Guard на подключенном клиенте.

Чтобы создать действительный запрос для Terminal Services SSP, нам необходимо выполнить следующие шаги:

  • Создайте входную структуру для функции, которую мы хотим вызвать.
  • Сериализуйте входные данные, используя сериализацию типа удаленного вызова процедур (RPC) .
    • Более подробную информацию по этому вопросу можно найти в блоге Джеймса Форшоу .
  • Оберните сериализованный ввод в структуру TSRemoteGuardInnerPacket.
    • Закодируйте объект как структуру ASN.1.
    • Зашифруйте объект, используя контекст безопасности Negotiate SSP.
  • Оберните TSRemoteGuardInnerPacketв структуру полезной нагрузки MS-RDPEAR.
    • Поле TsPkgContext полезной нагрузки MS-RDPEAR должно быть установлено в верхней части дескриптора контекста SSP служб терминалов от клиента.
  • Вызовите напрямую в Terminal Services SSP через API LsaCallAuthenticationPackage.
    • Используя поток, описанный в начале этого блога.
Если функция выполнена успешно, вызов LsaCallAuthenticationPackage вернет выходной буфер, который мы должны обработать в обратном порядке, чтобы получить выходную структуру для вызванной нами функции:

Называя блефом охранника по удостоверениям

После того как мы наконец получили необходимый нам примитив вызова, мы решили поэкспериментировать с интерфейсом Credential Guard, чтобы выяснить, сможем ли мы получить доступ к повторно используемым учетным данным.

Первым делом мы попытались получить Kerberos TGT. Для этого нам пришлось расшифровать структуру перенаправленных учётных данных, полученную в поле AuthInfo финального TSRequest .

AuthInfo для основных учётных данных представлял собой объект KERB_TICKET_LOGON , содержащий TGS для нашего удалённого хоста и TGT для пользователя-клиента. Сеансовый ключ TGS можно было расшифровать с помощью долгосрочного ключа удалённого хоста (например, ключа Kerberos, полученного из пароля его учётной записи), а зашифрованную часть TGT можно было расшифровать с помощью сеансового ключа TGS.

Однако даже после расшифровки зашифрованной части TGT его сеансовый ключ представлял собой зашифрованный блок данных, сгенерированный экземпляром Credential Guard на клиенте. Мы предположили, что, возможно, если мы обновим TGT через TGS-REQ, KDC вернёт TGT, в котором ключ не будет зашифрованным блоком данных Credential Guard.

Чтобы это сделать, нам пришлось подделать TGS-REQ для имени принципала службы « krbtgt/domain », но поскольку у нас не было доступа к ключу сеанса TGT, нам пришлось вызвать следующие функции из интерфейса Credential Guard, чтобы получить некоторые части:

  • BuildEncryptedAuthData
  • ComputeTgsChecksum
  • CreateApReqAuthenticator
Затем мы отправили TGS-REQ в KDC и расшифровали TGS-REP с помощью функции UnpackKdcReplyBody из интерфейса Credential Guard . Однако возвращённый TGS-REP всё ещё содержал TGT, сеансовый ключ которого представлял собой зашифрованный двоичный двоичный объект из экземпляра Credential Guard на клиенте. Это соответствует информации, представленной в разделе Credential Guard на сайте Microsoft Learn о состоянии защиты билетов Kerberos.

image_64e966.png

Фактически, мы не можем получить повторно используемые TGT, но, поскольку обычные TGS не защищены Credential Guard, мы могли бы выполнить те же действия, что и для запроса тикета для произвольной службы в домене, а затем внедрить тикет на другой хост, чтобы выдать себя за клиента с этой конечной точки. Хотя это интересно и имитирует кражу учётных данных, мы ищем более простой в использовании подход, не требующий постоянного доступа к хосту клиента.

Следующей идеей было использование интерфейса Credential Guard для NTLM для запроса ответов на запросы NTLM. Для этого интерфейс NTLM предоставляет следующие функции:

  • NTLMv1: NtlmCalculateNtResponse
  • NTLMv2: NtlmLm20GetNtlm3ChallengeResponse
Если мы еще раз посмотрим на раздел Credential Guard в Microsoft Learn, то в документации говорится, что NTLMv1 не может использовать наши учетные данные, если включен Credential Guard.

image_8b3101.png

Однако в предупреждении говорится, что использовать учетные данные не рекомендуется , поэтому мы по-прежнему скептически относимся к утверждению, что NTLMv1 не работает с Credential Guard.

image_11c6d4.png

AuthInfo с нашими перенаправленными учётными данными содержал дополнительные учётные данные для NTLM в виде объекта MSV1_0_REMOTE_SUPPLEMENTAL_CREDENTIAL . Мы можем использовать данные из этого объекта для подделки объекта MSV1_0_REMOTE_ENCRYPTED_SECRETS и передать его функции NtlmCalculateNtResponse вместе со статическим запросом 1122334455667788. Причина выбора этого статического запроса будет объяснена в следующем разделе.

Согласно разделу Credential Guard на сайте Microsoft Learn, это не должно работать, но о чудо! Мы получили вывод от функции.

image_647d26.png

Представьте себе наше удивление…

image_3c29e9.png

Если мы декодируем вывод, мы должны получить объект NtlmCredIsoRemoteOutput , содержащий идентификатор вызова функции, которую мы вызвали для получения вывода, идентификатор состояния, содержащий код ошибки, если функция не выполнена, и ответ NTLMv1 на наш вызов 1122334455667788 .

image_23cba0.png

Длина ответа (24 байта) соответствует ожидаемому формату NTLMv1. Чтобы убедиться, что ответ соответствует исходному паролю, можно выполнить целенаправленный взлом, используя список слов, содержащий только предполагаемый пароль, для проверки корректности.

image_e222e7.png

Как и ожидалось, пароль успешно взломан, и мы подтверждаем, что нам удалось успешно взаимодействовать с интерфейсом Credential Guard через RCG и запрашивать ответы на выбранные вызовы NTLMv1, несмотря на заявление в Microsoft Learn.

К сожалению, проблема всё ещё существует, поскольку для этого метода требуется наличие SYSTEM на удалённом хосте, что не всегда осуществимо. Гораздо более вероятным сценарием является возможность присоединить машину к домену и использовать её учётную запись в качестве удалённого хоста.

Однако в этом случае у машины нет физического хоста, поэтому нам пришлось бы запустить серверную часть нашей реализации на самом клиенте, выдавая себя за учетную запись машины удаленного хоста, но тогда проверка по шлейфу снова стала бы проблемой.

image_23833d.png

Поскольку Negotiate SSP распознает, получены ли токены с одного и того же хоста, нам придется заново реализовать уровни Negotiate SSP и Kerberos SSP таким же образом, как мы это сделали для Terminal Services SSP.

Повторная реализация Negotiate SSP (и Kerberos SSP)

Серверная часть Negotiate SSP и потоки аутентификации Kerberos SSP, которые мы хотим реализовать повторно, показаны на диаграмме ниже.

image_7bb676.png

Еще раз, из уважения к читателю и ради краткости, мы не будем подробно описывать процесс реверс-инжиниринга протоколов, а вместо этого сосредоточимся на общей идее работы протоколов, показанных на диаграмме выше.

Когда клиент инициирует поток аутентификации Negotiate SSP, он отправляет InitialNegToken , содержащий список MechTypes — список механизмов (протоколов аутентификации), поддерживаемых клиентом. Поскольку мы знаем, что службы терминалов требуют Kerberos, мы проверим, содержит ли список «Механизм Kerberos 5 GSS-API» (идентификатор объекта 1.2.840.113554.1.2.2). Этот токен также включает токен согласования для ожидаемого целевого механизма, которым в нашем случае является Kerberos.

В ответ на InitialNegToken сервер отправляет NegotiationToken , содержащий строку SupportedMech, указывающую, какой механизм из списка клиентов сервер выбрал для оставшейся части согласования. Кроме того, NegotiationToken содержит ответ на предоставленный клиентом токен от выбранного механизма, которым в нашем случае является Kerberos.

Оставшаяся часть коммуникации между сервером и клиентом использует NegotiationToken для передачи токенов из базового механизма, а в последнее сообщение, отправленное клиентом и сервером, они включают MechListMic — код целостности сообщения, рассчитанный по списку MechTypes для проверки того, что список не был изменен при передаче с целью снижения уровня безопасности аутентификации.

Для базовой аутентификации Kerberos клиент инициирует процесс аутентификации, отправляя запрос TGT-REQ типа «пользователь-пользователь» (U2U). Если на сервере ещё нет TGT в памяти, он отправляет AS-REQ в KDC и получает TGT в формате AS-REP. Затем сервер отправляет TGT обратно клиенту в формате TGT-REP типа «пользователь-пользователь».

Используя TGT сервера, клиент запрашивает TGS у KDC (это не показано на схеме выше) и предоставляет его серверу, отправляя AP-REQ. Сервер расшифровывает ключ из TGS в AP-REQ, используя свой долгосрочный ключ, а затем расшифровывает аутентификатор AP-REQ с помощью расшифрованного ключа, после чего отвечает клиенту AP-REP.

Ключ шифрования для установленного контекста безопасности зависит от факторов, которые имели место в процессе аутентификации Kerberos, в следующем порядке приоритета:

  • Если сервер включил подключ Acceptor в AP-REP, то используется этот ключ.
  • В противном случае, если клиент включил подключаемый ключ инициатора в AP-REQ, будет использоваться этот ключ.
  • В противном случае используется сеансовый ключ из TGT.
Поскольку мы хотим сохранить простоту процесса аутентификации и гарантировать надежную работу нашего инструмента, мы всегда будем отправлять подключаемый ключ акцептора в AP-REP, чтобы гарантировать использование правильного ключа шифрования для связи SSP служб терминалов.

Поскольку наш поток аутентификации теперь представляет собой пользовательскую реализацию и у нас больше нет доступа к установленному контексту безопасности в LSA, нам придется заново реализовать криптографические функции, которые ранее были доступны через функции EncryptMessage и DecryptMessage .

Обработчик обратного вызова для функции EncryptMessage называется SpSealMessage , а обработчик обратного вызова для функции DecryptMessage — SpUnsealMessage . При трассировке этих функций в Kerberos SSP мы обнаруживаем две функции: KerbSealMessageOld и KerbUnsealMessageOld . Документации о работе этих функций для AES128 (тип шифрования 17) и AES256 (тип шифрования 18) практически нет, поэтому нам пришлось выполнить их реверс-инжиниринг с нуля.

После того как мы все реализуем, мы можем создать единое оптимизированное приложение, которое может выполнять как клиентскую, так и серверную части потока аутентификации Terminal Services SSP и вызывать функциональные возможности из интерфейсов Credential Guard, используя учетные данные для произвольной учетной записи компьютера в качестве удаленного хоста без физического доступа к другому компьютеру.

Как меняются столы – Симулируем успех

Начнем с создания учетной записи машины SRV10 с помощью Powermad .

image_264760.png

Затем мы можем выполнить весь поток Terminal Services SSP, смоделировав удаленный хост с использованием учетной записи машины SRV10$ , которую мы только что присоединили к домену, и получить ответ NTLMv1 на вызов 1122334455667788 для нашего текущего пользователя.

image_fe7020.png

Для выполнения всего этого потока не требуется никаких привилегий, и поэтому мы можем вывести учетные данные нашего текущего пользователя в формате, поддающемся взлому, независимо от того, есть ли у нас привилегии или нет.

image_99c54e.png

Но что, если мы находимся на хосте, на котором Credential Guard не включён? Честно говоря, это не имеет значения. Как мы уже упоминали, интерфейс NtlmCredIsoRemote , используемый RCG на удалённом хосте для взаимодействия с клиентом при выполнении операций аутентификации, ретранслирует запросы либо в NtlmCredIsoIum , либо в NtlmCredIsoInProc в зависимости от того, включён ли Credential Guard.

В результате интерфейс RCG может взаимодействовать с Credential Guard, если он включен, а также со стандартным интерфейсом NTLM, если Credential Guard отключен, что позволяет реализовать данную технологию в любом случае.

image_df9a5c.png

Всё это замечательно, но нам всё ещё нужен доступ к учётной записи машины или к SYSTEM на удалённом хосте, чтобы использовать её учётную запись и провести атаку. А что, если мы не сможем добиться ни того, ни другого? У нас есть решение и для этого!

На чем мы не остановились ранее, так это на том, что нам нужна учетная запись компьютера, поскольку протокол Kerberos требует имена участников службы (SPN) в качестве целей для аутентификации, а учетные записи компьютеров по умолчанию имеют различные настроенные SPN. Однако мы также можем использовать другие типы учетных записей в качестве целевого «удаленного хоста», если у них настроено SPN.

image_1882f3.png

Фактически это означает, что для использования этой методики необходим либо доступ к учетной записи домена с настроенным SPN, либо разрешение на запись в объекте учетной записи домена (чтобы мы могли настроить SPN).

Однако, хотя ограничение выдачей только ответов NTLMv1 на запросы, адресованные текущему пользователю, может показаться не слишком впечатляющим, мы можем расширить эту технологию еще больше.

Как известно многим читателям, традиционные методы сброса учётных данных требуют привилегий на целевом хосте, и этот метод не является исключением при его максимальном использовании. Имея доступ к контексту SYSTEM на клиентском хосте, мы можем выдавать себя за токены других аутентифицированных пользователей и использовать этот метод в контексте этих пользователей.

image_0541c1.png

Честно говоря, изначально мы не были уверены, что это сработает…

image_ef1734.png

NTLM присоединился к чату

До сих пор мы нашли способы выгрузить учётные данные текущего субъекта из непривилегированного контекста (или всех субъектов из контекста SYSTEM) на хостах с включённым Credential Guard, используя протокол RCG. Но что, если у нас нет доступа к хостам, где включён Credential Guard, но есть доступ к серверу, где клиентские хосты аутентифицируются по RDP, возможно, с использованием протокола RCG?

Предположим, что у нас есть привилегии SYSTEM на сервере SRV02 , где администраторы прошли аутентификацию по RDP с использованием протокола RCG, а на клиентских хостах включен Credential Guard.

image_5ff0b3.png

Если мы попытаемся использовать ранее обсуждавшиеся приемы, то столкнемся со следующим исключением.

image_84bcc3.png

В частности, мы получаем код ошибки 0x8009030e ( SEC_E_NO_CREDENTIALS ), что вполне логично, поскольку у пользователей, от имени которых мы пытаемся действовать, нет привязанных к ним основных учётных данных, поскольку они используют протокол RCG для ретрансляции запросов аутентификации обратно клиенту. Как же нам перенаправить эти уже перенаправленные учётные данные?

Подумайте вот о чём. Как обычное приложение, которому требуется выполнить аутентификацию для сервиса, узнает, аутентифицирован ли пользователь приложения интерактивно на хосте или подключен через RCG с клиента, на котором включён Credential Guard? Конечно, нет.

Итак, как же приложение выполняет аутентификацию? Оно взаимодействует с поставщиком общих служб (SSP) для получения нужного протокола аутентификации и ожидает, что Windows позаботится обо всём остальном — и мы можем сделать то же самое!

Как мне указал мой коллега Эван МакБрум , разработчик LSA Whisperer , пакет аутентификации Microsoft v1.0, содержащий пакет безопасности NTLM, предоставляет интерфейс с функциональными возможностями безопасности для протоколов NTLMv1 и NTLMv2.

Одна из этих функций, MsV1_0Lm20GetChallengeResponse , более или менее идентична функциям вызова-ответа NTLMv1, которые мы ранее вызывали из интерфейса NTLM Credential Guard, но вместо этого взаимодействует со стандартным пакетом безопасности NTLM.

Вызов функции требует привилегий SYSTEM, но если мы вызовем функцию с ранее описанным статическим запросом и идентификатором сеанса входа (LUID) для сеанса целевого пользователя, мы можем получить ответы NTLMv1 для любого пользователя на хосте, который интерактивно аутентифицировался на хосте или через RCG, даже если на его клиентской системе запущен Credential Guard. Более того, этот метод не требует доступа к учётной записи с настроенным SPN, поэтому он имеет ещё меньше ограничений, чем метод Credential Guard, но не работает в клиентских системах, где пользователи входят в систему интерактивно с включённым Credential Guard.

image_379ebd.png

Интересно, что этот метод также позволяет извлекать учетные данные от интерактивно вошедших в систему пользователей на хостах, на которых Credential Guard не включен, при условии наличия у нас системных привилегий на хосте.

До сих пор мы рассматривали различные методы сброса учётных данных пользователей в систему, каждый из которых предъявляет свои требования. Для простоты понимания мы обобщили эти методы, их требования и возможности сброса учётных данных, защищённых Credential Guard, в таблице ниже.

Техника сбросаТребуется
СИСТЕМА
Требуется
учетная запись SPN
Можно ли сбросить
Credential Guard?
Извлечь собственные учетные данные (через протокол Remote Credential Guard)Х✓✓
Извлечь все учетные данные (через протокол Remote Credential Guard)✓✓✓
Извлечь все учетные данные (через пакет аутентификации Microsoft v1)✓ХХ
Теперь, когда мы рассмотрели практически все сценарии сброса учетных данных в современных системах Windows, с которыми вам когда-либо придется столкнуться, вы можете задаться вопросом: а зачем кому-то беспокоиться о хэшах NTLMv1, если их нельзя аутентифицировать напрямую?

Почему нам следует беспокоиться о NTLMv1?

Криптографическая схема NTLMv1, как показано на диаграмме ниже, считается очень слабой.

image_a9d31b.png

Коротко говоря, 16-байтовый NT-хеш пароля пользователя разделяется на три отдельных 7-байтовых фрагмента. Каждый фрагмент используется для получения ключа DES, который шифрует 8-байтовый запрос сервера. Три полученных 8-байтовых шифртекста затем объединяются для формирования итогового 24-байтового ответа NTLMv1.

Поскольку ключи формируются из 7-байтовых (56-битных) фрагментов, каждый ключ имеет ограниченное ключевое пространство, что позволяет с вычислительной точки зрения восстановить все три ключа независимо. Существуют предварительно вычисленные радужные таблицы для задания сервера 1122334455667788 , которые покрывают примерно 99,5–99,99% ключевого пространства, что позволяет выполнять поиск менее чем за 30 секунд на специализированном оборудовании.

Поскольку мы восстанавливаем NT-хеш, а не сам пароль, успешный поиск может привести к получению учётных данных, пригодных для непосредственного использования (с использованием Pass-The-Hash), независимо от сложности исходного пароля. NT-хеш также эквивалентен долгосрочному ключу RC4 для Kerberos и может использоваться для запроса билетов TGT и сервисных билетов.

Ранее crack.sh предоставлял бесплатный сервис для поиска по радужным таблицам для восстановления NT-хэшей из ответов NTLMv1, но недавно был закрыт из-за проблем с обслуживанием. В связи с его отсутствием был опубликован новый бесплатный сервис на сайте ntlmv1.com .

image_b69a0d.png

После отправки задания на взлом мы можем отслеживать его на сайте, а после его восстановления — получить NT-хеш. Обратите внимание, что этот сайт не такой быстрый, как crack.sh , и поиск может занять несколько минут.

image_092a84.png

Если вместо этого вы хотите получить пароль в виде открытого текста, вы можете взломать ответ NTLMv1 с помощью hashcat, но в этом случае вам не придется использовать вызов 1122334455667788 .

Защитные соображения

В настоящее время нам не известны какие-либо продукты EDR, которые отслеживают взаимодействие с LSA, и, насколько нам известно, не существует вспомогательных функций, которые позволяют стороннему продукту отслеживать эти взаимодействия неинвазивным способом.

Для поставщиков EDR мы рекомендуем подключить API LsaCallAuthenticationPackage и отслеживать вызовы пакета безопасности NTLM для ответов NTLM на вызов 1122334455667788 .

В настоящее время у нас нет никаких рекомендаций по защите от атак на основе RCG, поскольку входная полезная нагрузка зашифрована и не может быть легко исследована продуктом EDR.

Заключение

Credential Guard представляет собой значительный шаг вперёд в защите учётных данных Windows от традиционных методов дампинга, но дампинг учётных данных далёк от упадка. Даже в средах, использующих новейшие средства защиты, реализованные Microsoft, дампинг учётных данных остаётся действенным методом бокового распространения после эксплуатации.

В этом сообщении в блоге мы рассмотрим функцию безопасности, выделив сильные и слабые стороны этой технологии, а также представим новые методы сброса учетных данных как из непривилегированного, так и из привилегированного контекста.

Хронология раскрытия информации

Я сообщил об этой проблеме в Microsoft, заявив, что мы можем извлечь перенаправленные учетные данные пользователей, подключенных к серверу, и использовать Remote Credential Guard с сервера для связи с интерфейсами Credential Guard на клиенте для получения билетов Kerberos и ответов NTLM.

В хронологию раскрытия информации мы добавили три точки выхода для публикации нашего исследования:

  • 30 дней — 20 сентября 2025 г. — если Microsoft не подтвердит, что проблема была воспроизведена.
  • 60 дней — 20 октября 2025 г. — если Microsoft не подтвердит, что проблема будет устранена.
  • 90 дней – 19 ноября 2025 г. – Не подлежащий обсуждению срок раскрытия информации.
Раскрытие информации произошло в соответствии со следующей хронологией:

  • 21 августа 2025 г. — раскрытие информации Microsoft через портал MSRC.
  • 29 августа 2025 г. — Microsoft запросила дополнительные вопросы.
  • 3 сентября 2025 г. – На последующие вопросы были даны подробные ответы.
  • 16 сентября 2025 г. — Microsoft успешно воспроизвела проблему.
  • 24 сентября 2025 г. — Microsoft пришла к выводу, что не будет решать эту проблему.
Попробуйте сегодня!

Разработанный для этого исследования экспериментальный инструмент DumpGuard теперь доступен на GitHub .
 
Автоматический гарант это финансовая безопасность: Покупатель может быть уверен, что его деньги не попадут к продавцу до того, как он получит и проверит товар или услугу. Продавец, в свою очередь, защищен от не оплаты, так как гарант подтверждает наличие средств у покупателя до начала выполнения обязательств.
Назад
Сверху