!!!本实验仅供信息防御学习,请勿用于非法用途!!!
目录
操作系统:windows 2000 professional
软件:ollydbg原版、vc++6.0
!!!OD务必用原版,其他版本可能出现OD找不到出现入口地址的情况(如下图)!!!

(图有点糊)
VC++6.0 build版本:release版本(默认Debug版本)
如何编译时改为release版本?
(1)勾选组件:

(2)选择Win32 Release版本

此外,不能直接使用OD加载编译生成的exe文件,需要更改实时调试设置,改为“设置OllyICE为实时调试器”(请忽略OllyICE);设置为实时调试器后,编译C程序后,会直接跳到OD中。

另外,因为堆管理函数会检测当前进程的状态,如果不按上述的来,堆管理函数就会检测到当前进程处于调试态,而使用调试态堆管理策略。
调试态堆管理策略与常态堆管理策略有以下不同:
(1)调试堆不使用快表,只用空表分配;
(2)所有堆块都被加上了多余的16字节尾部防止溢出(防止程序溢出而不是堆溢出攻击),16字节中包括0字节的0xAB和8字节的0x00。如下图所示。

(3)块首的标志位不同。正常的块首标志为10.

- #include
- main()
- {
- //本地内存对象句柄
- HLOCAL h1,h2,h3,h4,h5,h6;
- HANDLE hp;
- //创建一个堆对象,可被调用者使用
- hp = HeapCreate(0,0x1000,0x10000);
- __asm int 3
-
- h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
- h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,5);
- h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
- h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
- h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,19);
- h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
-
- //free block and prevent coaleses
- HeapFree(hp,0,h1); //free to freelist[2]
- HeapFree(hp,0,h3); //free to freelist[2]
- HeapFree(hp,0,h5); //free to freelist[4]
-
- HeapFree(hp,0,h4); //coalese h3,h4,h5,link the large block to freelist[8]
-
-
- return 0;
- }
在VC编译后,直接会跳到OD中;

可以看到,程序停在CC INT3处,HeapCreate函数创建一个堆区,会返回指向堆的指针;
数据窗口跳转到0x0036000,堆表中包含的信息依次为:
!!!我们更关注偏移地址为0x178的空表索引区!!!

当一个堆初始化的时候,只有一个空闲态的大块,这个块被称为“尾块”。空表中含有128项,每项两个指针地址。除了偏移地址为0X178位置开始的四个字节,指向尾块,其余项均指向自己。
感谢前辈的分享精神,首先回顾快首中各字段的含义。
(1)占用态堆块的数据结构

(2)空闲态堆块的数据结构

在数据窗口中,跳转到0x360688处,0x360688前面的八个字节代表块首,0x360688开始为块身。块身的前八个字节为下一个节点的地址,因为尾块就是最后一个块,故指向索引表。

对于堆分配我们应该注意以下几点:
(1)堆块的大小包括块首在内,也就是如果请求32字节,实际会分配的堆块为40字节:8+32;
(2)堆块的单位为8字节,不足8字节的部分按8字节分配;
(3)按照内存块的大小不一样,对应的分配和释放方案如下:
| 分配 | 释放 | |
| 小块 | 首先进行快表分配; 若快表分配失败,进行普通空表分配; 若普通空表分配失败,使用堆缓存(heap cache)分配; 若堆缓存分配失败,尝试零号空表分配; 若零号空表分配失败,进行内存紧缩后再尝试分配; 若仍无法分配,返回NULL。 | 优先链入快表(只能链入四个空闲块); 如果快表满,则将其链入相应的空表。 |
| 大块 | 首先使用堆缓存进行分配; 若堆缓存分配失败,使用free[0]中的大块进行分配。 | 优先将其放入堆缓存 若堆缓存满,将其链入freelist[0] |
| 巨块 | 一般来说,巨快申请非常罕见,要用到虚分配方法 基本遇不到 | 直接释放,没有堆表操作 |
初始状态下,快表和空表都为空,不存在精确分配。请求将使用“次优块”进行分配,这个次优块就是位于偏移地址为0x0688处的尾块。
(4)由于次优分配的发生,分配函数会陆续从尾块中切走一些小块,并修改尾块块首的size信息,最后把free[0]指向新的尾块位置。
对于6次内存请求,实际分配情况:
| 句柄 | 申请字节数 | 实际分配(堆单位) | 实际分配字节数 |
| h1 | 3 | 2 | 16 |
| h2 | 5 | 2 | 16 |
| h3 | 6 | 2 | 16 |
| h4 | 8 | 2 | 16 |
| h5 | 19 | 4 | 32 |
| h6 | 24 | 4 | 32 |
申请完h1,查看数据窗口:

完成所有申请请求:

完成h1的释放,可见数据窗口,修改了flag位为00,并且将h1链入free[2]。
!!!注:块表只出现在可拓展的堆中,需要使用HeapCreate(0,0,0)命令,故,不会链入块表。

数据窗口跳转到0x360178处,可见空表索引区free[2]的指针指向0x00360688(即h1处)

释放h3,h3插入链表尾部,链入free[2];

具体指向请参考:(一次释放h1、h3、h5)

释放h5,链入free[4];

释放h4时,因为h3、h4、h5在一块,故会发生堆块合并现象。
做法:
(1)首先将这三个空闲块从空表中拿下;
(2)然后重新计算堆块的大小;
(3)最后按照合并后的大小把新块链入空表。
在数据窗口中,可以看到堆块大小已经改为08,且指向0x3601B8
在数据窗口中,跳转到0x3601B8处,可以发现0x3601B8就是空表索引free[8],free[4]处也指向了其本身。
