在 Windows Hello 受 VBS/VSM(VTL1 中的隔离 NgcIso 容器)+ TPM 保护的宿主机上,PIN 无法离线恢复,且在线暴力破解会被安全内核信任小程序(trustlet)阻止。唯一可靠的途径是在输入时捕获。
本项目通过在 LogonUI.exe 中负责打包所输入 PIN 的函数上设置 CPU 硬件断点来捕获 PIN,在向量化异常处理程序(Vectored Exception Handler)中从被捕获的线程上下文读取参数,并让原始、未经修改的指令继续执行,因此 PIN 登录仍能正常成功。
它还附带 Get-WHFBKeys.ps1,一个完整的 WHFB/NGC/TPM 枚举与密钥材料收集脚本。
已在 Windows 11 26200(家庭版)、Intel fTPM 2.0 且 VBS 运行的环境下完成端到端验证。
| 观察到的保护 | 后果 |
|---|---|
VBS / VSM 正在运行(Win32_DeviceGuard VBS 状态 = 2,存在 LsaIso.exe) | 密钥层级位于 VTL1(安全内核),VTL0 中的 SYSTEM 无法访问 |
NgcIso 容器(Container.json 中的 NgcIsoHeader_… / 在 NgcSvc 中加载的保护程序 blob NgcIsoCtnr.dll) | PIN 密钥在信任小程序内部解封,而非在普通用户模式中 |
TPM 密封的主材料(Container.json 中的 srk,impl 密钥类型) | 磁盘上无明文密钥,解封需要 TPM |
防暴力破解(Protectors.json 中的 softLockoutAttemptLimit = 5、sealedLockoutAuth) | 在线猜测会触发锁定,计数器由 VTL1 强制执行,无法从磁盘重置 |
在锁屏/登录界面,PIN 被输入到由 LogonUI.exe 托管的 NGC 凭据提供程序(以 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"
对 NgcSerializeConvenienceCredential(或 NgcPackAuthBuffer)进行经典的内联/跳板 Hook 会破坏序列化后的登录凭据。其症状隐蔽而棘手:登录界面只是重新加载且不显示任何错误,正确的 PIN 被拒绝,而反复拒绝会软锁定 PIN(迫使用户改用密码登录)。强制终止 NgcSvc 同样会破坏容器。
我们需要一种捕获原语,它能在不修改目标函数任何一个字节的代码、也不改变其控制流的前提下读取参数。
ngccap.dll(见 src/ngccap.c)被注入到 LogonUI.exe 中,并执行以下操作:
设置一个 CPU 硬件执行断点。 它解析
ngckeyenum!NgcSerializeConvenienceCredential,然后在进程的每一个线程上设置 DR0 = 目标地址 和
DR7 = L0 | exec | len-1(通过
CreateToolhelp32Snapshot → OpenThread → SuspendThread → GetThreadContext /
SetThreadContext 配合 CONTEXT_DEBUG_REGISTERS → ResumeThread)。
在向量化异常处理器(VEH)中处理陷阱。 当进入该函数时,CPU 会在执行指令之前引发 EXCEPTION_SINGLE_STEP。VEH 检查
ContextRecord->Rip == target,读取 Rcx → {buffer, length},复制出 ASCII PIN,然后:
EFlags 中设置恢复标志(RF),使断点不会立即再次触发,并且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"
路径可配置:ngccap.c 顶部的 LOGPATH 与 logonui_watch.ps1 中的 $plog 必须一致(默认 C:\Windows\Temp\pin_capture.log)。
Get-WHFBKeys.ps1 会报告 TPM 状态、设备加入 / NGC 预配、WHFB 策略、磁盘上的 NGC 容器存储(容器 → 保护器 → 密钥,自动提升至 SYSTEM)、NCrypt 密钥(Passport KSP / Platform Crypto Provider / Software KSP,含公钥导出)以及生物识别注册情况,并输出 JSON 报告。它从不提取私钥(TPM 密封、不可导出);它导出公钥、元数据和磁盘上的保护器 blob。
powershell -ExecutionPolicy Bypass -File .\Get-WHFBKeys.ps1 -DumpBlobs -OutDir .\out
离线路径和暴力破解路径均已被证明不可行,并且 PIN 是在所有者使用正确 PIN 正常登录时通过硬件断点 DLL 捕获的。
仅供教育和经授权的安全研究使用。不提供任何担保。