
Captura PINs do Windows Hello na entrada injetando uma DLL no LogonUI.exe que define um breakpoint de hardware da CPU em NgcSerializeConvenienceCredential, além de um enumerador WHFB/NGC/TPM.
Em um host onde o Windows Hello é protegido por VBS/VSM (um contêiner NgcIso isolado em VTL1) + um TPM, o PIN não é recuperável offline e o brute-force online é bloqueado pelo trustlet do kernel seguro. A única rota confiável é a captura na entrada.
Este projeto captura o PIN colocando um breakpoint de hardware de CPU na função que
empacota o PIN digitado dentro do LogonUI.exe, lendo o argumento a partir do contexto da thread interceptada em um Vectored Exception Handler, e deixando a instrução original e não modificada executar, de modo que o login por PIN ainda seja bem-sucedido normalmente.
Ele também inclui Get-WHFBKeys.ps1, um enumerador completo de WHFB/NGC/TPM e coletor de material de chave.
Validado de ponta a ponta no Windows 11 26200 (Home) com um Intel fTPM 2.0 e VBS em execução.
| Proteção observada | Consequência |
|---|---|
VBS / VSM em execução (status VBS do Win32_DeviceGuard = 2, LsaIso.exe presente) | A hierarquia de chaves reside em VTL1 (kernel seguro), inacessível a partir do SYSTEM em VTL0 |
Contêiner NgcIso (NgcIsoHeader_… em Container.json / blobs de protetor NgcIsoCtnr.dll carregados em NgcSvc) | O segredo do PIN é deslacrado dentro do trustlet, não no modo de usuário normal |
Material mestre lacrado pelo TPM (srk em Container.json, tipo de chave impl) | Nenhuma chave em texto claro no disco, o deslacramento requer o TPM |
Anti-hammering (softLockoutAttemptLimit = 5, sealedLockoutAuth em Protectors.json) | A adivinhação online bloqueia, o contador é imposto pelo VTL1, não pode ser redefinido a partir do disco |
Na tela de bloqueio / entrada, o PIN é digitado no provedor de credenciais NGC hospedado pelo
LogonUI.exe (executa como SYSTEM, não um PPL, iniciado sob demanda). O provedor entrega o
PIN digitado para ngckeyenum.dll!NgcSerializeConvenienceCredential, cujo arg1 (RCX) aponta para:
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"
O hooking clássico inline/trampoline de NgcSerializeConvenienceCredential (ou de
NgcPackAuthBuffer) corrompe a credencial de logon serializada. O sintoma é desagradável e
silencioso: a tela de login simplesmente recarrega sem erro, o PIN correto é rejeitado, e
rejeições repetidas bloqueiam o PIN (forçando o login por senha). Matar à força o NgcSvc
também desorganiza o contêiner.
Precisamos de uma primitiva de captura que leia o argumento sem alterar um único byte de código ou o fluxo de controle da função alvo.
ngccap.dll (veja src/ngccap.c) é injetada no LogonUI.exe e:
Arma um breakpoint de execução de hardware da CPU. Ela resolve
ngckeyenum!NgcSerializeConvenienceCredential, então define DR0 = alvo e
DR7 = L0 | exec | len-1 em todas as threads do processo (via
CreateToolhelp32Snapshot → OpenThread → SuspendThread → GetThreadContext /
SetThreadContext com CONTEXT_DEBUG_REGISTERS → ResumeThread).
Trata a armadilha em um Vectored Exception Handler. Quando a função é chamada, a CPU
levanta EXCEPTION_SINGLE_STEP antes de executar a instrução. O VEH verifica
ContextRecord->Rip == target, lê Rcx → {buffer, length}, copia o PIN ASCII,
então:
EFlags para que o breakpoint não dispare novamente de imediato, eEXCEPTION_CONTINUE_EXECUTION.A instrução original é executada, sem modificações. Como nenhum código foi corrigido e nenhum
trampoline/quadro de pilha extra foi introduzido, NgcSerializeConvenienceCredential é executada
exatamente como teria sido. A credencial é construída corretamente, o LSA a aceita, e o
login por PIN é bem-sucedido normalmente.
Autolimpa-se. Após a primeira captura, a DLL limpa DR0/DR7 em todas as threads e remove o VEH, deixando o processo intacto.
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). O LogonUI é executado como
SYSTEM, mas não é PPL, portanto um token de administrador elevado pode abri-lo e injetar nele.:: 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"
Os caminhos são configuráveis: LOGPATH no topo de ngccap.c e $plog em logonui_watch.ps1
devem corresponder (padrão C:\Windows\Temp\pin_capture.log).
Get-WHFBKeys.ps1 reporta o status do TPM, o ingresso no domínio / provisionamento NGC, a política WHFB, o armazenamento
de contêineres NGC em disco (contêineres → protetores → chaves, auto-elevando para SYSTEM), chaves NCrypt
(Passport KSP / Platform Crypto Provider / Software KSP com exportação de chave pública) e registro
biométrico, emitindo um relatório JSON. Ele nunca extrai chaves privadas (seladas pelo TPM, não exportáveis);
ele despeja chaves públicas, metadados e blobs de protetores em disco.
powershell -ExecutionPolicy Bypass -File .\Get-WHFBKeys.ps1 -DumpBlobs -OutDir .\out
offline e caminhos de força bruta demonstrados como fechados, e o PIN capturado enquanto o proprietário iniciava sessão normalmente com o PIN correto usando a DLL de breakpoint de hardware.
Apenas para uso educacional e de pesquisa de segurança autorizada. Sem garantia.