在讨论缓冲区溢出时,有人告诉我为应用程序编译自己的二进制文件(带有特定编译标志)而不是使用“主流二进制文件”会使攻击者更难利用缓冲区溢出。
显然是因为与主流二进制文件相比,它“改变了内存分配”。
- 这是真的?
- 它比地址空间布局随机化效率低吗?
- 操作系统内核中的新内存管理策略是否使这一点变得毫无意义?
在讨论缓冲区溢出时,有人告诉我为应用程序编译自己的二进制文件(带有特定编译标志)而不是使用“主流二进制文件”会使攻击者更难利用缓冲区溢出。
显然是因为与主流二进制文件相比,它“改变了内存分配”。
这大多是不真实的。使用与用于“主流”二进制文件的编译器版本不同的编译器,或者将其与不同的编译标志一起使用,可能会导致一些事情的顺序不同,但大多数代码元素可能会以相同的顺序出现. 只要它改变了关于缓冲区溢出利用的任何东西,重新编译可能会以完全相同的方式为攻击者减轻事情,这可能会使事情变得更加困难。
大多数缓冲区溢出利用包括践踏代码地址(例如,堆栈上的函数的返回地址,或某处的函数指针,特别是在 C++ 中的对象的 vtables 中),以便攻击者强制执行到他希望的地方。传统上,攻击者试图让执行跳转到他刚刚填充缓冲区的数据,但不可执行的堆栈和更普遍的所谓的W^X模型阻止了它的成功:数据不会被解释为代码。因此,攻击者现在针对 C 库(或类似的系统库,取决于操作系统),其中包含执行其他文件的好功能,例如 shell。这需要猜测所说的好功能在内存中。底线:如果您希望重新编译对此产生任何影响,那么您必须重新编译C 库,而不是应用程序。
地址空间布局随机化只是使函数地址不可猜测的一种通用方法。它比重新编译要彻底得多(即使假设重新编译确实会交换东西,但事实并非如此),因为地址空间布局随机化在每次执行时都会再次随机化。特别是,攻击者不会通过获取可执行文件的副本来了解目标函数地址,因为该地址尚未确定。
然而,地址空间布局随机化并不是灵丹妙药,因为它必须在严格的约束下运行:由于对齐(通常,加载地址必须是 4 kB 的倍数)和碎片问题(2 GB 地址),它不能将库放在任何地方空间(在 32 位系统上)太小,无法将库分散到各处;这会人为地阻止分配大内存块)。
(此外,“银弹”是一种杀死狼人的方法。只有当您的医学理念是射杀患者时,它才是“理想的解决方案”。)
缓冲区溢出是一个错误。如果攻击者可以用攻击者选择的字节以静默方式使您的应用程序覆盖其部分数据,那么攻击者可以造成的破坏没有先验限制。这就是发明像 Java 这样的语言的原因:至少,对于 Java,在缓冲区溢出时,您会立即发生线程崩溃,而不是无声的数据损坏。
正如标题中给出的问题“从源代码编译.. 是否可以防止缓冲区溢出攻击?”,答案通常是否定的。
但是,这里猜测您的朋友可能一直在想什么:当使用命令行标志调用时,当前版本的 GNU C 编译器 (GCC) 可以选择使用GCC Stack-Smashing Protector 。-fstack-protector
还有一些其他技术必须在编译时启用,请参阅下面的 Ubuntu 链接。我猜其中一个或多个是你朋友的意思,他认为现有的 Linux/BSD 还没有使用它。
但是如果你的 Linux/BSD 发行版已经使用了这些技术,那么你自己编译是没有用的。现在他们中的许多人都这样做了,这里是Ubuntu 在其服务器版本中所做的一个方便的概述(参见标题堆栈和堆保护器、指针混淆、5x ASLR 和 4x“内置...”)。
最后,并非所有软件都可以使用这些技术。如果您想自己执行此操作,那么您可能会花费大量时间来追踪难以捉摸的错误。您的上游分发供应商能够更好地应用这些更改,并为生成的二进制文件提供足够的测试和质量保证,以使其稳定。
这些技术很有帮助,并且确实增加了一层有意义的纵深防御。但是不要为此自己从源代码编译。而是选择具有良好安全工程思维的 Linux/BSD 发行版,并且已经根据您的需要应用了相关的保护机制(最小攻击面、缓冲区溢出保护,也许是 SELinux/AppArmor)。
[是否] 为应用程序(具有特定编译标志)编译您自己的二进制文件而不是使用“主流二进制文件”会使攻击者更难利用缓冲区溢出?
这取决于操作系统、编写应用程序的语言和编译器。
首先,编程语言必须是编译语言:C、C++、C#、Java、Objective-C、Delphi 等。解释型语言(JavaScript、PHP、Ruby 等)是从源代码运行(未编译),所以要修改内存行为你必须改变解释器的设置或源。显然,没有编译,没有保护。
其次,编程语言必须允许手动内存管理。Java 和 C# 使用自动内存管理,防止了基本的缓冲区溢出漏洞。C 和 C++ 允许手动管理动态内存。如果编程语言具有托管内存,那么编译将无济于事。
第三,应用程序中使用的编译器或库必须支持一些扩展的动态内存管理监视或控制。Microsoft C++、Intel C++、GNU C++、LLVM Clang C++ 编译器都支持 -fstack-protector,IBM XL C++ 支持 -qstackprotect。一些库,如 Avaya Labs 的 LibSafe,也有保护堆栈的机制。如果编译器不支持堆栈保护,并且没有可用的堆栈保护库,那么编译将无济于事。
第四,操作系统可能会提供一些保护。一些操作系统已经通过可执行位、堆栈基指针和将返回地址保存在寄存器中来保护堆栈。如果操作系统已经在保护堆栈,编译将无济于事。
它比地址空间布局随机化效率低吗?
地址空间随机化是一种防止攻击者使用众所周知的地址调用系统函数的技术。例如,如果 setuid() 始终位于地址 0xDEADBEEF,则攻击者可以尝试用 0xDEADBEEF 覆盖返回地址并执行 setuid。随机化不会防止缓冲区溢出,它只是防止使用静态地址值。
操作系统内核中的新内存管理策略是否使这一点变得毫无意义?
不必要。这取决于操作系统。一些操作系统更注重性能而不是安全性。这些操作系统实际上可能会通过内存管理技巧引入更多漏洞。
他所说的可能是在某些情况下原始二进制文件没有使用可用的保护进行编译,您可以重新编译它以启用它们。
现在的编译器和操作系统提供了许多高级的安全保护,但有时由开发人员将它们应用到他的应用程序上,其中一些是在编译时应用的,默认情况下可能会或可能不会“打开”。
也许最著名的例子是防止基于堆栈的缓冲区溢出。这种保护纯粹是基于编译器的:在编译时,额外的指令被添加到程序中,以便在堆栈中添加和检查金丝雀,这减轻了某种溢出(的结果)。但是,只有在例如 linux 中应用 -fstack-protector 标志或在 Windows 中应用著名的 /GS 标志时,才会添加此保护。在最近的操作系统和编译器中,这些标志默认是打开的,但情况并非总是如此。
其他这样的选项是(对于 gcc)-D_FORTIFY_SOURCE=2 对某些易受攻击的 glibc 函数添加一些健全性检查并防止其他一些缓冲区溢出,以及 -Wl,-z,relro 将某些内存区域标记为只读。
他可能意味着的另一件事是,您可以在编译时包含其他更安全的库,例如 libsafe,它使用具有更多健全性检查的“更安全”版本覆盖某些已知的不安全函数。
此外,Windows 中的 DEP 和 ASLR 使用 /NXCOMPAT 和 /DYNAMICBASE 启用。现在我认为visual studio默认启用了这些,但事实并非如此。您会对仍然没有启用这些标志的应用程序和 dll 的数量感到惊讶——甚至是由 adobe 和 microsoft 等公司提供的。恰当的例子:Adobe Flash 仅在版本 10 中获得了这些保护,之前未启用它们。漏洞利用编写者在寻找可利用目标(或提升权限等的方法)时寻找此类 dll 是一种常见的做法。