自定义Windows身份验证包触发“远程过程调用失败”登录错误排查
自定义Windows身份验证包“远程过程调用失败”问题排查与解决
问题背景
开发自定义Windows身份验证包时,在LsaApLogonUserEx2函数中调用外部DLL或REST API时触发以下问题:
- 登录界面弹出“远程过程调用失败”错误,随后机器短时间内自动关机
- 日志显示执行流程在调用DLL函数前终止,无异常记录
- 替换为存根代码(如
std::this_thread::sleep_for(std::chrono::seconds(10)))时,登录流程可正常完成 - 核心需求:必须在身份验证包内通过外部DLL从外部资源获取用户密码,无法改用凭据提供方案
相关代码实现
typedef int(__stdcall* ExportHelperFnType)(); int ExportHelperFunc() { HMODULE helperDll = LoadLibraryW(L"path to dll.dll"); if (helperDll) { TRACE("function loaded"); ExportHelperFnType ExportHelperFn = (ExportHelperFnType)GetProcAddress(helperDll, "ExportHelperFn"); TRACE("function loaded: %p", ExportHelperFn); // 函数地址已成功加载,但日志到此终止,无异常捕获 try { int rs = ExportHelperFn(); return rs; } catch (...) { TRACE("error"); } TRACE("done ExportHelperFn with error"); } return STATUS_SUCCESS; } extern "C" NTSTATUS NTAPI LsaApLogonUserEx2_Custom(IN PLSA_CLIENT_REQUEST ClientRequest, IN SECURITY_LOGON_TYPE LogonType, IN PVOID ProtocolSubmitBuffer, IN PVOID ClientBufferBase, IN ULONG SubmitBufferSize, OUT PVOID* ProfileBuffer, OUT PULONG ProfileBufferSize, OUT PLUID LogonId, OUT PNTSTATUS SubStatus, OUT PLSA_TOKEN_INFORMATION_TYPE TokenInformationType, OUT PVOID* TokenInformation, OUT PUNICODE_STRING* AccountName, OUT PUNICODE_STRING* AuthenticatingAuthority, OUT PUNICODE_STRING* MachineName, OUT PSECPKG_PRIMARY_CRED PrimaryCredentials, /* Not supported yet */ OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY* SupplementalCredentials) { NTSTATUS result = STATUS_SUCCESS; try { // 此处调用出现问题 result = ExportHelperFunc(); } catch (...) { TRACE("Error ExportHelperFunc: %d", result); } // 调用MSV1_0原生登录函数 result = MSV1_0LsaApLogonUserEx2(ClientRequest, LogonType, ProtocolSubmitBuffer, ClientBufferBase, SubmitBufferSize, ProfileBuffer, ProfileBufferSize, LogonId, SubStatus, TokenInformationType, TokenInformation, AccountName, AuthenticatingAuthority, MachineName, PrimaryCredentials, SupplementalCredentials); return result; }
错误原因分析
LSA运行上下文的严格限制
LSA(本地安全授权)服务运行在会话0的系统级上下文,该环境:- 无法访问用户会话专属资源(如桌面、用户注册表项)
- 网络访问受系统级防火墙/代理规则约束,用户态网络组件无法正常初始化
- 线程属于关键系统线程,长时间阻塞或触发异常会触发系统看门狗机制,直接导致系统重启
外部调用的依赖兼容性问题
- 外部DLL可能依赖仅在用户会话中存在的库或资源,加载后执行时触发内核级异常(无法被C++
catch(...)捕获) - cpr等第三方REST库依赖用户态网络配置,在会话0中无法完成初始化,导致调用挂起或崩溃
- 外部DLL可能依赖仅在用户会话中存在的库或资源,加载后执行时触发内核级异常(无法被C++
未处理异常触发系统保护机制
LSA是核心系统服务,其线程出现无响应或未捕获异常时,系统会启动故障恢复流程(自动重启)。C++的异常捕获无法覆盖内核级错误(如栈溢出、资源访问违规),因此日志无法记录异常细节。
可行解决方案
1. 适配会话0的外部DLL改造
- 确保外部DLL仅依赖系统级库(如kernel32.dll、advapi32.dll),移除用户态组件依赖
- 所有资源访问使用系统级路径(如
C:\Windows\System32),避免用户目录 - 彻底移除DLL中的GUI相关代码,会话0无桌面环境支持
2. 使用LSA兼容的网络API
- 替换cpr库,改用WinHTTP的系统级调用,确保在会话0中正常工作:
HINTERNET hSession = WinHttpOpen(L"CustomAuthPackage/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (hSession) { // 后续WinHttpConnect、WinHttpOpenRequest等调用 }
3. 异步调用避免阻塞LSA线程
- 将外部调用改为异步模式,防止阻塞关键系统线程:
- 在
LsaApLogonUserEx2_Custom中启动独立系统线程执行密码获取逻辑 - 线程完成后通过
LsaCallAuthenticationPackage回调通知身份验证流程继续 - 需设置合理超时,避免触发系统看门狗
- 在
4. 强化调试与日志
- 改用Windows事件日志替代自定义TRACE,会话0中TRACE输出可能无法被捕获
- 启用内核调试,通过WinDbg捕获LSA服务崩溃dump,定位具体异常原因
- 在DLL的
DllMain入口添加日志,确认DLL初始化状态
5. 密码预获取缓存方案
- 若允许,通过独立系统服务在系统启动时预获取用户密码,缓存到LSA秘密等安全存储中,身份验证包直接读取缓存,避免登录流程中发起外部调用
内容的提问来源于stack exchange,提问作者dtkien
相关产品推荐
相关产品推荐

