
Erfasst Windows Hello PINs bei der Eingabe durch Injektion einer DLL in LogonUI.exe, die einen CPU-Hardware-Breakpoint auf NgcSerializeConvenienceCredential setzt, plus einen WHFB/NGC/TPM-Enumerator.
Auf einem Host, auf dem Windows Hello durch VBS/VSM (einen isolierten NgcIso-Container in VTL1) + ein TPM geschützt ist, ist die PIN nicht offline wiederherstellbar, und Online-Brute-Force wird durch den Secure-Kernel-Trustlet blockiert. Der einzige zuverlässige Weg ist das Abfangen bei der Eingabe.
Dieses Projekt fängt die PIN ab, indem es einen CPU-Hardware-Breakpoint auf die Funktion setzt, die
die eingegebene PIN innerhalb von LogonUI.exe verpackt, das Argument aus dem Kontext des abgefangenen Threads
in einem Vectored Exception Handler ausliest und die ursprüngliche, unveränderte Anweisung ausführen lässt,
sodass die PIN-Anmeldung weiterhin normal erfolgreich ist.
Es enthält außerdem Get-WHFBKeys.ps1, einen vollständigen WHFB/NGC/TPM-Enumerator und Sammler für Schlüsselmaterial.
End-to-End validiert auf Windows 11 26200 (Home) mit einem Intel fTPM 2.0 und laufendem VBS.
| Beobachteter Schutz | Konsequenz |
|---|---|
VBS / VSM läuft (Win32_DeviceGuard VBS-Status = 2, LsaIso.exe vorhanden) | Die Schlüsselhierarchie liegt in VTL1 (Secure Kernel), unerreichbar aus SYSTEM in VTL0 |
NgcIso-Container (NgcIsoHeader_… in Container.json / Protector-Blobs NgcIsoCtnr.dll in NgcSvc geladen) | Das PIN-Geheimnis wird innerhalb des Trustlets entsiegelt, nicht im normalen User-Mode |
TPM-versiegeltes Master-Material (srk in Container.json, Schlüsseltyp impl) | Kein Klartextschlüssel auf der Festplatte, Entsiegeln erfordert das TPM |
Anti-Hammering (softLockoutAttemptLimit = 5, sealedLockoutAuth in Protectors.json) | Online-Raten führt zur Sperrung, der Zähler wird von VTL1 durchgesetzt und ist nicht von der Festplatte aus zurücksetzbar |
Am Sperrbildschirm / bei der Anmeldung wird die PIN in den NGC-Credential-Provider eingegeben, der von
LogonUI.exe gehostet wird (läuft als SYSTEM, kein PPL, wird bei Bedarf gestartet). Der Provider übergibt die
eingegebene PIN an ngckeyenum.dll!NgcSerializeConvenienceCredential, dessen arg1 (RCX) auf Folgendes zeigt:
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"
Klassisches Inline-/Trampoline-Hooking von NgcSerializeConvenienceCredential (oder von
NgcPackAuthBuffer) beschädigt die serialisierte Anmeldeinformation. Das Symptom ist unangenehm und
still: Der Anmeldebildschirm lädt sich einfach ohne Fehler neu, die korrekte PIN wird abgelehnt, und
wiederholte Ablehnungen sperren die PIN vorübergehend (was die Anmeldung mit Passwort erzwingt). Das gewaltsame Beenden von NgcSvc
stört den Container in ähnlicher Weise.
Wir benötigen eine Capture-Primitive, die das Argument liest, ohne ein einziges Byte des Codes oder den Kontrollfluss der Zielfunktion zu verändern.
ngccap.dll (siehe src/ngccap.c) wird in LogonUI.exe injiziert und:
Setzt einen CPU-Hardware-Ausführungs-Breakpoint. Sie löst
ngckeyenum!NgcSerializeConvenienceCredential auf und setzt dann DR0 = Ziel und
DR7 = L0 | exec | len-1 auf jedem Thread des Prozesses (über
CreateToolhelp32Snapshot → OpenThread → SuspendThread → GetThreadContext /
SetThreadContext mit CONTEXT_DEBUG_REGISTERS → ResumeThread).
Behandelt die Trap in einem Vectored Exception Handler. Wenn die Funktion betreten wird, löst die CPU
EXCEPTION_SINGLE_STEP vor der Ausführung der Instruktion aus. Der VEH prüft
ContextRecord->Rip == target, liest Rcx → {buffer, length}, kopiert die ASCII-PIN heraus
und:
EFlags, damit der Breakpoint nicht sofort erneut auslöst, undEXCEPTION_CONTINUE_EXECUTION zurück.Die ursprüngliche Instruktion läuft unverändert ab. Da kein Code gepatcht und kein
Trampoline/zusätzlicher Stack-Frame eingeführt wurde, wird NgcSerializeConvenienceCredential genau so ausgeführt,
wie es das sonst getan hätte. Die Anmeldeinformation wird korrekt erstellt, LSA akzeptiert sie, und
die PIN-Anmeldung gelingt normal.
Räumt sich selbst auf. Nach dem ersten Capture löscht die DLL DR0/DR7 auf allen Threads und entfernt den VEH, wodurch der Prozess unberührt bleibt.
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 läuft als
SYSTEM, ist aber nicht PPL, sodass ein erhöhtes Admin-Token es öffnen und injizieren kann.:: 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"
Pfade sind konfigurierbar: LOGPATH am Anfang von ngccap.c und $plog in logonui_watch.ps1
müssen übereinstimmen (Standard C:\Windows\Temp\pin_capture.log).
Get-WHFBKeys.ps1 meldet den TPM-Status, Device-Join / NGC-Bereitstellung, WHFB-Richtlinie, den
NGC-Container-Speicher auf der Festplatte (Container → Protektoren → Schlüssel, mit Selbst-Elevation zu SYSTEM), NCrypt-Schlüssel
(Passport KSP / Platform Crypto Provider / Software KSP mit Export des öffentlichen Schlüssels) und die biometrische
Registrierung und gibt dabei einen JSON-Bericht aus. Es extrahiert niemals private Schlüssel (TPM-versiegelt, nicht exportierbar);
es gibt öffentliche Schlüssel, Metadaten und Protektor-Blobs auf der Festplatte aus.
powershell -ExecutionPolicy Bypass -File .\Get-WHFBKeys.ps1 -DumpBlobs -OutDir .\out
offline- und Brute-Force-Pfade als geschlossen erwiesen, und die PIN wurde erfasst, während der Besitzer sich normal mit der korrekten PIN anmeldete, unter Verwendung der Hardware-Breakpoint-DLL.
Nur für Bildungszwecke und autorisierte Sicherheitsforschung. Keine Gewährleistung.