堆溢出研究二
調試中識別堆表
工具:ollydbg 2.0版本 & vc6.0(release模式) 編譯選項默認 os: windows2000
函數的抽離
在堆中進行內存分配的時候,C語言函數調用的是malloc()函數,c++中調用new()函數,當動態調試進入函數內部的時候察覺此兩個函數調用的都是底層 ntdll.dll中的 RtAllocateHeap()函數,所有的windows分配堆的函數在底層調用的都是此函數,這也死程序員可以看到的關於堆的最底層函數。因此研究堆分配,重點關注此函數即可。堆的調試
在此之前需要理解一個概念:調試堆與調試棧不同,不能直接載入或者attach 程序,否則堆管理策略就會採用調試狀態下的堆管理策略,使用調試狀態下的堆管理函數。
正常堆和調試堆的區別:
1.調試堆只採用空表分配,不採用快表分配
2.所有的堆塊末尾都加上十六個位元組的用來防止程序溢出,(僅僅是用來防止程序溢出,而不是堆溢出),其中這十六個位元組包括: 8 * 0xAB + 8 * 0x00 3.塊首的標誌標誌位不同,調試狀態下的堆和正常堆的區別如同debug下的PE文件和release下的PE文件類似,做堆移除實驗的時候,調試器中可以v正常運行的shellcode,單獨運行卻不行。很可能就是調試堆與正常堆的差異造成的。為拉避免採用調試狀態下的堆,我i們直接在程序中嵌入 int3 斷點,然後調用實時調試器即可: 源碼:
#include <windows.h>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); 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;}
step: 1. 調整ollydbg 為 just in time (實時調試器)
2. 直接進行編譯鏈接運行程序,根據程序中的 int3 斷點. ollydbg會直接斷在int3斷點出如圖所示:

如上圖所示可以、得到信息:發現進程堆地址為: 00130000 大小為0x6000 (此處可以通過函數 GetPcocessHeap()函數獲得句柄)如圖:

識別堆表
根據上圖中的信息我們直接轉到程序中創建出的堆地址 0x360000處在(數據窗口 直接 快捷鍵 ctrl + g )

堆初始化時的狀態
當堆剛被初始化的時候結構很簡單,
1. 其中只包含一個空閑大塊(稱為 「尾塊」) 2. 此尾塊地址位於 0x178(360178)處 (未啟用塊表的情況下)算上基地址就是 0x360688 (又稱為freelist【0】 )
對堆塊塊首做一個簡介 ####
堆塊的塊首佔八個位元組下面根據佔用態和空閑態分別介紹:

共同點:
0-2 位元組代表本快的大小(包括塊首)
2-4位元組表示計算單位是多少位元組不同點
Flags出 佔用態標誌是1 空閑態標誌是 0
空閑態塊首後的八個位元組為一對指針,分別是前向指針和後向指針。當堆塊變為佔用態的時候重新回分配數據。 實際上尾塊的起始位置是 0x360680因此根據地址 0x360680處八個位元組的情況可以知道:此尾塊的大小是 0x130 計算單位是 0x0008 個位元組 總大小是 0x980位元組。
調試中識別堆的分配,釋放,合併
堆塊的分配
我們直接在cpu窗戶 命令 F8單步執行程序到地址:0x00401028地址處也就是在源碼中我們執行完:
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
當h1被分配完以後直接查看地址:0x360178地址處的值:

此時的地址0x360178處的值已經從0x360688改變為0x360698 同時跳轉到 0x360698,如下圖:


堆塊的釋放
接著上面的程序執行,直接執行到地址:00401077地址處
HeapFree(hp,0,h1); //free to freelist[2] HeapFree(hp,0,h3); //free to freelist[2] HeapFree(hp,0,h5); //free to freelist[4]
分別釋放啦堆塊 h1 h3 h5這樣做是防止相鄰堆塊進行堆塊的合併。直接查看地址 0x360178地址處的值重點觀察變化的值如下圖:

從上圖中可以發現地址 0x360188 的值發生啦變化 從原來的指向自身現在變為指向:0x360688 0x3608A8
地址0x360198處的值變化為: 0x003606C8 和 0x003606c8 由上圖可知 h1 h3分別被釋放到 freelist[2] 空表中, h5被釋放到啦 freelist【4】空表中。根據freelist【2】 的空表索引 以及h1 h3堆塊的指針組,可以發現 :

堆表的合併
接著程序運行直接運行到地址 0x401080地址處,執行的是代碼:
HeapFree(hp,0,h4); //coalese h3,h4,h5,link the large block to freelist[8]
當釋放h4的時候會發生堆塊的合併現象(兩個連續的空閑塊就會發生合併)。首先是先從空表中將三個空閑塊摘下,重新計算合併後的堆塊的大小,然後合併成新的空閑塊,鏈入空表。如下圖所示分別為空表索引區狀態和合併後堆塊狀態:

注意事項
- 以上是空表中的堆塊的合併,並且只發生在空表中。
- 整個過程比較費時,繁瑣,在強調效率的情況下,堆塊合併就會被禁止,設置為佔用太。
- 空表中第一個塊的情況下不會向前發生合併,最後一個塊不會向後進行合併。
快表的申請與釋放
快表和空表的區別在於 HeapCreate()函數的參數的不同。
hp = HeapCreate(0,0,0);//塊表hp = HeapCreate(0,0x1000,0x10000);//空表
源碼:
#include <stdio.h>#include <windows.h>void main(){ HLOCAL h1,h2,h3,h4; 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,16); h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24); HeapFree(hp,0,h1); HeapFree(hp,0,h2); HeapFree(hp,0,h3); HeapFree(hp,0,h4); h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16); HeapFree(hp,0,h2);}
與空表的申請大致類似。
環境:與空表使用的環境一樣 直接在dunp窗口中進行跳轉到 0x360688處,此時發現快表為空。這也是為什麼要反覆申請釋放內存的原因,接下來分別申請 8,8,16,24位元組的內存,然後進行釋放,(快表未滿時釋放到快表中)。 先運行程序到地址 0x40109F處。此時直接觀察快表中的變化,此時發現讓然為空,下面運行釋放程序,直接單步執行命令運行到地址:0x401106處,這是觀察快表的變化如圖所示:

h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
此時申請的堆塊應該從塊表中申請,此時查看堆表區的索引:

從以上兩圖中可以看到當繼續申請內存的時候,是從快表lookside[2]處卸下的堆塊。當釋放的時候,還是將空閑堆塊釋放到此處執行代碼:
HeapFree(hp,0,h2);
執行完後繼續查看上圖中地址的值:

如圖所示:當釋放完堆塊後還是鏈接進入啦快表 looksize[2]
原文首發看雪論壇:[分享]堆溢出研究二-『軟體逆向』-看雪安全論壇
作者:TKMoma
推薦閱讀:
