缓冲区溢出的漏洞随着冯·诺依曼 1 构架的出 现就已经开始出现了。 在1988年随着莫里斯互联网蠕虫的广泛传播他们开始声名狼藉。不幸的是, 同样的这种攻击一直持续到今天。 1999年的17个CERT(Computer Emergency Response Team, 卡内基梅隆大学计算机紧急响应小组)的安全通告中, 他们中的10个直接是由软件的缓冲区溢出而 导致的。到目前为止,大部分的缓冲区溢出的攻击都是基于摧毁栈的方式。
大部分现代计算机系统使用栈来给进程传递参数并且存储局部变量。 栈是一种在进程映象内存的高地址内的后进先出(LIFO)的缓冲区。 当程序调用一个函数时一个新的“栈帧”会被创建。这个栈帧包含着 传递给函数的各种参数和一些动态的局部变量空间。“栈指针”记录着当前 栈顶的位置。 由于栈指针的值会因为新变量的压入栈顶而经常的变化,许多实现也提供了 一种"帧指针"来定位在栈帧的起始位置,以便局部变量可以更容易的被访问。 1调用函数的返回地址也同样存储在栈中, 由于在函数中的局部变量覆盖了函数的返回地址成为了栈溢出的一个原因, 这就潜在的准许了一个恶意用户可以执行他(她)所想运行的任何代码。
虽然基于栈的攻击是目前最广泛的,这也可以使基于堆的攻击(malloc/ free)变成可能。
C程序语言并不像其他一些编程语言一样自动的做数组或者指针的边 界检查。另外,C标准库还具有相当一些非常危险的操作函数。
strcpy(char *dest, const char *src) |
可导致dest缓冲区溢出 |
strcat(char *dest, const char *src) |
可导致dest缓冲区溢出 |
getwd(char *buf) |
可导致buf缓冲区溢出 |
gets(char *s) |
可导致s缓冲区溢出 |
[vf]scanf(const char *format, ...) |
可导致参数溢出 |
realpath(char *path, char resolved_path[]) |
可导致path缓冲区溢出 |
[v]sprintf(char *str, const char *format, ...) |
可导致str缓冲区溢出 |
下面的示例代码包含了一个缓冲区溢出的情况,它会覆盖函数的返回地址并且 立即跳过了紧随此函数之后调用。(授权于5)
#include <stdio.h>
void manipulate(char *buffer) {
char newbuffer[80];
strcpy(newbuffer,buffer);
}
int main() {
char ch,buffer[4096];
int i=0;
while ((buffer[i++] = getchar()) != '\n') {};
i=1;
manipulate(buffer);
i=2;
printf("The value of i is : %d\n",i);
return 0;
}
让我们来查看一下如果在输入回车之前输入160个空格后这个小程序 的内存映象是个什么样子。
[XXX figure here!]
很明显更多的恶意输入能被设计出执行实际的编译指令(例如 exec(/bin/sh))。
对于栈溢出的最直接的解决方法就是总是使用长度有限的内存和 字符串复制函数。strncpy和strncat
是C标准库的一部分。 这些函数接收一个不大于目标缓冲区长度的值作为参数。这些函数会从
源地址复制此值长的字节数到目标地址。然而这些函数还是有一些问题。
如果输入缓冲区的长度和目标缓冲区的一样长则函数不保证两者都以NUL 作为结束符。
长度参数在strncpy和strncat函数中同样的不一致很容易导致程序员在
正常使用时感到困惑。同时当复制一个较短的字符串到一个很大的缓冲 区中时相对于strcpy也有很重大的性能损失, 因为strncpy会用NUL填充所指定的长度。
在OpenBSD中,另一个内存复制的实现已经规避了这些问题。 函数strlcpy和strlcat
保证了当指定了非零的长度参数时目标字符串总是以NUL作为结束符。
关于这些函数的更多信息请参考7。OpenBSD 的strlcpy和strlcat
自从FreeBSD3.3的版本已经被引入了。
不幸的是扔然有相当数量的代码在广泛使用盲目的内存复制功能 而不是我们所提及到的任何有限制的复制例程。幸运的是还有另一个解 决方案。有一些编译器插件和库在C/C++中一直在做运行时的边界检查。
作为gcc代码生成器的一个小补丁StackGuard就是这样一款插件。 源自StackGuard 站点:
“StackGuard 检测并靠着保护在栈中的返回地址不 受到更改来防御针对栈的剧烈攻击。当函数被调用时StackGuard在栈中 紧邻返回地址放置了一个‘canary’(哨兵或探针)。如果函数返回时哨兵 已经被改变了,就是有针对栈的攻击实施了,那么程序会在syslog中发 出一个入侵警报并且停止运行。
“StackGuard作为gcc代码生成器的一个小补丁来实 现,特别是function_prolog()和function_epilog()程序。 增强的function_prolog()在函数开始时在栈中安装了哨兵,而 function_epilog()在函数退出时检查哨兵的完整性。任何其他破坏返 回地址的行为在函数返回时就这样被检测到了。
使用StackGuard重新编译你的程序可以有效的防止大部分的缓冲 区溢出的攻击,但是这仍然是个折衷的办法。
基于编译器的机制对于不能重新编译的只有二进制的软件完
全无用。对于这些情况仍还是有很多库可以对C库中的不安全的函数 (strcpy, fscanf, getwd等)重新实现并确保这些函数决不回写 栈指针。
libsafe
libverify
libparanoia
不幸的是这些基于库的防护有一些缺点。这些库仅仅保护和安全 相关的一小部分集合,他们忽略了实际的问题。如果程序使用参数 -fomit-frame-pointer进行编译的话这些防护也许会失败。同样,环境 变量LD_PRELOAD和LD_LIBRARY_PATH也可以被用户取消或者重置。
本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.