文章目录
  1. 1. 0x000 环境
  2. 2. 0x001 堆中的数据结构
  3. 3. 0x003总结和分析
  4. 4. 0x004 参考书籍

本系列文章讲述了windows 2000 下 堆溢出的方法


0x000 环境

  • 虚拟机 VirtualBox 5.0.20
  • 系统 windows 2000
  • 工具 VC++6.0 OllyDbg 1.10 汉化版

0x001 堆中的数据结构

  1. 堆块
    1) 堆块分为块首和块身
    2) 块首大小为(8字节)

    注:flag 01 表示堆块占用
    3) 空闲态的堆块在块首后有两个指针
  2. 堆表(空表和快表)
    1) 空表:双向回环链表
    2) 快表:单向链表
    3) 区别
    a) 以上两个都为128大小的指针数组 (空表每一项有两个指针,快表每一项有一个指针)
    b) 快表最多只有四个节点
    c) 空表除了数组的第一个元素外其他分别链接:数组下标*8 大小的堆块,数组的第一个元素链接着大于1kb的堆块,并升序排序
    d) 快表的堆块处于占用状态,不会发生堆块合并
    e) 快表的只存在精确分配,快表优先空表分配
  3. 内存块的大小
  4. 小块(小于1kb)
    分配方式:优先快表,其次空表非零元素(free[0]),然后堆缓存,最后空表零元素
  5. 大块(大于1kb小于512kb)
    分配方式:优先堆缓存,其次空表零元素
  6. 巨快(大于512kb)
    分配方式:虚分配

0x002 堆结构的验证和分析

  1. 空表结构的验证
    1) 验证代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include <stdio.h>
    #include <windows.h>
    int main()
    {
    HLOCAL h1,h2,h3,h4;
    HANDLE hp;
    hp = HeapCreate(0,0x1000,0x10000); //堆创建
    __asm int 3
    h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,2); //申请4块内存
    h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
    h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,17);
    h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,20);
    HeapFree(hp,0,h1); //释放掉1和3,内存不相联不会发生堆块合并
    HeapFree(hp,0,h3);
    HeapFree(hp,0,h2); //开始发生堆块的合并
    HeapFree(hp,0,h4);
    HeapDestroy(hp); //堆销毁
    return 0;
    }

2)只有空表的堆块分配

a) 在VC++6.0中编译成release版本,同时设置OD为默认调试器,再运行程序会崩溃(int 3断点,为了防止调试堆),这时候已经调用HeapCreate函数创建了一个堆,通过看函数的返回值可以确定堆堆区的起始位置

b) 通过OD 可以跳转到这块堆区(在偏移0x178)的位置是空表的头,如图可以看到,当前的free[0]的值是0x00360680,而其他元素都是指向自身的。

c)通过OD跳转到0x00360680的位置可以看到,红色的部分是一个堆块大小为0x130

d)继续单步运行程序,直到分配完四个堆块全部分配完毕,如下

结合堆块的结构和源代码可以清楚的看出来图中选中的部分是源代码中所分配的内存块 ,各个红色框就是源代码中h1,h2,h3,h4所分配的内存块(要分配的内存不够8的倍数,向上取八的倍数)

3)只有空表的堆块释放

a) 单步运行,释放掉第一个堆块,堆中变化如下

红色框中的就是h1释放以后的内存块,可以看到释放后他将链接到0x00360188的位置,也就是空表中的free[2] (问题1)

b) 继续释放h3,结果如下,释放后的内存块链接到0x00360198的位置,也就是free[4]

c) 在释放掉h2 由于,内存相连将会发生堆块合并,如图可以看到,释放过后,h1,h2,h3,发生了合并,形成了选中部分的一块,修改了块大小,修改了链接位置0x003601B8也就是free[8]

d) 将最后一块内存释放,内存再一次合并,恢复最开始的形态,链接到0x00360178也是就free[0]

2.快表的结构验证

1) 验证代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <windows.h>
int main()
{
HLOCAL h1,h2,h3,h4,h5,h6,h7;
HANDLE hp;
hp = HeapCreate(0,0,0); //堆创建带有快表的
__asm int 3
//快表结构的验证
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8); //申请内存,以构成快表
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
HeapFree(hp,0,h1); //释放掉申请的内存,以构成快表(因为快表初始化是空的,而且不会发生堆块合并)
HeapFree(hp,0,h2);
HeapFree(hp,0,h3);
HeapFree(hp,0,h4);
//堆分配顺序的验证
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8); //再申请6块内存(前两块会从快表中分配)
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
HeapFree(hp,0,h1); //释放掉申请的内存
HeapFree(hp,0,h2);
HeapFree(hp,0,h3);
HeapFree(hp,0,h4); //快表对应项以满
HeapFree(hp,0,h5); //会连接到空表的free[2],因为快表只能有4项,并且h5没有释放因此没有发生堆块合并,不会连接到free[0]
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8); //会从快表分配而不会从空表分配
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8); //会从空表分配
h7 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16); //会从空表分配(快表对应Look[3]的项为空)
HeapDestroy(hp); //堆销毁
return 0;
}

2) 快表结构验证

a) 使用VC++6.0编译程序release版本并运行,程序崩溃,使用OD调试,段在堆创建之后,和上面的一样,可以看到空表的第零项不是上次的0x00360688,因为快表占了这个位置,我们可以跳转到0x00360688的位置查看一下

b) 堆刚创建快表是空的

c) 单步执行程序,分配内存块,再释放掉刚申请的内存,由于,优先将内存链接到快表中,所以可以看到如下图的情况,第一个红色的框是lookaside[2],第二个红色的框是lookaside[4](问题2),因为代码中申请的是两个8个字节大小的内存块,和两个24字节大小的内存块,释放后,会分别链接到对应的lookaside数组中。

d) 转到0x00361e88的内存,可以看到快表中的flag都是01(占用状态),所以快表中的堆块不会发生合并。

3) 堆分配和释放的顺序的验证

a) 继续调试上面的程序,再次申请6块8字节大小的内存块,由于前面申请过2块8字节大小的内存块,所有前两次的分配如下图,快表中的内存被再次分配出去,快表再次变成空。

b) 再次,申请就会从free[0],中进行分配,再将他们前四块释放掉,这时候快表中lookaside[2]对应的项就会装满,跟随lookaside[2]中的链表头,可以找到对应的四个节点,如下面第二个图中四个红框所示

c) 继续释放,如图可以看到,h5这块内存被链接到了free[2]对应的位置中

d) 再次申请四块内存,如图可以以看到空表中并没有变化,由此可以证明,这四块内存是从快表中分配的。

e) 再次分配第五快内存,这次是从空表中free[2]中分配的。

f) 再次分配,一块16字节大小的内存块之后,堆中变化如下图(选中部分为新分配的内存块),因为快表中lookaside[3]为空,并且空表free[3]也为空,所有从free[0]中”切割”出现”找零钱”现象。

4) 堆表分配和释放总结
由以上实验证明,在有快表的时候,分配小块内存,优先分配快表,再在空表中分配,释放与分配相反,优先链接快表,在快表满了以后,在链接空表。在没有快表的时候,直接使用空表分配,释放后链接到空表中。

0x003总结和分析

通过两次的实验可以巩固对堆结构的理解和堆管理的方式的学习,为下一步的堆溢出做准备,通过自己的理解去构造程序去分析堆结构,可以进一步的加深自己对知识的理解。
注:
问题1:
对堆结构的分析发现不管是空表还是块表free[1]好像都没有被用到,不会有堆块链接到上面(这是我的疑问没有得出结果)。
问题2:
lookaside[1]链接的到底是8字节大小的堆块,还是16字节大小的,这个需要读了WRK源码可能才会知道。

0x004 参考书籍

1. 0day安全 软件漏洞分析技术 (第二版)
2. 软件调试

文章目录
  1. 1. 0x000 环境
  2. 2. 0x001 堆中的数据结构
  3. 3. 0x003总结和分析
  4. 4. 0x004 参考书籍