Windwos堆管理体系以及溢出利用

《0day安全》学习笔记,主要讨论WIndows2000~WIndowsSP1平台的堆管理策略。

 

0X01 堆与栈的区别

栈空间是在程序设计时已经规定好怎么使用,使用多少内存空间。典型的栈变量包括函数内部的普通变量、数组等。栈变量在使用的时候不需要额外的申请操作,系统栈会根据函数中的变量声明自动在函数栈中给其预留空间。栈空间由系统维护,它的分配和回收都由系统来完成,最终达到栈平衡。所有这些对程序员都是透明的。

 

堆具备以下特性:

1.堆是程序运行时动态分配的内存。所谓动态是指所需内存的大小在程序设计时不能预先决定的,需要在程序运行时参考用户的反馈。

2.堆在使用时需要程序员使用专用的函数进行申请,如C语言中的malloc等函数、C++中的new函数等都是最常见的分配堆内存的函数。堆内存申请有可能成功,也有可能失败,这与申请内存的大小、机器性能和当前运行环境有关。

3.一般用一个堆指针来使用申请的内存,读、写、释放都是通过这个指针来完成。

4.使用完毕后要通过堆释放函数进行回收这片内存,否则会造成内存泄漏。如free,delete等。

 

 

0X02堆的数据结构与管理策略

对于管理系统来说,响应程序的内存使用申请就意味着要在“杂乱”的堆区中辨别哪些内存是正在被使用的,哪些内存是空闲的,并最终“寻找”到一片“恰当”的空闲内存区域,以指针形式返回给程序。

 

堆块:堆区额内存按不同大小组织成块,以堆块为单位进行标识,而不是传统的按字节标识。一个堆块包括两个部分:块首和块身。块首是一个堆块头部的几个字节,用来标识这个块首自身的信息,例如,大小、空闲或占用。块身是紧跟在块首后面的部分,也是最终分配给用户使用的数据区。

注意:块管理系统返回的指针一般是块身的起始位置,连续申请内存就是发现返回的内存之间存在“空隙”,那就是块首。

 

堆表:堆表一般位于堆区的起始位置,用于检索堆区中所有堆块的总要信息,包括堆块的位置、堆块的大小、空闲或占用等。 堆表的数据结构决定了整个堆区的组织方式。堆表往往不知一种数据结构:如平衡二叉树等。

堆的内存组织如图所示:

Windows中占用态的堆被使用它的程序管理,堆表只是管理空闲态的堆块。

其中最重要的堆表有两种:空闲双向链表Freelist空表)以及快速单项链表Lookaside快表

 

空表:
空闲堆块块首包含一对指针,这对指针把空闲堆块组织成双向链表。按照堆块大小的不同,空表总共被分成128条。

堆区一开始的堆表区中有一个128项的指针数组,被称作空表索引。该数组每一项包含两个指针,用于标识一条空表。

比如空表的第二项free[1]标识了项中所有大小为8字节的空闲堆块,之后每隔索引指示的空闲堆块递增8字节,free[2]标识16字节,free[3]标识24字节空闲堆块,free[127]标识1016字节空闲堆块。

                        空闲堆块大小 = 索引项 * 8字节

 

把空闲堆块链入不同的空表,可以方便管理。空表第一项free[0]链入所有大于等于1024字节的堆块(小于512K)。这些堆块按照各自的大小在零号空表中升序排列。

 

 

 

 

快表

快表是Windows用来加速分配而采用的一种堆表。这类单向链表中不会发生堆块合并(其中空闲堆块块首置为占用态)。

快表也有128条,组织结构与空表类似,只是堆块按单链表组织,而且每条快表最多只有4个节点。

 

 

 

0X03  堆的操作

堆得操作分为:堆的分配、堆得释放、堆得合并。分配与释放是由程序执行的,堆的合并是由堆管理系统自动完成的。

 

1.堆的分配

堆分配分三类:快表分配,普通表分配,零号空表(free[0])分配。

 

从快表分配:寻找大小到大小匹配的空闲堆块、将其状态修改为占用态、把它从堆表中卸下,最后返回一个指向堆块块身的指针给程序使用。

 

普通表分配:首先寻找最优的空闲块分配,若失败,则寻找次优的空闲块分配。

零号空表分配:按照大小升序链着大小不同的空闲块,找最优结果。

注意:当空表中找不到最优的堆块时,会发生次优分配,即从大块按照请求的大小精确割出一块进行分配,然后给剩下部分重新标注块首,链入空表。

 

2.堆块释放

将堆块状态改为空闲,链入相应的堆表。所有的释放块都链入堆表的末尾。

 

3.堆块合并

经过反复的申请与释放,堆区产生很多内存碎片。堆管理系统发现两个空闲堆块彼此相邻,就会进行堆块合并。 包括将两个块从空闲链表中卸下、合并堆块、调整合并后大块的块首信息、将新块重新链入空闲链表。

 

几个注意点:

1.快表空闲块被置为占用态,所以不会发生堆块合并操作。

2.快表只有精确分配时才会分配。

3.分配与失败有限使用快表,失败用空表。

 

 

0X04   DWORD SHOOT 堆溢出利用原理

 

堆溢出的利用的精髓就是精心构造的数据溢出下一个堆块的块首,改写块首的前向指针和后向指针,然后在分配、释放、合并等操作发生时获得一次向内存任意地址读写任意数据的机会。

原理:

 

Int remove(ListNode * node)

{

    node->blink->flink = node -> flink;

    Node->flink->blink= node ->blink;

    Return0;

}

 

 

那么四个字节的利用我们可以做什么呢?

 

1.内存变量:修改能影响程序执行的重要标志变量,例如更改身份验证函数的返回值。

2.代码逻辑:修改代码段重要函数关键逻辑,如程序分支处的判断逻辑。

3.函数返回地址:堆溢出也可以利用DWORD SHOOT更改函数返回地址。

4.攻击异常处理:程序产生异常,Windows转入异常处理机制,包括SEH等。

5.函数指针:如C++的虚函数调用。改写这些指针后,函数调用往往就可以劫持进程。

6.PEB中线程同步函数入口地址:每个进程PEB存放着一对同步指针,指向RtlEnterCriticalSection()和RtlLeaveCriticalSection(),并且被ExitProcess()函数调用。

 

 

 

 

 

posted on 2016-07-26 15:23  zbility  阅读(1999)  评论(0编辑  收藏  举报

导航