什么是“栈溢出”和“堆溢出”?

栈溢出是由于C语言系列没有内置检查机制来确保复制到缓冲区的数据不得大于缓冲区的大小,因此当这个数据足够大的时候,将会溢出缓冲区的范围。

堆溢出的产生是由于过多的函数调用,导致调用堆栈无法容纳这些调用的返回地址,一般在递归中产生。堆溢出很可能由无限递归(Infinite recursion)产生,但也可能仅仅是过多的堆栈层级。

int f(int x)

{

int a[10]

a[11] = x

}

这个就是栈溢出,x被写到了不应该写的地方。在特定编译模式下,这个x的内容就会覆盖f原来的返回地址。也就是原本应该返回到调用位置的f函数,返回到了x指向的位置。一般情况下程序会就此崩溃。但是如果x被有意指向一段恶意代码,这段恶意代码就会被执行。

堆溢出相对比较复杂,因为各种环境堆的实现都不完全相同。但是程序管理堆必须有额外的数据来标记堆的各种信息。堆内存如果发生上面那样的赋值的话就有可能破坏堆的逻辑结构。进而修改原本无法访问的数据。

int f(char *s, int n)

{

char a[10]

memcpy(a, s, n)

...

这个是栈溢出比较真实一点的例子,如果传入的数据长度大于10就会造成溢出,进而改变f的返回地址。只要事先在特定地址写入恶意代码,代码就会被执行。

其实堆溢出与格式串溢出特像,就是“一个format strings“的bug可以使往任何数据写到任何地方 你的症状是因为你浏览的页面的脚本中的函数出现了过度的递归调用,比如递归了4096次,然后就溢出了 一、堆溢出后的后果; 现在的系统管理堆,为了查询的高效快速,一般都使用的双向链表结构。我们来看 双向链表管理的时候的删除操作。*a,*b,*p1,*p2,*c,*d都是指针,考虑双向链表, a,b--->p1,p2--->c,d,其中由双向链表有*b=p1,*p1=a,*p2=c。如果是空闲内存链表, 那么申请使用p1、p2指向的内存,或者如果是使用内存链表,释放p1、p2指向的内存, 都会从这个链表中删除p1,p2。删除后的链表是a,b--->c,d,应该有内存改写操作: *b=c,*c=a。这时代码是经过链表检索从a,b得到的p1,p2,一般为了高效等就不会记忆 a,b,因为双向链表就是为了从一个结点可以方便的得到上级和下级结点。所以那两条 内存操作都将会转换成p1、p2相关的操作。根据前面得到的信息,经过简单的代换就可 以得到我们需要的代码: *b=p1,*p1=a,*p2=c,b=a+1 *b=c--->*(a+1)=c--->*(*p1+1)=*p2 *c=a--->*(*p2)=*p1 两次指针,可能看起来不是很习惯,再把*p1记成p1,*p2记成p2,那么就有: *p2=p1 *(p1+1)=p2 这就是堆溢出后导致的两个写内存操作,以后考虑堆溢出,不考虑细节的话基本上就可 以用这两条代码代替。这时的p1、p2已经不是指那链表的位置了,而是指里面的内容。 二、利用。 要利用堆溢出,就考虑上面总结的两条代码的利用就是了。 1、改写内存参数,直接利用。可以改写重要变量,还有一个字符串的长度,格式 串等,让起改写后再产生别的溢出等。这要改写一般只能改写数据段里面的东西,因为 这位置相对固定并且可写。 2、改写函数指针性质的调用入口等。这个要写远程通用程序的难点还是shellcode 的定位问题,只要有一个定位shellcode的办法,基本上就可以有一种堆溢出的利用办 法。 (1)、unix等系统下面的s位程序的本地溢出。这个应该算比较简单好利用的一个 应用了。shellcode通过环境块等传递,就可以比较精确的找到shellcode的位置。这样 只需要改写一个函数指针内容,让其指向shellcode就可以了。 (2)、可以设置对一些可用于传递的shellcode域的读写断点,发现如果上面两条 指令执行完后有如: lea ebx,[ebp+0xxx] push ebx call dword ptr [0xxx] 这样的代码,并且那ebx指向可传递shellcode的域,就能方便的利用,这样两个指针就 可以分别选取0xxxx和一个具有jmp ebx功能的地址。注意可能会要考虑前面两条 指令或者那链表操作时候可能发生的异常。 (3)、利用p1、p2自身信息定位。 能够覆盖p1、p2,一般p1、p2前面会有一段空间能够控制,如果覆盖的长度可以控 制。可以考虑保留p2的高位,改写其低位让起指向shellcode。这个要注意这时 shllcode的前面4字节会被调用函数入口地址覆盖,所以不是所有会调用的函数入口都 可以用。 (4)、利用SEH指针。 其实那双向链表经常是a,b--->p1,p2--->a,b的形式,所以把SEH链首fs:0,位置一 般在ds:7ffx,同一系统比较固定覆盖,然后利用p2原来的值。这个有很多问题,跳 到p1后,p1不是可控制代码,还有系统的SEH处理程序会检测fs:0首指针,如果不是指 向堆栈位置,拒绝执行。主要是这个思路看能不能有别的办法。 这个又考虑了覆盖fs:0首指针的低字节,这样异常结构链表位置就发生了变化,如果指 向的另一块堆栈位置能够控制,就可以利用伪造异常链表的办法获得控制。其实单字节 的溢出覆盖ebp低字节的利用原理也是差不多。而堆栈里面,往往会有动态BUFF拷贝过 去的数据可以控制,所以这个要求也是容易办到。现在确定的是这个指针的位置不是固 定的,有一定的变化范围。可能是0x7ff93000 、0x7ff9b000这样的值,但变化不是多 大。 (5)、改写PEB指针,跳转到ret 0xxxx这样的指令处,通过改变堆栈指针的办法, 利用堆栈里面的可控制数据得到控制。 三、实际应用例子。.asp的分块编码堆溢出,其实这个漏洞不应该叫分块编码漏 洞,因为漏洞原因不在于分块编码。 上面(2)、(3) 基本上都是可行的,但因为.asp的特殊之处,又使得上面的 (2)、(3)对于这个漏洞不好使用。 1、对于(2),由于.asp的那段溢出没安装好异常处理程序,所以在那链表结构的 处理过程中一般会发生异常,从而程序结束。如果精心构造覆盖p1、p2和用于链表处理 的标记、长度的数据,可以不会发生异常,但处理完后马上又被别的不容易控制的数据 覆盖,再次导致异常。 这个现在在win2000+sp2的wam.dll里面找到一处可以利用的代码,但只能是下次重新连 接才能得到控制。 2、对于(3),由于申请的0字节内存,加上内存管理的8字节对齐,可以得到8个字 节,再除去两条代码写内存浪费4字节,只剩下4字节,所以基本上不能用。利用另一个 方式,可以申请大量内存,但这时只能是覆盖0xc000字节(问题在这),因为内存管理 的8字节对齐,不可能只覆盖p2的低字节。 3、对于(4),已经写出利用程序。 4、对于(5),显然是可利用的,并且可能会写得比较通用。现在需要的是找一个通用 的指针位置,就是没有语言版本、sp包的问题,所有的都通用。否则就需要有版本参 数。 外面发布的代码。基本上都是利用覆盖一个异常处理句柄入口地址(有版本问题),再 加上猜测的shellcode地址,所以通用性大打折扣。其实一般堆溢出,利用上面的 (2)、(3)还是可以做得比较通用的。 WINDOWS系统的数据结构PEB总是映射到 0x7ffdf000,其偏移0x20就是一个函数指 针,所以就有版本无关的一个指针位置0x7ffdf020。覆盖这个指针shellcode得到控制 后需要恢复,因为很多调用要用到这个指针。这个调用的函数地址是ntdll.dll的 RtlEnterCriticalSection。


欢迎分享,转载请注明来源:民族网

原文地址:https://www.minzuwang.com/life/1024099.html

最新推荐

发表评论

评论将在审核通过后展示