#include <windows.h>
int main()
{
HLOCAL h1,h2,h3,h4,h5,h6;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000);//建立一個新的堆
_asm int 3//中斷,進入OD偵錯
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);
_asm int 3
HeapFree(hp,0,h1);
HeapFree(hp,0,h3);
HeapFree(hp,0,h5);
_asm int 3
HeapFree(hp,0,h4);
_asm int 3
return 0;
}
簡單理解空表
環境:windows xp
編譯器:vc++
偵錯程式:OD
1.把OD設成預設偵錯程式,開啟這段程式觸發異常正好卡在HeapCreate後面,這時候的正好把新建好的堆的地址傳入eax。
試了下Heap_Vis這個外掛,好像不是那麼好用,我用了下,直接給我卡死了,最後還是靠Ctrl+Alt+Del才救回來的(有可能是外掛衝突了?懶得去搞了
2.找到堆的地址發現堆的結構前面先是一段段表。中間是虛分配表,因為堆才初始化,沒有虛分配記錄所以全都為NULL,然後就是32位元的bitmap應該是來統計堆表的分配,如果表被佔用則,該對應的bit位上為1.
然後接下來就是空表索引區,但是由於都沒有被佔用,所有的空表都指向自己的位置。
其中零號空表(0x003a0178)指向的是尾塊。
PS:堆表表頭的結構為
Byte | Name |
---|---|
1~2 | Self Size 自身的長度 |
3~4 | Previous chunk size 上一節的長度 |
5 | Segment Index 段索引 |
6 | Flags 該位為1的時候,表示該表被佔用了 |
7 | Unused bytes |
8 | Tag index |
9~b | Flink in freelist (佔用態) |
c~f | Blink in freelist (佔用態) |
3.觀察到零號空表指向的尾塊表頭,表頭下面的就是指向零號空表的雙向連結串列。實際上這個堆塊開始於 0x0030680,一般參照堆塊的指標都會躍過 8 位元組的塊首,直接指向資料區。Self Size為0x130,因為堆中一個單位對應的都是八個位元組,所以總空間為 0x130 * 8。
(1)堆塊的大小包括了塊首在內,即如果請求 32 位元組,實際會分配的堆塊為 40 位元組:8位元組塊首+32 位元組塊身。
(2)堆塊的單位是 8 位元組,不足 8 位元組的部分按 8 位元組分配。
(3)初始狀態下,快表和空表都為空,不存在精確分配。請求將使用「次優塊」進行分配。這個「次優塊」就是位於偏移 0x0680 處的尾塊。
(4)由於次優分配的發生,分配函數會陸續從尾塊中切走一些小塊,並修改尾塊塊首中的size 資訊,最後把 freelist[0]指向新的尾塊位置。
3.把斷點的位置移到HeapAlloc的後面觀察每個空間的申請。
0x003a0680到0x003a06e0就是1到6號申請的空間。
開頭是02,則代表分配了2 * 8個位元組的空間。
開頭是04,則代表分配了4 * 8個位元組的空間。
然後這個時候再看尾塊的表頭的Self Size為0x120,因為前面分配了0x10的空間(2 * 4 + 4 * 2 = 0x10)
這時零表中指向尾塊的指標也變成了0x003a0700
4.把斷點的位置移到HeapFree h5的後面,觀察h1和h3鏈入了Freelist[2],h5鏈入了Freelist[4],同時觀察Flag位都變為了0,意味著恢復成了空閒態。
5.又把斷點的位置移到HeapFree之後觀察堆表h1鏈入了Freelist[2],h3脫離了Freelist[2]與h1組成的連結串列鏈入了Freelist[8],同時觀察h3開頭的02已經變成了08,此時已經完成了h3,h4,h5的合併。h5也從Freelist[4]中取下了。
堆塊合併的過程。堆塊合併可以更加有效地利用記憶體,但往往需要修改多處指標。因此,堆塊合併只發生在空表中。在強調分配效率的快表中,堆塊合併一般會被禁止(通過設定堆塊為佔用態)。另外,空表中的第一個塊不會向前合併,最後一個塊不會向後合併。
Freelist中指向的地址都自動跳過了堆塊頭的,也就是跳過了那8個位元組。
觀察此時Freelist[4]已經變為指向自身,Freelist[2]也只鏈入了一個h1
PS:偵錯堆好像不能用OD直接載入,必須的在程序中偵錯,不然堆的位置就會出現亂碼。