现在堆溢出的研究有点升温,暴露出来的堆溢出漏洞也越来越多,所以就有必要研究有效的堆溢出攻击方法了。由于堆溢出只是溢出的一种形式,原理已经介绍得比较多,也比较简单,所以就不再做这方面的介绍。
一、堆溢出后的后果
现在的系统管理堆,为了查询的高效快速,一般都使用的双向链表结构。我们来看双向链表管理的时候的删除操作。*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 [0xxxxxxxxx]
这样的代码,并且那ebx指向可传递shellcode的域,就能方便的利用,这样两个指针就可以分别选取0xxxxxxxxx和一个具有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:7ffxxxxx,同一系统比较固定覆盖,然后利用p2原来的值。这个有很多问题,跳到p1后,p1不是可控制代码,还有系统的SEH处理程序会检测fs:0首指针,如果不是指向堆栈位置,拒绝执行。主要是这个思路看能不能有别的办法。这个又考虑了覆盖fs:0首指针的低字节,这样异常结构链表位置就发生了变化,如果指向的另一块堆栈位置能够控制,就可以利用伪造异常链表的办法获得控制。其实单字节的溢出覆盖ebp低字节的利用原理也是差不多。而堆栈里面,往往会有动态BUFF拷贝过去的数据可以控制,所以这个要求也是容易办到。现在确定的是这个指针的位置不是固定的,有一定的变化范围。可能是0x7ff93000 、0x7ff9b000这样的值,但变化不是多大。
(5)、改写指针,跳转到ret 0xxxxx这样的指令处,通过改变堆栈指针的办法,利用堆栈里面的可控制数据得到控制。
||||||三、实际应用例子
.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)还是可以做得比较通用的。