缓冲区溢出Word格式.docx
《缓冲区溢出Word格式.docx》由会员分享,可在线阅读,更多相关《缓冲区溢出Word格式.docx(16页珍藏版)》请在冰豆网上搜索。
简单来说,缓冲区溢出是由于程序对于用户的输入没有足够的考虑,用户的这些“意外”的输入覆盖了程序的堆栈空间(注意在win32平台下,堆和栈是两个不同的概念,这点会在下面详细说明),当程序中处理这些输入的函数返回时,意外返回到了用户设计的地址,或者直接引发程序发生异常,这些意外的输入如果覆盖到异常处理的指针,那么程序就会转到用户设计的地址去执行。
在开始前,要说明的一点是以下都是在win32平台进行论述,涉及到的API是win32SDK提供的API。
目录
摘要2
目录2
一.什么堆,什么栈,二者有何不同?
2
二.如何产生溢出3
三.如何利用溢出3
3.1溢出点的定位4
3.2构造ShellCode5
3.2.1返回地址的确定5
3.2.2编造适用的机器码7
四.Cmail栈溢出分析8
4.1问题的发现:
8
4.2溢出点的确定9
4.3返回地址9
4.4ShellCode9
4.5编写溢出利用程序:
10
五.总结13
首先,从数据结构的角度来说:
堆和栈是两种不同的基本数据结构。
先说说栈,栈的英文表达是stack,它是cpu直接支持的数据结构,这从80*86提供的push,pop等栈操作指令就可以看出。
而堆是系统所实现的一种数据结构,也就是说系统提供了一整套机制来实现"
堆"
这种数据结构,这种数据结构具体又是怎样的?
关于这一点,Microsoft并没有公开,不过,国内外的很多安全爱好者通过反汇编研究了堆的具体实现和一些管理结构,并且这套机制在windows2000和windowsXP下并不相同,同一种操作系统,不同版本的ServerPack也不同,如XP下的SP1和SP2。
对于应用层的程序员来说,通过堆管理函数获得的内存块直接拿过来用就行了,而堆的一些标识性的数据结构是由系统来维护的,程序员不需要也没有精力去管这些,但对于搞溢出研究来说,不了解系统对堆的管理机制,调堆溢出的程序是很困难的,更不用说去写堆溢出的利用程序。
然后,从内存的角度来看
在内存看来,栈和堆都是一块块连续的内存,只不过栈的生长方向是从高地址向低地址生长,而堆就是一块普通的内存块,它和其他的内存一样,遵循"
高字节入高地址,低字节入低地址"
的小尾方式。
程序中函数的临时变量,包括用于接收用户输入的缓冲区即可以定义在栈中,也可以定义在堆中。
不同的是在栈中申请内存,只要显式的声明一下就可以用了,不需要用内存管理函数向操作系统申请,比如你在函数中用bufferchar[1024]={0}定义一块1K字节的内存,这种定义的内存都是在栈中分配的,具体的表现是这句定义的直接后果是在函数的入口点有一句addesp,-400(注意,400是0x400),意思栈向低地址生长了1024个字节,即在栈中预留出1024个字节的空间。
而要想在堆中申请内存,必须通过堆管理函数向操作系统申请。
这个过程是先用HeapCreate创建一个私有堆,再用HeapAlloc在创建的这个堆中申请内存,最后申请的内存要用HeapFree来释放,创建的堆用HeapDestroy来销毁。
堆和栈有以上种种不同,于是决定了发生在堆中的溢出和发生在栈中的溢出也是不同的,发生在栈中的溢出可以直接覆盖程序的返回地址,而发生在堆中的溢出只能覆盖堆的管理结构,改变一些寄存器的指针。
因此,对于这两种溢出,利用方法也不同,构造的“畸形”输入也不同。
关于堆的溢出,在xpsp1以前的系统是可以利用的,并且利用起来不算困难,但是xp的sp2以及windows2003sp1对堆的管理结构做出了很大的改进,即加入所谓的堆保护,使得即使能在堆上造成溢出,也很难利用。
目前尚未有人公开发布突破这种“堆保护”的方法。
所以,本文暂只讲述栈溢出,及栈溢出的利用。
二.如何产生溢出
从溢出的原理我们得知,溢出是由于用户的非常规输入造成的,对于一个程序,我们先
找到所有可以从外部提交输入的接口点,然后不按常规出牌,给它一个超长的输入,或一定得是正数的地方给它一个负数,然后观察程序的反映,如果造成了异常,一般来说这个地方存在溢出点。
三.如何利用溢出
确定一个程序存在溢出引起异常,原因一般有两种可能:
一种是程序访问了非法的地址,
如moveax,[esi]这时esi指向的地址不可读,或者函数返回的地址被我们的输入覆盖,eip指向的内存不可执行或者无法识别出是什么指令。
这两种原因可以从系统的出错对话框来识别。
如果系统设置为在出错时不显示错误对话框,可以用调试器加载执行程序或附加进程或用调试器捕获异常,出错时根据调试器的反映来确定是哪种异常。
前一种异常不好利用,需要多次构造不同的输入引发异常,并通过调试器跟踪程序发生异常的过程,搞清楚程序对用户的输入做了如何的处理产生了异常,才能构造出成功的ShellCode利用溢出。
后一种异常比较容易分析,一般报错时弹出的对话框说明的地址就是我们ShellCode中某些字节,通过某种方法可以很快地找到溢出所发的具体位置。
下面主要说明如何利用后一种溢出。
3.1溢出点的定位
所谓溢出点,是指对于我们的输入覆盖到了函数的返回地址,当函数执行ret或retn指
令返回的时候,把当前堆栈中esp指向的双字作为返回地址赋给EIP,而这个双字相对于我们输入的偏移位置,就是所谓的溢出点。
找到这个溢出点很重要,因为我们要让函数返回到我们希望它返回的地址去执行,在这个地址里,程序开始执行我们在输入中构造的代码。
那么,如何找到这个溢出点呢?
方法有多种,目前最有效的是“2分法”,这个命名的出处及发明者经网络四处转载已无从考究,我最先看到这个算法是在<
<
黑客防线>
>
的04年第5期,总期第41期。
2分法主要是由2个for循环构成的,其不分先后,举个例子
一个是
for(inti=0;
i<
800;
i++)
buffer[i]=i/100+100;
另一个是
buffer[i]=i%100+100;
(buffer中存放的是用来覆盖栈的数据)
第一个buffer,也就是通过/运算得到的buffer,是为了定位溢出点所在的高位地址
第二个buffer,也就是能过%运算得到的buffer,是为了定位溢出点所在的低位地址
通过两次报错得到的指令地址计算出溢出点的过程有些像通过PDE和PTE将虚拟地址映射到物理地址的过程,下面为了说明的方便,将通过%计算出的buffer引起报错得到的指令地址中的其中一个字节(这个地址其四个字节的内容是相等的)称为页偏移,记为n1,将通过/运算溢出得到的指令地址双字中的低字节称为页内偏移,记为n2,有了n1和n2,则溢出点的计算公式为:
溢出点=(n1-100)*100+(n2-100)(里面所有的值都是以10进制表示的)
那为什么要用这两个for循环,而这两个for循环为什么要这么设计?
先来看/运算对应的for循环
buffer[i]=i/100+100;
此循环结束后构造出来buffer的结构如下:
0x64,0x64,...,0x64100个第1组
0x65,0x65,...,0x65100个第2组
.........
0x6B,0x6B,...,0x6B100个第8组
通过这个buffer造成溢出报错得到EIP的内容(记为n1),我们可以知道溢出点位于哪一组中,于是(n1-100)*100,就是溢出点的组偏移
再来看看%运算对应的循环
0x64,0x65,0x66,...,0xC7100个第1组
0x64,0x65,0x66,...,0xC7100个第2组
.........
0x64,0x65,0x66,...,0xC7100个第8组
通过这个buffer造成溢出报错得到EIP的内容(记为n2),我们可以知道溢出点中位于组中的具体位置,于是n2-100就是溢出点在组中的相对偏移
最终(n1-100)*100+n2-100就是溢出点的值
2分法可以抽象一下:
for(i=0;
x;
buffer[i]=i/(or%)n+t;
上式中有3个参量:
xnt
x决定buffer大小
n为每一块中的字节数
块数=x/n
t我称为协调因子,用于协调buffer中放的是ascii码还是其他什么东西
于是,通过2分法两次报错得到的n1和n2,由溢出点计算公式就可以计算溢出点的具体位置,在这个位置上存放的双字在函数执行ret指令时将赋给EIP,即这个双字替换了函数的原返回地址,我们可以让函数随意返回地我们希望他返回的地方。
3.2构造ShellCode
ShellCode,就是我们给程序的输入,其功能是让程序处理我们输入的函数发生溢出,并返回到ShellCode中指定的地址去执行ShellCode中的代码。
从上面的表述中可以看出,ShellCode主要有两部分组成,一部分是那个返回地址,一部分是可以执行的代码。
3.2.1返回地址的确定
我们定位到了溢出点,那么,该让函数返回到哪个地址中去呢?
搞清楚这个问题,要看看发生溢出时栈中的数据内容,下面是后面提到的示例中发生溢出的栈内容:
02D690107FFA9C1B
02D69014B808EC83
02D690182E646D63
02D6901CBB240489
02D6902020667966
02D690240101EB81
02D690285C892001
02D6902C056A0424
下面是将要执行的代码
004017EB|.81C420020000addesp,220
004017F1|.C3retn
这时,EIP的值为004017F1,即下条将要执行的指令是retn
02D69010地址就是那个溢出点,这个地方存放的是我们在ShellCode中构造的返回地址,地址后面是可ShellCode中的可执行代码。
执行ret时,02D69010地址处的内容7FFA9C1B将赋给EIP,这时系统跳转到7FFA9C1B处去执行,为什么要让系统跳转到这个地址呢?
先来看看这个地址处的内容
7FFA9C1B54pushesp
7FFA9C1CC3retn
当系统跳转到这里