微软:结合验证与调用的CFG检查解析
微软工程师解析了控制流保护(CFG)中一种将验证与调用合并的实现,展示了 x86-64 与 AArch64 两种架构下的汇编差异与寄存器约定,并说明如何从中提取坏指针信息。文章强调合并版本为保留调用参数而改变了寄存器使用。
微软工程师解析了控制流保护(CFG)中一种将验证与调用合并的实现,展示了 x86-64 与 AArch64 两种架构下的汇编差异与寄存器约定,并说明如何从中提取坏指针信息。文章强调合并版本为保留调用参数而改变了寄存器使用。
背景
- 控制流保护(Control Flow Guard,CFG)在 Windows 中用于防止非法函数指针调用。
- 除了单独的验证函数(validate-only),还有将验证与直接跳转调用合并的实现(validate-and-call)。
核心差异(x86-64)
- validate-only:
- 参数放在 rcx,使用 rax 和 rdx 作为临时寄存器,成功后 ret 返回。
- 失败时会把结果放到 rax 再处理(例如跳转到错误路径)。
- validate-and-call:
- 参数放在 rax,使用 r10 和 r11 作为临时寄存器,成功后 jmp rax 直接跳转到目标函数,保留调用时的参数寄存器不被破坏。
- 失败时坏指针已经位于 rax,无须移动。
核心差异(AArch64)
- validate-only:
- 使用 x15 作为输入地址,xip0/xip1 作临时寄存器,成功后返回(ret 或 br 到调用者)。
- validate-and-call:
- 使用 x9 作为输入地址,保留参数寄存器,最终 b r9 直接跳转到目标。坏指针可从 x9 提取。
设计动机与要点
- 合并实现的动机是常见的调用模式:在验证成功后通常会紧接着调用目标,因此将两步合并可省去一次返回/跳转并保留参数寄存器。
- 必然影响调用约定:为避免覆盖调用参数,合并版本必须选择不会与调用约定冲突的寄存器作为临时。
- 从实现角度可以通过移位/索引操作从表中提取出“坏指针”的原始值(分别为 rax 或 x9 在合并版本中)。
对安全研究与漏洞分析的价值
- 汇编级别的对比有助于:
- 静态或动态分析时识别 CFG 验证点与跳转路径;
- 在漏洞利用或修复中准确定位坏指针来源;
- 理解不同架构和调用约定对安全机制实现的影响。
结论
- validate-and-call 与 validate-only 在逻辑上等价,但寄存器分配与控制流结尾不同,体现了为兼顾性能与调用语义而作的工程权衡。
这篇汇编级对比对安全分析和低层调试非常有价值,凸显了调用约定对保护机制实现的影响。
不错过任何一条 AI 大事
订阅 AIFlux 早报,每天 3 分钟看懂产业动态。

