应用程序验证程序 - 常见问题解答 (常见问题解答)

常规问题

下面是收到的有关应用程序验证程序常规用法的问题列表。

什么是应用程序验证程序?

应用程序验证程序是一种运行时验证工具,用于查找 Microsoft Windows 应用程序中的 bug。 由于它是运行时工具,因此需要执行应用程序代码才能进行验证。 因此,良好的测试覆盖率至关重要。

应用程序验证程序的典型使用方案是为感兴趣的应用程序启用它 (请参阅下面的问题,了解如何) 然后运行为应用程序编写的所有测试。 对于以调试程序中断或验证程序日志条目的形式发现的任何 bug,你将收到通知。

如何实现卸载应用程序验证程序?

若要卸载应用程序验证程序,请依次单击“开始”、“添加或删除程序”、“删除程序”、“应用程序验证程序”和“删除”来访问控制面板。

如何实现启动应用程序验证程序?

安装应用程序验证程序后,可以通过在程序列表中访问它来启动它,也可以在命令行上键入Appverif.exe来启动它。 为此,请转到命令提示符或“启动”菜单的“运行”框。 键入appverif.exe,然后按 Enter。 这将启动应用程序验证程序。

Appverifer.exe二进制文件安装在系统目录中,用于进行工具设置。

日志存储在何处?

日志存储在 %USERPROFILE%\AppVerifierLogs 中

如果在使用应用程序验证程序时遇到问题,该怎么办?

请确保运行的是最新版本。 请考虑在不同的电脑甚至 Windows 版本上尝试同一应用。

应用程序验证程序是否验证托管代码?

AppVerifier 关心操作系统和应用程序之间的接口。 因此,除非托管代码正在对与堆、句柄、关键部分等关联的本机 API 执行互操作。测试用例不会提供与已验证接口的任何交互。

建议利用托管调试助手来验证托管代码。 有关详细信息,请参阅 使用 Windows 调试器调试托管代码

调试器问题

以下是收到的有关调试器的问题列表。

为什么我收到一个错误,指出我需要调试器?

应用程序验证程序中的“基本信息”验证层要求在调试器下运行应用程序。 如果在选择测试之前没有与应用程序关联的调试器,则会收到一个对话框,提醒你将需要在调试器下运行应用程序以获取记录的信息。

如何实现调试器下运行应用程序?

请参阅调试器安装和设置主题 - 使用 Windows 调试入门

在没有任何其他检测的情况下,如何实现测试堆栈扩展?

通常,堆栈扩展应独立于其他验证层(包括堆)进行测试。 原因如下:每个验证层使用一些例程对 API 或导出的点进行“滚动”。

例如,对 CreateFileA 的调用将是对 appvocre 的调用!NS_SecurityChecks::CreateFileA,可能会调用 appvcore!NS_FillePaths::CreateFileA,可能调用 kernel32!CreateFileA,可能会调用验证程序!AVrfpNtCreateFile,将调用 ntdll!NtCreateFile。 可以看到,检测又添加了 3 个“堆叠”函数调用,其中每个调用可能且将消耗更多堆栈。

在下面的示例中,LH-verifier.dll在每个 DllMain 中“启动”,并且“已检测”堆代码路径将增加更多的堆栈使用量。 由于调试器中注入的线程不使用IMAGE_NT_HEADERS默认值,因此初始提交的堆栈不足以完成线程的 APC 状态, (处于 APC 状态的线程执行初始化代码) 。

如果要使用 Stack-Ckecs,可能是 FirstChanceAccessViolation 时应使用的唯一其他验证层。

使用 !avrf 扩展时,我收到“未为此进程启用应用程序验证程序...”。

收到完整错误: Application verifier is not enabled for this process. Use appverif.exe tool to enable it.

你可能只启用了填充码验证层和/或处于“纯”模式的堆。 以下是一些可能的原因。

测试方案问题

下面是围绕不同测试方案收到的问题列表。

如何在我的服务上而不是其他服务上启用应用程序验证程序?

在 System32 目录中创建svchost.exe的副本,并调用该副本“Mysvchost.exe”。

使用 regedit 打开 HKLM\System\CurrentControlSet\Services\MyService。

编辑值“ImagePath”,该值类似于“%SystemRoot%\system32\svchost.exe -k myservice”,并将svchost.exe更改为“Mysvchost.exe”。

将“Mysvchost.exe”添加到 AppVerifier 列表,并检查所需的测试。

重新启动。

如何实现从 WOW64 下运行的 32 位应用程序启动的 64 位应用程序上运行应用程序验证程序?

简单版本:在给定应用程序上启用验证程序设置的黄金规则是匹配工具和目标进程的位数。 也就是说:对 32 位应用程序使用 32 位appverif.exe (在 WoW64) 下运行,并为本机 64 位本机目标使用 64 位AppVerif.exe。

长版本:应用程序验证程序设置是“核心”设置和“填充码”设置的正确联合。

核心设置 - 核心设置存储在映像文件执行选项下。

从启动应用程序读取“调试器”值。 因此,如果要使 32 位devenv.exe启动 64 位my.exe并在调试器下运行,则必须使用 WoW6432Node 下的 32 位注册表项。 对于 32 位进程,从这两个位置(本机 IFEO 和 WoW6432Node)读取其他值。

原因如下:在 WoW 下运行的 32 位进程是运行 Wow64 仿真循环的 64 位进程。 因此,每个 32 位进程先是一个 64 位进程,然后是一个 32 位进程。 64 位 IFEO 将在Wow64cpu.dll代码上启用验证程序,而 32 位 IFEO 将在 32 位代码上启用验证程序。

从最终用户的角度来看,verifier.dll在 64 位世界中加载两次 (一次,在 32 位世界中加载一次) 。 由于大多数人不关心验证wow64cpu.dll,因此 32 位进程最接受的行为是仅验证 32 位部分。 这就是为什么“始终匹配位数”的黄金规则适用的原因。

如何实现调试在非交互式窗口工作站中运行的服务

若要调试在非交互式窗口工作站中运行的服务,请执行以下 (仅适用于使用 ntsd/windbg) :

将项添加到 HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image 文件执行选项下的注册表。 此密钥的名称应为进程的名称 (service.exe) 。

创建名为 Debugger 的REG_SZ值,并将此值设置为调试器所在的路径。 它必须包含完整路径,而不仅仅是调试器的名称。 命令应包括 –server 选项以及调试器应侦听的特定端口或端口范围。 例如,c:\debuggers\ntsd.exe –server tcp:port=5500:5600 –g –G。

通过使用 –remote 选项运行调试器,连接到调试器服务器。 例如:windbg.exe –remote tcp:=localhost,port=55xx,其中“xx”是 00 到 99(如果在服务器上使用了范围)的某个数字。

AppVerifier 是否执行泄漏检测? 在 Windows 7 及更高版本下,有一个泄漏检查选项,用于检测进程何时泄漏内存。 在早期的操作系统下,AppVerifier 不会测试应用程序的泄漏检测,而是查找其他内存问题。

出于安全问题,建议使用哪些测试?

  • 句柄数
  • 堆栈仅对可能使计算机关闭) 的服务和重要进程 (

请记住,ObsoleteAPICalls 将对 MSDN 中列为已过时或已弃用的 API 的每个调用发出警告。 应用程序切换到新 API 是否重要,应根据具体情况决定。 有些 API 很危险,有些 API 只是被具有更多选项的较新 API 取代。 请查看编写安全代码的第二部分的“危险 API”部分,了解详细信息的第二个新增内容。

对于需要高度可靠的应用程序(如服务和服务器程序),还应启用 Stacks 检查。 这会通过禁用堆栈增长来检查堆栈提交大小是否足够。 如果应用程序立即退出并发生堆栈溢出,这意味着需要使用更大的堆栈提交大小重新编译应用程序。 如果你是测试人员,在使用 Stacks 检查时遇到应用程序问题,请提交 bug,将其分配给开发人员,然后继续测试。

测试特定问题

下面是有关测试的问题列表。 单击问题以查看响应:

关键部分泄漏是否重要?

每当泄漏关键部分时,就会泄漏以下内容:事件句柄、少量的内核池和少量的堆分配。 如果进程退出,这些将被清理。

如果你的过程应该长时间保持活动,那么这些泄漏可能会咬你。 由于修复在 99% 的情况下非常简单, (开发人员只是忘记调用 RtlDeleteCriticalSection) 你应该解决这些问题。

能否以编程方式处理堆栈溢出?

在初始线程函数中建立异常处理程序不能保证捕获可能引发的潜在堆栈溢出。 这是因为调度异常的代码还需要一点堆栈才能在当前激活记录之上执行。 由于我们刚刚失败了堆栈扩展,因此很可能在尝试调度第一个异常时单步执行已提交的堆栈的末尾并引发第二个异常。 双重错误异常将无条件地终止进程。

LoaderLock 测试在调用 DestroyWindow 时出现错误。 为什么无法在 DllMain 中调用 DestroyWindow? 你无法控制要分离的线程。 如果不是创建窗口的线程,则无法销毁窗口。 因此,你泄漏了窗口,下一次窗口收到消息时,由于 Wndproc 已卸载而崩溃。

在获取进程分离之前,需要销毁窗口。 危险并不在于 user32 将被卸载。 危险在于你被卸载了。 因此,窗口收到的下一条消息将使进程崩溃,因为 user32 会将消息传递到不再存在的 Wndproc。

Microsoft Windows 操作系统具有线程相关性。 进程分离不会。 加载程序锁并不是真正的大问题:问题出在 Dllmain 上。 进程分离是 DLL 最后一次运行代码。 在返回之前,你必须摆脱一切。 但是,由于 Windows 具有线程相关性,如果使用的是错误的线程,则无法清理窗口。

如果有人安装了全局挂钩,加载程序锁将进入图片, (例如 spy++ 正在运行) 。 在这种情况下,将进入潜在的死锁方案。 同样,解决方案是在获取进程分离之前销毁窗口。

增加初始堆栈提交以避免溢出是否成本高昂?

提交堆栈时,只需保留页面文件空间。 性能不会受到影响。 实际上未使用任何物理内存。 如果实际触摸了提交的堆栈空间,则唯一会出现额外的成本。 但是,即使未预先提交堆栈,这种情况也会发生。

让我们看看使所有服务在svchost.exe防弹运行的成本是多少。 在测试计算机上,我得到 9 个svchost.exe进程,总共有 139 个线程。 如果将每个线程的默认堆栈设置为 32K,则需要大约 32K x 200 ~ 6.4 Mb 的页面文件空间来提前提交所有堆栈。

这是一个相当小的代价来支付可靠性。

保留的堆栈大小如何?

有一些有趣的项目,例如 IA64/AMD64 上的异常调度,需要“意外”的额外堆栈。 RPC 工作线程上可能会发生一些处理,这些线程的堆栈要求是经过合理尝试来度量这些线程。

首先,你应该了解进程中的所有线程池。 具有 alertable-wait-threads 的 NT-Thread-Pool 有时很特殊,因为例如,如果使用 SQL 中的数据库组件,它将在作为用户 APC 目标的线程上使用可警报睡眠。 这可能会导致嵌套调用出现问题。

了解所有线程池后,了解如何控制其堆栈要求。 例如,RPC 读取堆栈提交的注册表项。 WDM 泵线程从映像获取该线程。 对于其他线程池,里程可能会有所不同。

当所有线程都清除时,可以执行一些操作。 只有线程频繁来去去时,没有巨大的预留空间才有助于解决空间碎片问题。 如果拥有一个稳定的线程池,那么在减少保留空间方面也可能有优势。 它确实有助于为堆节省地址空间和用户的地址空间。

对于如何为 LINKER_STACKCOMMITSIZE= 选择正确的大小,是否有建议?

该值应按页面大小 (4k/8k 进行除法,具体取决于 CPU) 。 下面是确定所需大小所需的一些准则:

  1. 将具有潜在未绑定深度 (或至少用户诱导的高深度) 的任何递归函数转换为迭代。

  2. 减少使用 alloca。 使用堆或 safealloca。

  3. 运行 Prefast 并减小堆栈大小检查 (说 8k) 。 修复那些标记为使用过多堆栈的函数。

  4. 将堆栈提交设置为 16k。

  5. 在应用程序验证程序的“堆栈”检查的调试器下运行一组测试。

  6. 当你看到堆栈溢出时,会确定最严重的违规者并修复它们。 (请参阅步骤 5.)

  7. 当无法将堆栈使用量再减少 8k 时。 如果 > 64k 存在问题,请调回 64k 并参阅步骤 6。 否则,请转到步骤 5。

堆测试的内存要求是什么?

对于完整的堆测试,需要 256MB 的 RAM 和至少 1GB 的页面文件。 对于正常的堆测试,至少需要 128MB 的 RAM。 没有特定的处理器或磁盘要求。

为什么我收到ALL_ACCESS停止?

任何使用_ALL_ACCESS的应用程序都会呈现其正在访问的对象,无法审核,因为审核日志不会反映你实际对对象执行的操作,而只会反映你要求对对象执行的操作。

这种情况会为更狡猾的攻击创建伪装。 管理员扫描正在进行的攻击活动不会发现请求对密钥 X ALL_ACCESS的人有任何问题,因为特定应用程序总是这样做。 管理员会认为“此人可能只是运行Word”。 管理员无法判断黑客已经渗透了我的帐户,现在正在探测系统,以确定我拥有哪些访问权限,他可以利用这些访问权限来达到他的邪恶目的。 一切皆有可能。

ALL_ACCESS的 ACL 问题是必须始终获得授权。 如果我们希望有一天拒绝你删除对特定密钥的访问权限,我们将无法删除。 即使你实际上没有删除密钥,我们也会破坏你的应用程序,因为你会请求删除访问权限。

为什么我没有从堆和锁定测试中获取任何日志?

这些测试是在操作系统 (中构建的验证层,而不是在包中) 并报告调试器中的错误。 如果运行启用了这些测试的应用程序并且没有崩溃,则它们不会报告任何问题。

如果确实遇到崩溃,则需要在调试器下运行,或将应用程序传递给开发人员以更密切地进行测试。

为什么故障注入不起作用?

根据客户反馈,在 2007 年 2 月之后发布的 AppVerifier 版本中,故障注入概率更改为百万分之一。 因此,0n20000 的概率为 2%,0n500000 的概率为 50%,依此而论。

!avrf –flt 调试器扩展可用于在调试器中动态更改概率。 但是,应打开进程的低资源模拟检查才能使此过程正常工作。

!avrf 调试器扩展是调试器包随附exts.dll的一部分。 支持概率更改的 !avrf 中的更改位于最新的调试器包中。 如果遇到故障注入问题,请更新调试器和 AppVerifier 包。

为什么泄漏验证程序不报告某些资源泄漏?

加载 DLL 或 EXE 模块时,泄漏验证程序不会报告任何资源泄漏。 卸载模块时,如果模块分配的任何资源尚未释放,则泄漏验证程序将发出停止。

若要检查由加载的 DLL 或 EXE 分配的资源,请使用 !avrf -leak 调试器扩展。

另请参阅

应用程序验证程序 - 概述

应用程序验证工具 - 功能

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

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

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

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