应用程序验证工具 - 应用程序验证工具中的测试

基础知识

至少应在选择“基本信息”设置的情况下运行应用程序验证工具。 其中每一个都会测试会导致崩溃或其他负面情况的区域,这些方案对客户体验产生直接和有显著影响。

  • 异常 - 确保应用程序不使用结构化异常处理隐藏访问冲突
  • 句柄 - 测试以确保应用程序未尝试使用无效句柄
  • - 检查堆中的内存损坏问题
  • 泄漏 - 通过跟踪 dll 在卸载 dll 时未释放的资源来检测泄漏
  • 锁 - 验证关键部分的正确用法
  • 内存 - 确保正确使用虚拟空间操作的 API(例如 VirtualAlloc、MapViewOfFile)
  • SRWLock - 验证精简读取器/写入器(SRW)锁的正确用法。
  • Threadpool - 确保在回调后(如脏线程池线程和其他线程池相关问题)后正确使用线程池 API,并在工作线程状态上强制实施一致性检查。
  • TLS - 确保正确使用线程本地存储 API

有关这些测试生成的停止代码异常的信息,请参阅 应用程序验证程序 - 停止代码和定义。 有关调试这些失败的信息,请参阅 应用程序验证程序 - 调试应用程序验证程序停止

兼容性

兼容性验证层测试有助于识别可能存在 Microsoft Windows 操作系统问题的应用程序。 其中许多检查还可用于测试徽标/认证要求。

有关这些测试生成的停止代码异常的信息,请参阅 应用程序验证程序 - 停止代码和定义

HighVersionLie - 识别 Windows 中一些最常见的应用程序兼容性问题的问题。 错误地检测操作系统的版本或使用硬编码的版本信息可能会导致应用程序在以后的操作系统上失败。

克鲁兹

并发模糊(Cuzz)验证层检测并发 bug 和数据争用条件。 Cuzz 通过在应用程序代码中的关键点注入随机延迟来调整线程计划。 以下方案演示了 Cuzz 验证层可以检测到的并发 bug 的类型。

应用程序具有父线程和子线程。 父线程启动子线程,然后为结构分配内存。

// Parent Thread
StartChildThread(...);
g_pointer = ... malloc(...);

子线程取消引用指针。

//Child Thread
LONG value = g_pointer->someMember;

上述代码具有并发 bug。 如果子线程尝试在父线程分配内存之前取消引用指针,则指针将无效。 bug 不太可能自行显示,因为在大多数情况下,父线程将在子线程启动之前分配内存。 但在极少数情况下,子线程可以开始并尝试在父线程分配内存之前取消引用指针。

Cuzz 验证层增加了查找并发 bug 的可能性,如前面的示例中所示的 bug。 除了插入延迟之外,Cuzz 不会执行任何其他检查。 因此,没有与 Cuzz 直接关联的验证停止。 但是,如果启用 Cuzz 会导致并发 bug 本身出现,其他验证层将受益。 例如,如果争用条件导致堆溢出,则堆验证层将找不到错误,除非争用条件在运行时自行显示。 通过增加发生争用条件的概率,Cuzz 提高了堆层在识别错误时的有效性。

若要获得 Cuzz 的最大优势,请对尽可能多的测试启用 Cuzz,并多次重复相同的测试。 可以在所有测试上启用 Cuzz,包括手动测试、功能测试和压力测试。 此外,请尽可能多地启用应用程序验证程序验证层。

可以通过向 Cuzz 提供相同的随机种子来增加重现 bug 的可能性(请参阅属性)。

Cuzz 仅在 Win32 同步 API 调用上插入延迟。

Cuzz 属性

以下属性可用于 Cuzz 验证层。 若要设置属性,请在应用程序验证程序用户界面中选择 Cuzz 层,然后打开“属性”窗口。

properties 说明
FuzzingLevel 控制 Cuzz 的模糊级别。 对于时间关键型应用程序,将此值设置为 1,常规应用程序将设置为 4。
RandomSeed 库兹一开始使用的随机种子。 如果将此项设置为 0,Cuzz 将生成基于时间的随机种子。

低资源模拟

低资源模拟尝试在低资源(例如内存不足)下模拟环境。 此模拟将识别内存不足时发生的 bug。 这也称为故障注入。 可以在低资源下模拟环境,在该环境中可以定义指示故障概率调用的数字(0-100):

  • 等待(例如,WaitForXXXX API)。
  • Heap_Alloc(堆分配 API)。
  • Virtual_Alloc(虚拟内存分配 API)。
  • 注册表(注册表 API)。
  • 文件 (文件 API,如 CreateFile)。
  • 事件(事件 API,如 CreateEvent)。
  • MapView (MapView API,如 CreateMapView)。
  • Ole_Alloc (Ole API,如 SysAllocString)。

低资源模拟(也称为故障注入)尝试在低资源下模拟环境,例如内存不足。 这将识别内存不足条件下的 bug。

低资源模拟属性

若要编辑属性,请在“测试”区域中检查低资源模拟检查框,右键单击并选择属性:

properties 说明
包括 限制错误仅在指定的 dll 中发生。 每个行没有路径的一个 dll 名称。 如果指定了“*”,则所有模块都会发生错误。
Exclude 排除指定模块的错误。 每个行没有路径的一个 dll 名称。
超时 在进程初始化时没有故障时,请提供一个时间段(以毫秒为单位)。
Wait 指示 WaitForXXXX API 的错误概率的数字 [0 – 1000000]。
Heap_Alloc 指示堆分配 API 的错误概率的数字 [0 – 1000000]。
Virtual_Alloc 一个数字 [0 – 1000000] ,指示虚拟内存分配 API 的错误概率。
注册表 指示注册表 API 的错误概率的数字 [0 – 1000000]。
文件 一个数字 [0 – 10000000] ,指示文件 API(如 CreateFile)的错误概率。
活动 一个数字 [0 – 1000000] ,指示事件 API(如 CreateEvent)的错误概率
MapView 一个数字 [0 – 1000000] ,指示 MapView API(如 CreateMapView)的错误概率。
Ole_Alloc 一个数字 [0 – 10000000] ,指示 Ole API(如 SysAllocString)的错误概率。
堆栈 每个 Windows 应用程序线程都以堆栈保留和堆栈提交大小开头。 在正常使用下,每当堆栈上需要更多的空间时,堆栈提交就会增长。 有关详细信息,请参阅 创建线程线程堆栈大小 。 如果系统内存不足,堆栈提交增长可能会失败。 无法扩展其堆栈且整个应用程序的线程很可能崩溃。 对于重要的系统进程(例如服务),这种崩溃是不可接受的。 Stacks 检查将禁用正在验证的应用程序的任何堆栈增长,因此它将模拟堆栈增长失败,而无需模拟整个系统内存不足的情况。 当应用程序尝试扩展堆栈时,将引发异常。 不会生成验证程序停止。

LuaPriv

受限的用户帐户特权预测器(LuaPriv)测试既是预测测试,也是诊断测试,用于显示与使用管理权限运行应用程序相关的问题,以及如果以较少的权限运行应用程序(通常为普通用户),该应用程序是否也适用。

也称为 UAC 检查,有限用户帐户特权预测器(LuaPriv)有两个主要目标:

  • 预测:使用管理特权运行应用程序时,预测当使用较低特权(通常以普通用户身份)运行时应用程序是否将正常运行。 例如,如果应用程序向仅允许管理员访问的文件写入数据,则当以非管理员身份运行时,应用程序将无法向同一文件写入数据。

  • 诊断:使用非管理员特权运行时,识别当前运行可能已存在的潜在问题。 继续前面的示例,如果应用程序尝试向仅允许管理员组成员访问的文件写入数据,则应用程序将收到 ACCESS_DENIED 错误。 如果应用程序无法正常运行,则此操作可能是罪魁祸首。

LuaPriv 识别以下类型的问题:

潜在问题 描述
受限命名空间 在没有命名空间的情况下创建命名的同步对象(事件、信号灯、互斥等)可能会使在某些操作系统上没有特权的情况下运行变得复杂,因为操作系统可能会选择将对象放置在受限的命名空间中。 在受限命名空间(例如全局命名空间)中创建此类对象需要 SeCreateGlobalPrivilege,该特权仅授予给管理员。
如果 LuaPriv 检测到这两个问题,则会标记它们。
硬管理员检查 某些应用程序询问用户的安全令牌,以了解他/她拥有多少特权。 在这些情况下,应用程序可能会根据它认为用户拥有的能力来更改其行为。
LuaPriv 对返回此信息的 API 调用进行标记。
请求特权 在执行需要安全相关特权(例如 SeTcbPrivilege 或 SeSecurityPrivilege)的操作之前,应用程序可能会尝试启用该特权。
LuaPriv 会对启用安全相关特权的尝试进行标记。
缺少特权 如果应用程序尝试启用用户没有的特权,则可能表明该应用程序需要特权,这可能会导致行为差异。
LuaPriv 会对失败的特权请求进行标记。
INI 文件操作 尝试以非管理员用户身份写入到映射的 INI 文件(WritePrivateProfileSection 和类似 API)可能会失败。
LuaPriv 会对此类操作进行标记。
拒绝访问 如果应用程序尝试访问对象(文件、注册表项等),但尝试由于访问权限不足而失败,则应用程序可能应当以比它拥有的特权更多的特权运行。
LuaPriv 会对失败且出现 ACCESS_DENIED 和类似错误的对象打开尝试进行标记。
拒绝 ACE 如果对象在其 DACL 中具有拒绝 ACE,则它会显式拒绝对特定实体的访问。
这不常见,并且会使预测变得困难,因此 LuaPriv 在发现拒绝 ACE 时会对它们进行标记。
访问受限 如果应用程序尝试打开未授予普通用户权限的对象(例如,尝试写入到只能由管理员写入的文件),则该应用程序在以普通用户身份运行时可能无法正常工作。
LuaPriv 会对此类操作进行标记。
MAXIMUM_ALLOWED 如果应用程序打开 MAXIMUM_ALLOWED 的某个对象,则在其他位置将执行对该对象的实际访问检查。 执行此操作的大多数代码无法正常工作,而且在不使用特权的情况下运行时几乎肯定会以不同的方式工作。
因此,LuaPriv 会对 MAXIMUM_ALLOWED 的所有事件进行标记。

杂项

杂项测试中捕获了通常被忽视的问题。

  • 危险 API - 跟踪应用程序是否使用以下不安全操作:
    • 对 TerminateThread 的危险调用。
    • 内存不足的情况下的潜在堆栈溢出。
    • 在多个线程仍在运行时调用的退出进程。
    • 在 DllMain 期间调用 LoadLibrary。
    • 在 DllMain 期间调用 FreeLibrary。
  • 脏堆栈使用内存模式填充堆栈未使用的部分(定期)。 这有助于检测该线程上下文中将来函数调用中未初始化的变量。
  • TimeRollOver 强制 GetTickCount 和 TimeGetTime API 滚动更新的速度比平时要快。 这样,应用程序就可以更轻松地测试其处理时间滚动更新。

杂项属性

危险 API 检查有一个可以更改的属性:

DllMainCheck - 在 DllMain 处于活动状态时检查 LoadLibrary/FreeLibrary 调用。

网络

网络测试查找 WinSock API 的不当使用。 例如,如果在成功 WSAStartup() 之前调用网络 API,或者在成功调用 WSACleanup() 调用后调用。 有关 WinSock 的详细信息,请参阅 winsock.h 标头Windows 套接字 2

属性

以下属性可用于 Net 验证层。 若要设置属性,请在应用程序验证程序用户界面中选择网络提供程序,然后打开“属性”窗口。

properties 说明
FragmentsEnabled 启用 TCP IPv4 和 IPv6 套接字收到的数据流碎片。
FragmentSize 指定任何 Winsock 接收 API 调用的缓冲区中返回的最大字节数。

FragmentsEnabled 属性使网络验证程序提供程序中的功能能够促进应用程序在网络外分析 TCP 流的测试和验证。 启用后,对 Winsock 接收数据的所有调用将仅接收高达 FragmentSize 字节,除非应用程序在返回前需要填充整个缓冲区(由MSG_WAITALL标志控制)。 由于 TCP 协议和 Winsock 都无法保证可能返回到缓冲区中的字节数,因此启用此检查将有助于验证代码分析网络外数据流是否正确(独立于每次调用 Winsock 收到的字节数)。 流分析器中的问题一直是高调 bug 的来源,并且提供了这些属性以方便验证正确性,因为这尤其难以测试。 注意:这不会更改返回的数据 -- 它只会以特定速率减慢速度:应用程序的行为方式应与启用或禁用此方式完全相同。

下面的命令行使所有传入 TCP 流碎片化为在myApp.exe中创建的所有 TCP IPv4 和 IPv6 套接字以及myApp.exe加载的所有二进制文件。

appverif -enable Networking -for myApp.exe -with Networking.FragmentsEnabled=True Networking.FragmentSize=10

!avrf 调试器扩展

!avrf -net -socket count - 显示打开和关闭的套接字句柄计数

!avrf -net -socket dump [-v] [HANDLE] - 显示套接字句柄(s),详细或不显示。

!avrf -net -wsastacks - 显示 WSAStartup/WSACleanup 的堆栈跟踪的当前 WSA 初始化计数和时间顺序列表。

!avrf -net -wsastacks count - 显示当前的 WSA 初始化计数。

!avrf -net -socket count - 此命令将提供正在跟踪的套接字句柄总数,包括已打开和关闭。 请注意,这些跟踪在循环队列中,因此跟踪的总数有上限。 调用分配套接字句柄的 Winsock API 之一时,套接字将添加到打开的列表中。 例如,socket()、WSASocket()、accept()。 当在该套接字句柄上调用 closesocket() 函数时,套接字将从打开的列表移动到关闭列表。

!avrf -net -socket dump [-v] [HANDLE] - 此命令将枚举套接字句柄。 “-socket dump”将按其 SOCKET 值列出所有已跟踪的已打开和关闭的套接字句柄。 可选 -v 标志将在打印每个 SOCKET 值后立即打印打开或关闭调用堆栈。 可选的 HANDLE 字段将仅列出指定的 SOCKET 句柄及其打开或关闭调用堆栈。

下面是各种 -socket 用法选项的示例:

0:008> !avrf -net -socket count
Number of open socket handles   = 16
Number of closed socket handles = 12
 
0:008> !avrf -net -socket dump
CLOSED SOCKET HANDLE - 0x47c
CLOSED SOCKET HANDLE - 0x2cc
CLOSED SOCKET HANDLE - 0x8c4
CLOSED SOCKET HANDLE - 0x6bc
CLOSED SOCKET HANDLE - 0x44c
CLOSED SOCKET HANDLE - 0x578
CLOSED SOCKET HANDLE - 0x6f4
CLOSED SOCKET HANDLE - 0x5b4
CLOSED SOCKET HANDLE - 0x4d8
CLOSED SOCKET HANDLE - 0x3cc
CLOSED SOCKET HANDLE - 0x4fc
CLOSED SOCKET HANDLE - 0x4e0
OPEN SOCKET HANDLE - 0xfd4
OPEN SOCKET HANDLE - 0x7d8
OPEN SOCKET HANDLE - 0xf8c
OPEN SOCKET HANDLE - 0xf88
OPEN SOCKET HANDLE - 0xae0
OPEN SOCKET HANDLE - 0xe58
OPEN SOCKET HANDLE - 0xdfc
OPEN SOCKET HANDLE - 0xcf8
OPEN SOCKET HANDLE - 0xa18
OPEN SOCKET HANDLE - 0x7a0
OPEN SOCKET HANDLE - 0x7b0
OPEN SOCKET HANDLE - 0x534
OPEN SOCKET HANDLE - 0xcdc
OPEN SOCKET HANDLE - 0x1f0
OPEN SOCKET HANDLE - 0x444
OPEN SOCKET HANDLE - 0x8bc
 
0:008> !avrf -net -socket dump -v 0x47c
 
The socket handle is closed
 
vfNet!VfHookclosesocket
WININET!ICSocket::_UnSafeCloseSocket
WININET!ICSocket::Dereference
WININET!CFsm_GetConnection::RunSM
WININET!CFsm::Run
WININET!DoFsm
WININET!HTTP_REQUEST_HANDLE_OBJECT::OpenConnection_Fsm
WININET!CFsm_OpenConnection::RunSM
WININET!CFsm::Run
WININET!DoFsm
WININET!HTTP_REQUEST_HANDLE_OBJECT::OpenConnection
WININET!HTTP_REQUEST_HANDLE_OBJECT::MakeConnection_Fsm
WININET!CFsm_MakeConnection::RunSM
WININET!CFsm::Run
WININET!DoFsm
WININET!HTTP_REQUEST_HANDLE_OBJECT::SendRequest_Fsm
WININET!CFsm_SendRequest::RunSM
WININET!CFsm::Run
WININET!DoFsm
WININET!HTTP_REQUEST_HANDLE_OBJECT::HttpSendRequest_Start
WININET!CFsm_HttpSendRequest::RunSM
WININET!CFsm::Run
WININET!CFsm::RunWorkItem
SHLWAPI!ExecuteWorkItemThreadProc
vfbasics!AVrfpRtlWorkerCallback
ntdll!RtlpTpWorkCallback
ntdll!TppWorkerThread
kernel32!BaseThreadInitThunk
ntdll!__RtlUserThreadStart
ntdll!_RtlUserThreadStart

!avrf -net -wsastacks [count]

Winsock 要求应用程序开发人员在进行任何 Winsock 调用之前至少调用一次 WSAStartup()。 这由 Winsock 进程范围跟踪。 初始引用计数指示 Winsock 库(ws2_32.dll)初始化和加载 Winsock 目录和提供程序。 对 WSAStartup 的进一步调用会递增引用计数。 Winsock 还要求应用程序开发人员在“完成”调用 Winsock 后调用 WSACleanup()。 对 WSACleanup 的调用必须与之前对 WSAStartup()的调用正确配对。 对 WSACleanup() 的调用会减少进程范围的引用计数。 当引用计数降至零时,Winsock 会释放其资源并卸载 Winsock 目录和提供程序。

此命令将提供当前“WSAStartup”初始化例程的总体引用计数值,并列出调用调用过程中对 WSAStartup 和 WSACleanup 的调用堆栈。 请注意,这是在固定循环队列中维护的,因此不能保证它已完成 - 仅 N 个最近的调用。

下面是各种 -wsastacks 用法选项的示例:

0:008> !avrf -net -wsastacks count
 
Current WSARefCount: 1 (WSAStartup call count minus WSACleanup call count for the target process)
 
 
0:008> !avrf -net -wsastacks
 
Current WSARefCount: 1 (WSAStartup call count minus WSACleanup call count for the target process)
 
 
THREAD ID: 0xe4c called WSAStartup
vfNet!WSAInitStacks<NetAllocatorViaPrivateHeap>::AddWSAStackTrace
vfNet!VfHookWSAStartup
WININET!LoadWinsock
WININET!GlobalDataInitialize
WININET!InternetSetOptionA
WININET!InternetSetOptionW
IEFRAME!LCIEUpdateSessionStartTime
IEFRAME!LCIETab_ThreadProc
iertutil!_IsoThreadProc
vfbasics!AVrfpStandardThreadFunction
kernel32!BaseThreadInitThunk
ntdll!__RtlUserThreadStart
ntdll!_RtlUserThreadStart

NTLM

此应用程序验证程序插件监视单个进程对身份验证 API AcquireCredentialsHandle 和 InitializeSecurityContext 的调用,以检测 NTLM 协议的使用。 NTLM 是过时的身份验证协议,存在缺陷,可能会损害应用程序和操作系统的安全性,不应使用。

NTLM 身份验证风险

过时的 NTLM 身份验证协议最重要的缺点是缺少服务器身份验证,这可能导致攻击者欺骗用户连接到欺骗服务器。 作为缺少服务器身份验证的结果,使用 NTLM 的应用程序也可能容易受到称为“反射”攻击的攻击类型。 后者允许攻击者将用户的身份验证对话劫持到合法服务器,并使用它向用户的计算机对攻击者进行身份验证。 NTLM 的漏洞和利用方式是提高安全社区研究活动的目标。

尽管 Kerberos 已推出多年,但许多应用程序仍会编写为仅使用 NTLM。 这不需要减少应用程序的安全性。 但是,Kerberos 在所有方案中都不能替换 NTLM ,主要是那些客户端需要向未加入域的系统进行身份验证(家庭网络可能是其中最常见的网络)。 Negotiate 安全包允许向后兼容,只要可能,就可以使用 Kerberos,并且仅当没有其他选项时还原 NTLM。 切换代码以使用 Negotiate 而不是 NTLM 会显著增加客户的安全性,同时引入很少或没有应用程序兼容性。 协商本身不是一颗银弹 - 在某些情况下,攻击者可以强制降级到 NTLM,但这些攻击要更加困难。 但是,一个立即改进是,编写为正确使用 Negotiate 的应用程序自动不受 NTLM 反射攻击的影响。

通过对使用 NTLM 的最终警告:在 Windows 中,可以在操作系统级别禁用 NTLM 的使用。 如果应用程序对 NTLM 有硬依赖关系,则禁用 NTLM 时,它们将无法进行身份验证。

哪些因素会导致 NTLM 在应用程序中“硬编码”?

有两个因素将导致对 NTLM 的硬依赖。 第一个是显式选择 NTLM 作为应用程序要使用的身份验证包。 对于某些协议和 API,NTLM 的选择是显而易见的,例如调用 API AcquireCredentialsHandle ()。 对于其他协议,它可能不太明显。 例如,当 RPC 通过网络使用 RPC 时,RPC 的默认身份验证包(RPC_C_AUTHN_DEFAULT)实际上是 NTLM 的别名,甚至显式标志(RPC_C_AUTH_WINNT)中没有任何 NTLM 缩写。 这种构造使选择 NTLM 变得更容易,而无需知道你已这样做。

就 NTLM 而言,开发人员应使用其他身份验证方法,例如 Negotiate 包(有时也称为 SPNEGO 或 SNEGO 包)。 包选择需要在客户端和服务器组件上匹配,以便 Negotiate 能够尝试使用 Kerberos - 因此应用程序客户端和服务器部分都需要使用 Negotiate。 如果任一方都使用 NTLM(与旧版本一样),协商仍然有效,但始终还原 NTLM。 如何告知应用程序使用 Negotiate 因协议而异。 部分最常见的协议(RPC、LDAP、DCOM、HTTP)将在主题 5000 中详细介绍 - 应用程序已显式选择 NTLM 包。

导致 NTLM 使用的第二个因素是客户端未向身份验证过程提供有效的服务器目标名称。 在支持或需要相互身份验证(如 Kerberos)的协议中,目标名称用于实现相互身份验证。 身份验证 API(如 InitializeSecurityContext)采用可选参数,通常称为“TargetName”、“PrincipalName”或“ServerPrincipalName”。 这是域控制器用于选择正确的域帐户来获取目标服务的凭据的标识符。 由于 NTLM 没有服务器身份验证的概念,因此 NTLM 不需要此参数才能成功进行身份验证。 另一方面,Kerberos 要求客户端获取对客户端进行身份验证的服务有效的服务票证。 如果未指定目标名称或无效的目标名称,Kerberos 身份验证将始终失败。 选择“协商”作为包时,不提供任何目标名称(或无效的目标名称)将导致完全跳过 Kerberos 并使用 NTLM。 大多数身份验证 API 将目标名称作为可选参数接受(如果 NULL 没有错误)。 除非开发人员重写它并提供显式目标名称 NTLM(此外,可反射 NTLM)是结果。

NTLM 插件的工作原理

验证程序插件检测到以下错误:

  • NTLM 包直接在对 AcquireCredentialsHandle(或更高级别的包装 API)的调用中指定。

  • 对 InitializeSecurityContext 的调用中的目标名称为 NULL。 在这种情况下,Negotiate 直接回退到 NTLM。

  • 对 InitializeSecurityContext 的调用中的目标名称不是格式正确的 SPN、UPN 或 NetBIOS 样式域名。 在这种情况下,域控制器返回“找不到主体”错误,这会导致 Negotiate 回退到 NTLM。

插件还会在检测到降级到 NTLM 时记录警告;例如,当域控制器找不到 SPN 时。 这些仅记录为警告,因为它们通常是合法的案例,例如,在向未加入域的系统进行身份验证时。

配置插件停止选项

默认情况下,所有分类为 Error 的事件都设置为导致调试中断。 所有警告事件都设置为仅记录事件详细信息。

错误事件导致停止/中断:

  • 5000 – 应用程序已显式选择 NTLM 包

  • 5001 – 协商包列表仅包含 NTLM

  • 5002 – 协商包列表错误的 NTLM 排除

  • 5003 – 服务器没有目标名称或格式不正确的目标名称

记录的警告事件:

  • 5010 – 降级到检测到 NTLM

NTLM 停止

5000 – 应用程序已显式选择 NTLM 包

严重性 - 错误

应用程序或子系统在对 AcquireCredentialsHandle 的调用中显式选择 NTLM 而不是 Negotiate。 即使客户端和服务器有可能使用 Kerberos 进行身份验证,但 NTLM 的显式选择也阻止了这一点。

如何修复此错误

此错误的解决方法是选择协商包代替 NTLM。 完成此操作的方式取决于客户端或服务器使用的特定网络子系统。 下面给出了一些示例。 应查阅有关正在使用的特定库或 API 集的文档。

应用程序使用的 API(参数) 值不正确 正确值 备注
AcquireCredentialsHandle (pszPackage) “NTLM” NEGOSSP_NAME或“协商”
RPC 客户端:RPCBindingSetAuthInfoEx RPCBindingSetAuthInfoEx (AuthnSv) RPC 服务器:RPCServerRegisterAuthInfo(AuthnSvc) RPC_C_AUTHN_WINNT或RPC_C_AUTH_DEFAULT RPC_C_AUTH_GSS_NEGOTIATE RPC 服务器注册 NTLM/WINNT 包不是错误。 这通常需要支持仅支持 NTLM 的旧客户端。 如果仅注册 NTLM 包,这是一个错误,因为这会强制所有客户端使用 NTLM,即使它们能够使用 Kerberos 也是如此。
DCOM:SetBlanket CoSetProxyBlanket (dwAuthnSvc) CoCreateInstanceEx (作为 COAUTHINFO 结构的 dwAuthnSvc 成员传递,后者本身是传递给 API 的 CO标准版RVERINFO 结构的成员) RPC_C_AUTHN_WINNT RPC_C_AUTHN_DEFAULT或RPC_C_AUTHN_GSS_NEGOTIATE 仅当通信始终通过网络发生时,才应使用协商。 如果同一台计算机上的客户端和服务器之间发生 DCOM 调用,则必须使用 DEFAULT 并允许 DCOM 选择要使用的正确包。
LDAP:ldap_bind_s(方法) LDAP_AUTH_NTLM LDAP_AUTH_NEGOTIATE
HTTP WinHTTPSetCredentials (AuthScheme) WINHTTP_AUTH_SCHEME_NTLM WINHTTP_AUTH_SCHEME_NEGOTIATE

5001 – 协商包列表仅包含 NTLM

严重性 - 错误

使用 AcquireCredentialsHandle 时,可以提供可供 Negotiate 使用或忽略的包列表。 根据指定的列表,这可能会替代 Negotiate 中内置的逻辑,以便选择最合适的安全身份验证包。 如果包列表仅包含 NTLM 或排除 Kerberos,则结果与完全绕过 Negotiate 完全相同,并直接显式选择 NTLM SSP 包。

仅当直接调用 AcquireCredentialsHandle 时,才能指定子包列表,因为大多数高层 API(如 RPC)不允许调用方控制 Negotiate 包列表。

Microsoft 不建议应用程序尝试以这种方式操作协商包列表。

如何修复此错误

使用 Negotiate 包而不指定子包的列表,或确保包含 Kerberos。

应用程序使用的 API(参数) 值不正确 正确值
AcquireCredentialsHandle (作为 pAuthData 参数传递标准版C_WINNT_AUTH_IDENTITY_EX结构的 PackageList 成员) "!Kerberos“或”NTLM” NULL 或“Kerberos、NTLM”或“Kerberos,!NTLM“或”!NTLM”

5002 – 协商包列表错误的 NTLM 排除

严重性 - 警告

调用 AcquireCredentialsHandle 时,应用程序尝试从 Negotiate 支持的包列表中排除 NTLM。 但是,错误的语法已用于排除 NTLM,因此它仍保留在列表中。

如何修复此错误:使用以下语法从 Negotiate 中排除 NTLM 包:

应用程序使用的 API(参数) 值不正确 正确值
AcquireCredentialsHandle (作为 pAuthData 参数传递标准版C_WINNT_AUTH_IDENTITY_EX结构的 PackageList 成员) “-NTLM” "!NTLM”

5003 – 服务器没有目标名称或格式不正确的目标名称

严重性 - 错误

使用 Negotiate 包时,提供 null 或无效的目标名称(有时称为主体名称)将导致 Kerberos 失败,NTLM 将用于其位置。 进行身份验证调用时,应始终指定有效的目标名称。 目标名称是一个唯一标识符,允许域控制器获取应用程序尝试进行身份验证的服务器帐户详细信息。 域控制器获得此信息后,它可以生成客户端和服务器可以理解(可解密)的相应 Kerberos 票证。

如何修复此错误

目标名称可以采用三种不同的格式指定,每个名称都可以由域控制器用来查找正确的服务器帐户对象。 这些格式包括服务主体名称(SPN)、用户主体名称(UPN)和 NetBIOS 两部分的域\帐户名称。 SPN 是最常见的形式,也是与其他 Kerberos 实现互操作最多的形式。 对 SPN 的全面讨论超出了本文档的范围,但最简单的最常见的 SPN 窗体有两个部分:服务类和主机名。 服务类标识服务器应用程序的类型(例如 http 或 ldap 等特定应用程序类型或作为主机的泛型)。第二部分是服务器的完全限定域名或平面 (NetBIOS) 名称。 Windows 客户端和服务器会自动为 FQDN 和平面名称注册“主机”SPN。 域控制器还会将大约 40 个特定于应用程序的服务类映射到“主机”SPN,例如“http”、“ldap”、“rpc”、“tapi”等。

o 为在服务器操作系统(例如 localsystem、网络服务或 localservice)客户端应用程序的上下文中运行的应用程序指定目标名称,可以使用自动注册的“主机”SPN 或其别名之一。 若要对域用户帐户上下文中运行的应用程序进行身份验证,必须为该帐户注册 SPN。

对于用户帐户,也可以使用从用户帐户名称和帐户所在的域生成的隐式 UPN 窗体: useraccountname@domain.dom 尽管可以为用户帐户创建其他 UPN(使用可为每个域创建的 UPN 后缀),但这些将不能用作 Kerberos 目标名称 -- 只有与实际登录帐户名和实际域对应的 UPN 才能使用该帐户。

最后,在作为 localsystem、networkservice 或 localservice 运行的服务的情况下,仍可以使用 NT4 样式的域\用户名(或 domain\computername)。 这适用于在域用户帐户或计算机帐户上下文中运行的目标。

应用程序使用的 API(参数) 设置目标名称的参数 备注
InitializeSecurityContext pszTargetName
RPC 客户端:RPCBindingSetAuthInfoEx RPCBindingSetAuthInfoEx (AuthnSv) ServerPrincipalName 这应该是运行服务器/服务的帐户的目标名称。 它不必与 RPCServerRegisterAuthInfo 中设置的值相同
DCOM:SetBlanket CoSetProxyBlanket (dwAuthnSvc) CoCreateInstanceEx (作为 COAUTHINFO 结构的 dwAuthnSvc 成员传递,后者本身是传递给 API 的 CO标准版RVERINFO 结构的成员) pServerPrincName 可以使用COLE_DEFAULT_PRINCIPAL让 COM 从绑定信息中自动选择名称
LDAP:无 LDAP 客户端自动生成。
HTTP none WinHTTP 和 WinInet 从 URL 服务器名称提供目标名称

5010 – 降级到检测到 NTLM

严重性 - 警告

即使应用程序特定的 Negotiate 并使用格式正确的目标名称,也会导致 Negotiate 降级到 NTLM。 根据情况,这可能表示错误或预期行为。 例如,当计算机不是域的一部分或正在使用域控制器无法访问的位置时,预计 Negotiate 会以无提示方式降级,以允许应用程序使用 NTLM 进行身份验证。 但是,如果域控制器可用且通常希望 Kerberos 使用它,则此停止几乎可以肯定地指示出现问题。

如何修复此错误

假设你已确定 Kerberos 应该在此情况下使用,而不是 NTLM,则降级的原因有很多:

• 即使目标名称的格式正确,但域(或林)中不存在。

o 应检查在客户端应用程序中生成正确的目标名称。 服务类是否正确? 主机名是否正确?

o 服务器进程是否在计算机或其他域帐户的上下文中运行。 在以前的案例中,SPN 会自动注册,在后一种情况下,可能需要注册 SPN 或使用替代形式,例如隐式 UPN 或平面名称。

o 是否存在阻止与域控制器或 DNS 服务器的通信的网络连接问题?

o 目标 SPN 是否在多个帐户上注册? 这将导致域控制器拒绝身份验证尝试。

打印

打印验证程序有助于查找并解决应用程序调用打印子系统时可能导致的问题。 打印验证工具面向打印子系统的两个层,即 PrintAPI 层和 PrintDriver 层。

打印 API 层

打印验证工具会测试程序与 Winspool.drv 和 prntvpt.dll 之间的接口,并测试这些 DLL 的接口。 你可以在由 winspool.drv 和 prntvpt.dll 导出的 API 的 MSDN 帮助部分中查看有关调用此接口中函数的规则。

打印驱动程序层

打印验证工具还测试核心打印驱动程序(例如 UNIDRV.DLL、UNIDRUI.DLL、PSCRIPT5.DLL、PS5UI.DLL 或 MXDWDRV.DLL)与打印驱动程序插件之间的接口。可以在 MSDN 和 WDK 中找到有关此接口的信息。

通常,只有调试版本运行应用程序验证工具,因此性能通常不是问题。 如果使用此检查或任何其他应用程序验证工具检查时导致性能问题,请一次运行一项检查,直到执行完所有需要的检查。

WebServices

Windows Webservices API (WWSAPI) 验证层

WWSAPI 插件允许开发人员捕获以下实例:

  • 调用的 WWSAPI 引用无效的内部 WWSAPI 对象。

  • 调用的 WWSAPI 引用已使用的单个线程对象。

  • 一个通过异步调用挂起释放的内部对象。

  • 从短线程调用阻止 API。

此外,此插件:

  • 跟踪对象从实例化到删除的用法。

  • 强制可以异步完成的调用以同步方式完成。 这是为了防止应用程序根据特定行为从调用中获取WS_ASYNC_CONTEXT。

  • 当 API 传递错误对象或对象正在使用 !avrf –ws –obj 调试器扩展时,提供人工可读说明(如下所示)

  • 对于通道、服务代理和服务主机,跟踪的每个调用将显示对象的当前状态。

默认情况下,启用以下检查器:

内部对象有效的 Property NameDescriptionValidateObjectValidates 在其生存期内使用对象

可通过属性 UI 在此提供程序中启用的其他检查程序包括:

属性 NameDescriptionCheckTimeoutValidates,异步函数在超时范围内完成,指定为 TimeoutValForceSyncForce,在向 API 提供WS_ASYNC_CONTEXT上下文时要采用的同步路径。

提供了调试器扩展(!avrf –ws –obj),它显示打开和关闭的内部 WWSAPI 对象。 如果此扩展由对象后缀,它将显示有关此对象的用法的详细信息。

!avrf -ws –obj

此命令显示正在跟踪的内部 WWSAPI 对象,这些对象同时被创建和关闭。 请注意,关闭的对象存储在循环队列中,因此跟踪的对象总数有上限。

成功完成以下 API 时添加对象:WsCreateChannel()、WsCreateChannelForListener()、WsCreateServiceHost()、WsCreateServiceProxy()、WsCreateServiceProxyFromTemplate()、WsCreateError()、WsCreateHeap()、WsCreateHeap()、WsCreateListener()、WsCreateMetadata()、WsCreateMessage()、WsCreateMessageForChannel()、WsCreateReader()、WsCreateWriter()、WsCreateXmlBuffer()、WsReadXmlBuffer()、WsReadXmlBufferFromBytes()

调用并完成相应的 WsFree*() 函数时,对象将从创建的列表中移动到释放列表。

!avrf –ws –obj [OBJECT]

此命令显示内部 WWSAPI 对象的用法。 使用信息包括创建、使用和释放对象的堆栈。 如果对象是通道、服务主机或服务代理,则会在使用对象调用 API 之前显示该对象的状态。

下面是 !avrf –ws –obj 用法选项的示例:

0:001> !avrf -ws -obj
Objects dependent on internal objects allocated:


Objects currently allocated:

 0x00000000048566C0 (Type=Heap, Thread=0x000001bc, Pending Operations=0)
 0x0000000001BE6780 (Type=Error, Thread=0x000001bc, Pending Operations=0)
 0x0000000001C13580 (Type=Service Proxy, Thread=0x000001bc, Pending Operations=0)

Freed objects:

 0x0000000001C17170 (Type=Service Proxy, Thread=0x000001bc)
 0x0000000004856730 (Type=Heap, Thread=0x000001bc)
 0x0000000001BE6820 (Type=Error, Thread=0x000001bc)

0:001> !avrf -ws -obj 0x0000000001C13580

Object @ 0x0000000001C13580
        Type = Service Proxy
        Thread = 0x000001bc
        Internal Reference = 0x00000000026C5E80

Created stack:
  vfnws!VfHookWsCreateServiceProxy+0x00aa
  BLUESTONE!WST_WebServices::WsCreateServiceProxy+0x00d8
  BLUESTONE!ServiceProxy::Connect+0x0116
  BLUESTONE!ServiceModel_SimpleTest::SimpleClient+0x0607
  BLUESTONE!ServiceModelTestGroup_Simple_Test02_Run+0x0041
  BLUESTONE!Fnshell2::FnshellConfiguration::RunTest+0x002e
  BLUESTONE!Fnshell2::TESTCASE::Run+0x00d6
  BLUESTONE!fnsMsgProc+0x02d6
  BLUESTONE!fnsRunTestsWorkerThread+0x085f
  KERNEL32!BaseThreadInitThunk+0x000d
  ntdll!RtlUserThreadStart+0x001d

Last 4 operations

Operation #1 created in thread 0x00000000000001BC

Service proxy state before operation = Created

Callstack:
  vfnws!VfHookWsGetServiceProxyProperty+0x0053
  BLUESTONE!WST_WebServices::WsGetServiceProxyProperty+0x009b
  BLUESTONE!ServiceProxy::GetState+0x004b
  BLUESTONE!ServiceProxy::VerifyState+0x001c
  BLUESTONE!ServiceProxy::Connect+0x01c7
  BLUESTONE!ServiceModel_SimpleTest::SimpleClient+0x0607
  BLUESTONE!ServiceModelTestGroup_Simple_Test02_Run+0x0041
  BLUESTONE!Fnshell2::FnshellConfiguration::RunTest+0x002e
  BLUESTONE!Fnshell2::TESTCASE::Run+0x00d6
  BLUESTONE!fnsMsgProc+0x02d6
  BLUESTONE!fnsRunTestsWorkerThread+0x085f
  KERNEL32!BaseThreadInitThunk+0x000d
  ntdll!RtlUserThreadStart+0x001d

Operation #2 created in thread 0x00000000000001BC

Service proxy state before operation = Created

Callstack:
  vfnws!VfHookWsOpenServiceProxy+0x0079
  BLUESTONE!WST_WebServices::WsOpenServiceProxy+0x0092
  BLUESTONE!ServiceProxy::Connect+0x03d3
  BLUESTONE!ServiceModel_SimpleTest::SimpleClient+0x0607
  BLUESTONE!ServiceModelTestGroup_Simple_Test02_Run+0x0041
  BLUESTONE!Fnshell2::FnshellConfiguration::RunTest+0x002e
  BLUESTONE!Fnshell2::TESTCASE::Run+0x00d6
  BLUESTONE!fnsMsgProc+0x02d6
  BLUESTONE!fnsRunTestsWorkerThread+0x085f
  KERNEL32!BaseThreadInitThunk+0x000d
  ntdll!RtlUserThreadStart+0x001d

Operation #3 created in thread 0x00000000000001BC

Service proxy state before operation = Open

Callstack:
  vfnws!VfHookWsGetServiceProxyProperty+0x0053
  BLUESTONE!WST_WebServices::WsGetServiceProxyProperty+0x009b
  BLUESTONE!ServiceProxy::GetState+0x004b
  BLUESTONE!ServiceProxy::VerifyState+0x001c
  BLUESTONE!ServiceProxy::Connect+0x0484
  BLUESTONE!ServiceModel_SimpleTest::SimpleClient+0x0607
  BLUESTONE!ServiceModelTestGroup_Simple_Test02_Run+0x0041
  BLUESTONE!Fnshell2::FnshellConfiguration::RunTest+0x002e
  BLUESTONE!Fnshell2::TESTCASE::Run+0x00d6
  BLUESTONE!fnsMsgProc+0x02d6
  BLUESTONE!fnsRunTestsWorkerThread+0x085f
  KERNEL32!BaseThreadInitThunk+0x000d
  ntdll!RtlUserThreadStart+0x001d

Operation #4 created in thread 0x00000000000001BC

Service proxy state before operation = Open

Callstack:
  vfnws!VfHookWsCall+0x00a6
  BLUESTONE!DefaultBinding_ICalculator_Add+0x008b
  BLUESTONE!ServiceModelTestGroup_Simple_Function+0x010a
  BLUESTONE!ServiceModel_SimpleTest::SimpleClient+0x069a
  BLUESTONE!ServiceModelTestGroup_Simple_Test02_Run+0x0041
  BLUESTONE!Fnshell2::FnshellConfiguration::RunTest+0x002e
  BLUESTONE!Fnshell2::TESTCASE::Run+0x00d6
  BLUESTONE!fnsMsgProc+0x02d6
  BLUESTONE!fnsRunTestsWorkerThread+0x085f
  KERNEL32!BaseThreadInitThunk+0x000d
  ntdll!RtlUserThreadStart+0x001d

Asynchronous Callback = BLUESTONE!ServiceModelTestGroup_Simple_Callback
Asynchronous CallbackState = 0x0000000005EBDC30

Completed asynchronously with HRESULT=0x00000000 in thread 0x00000000000001BC

Asynchronous callback stack:
  vfnws!VfHookWsCall+0x00e3
  BLUESTONE!DefaultBinding_ICalculator_Add+0x008b
  BLUESTONE!ServiceModelTestGroup_Simple_Function+0x010a
  BLUESTONE!ServiceModel_SimpleTest::SimpleClient+0x069a
  BLUESTONE!ServiceModelTestGroup_Simple_Test02_Run+0x0041
  BLUESTONE!Fnshell2::FnshellConfiguration::RunTest+0x002e
  BLUESTONE!Fnshell2::TESTCASE::Run+0x00d6
  BLUESTONE!fnsMsgProc+0x02d6
  BLUESTONE!fnsRunTestsWorkerThread+0x085f
  KERNEL32!BaseThreadInitThunk+0x000d
  ntdll!RtlUserThreadStart+0x001d


Closed stack:

0:001>

服务

服务测试,检查以正确使用 Windows 服务。 例如,正在正确启动和停止服务。 有关这些测试生成的停止代码异常的信息,请参阅 应用程序验证程序 - 停止代码 - 服务

性能

Perf 测试检查,以有效使用影响系统性能和能耗的 API,例如调用使用不正确的等待期的 Windows 函数。 有关这些测试生成的停止代码异常的信息,请参阅 应用程序验证程序 - 停止代码 - Perf

Hangs 测试使用导致系统无响应的 API,例如 DllMain 线程正在等待被阻止的另一个线程。 有关这些测试生成的停止代码异常的信息,请参阅 应用程序验证程序 - 停止代码 - 挂起

ARM64EC支持

以下提供程序不支持ARM64EC,因此会崩溃在 ARM64EC 中运行的程序:

另请参阅

应用程序验证程序 - 概述

应用程序验证程序 - 功能

应用程序验证程序 - 测试应用程序

应用程序验证程序 - 停止代码和定义

应用程序验证程序 - 调试应用程序验证程序停止

应用程序验证程序 - 常见问题