缓冲区溢出漏洞浅析(三)

前面发了两篇都是关于C语言缓冲区溢出的文章,有的同行问,是否C#、Java语言有缓冲区溢出问题吗?答案是否定的。由于C#、Java语言需要虚拟机去执行,属于托管语言,虚拟机会自动检查边界。一般不会出现缓冲区溢出。但是通过JNI调用本机代码、以及JVM/CLR/ETC等虚拟机可能出现这些问题。所以缓冲区溢出最突出的语言是C和C++。

下面我们再深一步了解缓冲区溢出的原理。

缓冲区是一块连续的计算机中的内存区域,可保存相同数据类型的多个实例,例如代码段、数据段、堆和栈。栈存放函数变量和实参、堆是提供动态申请内存、静态数据区存放全局或静态数据。C/C++语言中,通常使用字符数组和malloc/new之类内存分配函数申请缓冲区。缓冲区溢出是指数据被添加到分配给该缓冲区的内存块之外。缓冲区溢出是最常见的程序缺陷,严重程序缺陷导致可能被攻击者利用,成为安全漏洞。

按照冯·诺依曼存储程序原理,程序代码是作为二进制数据存储在内存的,同样程序的数据也在内存中,因此直接从内存的二进制形式上是无法区分哪些是数据哪些是代码的,这也为缓冲区溢出攻击提供了可能。 恶意攻击者利用缓冲区溢出攻击的最终目的就是希望系统能执行这块可读写内存中已经被蓄意设定好的恶意代码。

栈帧结构的引入为高级语言中实现函数或过程调用提供直接的硬件支持,但由于将函数返回地址保存在程序员可见的堆栈中,这样给系统安全带来隐患。攻击者可以将函数返回地址修改为指向一段精心安排的恶意代码,则可达到危害系统的目的。此外,堆栈的正确恢复依赖于压栈的EBP值的正确性,但EBP域邻近局部变量,若编程中有意无意地通过局部变量的地址偏移窜改EBP值,则程序的行为将变得非常危险。

栈存取一般是按照向下增长的方式存取的,例如push 0x0021fd67(0x0021fd67是一个变量的值)到栈,按照如图进入栈空间,高字节在高位,低字节在低位,这种方式就是所谓的“大端”方式。为什么栈是这种存取方式呢?其实原因就是当初设计计算机内存如何高效使用时,堆和栈公用相同的空间,最大利用方式是堆从某一个地址增长,而栈从某一个高地址减小,这样向中间增长,保证堆和栈都能先申请先利用,直到最后没有空间(当然这时候发生堆栈溢出,程序会崩溃),来保证最大的内存利用率。

 

这里补充一下大端和小端的知识。大端/小端就是Big-Endian/Little-Endian问题。大端:高位字节存在高地址上,低位字节存在低地址上
小端:低位字节存在高地址上,高位字节存在低地址上 
有两种常见的方法来判断是大端还是小端,下面代码来自于网络。
方法一:使用指针
 

int x=1;
if(*(char*)&x==1)
    printf("little-endian\n");
else
    printf("big-endian\n");

方法二:使用联合
 

union{
    int i;
    char c;
}x;
x.i=1;
if(x.c==1)
    printf("little-endian\n");
else
    printf("big-endian\n");

由于C/C++语言没有数组越界检查机制,当向局部数组缓冲区里写入的数据超过为其分配的大小时,就会发生缓冲区溢出。攻击者可利用缓冲区溢出来窜改进程运行时栈,从而改变程序正常流向,轻则导致程序崩溃,重则系统特权被窃取。

 除通过使堆栈缓冲区溢出而更改返回地址外,还可改写局部变量(尤其函数指针)以利用缓冲区溢出缺陷。

缓冲区溢出有常见3种:

(1)激活记录(Activation Records)

这是一种比较常见的溢出方式,每当调用一个函数时,在堆栈中留下一个激活记录,它包含了函数结束时返回的地址。攻击者通过溢出这些自动变量,使这个返回地址指向攻击代码。通过改变程序的返回地址,当函数调用结束时,程序就跳转到攻击者设定的地址,而不是原先的地址。

(2)函数指针(Function Pointers)

在C语言中,void(*foo)()声明了一个返回值为void函数指针的变量foo。函数指针可以用来定位任何地址空间,所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出这个缓冲区来改变函数指针。在某一时刻,当程序通过函数指针调用函数时,程序的流程就按攻击者的意图实现转移。

(3)长跳转缓冲区(Longjmp buffers)

在C语言中包含了一个简单的检验/恢复系统,即setjmp/longjmp,其作用是在检验点设定setjmp(buffer),而用longjmp(buffer)来恢复检验点。但是如果攻击者能够进入缓冲区的空间,longjmp(buffer)实际是跳转到攻击者的代码。和函数指针一样,longjmp缓冲区能够指向任何地方,所以攻击者所要做的就是找到一个可供溢出的缓冲区。

在进行溢出缓冲区攻击时,常常需要在一个字符串里综合了代码植入和激活记录。攻击者需要定位一个可供溢出的自动变量,然后向程序传递一个很大的字符串,在引发缓冲区溢出改变激活记录的同时植入代码。代码植入和缓冲区溢出不一定要在一次动作内完成。攻击者可以在一个缓冲区内放置代码,这时不能溢出缓冲区。然后,攻击者通过溢出另外一个缓冲区来转移程序的指针。这种方法一般用来解决可供溢出的缓冲区不够大的情况。

如果攻击者想使用已经驻留的代码而不是从外部植入代码,就必须先把代码参数化。如在libc中的部分代码段会执行exec(some),其中some就是参数。攻击者使用缓冲区溢出改变程序的参数,再利用另一个缓冲区溢出使程序指针指向libc中特定的代码段

下面举个例子:

// wshell.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

int fun(void)

{

    int a=10,*p=NULL;

    a=20;

    p=(int*)((char *)&a + 16);

    *p+=16;

    return a;

}

int _tmain(int argc, _TCHAR* argv[])

{

    int b=11;

    b = fun();

    printf("printf aaa\n");

    printf("printf bbb\n");

    return 0;

}

通过VS2012编辑编译、在调试模式下,可以查看到汇编代码如下:

 

ESP扩展栈指针寄存器,存放的是函数栈顶顶指针。fun函数调用返回地址是0x0021149A,而fun函数执行完成后,返回地址是调用函数指令后跟的指令地址,要跳转到0x002114AA地址,两条指令之间相差16。变量a的地址是0x004bf9b8,fun函数返回地址存放在0x004bf9b8+16=0x004bf9C8中,所以在fun函数中,要取变量a地址,相加16之后,得到返回地址。

由于Vistual  C++编译后会检查指针越界等缺陷,所以会抛出异常,可以忽略掉异常,继续执行,只打你出printf bbb字符串。也就是第一个字符串通过fun函数地址跳转,跳转到第二个打印语句。

 

 

如何防范缓冲区溢出呢?

防范缓冲区溢出问题的准则是:

确保对输入数据做边界检查。相对于系统安全,不要担心因为添加检查而影响程序效率。不要为接收数据预留相对过小的缓冲区,大量数据处理应通过malloc/new分配堆空间来解决;

在将数据读入或复制到目标缓冲区前,检查数据长度是否超过缓冲区空间。

检查以确保不会将过大的数据传递给别的程序,尤其是第三方商用软件库——不要设想关于其他人软件行为的任何事情。因为很多商用库也是程序员编写的,早期对于风险防范关注较少,难免出现各种漏洞。

若有可能,改用具备防止缓冲区溢出内置机制的高级语言(Java、C#等)。但许多语言依赖于C库,或具有关闭该保护特性的机制(为速度而牺牲安全性)。

可以借助某些底层系统机制或检测工具(如对C数组进行边界检查的编译器)。许多操作系统(包括Linux和Solaris)提供非可执行堆栈补丁,但该方式不适于这种情况:攻击者利用堆栈溢出使程序跳转到放置在堆上的执行代码。此外,存在一些侦测和去除缓冲区溢出漏洞的静态分析工具(例如北大CoBOT、Checkmarx等)和动态工具,甚至采用grep命令自动搜索源代码中每个有问题函数的实例。

但是即使采用上面这些保护手段,程序员自身也可能犯其他许多错误,导致引入缺陷。例如,当使用有符号数存储缓冲区长度或某个待读取内容长度时,攻击者可将其变为负值,从而使该长度被解释为很大的正值。经验丰富的程序员还容易过于自信地使用某些危险的库函数,如对其添加自己总结编写的检查,或错误地推论出使用的具有潜在危险的函数在某些特殊情况下是"安全"的。

 

关注安全,关注作者

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

manok

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值