
Перехватывает PIN-коды Windows Hello при вводе путём внедрения DLL в LogonUI.exe, которая устанавливает аппаратную точку останова процессора на NgcSerializeConvenienceCredential, а также перечислитель WHFB/NGC/TPM.
На хосте, где Windows Hello защищён VBS/VSM (изолированный контейнер NgcIso в VTL1) + TPM, PIN не поддаётся восстановлению офлайн, а онлайн-брутфорс блокируется secure-kernel trustlet. Единственный надёжный путь — перехват при вводе.
Этот проект перехватывает PIN, устанавливая аппаратную точку останова CPU на функцию,
которая упаковывает введённый PIN внутри LogonUI.exe, считывая аргумент из контекста
остановленного потока в Vectored Exception Handler, и позволяя исходной, неизменённой
инструкции выполниться, так что вход по PIN по-прежнему проходит нормально.
Он также включает Get-WHFBKeys.ps1 — полный перечислитель WHFB/NGC/TPM и сборщик
ключевого материала.
Проверено сквозным тестом на Windows 11 26200 (Home) с Intel fTPM 2.0 и работающим VBS.
| Наблюдаемая защита | Последствие |
|---|---|
VBS / VSM запущены (статус VBS в Win32_DeviceGuard = 2, присутствует LsaIso.exe) | Иерархия ключей находится в VTL1 (secure kernel), недоступна из SYSTEM в VTL0 |
Контейнер NgcIso (NgcIsoHeader_… в Container.json / блобы-протекторы NgcIsoCtnr.dll, загруженные в NgcSvc) | Секрет PIN распаковывается внутри trustlet, а не в обычном пользовательском режиме |
Мастер-материал, запечатанный TPM (srk в Container.json, тип ключа impl) | На диске нет открытого ключа, распаковка требует TPM |
Анти-подбор (softLockoutAttemptLimit = 5, sealedLockoutAuth в Protectors.json) | Онлайн-угадывание приводит к блокировке, счётчик контролируется VTL1 и не сбрасывается с диска |
На экране блокировки / входа PIN вводится в поставщик учётных данных NGC, размещённый в
LogonUI.exe (работает как SYSTEM, не PPL, запускается по требованию). Поставщик
передаёт введённый PIN в ngckeyenum.dll!NgcSerializeConvenienceCredential, чей arg1 (RCX) указывает на:
struct { // arg1
uint64_t type; // 1 (convenience-PIN credential)
void* buffer; // -> ASCII PIN, e.g. 0x32 0x30 0x30 0x32 = "2002" example PIN
uint32_t length; // digit count (e.g. 4)
};
// arg2 = full NGC key name "S-1-5-...-1001/<container-guid>//<key-guid>"
// arg3 = username, arg4 = computername, arg5 = SID, arg6 = "Negotiate"
Классическое перехватывание методом inline/trampoline функции NgcSerializeConvenienceCredential (или
NgcPackAuthBuffer) повреждает сериализованные учётные данные для входа. Симптом неприятный и
незаметный: экран входа просто перезагружается без ошибки, правильный PIN-код отклоняется, а
повторные отказы блокируют PIN-код (вынуждая входить по паролю). Принудительное завершение NgcSvc
аналогичным образом нарушает работу контейнера.
Нам нужен примитив захвата, который читает аргумент не изменяя ни единого байта кода или потока управления целевой функции.
ngccap.dll (см. src/ngccap.c) внедряется в LogonUI.exe и:
Устанавливает аппаратную точку останова на выполнение. Она разрешает
ngckeyenum!NgcSerializeConvenienceCredential, затем задаёт DR0 = target и
DR7 = L0 | exec | len-1 для каждого потока процесса (через
CreateToolhelp32Snapshot → OpenThread → SuspendThread → GetThreadContext /
SetThreadContext с CONTEXT_DEBUG_REGISTERS → ResumeThread).
Обрабатывает ловушку в Vectored Exception Handler. При входе в функцию процессор
вызывает EXCEPTION_SINGLE_STEP до выполнения инструкции. VEH проверяет
ContextRecord->Rip == target, читает Rcx → {buffer, length}, копирует ASCII PIN-код,
затем:
EFlags, чтобы точка останова не срабатывала сразу повторно, иEXCEPTION_CONTINUE_EXECUTION.Исходная инструкция выполняется без изменений. Поскольку код не был пропатчен и не был
введён трамплин/дополнительный стековый кадр, NgcSerializeConvenienceCredential выполняется
в точности так, как выполнялась бы без вмешательства. Учётные данные формируются корректно, LSA их принимает, и
вход по PIN-коду проходит нормально.
Самоочищается. После первого захвата DLL очищает DR0/DR7 во всех потоках и удаляет VEH, оставляя процесс в исходном состоянии.
WHFB-PIN-Capture/
├─ README.md
├─ Get-WHFBKeys.ps1 # WHFB/NGC/TPM enumerator + key-material harvester
└─ src/
├─ ngccap.c # HW-breakpoint capture DLL
├─ build.bat # cl.exe build (MSVC 2022 Build Tools)
└─ logonui_watch.ps1 # injects ngccap.dll into LogonUI on sight
SeDebugPrivilege). LogonUI работает как
SYSTEM, но не является PPL, поэтому токен администратора с повышенными правами может открыть и внедрить его.:: 1) build the capture DLL
cd src
build.bat
:: 2) start the injector (elevated admin PowerShell)
powershell -ExecutionPolicy Bypass -File .\logonui_watch.ps1
3) Lock the workstation (Win+L) and sign back in with your PIN, it works normally.
4) Read the captured PIN:
type C:\Windows\Temp\pin_capture.log
-> >>> PIN CAPTURED = "1234"
Пути настраиваются: LOGPATH в начале ngccap.c и $plog в logonui_watch.ps1
должны совпадать (по умолчанию C:\Windows\Temp\pin_capture.log).
Get-WHFBKeys.ps1 сообщает статус TPM, присоединение к устройству / подготовку NGC, политику WHFB,
хранилище контейнеров NGC на диске (контейнеры → протекторы → ключи, с самоповышением до SYSTEM), ключи NCrypt
(Passport KSP / Platform Crypto Provider / Software KSP с экспортом открытого ключа) и регистрацию биометрии,
выводя отчёт в формате JSON. Он никогда не извлекает закрытые ключи (запечатанные TPM, неэкспортируемые);
он выгружает открытые ключи, метаданные и блобы протекторов на диске.
powershell -ExecutionPolicy Bypass -File .\Get-WHFBKeys.ps1 -DumpBlobs -OutDir .\out
офлайн- и brute-force-пути показаны закрытыми, а PIN перехвачен в момент, когда владелец вошёл обычным образом с правильным PIN, с помощью DLL с аппаратной точкой останова.
Только для образовательного использования и авторизованных исследований в области безопасности. Без гарантий.