一个完全不同的方法是使用执行边界检查的编译方法(有关列表,请参阅 [Sitaker 1999])。在我看来,这些工具在拥有多层防御方面非常有用,但将此技术用作唯一的防御手段是不明智的。原因至少有两个。首先,这些工具通常只提供针对缓冲区溢出的部分防御(而“完整”的防御通常慢 12-30 倍);C 和 C++ 的设计初衷就不是为了防止缓冲区溢出。其次,对于开源程序,您无法确定将使用哪些工具来编译程序;对于给定系统,使用默认的“普通”编译器可能会突然打开安全漏洞。
更有用的工具之一是“StackGuard”,它是标准 GNU C 编译器 gcc 的修改版。StackGuard 的工作原理是在返回地址前插入一个“保护”值(称为“金丝雀”);如果缓冲区溢出覆盖了返回地址,金丝雀的值(希望如此)会发生变化,系统会在使用它之前检测到这一点。这非常有价值,但请注意,这并不能防止缓冲区溢出覆盖其他值(攻击者可能仍然可以使用这些值来攻击系统)。目前正在努力扩展 StackGuard,使其能够向其他数据项添加金丝雀,称为“PointGuard”。PointGuard 将自动保护某些值(例如,函数指针和 longjmp 缓冲区)。但是,使用 PointGuard 保护其他变量类型需要程序员的特定干预(程序员必须确定哪些数据值必须使用金丝雀进行保护)。这可能很有价值,但很容易意外地遗漏对您认为不需要保护的数据值的保护 - 但实际上需要保护。有关 StackGuard、PointGuard 和其他替代方案的更多信息,请参阅 Cowan [1999]。
IBM 基于 StackGuard 的思想开发了一种名为 ProPolice 的堆栈保护系统。IBM 未在其当前网站中包含 ProPolice - 它只是被称为“用于保护应用程序免受堆栈粉碎攻击的 GCC 扩展”。与 StackGuard 类似,ProPolice 也是一个 GCC(Gnu Compiler Collection)扩展,用于保护应用程序免受堆栈粉碎攻击。用 C 语言编写的应用程序通过在编译时自动将保护代码插入到应用程序中来受到保护。然而,ProPolice 与 StackGuard 略有不同,它增加了三个功能:(1)重新排列局部变量,将缓冲区放在指针之后(以避免可能用于进一步破坏任意内存位置的指针损坏),(2)将函数参数中的指针复制到局部变量缓冲区之前的区域(以防止可能用于进一步破坏任意内存位置的指针损坏),以及(3)从某些函数中省略检测代码(它基本上假设只有字符数组是危险的;虽然这并非完全正确,但在很大程度上是正确的,因此 ProPolice 在保持其大部分保护能力的同时具有更好的性能)。IBM 网站包含有关如何使用此保护构建 Red Hat Linux 和 FreeBSD 的信息;OpenBSD 已经将 ProPolice 添加到其基本系统中。我认为这非常有希望,我希望看到此功能包含在未来版本的 gcc 中并在各种发行版中使用。事实上,我认为这种功能应该成为默认设置 - 这意味着在大多数情况下,最大一类攻击将不再使攻击者能够获得控制权。
作为一个相关问题,在 Linux 中,您可以修改 Linux 内核,使堆栈段不可执行;Linux 的确存在这样的补丁(请参阅 Solar Designer 的补丁,其中包含此内容,网址为 http://www.openwall.com/linux/)。但是,截至撰写本文时,这尚未构建到 Linux 内核中。部分原因是这种保护比看起来要少;攻击者可以简单地强制系统调用程序中已有的其他“有趣”位置(例如,在其库、堆或静态数据段中)。此外,有时 Linux 确实需要在堆栈中使用可执行代码,例如,实现信号和实现 GCC “trampolines”。Solar Designer 的补丁确实处理了这些情况,但这确实使补丁变得复杂。就我个人而言,我希望看到它合并到主要的 Linux 发行版中,因为它确实使攻击变得更加困难,并且可以防御一系列现有攻击。但是,我同意 Linus Torvalds 和其他人的观点,即这并没有增加它看起来会增加的保护量,并且可以相对容易地规避。您可以在 http://old.lwn.net/1998/0806/a/linus-noexec.html 阅读 Linus Torvalds 对未包含此支持的解释。
简而言之,最好首先致力于开发一个正确的程序,使其能够防御缓冲区溢出。然后,在您完成此操作后,请务必使用 StackGuard 等技术和工具作为额外的安全网。如果您努力消除了代码本身中的缓冲区溢出,那么 StackGuard(以及类似的工具)可能会更有效,因为 StackGuard 需要保护的“盔甲中的裂缝”会更少。