发布网友 发布时间:2022-05-06 18:19
共1个回答
热心网友 时间:2023-10-14 19:30
缓冲区是用户为程序运行时在计算机中申请的一段连续的内存,它保存了给定类型的数据。缓冲区溢出指的是一种常见且危害很大的系统攻击手段,通过向程序的缓冲区写入超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其他的指令,以达到攻击的目的。更为严重的是,缓冲区溢出攻击占了远程网络攻击的绝大多数,这种攻击可以使得一个匿名的Internet用户有机会获得一台主机的部分或全部的控制权!由于这类攻击使任何人都有可能取得主机的控制权,所以它代表了一类极其严重的安全威胁。
缓冲区溢出攻击的目的在于扰乱具有某些特权运行的程序的功能,这样可以使得攻击者取得程序的控制权,如果该程序具有足够的权限,那么整个主机就被控制了。一般而言,攻击者攻击root程序,然后执行类似“exec(sh)”的执行代码来获得root的shell。为了达到这个目的,攻击者必须达到如下的两个目标:在程序的地址空间里安排适当的代码;通过适当地初始化寄存器和存储器,让程序跳转到事先安排的地址空间执行。根据这两个目标,可以将缓冲区溢出攻击分为以下3类。
【缓冲区溢出分类】
控制程序转移到攻击代码
这种方法指在改变程序的执行流程,使之跳转到攻击代码。最基本方法的就是溢出一个没有边界检查或者其他弱点的缓冲区,这样就扰乱了程序的正常的执行顺序。通过溢出一个缓冲区,攻击者可以用近乎暴力的方法改写相邻的程序空间而直接跳过了系统的检查。
1.2.1激活纪录(Activation Records)
每当一个函数调用发生时,调用者会在堆栈中留下一个激活纪录,它包含了函数结束时返回的地址。攻击者通过溢出这些自动变量,使这个返回地址指向攻击代码。通过改变程序的返回地址,当函数调用结束时,程序就跳转到攻击者设定的地址,而不是原先的地址。这类的缓冲区溢出被称为“stack smashing attack”,是目.前常用的缓冲区溢出攻击方式。
1.2.2函数指针(Function Pointers)
C语言中,“void (* foo)()”声明了一个返回值为void函数指针的变量foo。函数指针可以用来定位任何地址空间,所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出这个缓冲区来改变函数指针。在某一时刻,当程序通过函数指针调用函数时,程序的流程就按攻击者的意图实现了!它的一个攻击范例就是在Linux系统下的super probe程序。
1.2.3长跳转缓冲区(Longjmp buffers)
在C语言中包含了一个简单的检验/恢复系统,称为setjmp/longjmp。意思是在检验点设定“setjmp(buffer)”,用“longjmp(buffer)”来恢复检验点。然而,如果攻击者能够进入缓冲区的空间,那么“longjmp(buffer)”实际上是跳转到攻击者的代码。象函数指针一样,longjmp缓冲区能够指向任何地方,所以攻击者所要做的就是找到一个可供溢出的缓冲区。一个典型的例子就是Perl 5.003,攻击者首先进入用来恢复缓冲区溢出的的longjmp缓冲区,然后诱导进入恢复模式,这样就使Perl的解释器跳转到攻击代码上了!
最简单和常见的缓冲区溢出攻击类型就是在一个字符串里综合了代码殖入和激活纪录。攻击者定位一个可供溢出的自动变量,然后向程序传递一个很大的字符串,在引发缓冲区溢出改变激活纪录的同时殖入了代码。这个是由Levy指出的攻击的模板。因为C语言在习惯上只为用户和参数开辟很小的缓冲区,因此这种漏洞攻击的实例不在少数。
代码殖入和缓冲区溢出不一定要在一次动作内完成。攻击者可以在一个缓冲区内放置代码,这是不能溢出缓冲区。然后,攻击者通过溢出另外一个缓冲区来转移程序的指针。这种方法一般用来解决可供溢出的缓冲区不够大的情况。
如果攻击者试图使用已经常驻的代码而不是从外部殖入代码,他们通常有必须把代码作为参数化。举例来说,在libc中的部分代码段会执行“exec(something)”,其中something就是参数。攻击者然后使用缓冲区溢出改变程序的参数,利用另一个缓冲区溢出使程序指针指向libc中的特定的代码段。 内存溢出已经是软件开发历史上存在了近40年的“老大难”问题,象在“红色代码”病毒事件中表现的那样,它已经成为黑客攻击企业网络的“罪魁祸首”。
如在一个域中输入的数据超过了它的要求就会引发数据溢出问题,多余的数据就可以作为指令在计算机上运行。据有关安全小组称,操作系统中超过50%的安全漏洞都是由内存溢出引起的,其中大多数与微软的技术有关。
微软的软件是针对台式机开发的,内存溢出不会带来严重的问题。但现有台式机一般都连上了互联网,内存溢出就为黑客的入侵提供了便利条件。 数据类型超过了计算机字长的界限就会出现数据溢出的情况。导致内存溢出问题的原因有很多,比如:
(1) 使用非类型安全(non-type-safe)的语言如 C/C++ 等。
(2) 以不可靠的方式存取或者复制内存缓冲区。
(3)编译器设置的内存缓冲区太靠近关键数据结构。 1.内存溢出问题是 C 语言或者 C++ 语言所固有的缺陷,它们既不检查数组边界,又不检查类型可靠性(type-safety)。众所周知,用 C/C++ 语言开发的程序由于目标代码非常接近机器内核,因而能够直接访问内存和寄存器,这种特性大大提升了 C/C++ 语言代码的性能。只要合理编码,C/C++应用程序在执行效率上必然优于其它高级语言。然而,C/C++ 语言导致内存溢出问题的可能性也要大许多。其他语言也存在内存溢出问题,但它往往不是程序员的失误,而是应用程序的运行时环境出错所致。
2. 当应用程序读取用户(也可能是恶意攻击者)数据,试图复制到应用程序开辟的内存缓冲区中,却无法保证缓冲区的空间足够时(换言之,假设代码申请了 N 字节大小的内存缓冲区,随后又向其中复制超过 N 字节的数据)。内存缓冲区就可能会溢出。想一想,如果你向 12 盎司的玻璃杯中倒入 16 盎司水,那么多出来的 4 盎司水怎么办?当然会满到玻璃杯外面了!
3. 最重要的是,C/C++编译器开辟的内存缓冲区常常邻近重要的数据结构。假设某个函数的堆栈紧接在在内存缓冲区后面时,其中保存的函数返回地址就会与内存缓冲区相邻。此时,恶意攻击者就可以向内存缓冲区复制大量数据,从而使得内存缓冲区溢出并覆盖原先保存于堆栈中的函数返回地址。这样,函数的返回地址就被攻击者换成了他指定的数值;一旦函数调用完毕,就会继续执行“函数返回地址”处的代码。非但如此,C++ 的某些其它数据结构,比如 v-table 、例外事件处理程序、函数指针等,也可能受到类似的攻击。
具体的例子
请思考:以下代码有何不妥之处?
void CopyData(char *szData) {
char cDest[32];
strcpy(cDest,szData);
// 处理 cDest
...
}
奇怪,这段代码好像没什么不对劲啊!确实,只有调用上述 CopyData() 才会出问题。例如:这样使用 CopyData() 是安全的:
char *szNames[] = {Michael,Cheryl,Blake};
CopyData(szName[1]);
为什么呢?因为数组中的姓名(Michael、Cheryl、Blake)都是字符串常量,而且长度都不超过 32 个字符,用它们做 strcpy() 的参数总是安全的。再假设 CopyData 的唯一参数 szData 来自 socket套接字或者文件等不可靠的数据源。由于 strcpy 并不在乎数据来源,只要没遇上空字符,它就会一个字符一个字符地复制 szData 的内容。此时,复制到 cDest 的字符串就可能超过 32 字符,进而导致内存缓冲区 cDest 的溢出;溢出的字符就会取代内存缓冲区后面的数据。不幸的是,CopyData 函数的返回地址也在其中!于是,当 CopyData函数调用完毕以后,程序就会转入攻击者给出的“返回地址”,从而落入攻击者的圈套!授人以柄,惨!
前面提到的其它数据结构也可能受到类似的攻击。假设有人利用内存溢出漏洞覆盖了下列 C++ 类中的 v-table :
void CopyData(char *szData) {
CFoo foo;
char cDest[32];
strcpy(cDest,szData);
foo.Init();
}
与其它 C++ 类一样,这里的 CFoo 类也对应一个所谓的 v-table,即用于保存一个类的全部方法地址的列表。若攻击者利用内存溢出漏洞偷换了 v-table 的内容,则 CFoo 类中的所有方法,包括上述 Init() 方法,都会指向攻击者给出的地址,而不是原先 v-table 中的方法地址。顺便说一句,即使你在某个 C++ 类的源代码中没有调用任何方法,也不能认为这个类是安全的,因为它在运行时至少需要调用一个内部方法——析构器(destructor)!当然,如果真有一个类没有调用任何方法,那么它的存在意义也就值得怀疑了。