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

常规问题

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

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

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

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

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

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

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

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

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

日志存储在何处?

日志存储在 %USERPROFILE%\AppVerifierLogs 中

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

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

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

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

建议利用托管调试助手来验证托管代码。 在使用 Windows 调试器调试托管代码时阅读有关它们的详细信息。

是否支持ARM64EC?

应用程序验证程序不支持ARM64EC。

调试器问题

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

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

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

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

请参阅调试器安装和安装主题 - Windows 调试入门

如何实现测试堆栈扩展,没有任何其他检测?

通常,堆栈扩展应该与其他验证层(包括堆)隔离进行真正测试。 原因如下:每个验证层“thunks”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 位应用程序(在 WoW64 下运行)使用 32 位appverif.exe,并将 64 位AppVerif.exe用于本机 64 位本机目标。

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

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

从启动应用程序读取“调试器”值。 因此,如果要让 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 不会测试应用程序以检测泄漏,而是查找其他内存问题。

建议对安全问题进行哪些测试?

  • 句柄
  • 堆栈(仅适用于可以关闭计算机的服务和重要进程)

请记住,已过时的APICalls 只会对 MSDN 中列为已过时或已弃用的 API 的每个调用发出警告。 如果应用程序切换到新 API 非常重要,则应按案例决定。 某些 API 很危险,有些 API 只是被具有更多选项的较新的 API 取代。 查看编写安全代码的第二部分的“危险 API”部分,了解详细信息。

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

测试特定问题

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

关键节泄漏是否重要?

每当泄漏关键部分时,将泄漏以下内容:事件句柄、少量内核池和小型堆分配。 如果进程退出,这些操作将得到清理。

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

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

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

LoaderLock 测试正在给出有关调用 DestroyWindow 的错误。 为什么无法在 DllMain 中调用 DestroyWindow? 你无法控制要分离哪个线程。 如果它与创建窗口的线程不同,则无法销毁窗口。 因此,你泄漏了窗口,下次窗口收到消息时,会崩溃,因为 Wndproc 已被卸载。

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

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

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

增加初始堆栈提交是否昂贵,以避免溢出?

提交堆栈时,只需保留页面文件空间。 性能没有影响。 实际不使用任何物理内存。 如果实际触摸提交的堆栈空间,则会发生唯一的额外费用。 但是,即使不提前提交堆栈,也会发生这种情况。

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

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

保留堆栈大小如何?

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

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

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

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

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

该值应按页面大小(4k/8k(具体取决于 CPU)来分割。 下面是确定需要的大小的一些准则:

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

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

  3. 运行预制,减少堆栈大小检查(例如 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 调试器扩展。

另请参阅

应用程序验证程序 - 概述

应用程序验证工具 - 功能

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

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

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

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