用于实现平台安全性的控制流防护

什么是控制流防护?

控制流防护 (CFG) 是一种高度优化的平台安全功能,旨在防止内存损坏漏洞。 通过对应用程序可以从哪里执行代码施加严格限制,使得利用缓冲区溢出等漏洞执行任意代码的行为更加困难。 CFG 扩展了以前的攻击缓解技术,例如 /GS(缓冲区安全检查)数据执行防护 (DEP)地址空间布局随机化 (ASLR)

使用 CFG 可以帮助:

  • 防止内存损坏和勒索软件攻击。
  • 将服务器的功能限制在特定时间点所需的范围内,以减少攻击面。
  • 使通过缓冲区溢出等漏洞利用任意代码变得更加困难。

此功能在 Microsoft Visual Studio 中提供,可在 Windows 的 CFG 感知版本上运行;客户端使用 Windows 10 和 Windows 11,服务器端使用 Windows Server 2019 及更高版本。

强烈建议开发人员为其应用程序启用 CFG。 无需为代码的每个部分启用 CFG,因为已启用 CFG 和非 CFG 的混合代码将正常执行。 但是,未能为所有代码启用 CFG 可能会在保护中产生漏洞。 此外,已启用 CFG 的代码在 Windows 的 CFG 不感知版本上正常工作,因此与它们完全兼容。

如何启用 CFG?

在大多数情况下,无需更改源代码。 只需向 Visual Studio 项目添加一个选项,编译器和链接器将启用 CFG。

最简单的方法是导航到项目 | 属性 | 配置属性 | C/C++ | 代码生成,并为控制流防护选择是 (/guard:cf)

Visual Studio 中的 cfg 属性

或者,将 /guard:cf 添加到项目 | 属性 | 配置属性 | C/C++ | 命令行 | 其他选项(适用于编译器),将 /guard:cf 添加到项目 | 属性 | 配置属性 | 链接器 | 命令行 | 其他选项(适用于链接器)。

编译器的 cfg 属性链接器的 cfg 属性

有关其他信息,请参阅 /guard(启用控制流防护)

如果是从命令行生成项目,则可以添加相同的选项。 例如,如果要编译名为 test.cpp 的项目,请使用 cl /guard:cf test.cpp /link /guard:cf

还可以使用内存管理 API 中的 SetProcessValidCallTargets 动态控制 CFG 认为有效的 icall 目标地址集。 同一 API 可用于指定页面是无效还是有效的 CFG 目标。 默认情况下,VirtualProtectVirtualAlloc 函数将把可执行文件和已提交页面的指定区域视为有效的间接调用目标。 可以替代此行为,例如在实现实时编译器时,通过在调用 VirtualAlloc 时指定 PAGE_TARGETS_INVALID,或在调用 VirtualProtect 时指定 PAGE_TARGETS_NO_UPDATE,如内存保护常量中所述。

如何判断二进制文件是否处于控制流防护之下?

使用 /headers/loadconfig 选项从 Visual Studio 命令提示符运行 dumpbin 工具(包含在 Visual Studio 安装中):dumpbin /headers /loadconfig test.exe。 CFG 下的二进制文件的输出应显示标头值包括“Guard”,并且加载配置值包括“CF Instrumented”和“FID table present”。

dumpbin /headers 的输出

dumpbin /loadconfig 的输出

CFG 的工作原理如何?

软件漏洞通常通过向正在运行的程序提供不太可能、不寻常或极端的数据来利用。 例如,攻击者可以通过向程序提供远超预期的输入来利用缓冲区溢出漏洞,因此过度运行该程序保留的区域来暂停响应。 这可能会损坏可能保存函数指针的相邻内存。 当程序通过此函数进行调用时,它随后会跳转到攻击者指定的异常位置。

但是,CFG 的编译和运行时支持的强大组合实现了控制流完整性,严格限制了间接调用指令可以执行的位置。

编译器执行以下操作:

  1. 为编译后的代码添加轻量级安全检查。
  2. 标识应用程序中作为间接调用有效目标的函数集。

运行时支持,由 Windows 内核提供:

  1. 有效地维护标识有效间接调用目标的状态。
  2. 实现验证间接调用目标是否有效的逻辑。

举例说明:

cfg 伪代码

如果 CFG 检查在运行时失败,Windows 会立即终止程序,从而破坏任何尝试间接调用无效地址的攻击。

另请参阅

/guard(启用控制流保护)

/GUARD(启用防护检查)