什么是缓冲区溢出? 缓冲区以前可能被定义为“包含相同数据类型的实例的一个连续计算机内存块”。在 C 和 C++ 中,缓冲区通常是使用数组和诸如 malloc() 和 new 这样的内存分配例程来实现的。极其常见的缓冲区种类是简单的字符数组。溢出 是指数据被添加到分配给该缓冲区的内存块之外。
如果攻击者能够导致缓冲区溢出,那么它就能控制程序中的其他值。虽然存在许多利用缓冲区溢出的方法,不过最常见的方法还是“stack-smashing”攻击。Elias Levy (又名为 Aleph One)的一篇经典文章“Smashing the Stack for Fun and Profit”解释了 stack-smashing 攻击,Elias Levy 是 Bugtraq 邮件列表(请参阅 参考资料 以获得相关链接)的前任主持人。
为什么缓冲区溢出如此常见? 在几乎所有计算机语言中,不管是新的语言还是旧的语言,使缓冲区溢出的任何尝试通常都会被该语言本身自动检测并阻止(比如通过引发一个异常或根据需要给缓冲区添加更多空间)。但是有两种语言不是这样:C 和 C++ 语言。C 和 C++ 语言通常只是让额外的数据乱写到其余内存的任何位置,而这种情况可能被利用从而导致恐怖的结果。更糟糕的是,用 C 和 C++ 编写正确的代码来始终如一地处理缓冲区溢出则更为困难;很容易就会意外地导致缓冲区溢出。除了 C 和 C++ 使用得 非常 广泛外,上述这些可能都是不相关的事实;例如,Red Hat Linux 7.1 中 86% 的代码行都是用 C 或 C ++ 编写的。因此,大量的代码对这个问题都是脆弱的,因为实现语言无法保护代码避免这个问题。
在 C 和 C++ 语言本身中,这个问题是不容易解决的。该问题基于 C 语言的根本设计决定(特别是 C 语言中指针和数组的处理方式)。由于 C++ 是最兼容的 C 语言超集,它也具有相同的问题。存在一些能防止这个问题的 C/C++ 兼容版本,但是它们存在极其严重的性能问题。而且一旦改变 C 语言来防止这个问题,它就不再是 C 语言了。许多语言(比如 Java 和 C#)在语法上类似 C,但它们实际上是不同的语言,将现有 C 或 C++ 程序改为使用那些语言是一项艰巨的任务。
然而,其他语言的用户也不应该沾沾自喜。有些语言存在允许缓冲区溢出发生的“转义”子句。Ada 一般会检测和防止缓冲区溢出(即针对这样的尝试引发一个异常),但是不同的程序可能会禁用这个特性。C# 一般会检测和防止缓冲区溢出,但是它允许程序员将某些例程定义为“不安全的”,而这样的代码 可能 会导致缓冲区溢出。因此如果您使用那些转义机制,就需要使用 C/C++ 程序所必须使用的相同种类的保护机制。许多语言都是用 C 语言来实现的(至少部分是用 C 语言来实现的 ),并且用任何语言编写的所有程序本质上都依赖用 C 或 C++ 编写的库。因此,所有程序都会继承那些问题,所以了解这些问题是很重要的。
导致缓冲区溢出的常见 C 和 C++ 错误 从根本上讲,在程序将数据读入或复制到缓冲区中的任何时候,它需要在复制 之前 检查是否有足够的空间。能够容易看出来的异常就不可能会发生 —— 但是程序通常会随时间而变更,从而使得不可能成为可能。
遗憾的是,C 和 C++ 附带的大量危险函数(或普遍使用的库)甚至连这点(指检查空间)也无法做到。程序对这些函数的任何使用都是一个警告信号,因为除非慎重地使用它们,否则它们就会成为程序缺陷。您不需要记住这些函数的列表;我的真正目的是说明这个问题是多么普遍。这些函数包括 strcpy(3)、strcat(3)、sprintf(3)(及其同类 vsprintf(3))和 gets(3)。scanf()函数集(scanf(3)、fscanf(3)、sscanf(3)、vscanf(3)、vsscanf(3) 和 vfscanf(3))可能会导致问题,因为使用一个没有定义最大长度的格式是很容易的(当读取不受信任的输入时,使用格式“%s”总是一个错误)。
Microsoft 的库中还有在相应平台上导致同类问题的其他函数(这些函数包括 wcscpy()、_tcscpy()、_mbscpy()、wcscat()、_tcscat()、_mbscat() 和 CopyMemory())。注意,如果使用 Microsoft 的 MultiByteToWideChar() 函数,还存在一个常见的危险错误 —— 该函数需要一个最大尺寸作为字符数目,但是程序员经常将该尺寸以字节计(更普遍的需要),结果导致缓冲区溢出缺陷。
另一个问题是 C 和 C++ 对整数具有非常弱的类型检查,一般不会检测操作这些整数的问题。由于它们要求程序员手工做所有的问题检测工作,因此以某种可被利用的方式不正确地操作那些整数是很容易的。特别是,当您需要跟踪缓冲区长度或读取某个内容的长度时,通常就是这种情况。但是如果使用一个有符号的值来存储这个长度值会发生什么情况呢 —— 攻击者会使它“成为负值”,然后把该数据解释为一个实际上很大的正值吗?当数字值在不同的尺寸之间转换时,攻击者会利用这个操作吗?数值溢出可被利用吗? 有时处理整数的方式会导致程序缺陷。