堆溢出研究二

調試中識別堆表

工具: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斷點出如圖所示:

如上圖所示程序斷點段在拉地址 VA = 0040101D處,此時使用快捷鍵 ALT + M 查看內存映射窗口來到如圖所示重點部分已經標註出來:

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

還有我們程序中創建出來的堆地址是0x00360000 size = 0x1000

識別堆表

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

對於上圖來到地址 0x360000處後,根據和堆溢出有關的數據結構我們直接關注 空表索引區即可(即偏移地址 0x178地址處):

堆初始化時的狀態

當堆剛被初始化的時候結構很簡單,

1. 其中只包含一個空閑大塊(稱為 「尾塊」)

2. 此尾塊地址位於 0x178(360178)處 (未啟用塊表的情況下)算上基地址就是 0x360688 (又稱為freelist【0】 )

3.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,如下圖:

如上圖所示:在地址0x360698出值為0x360178 鏈表的條件。

同樣的根據地址0x360688處的值即:分配的h1可以發現,h1的大小是 0x002 size = 16bytes

接下來直接運行到地址 0x00401059 此時直接查看 0x360178的地址出看到 值已經更改為:0x360708.接下來直接來到0x360680處進行查看

h1 - h6的分配情況如下圖所示:

如上所示:尾塊現在的地址是:0x360700 大小是 0x120 = 0x130 - 0x2 * 4 - 0x4 * 2

以上從h1 - h6的分配情況驗證啦 空表分配中的找零錢現象(從一個大塊中依次一小塊一小塊地進行切割)

堆塊的釋放

接著上面的程序執行,直接執行到地址: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堆塊的指針組,可以發現 :

如圖所示左邊箭頭是前向指針,順序為 Frllist -> h1 > h3 右邊是後向指針 順序是 h3> h1 > freelist[2]

對於h5堆快倒是沒啥 ,freelist【5】直接索引到 地址 0x3606c8

堆表的合併

接著程序運行直接運行到地址 0x401080地址處,執行的是代碼:

HeapFree(hp,0,h4); //coalese h3,h4,h5,link the large block to freelist[8]

當釋放h4的時候會發生堆塊的合併現象(兩個連續的空閑塊就會發生合併)。首先是先從空表中將三個空閑塊摘下,重新計算合併後的堆塊的大小,然後合併成新的空閑塊,鏈入空表。如下圖所示分別為空表索引區狀態和合併後堆塊狀態:

如上圖所所示:地址 0x3606A0處的值 0x0008 即是:合併後的堆塊的大小。後八個位元組的指針對,則指向空表的索引區。

注意事項

  1. 以上是空表中的堆塊的合併,並且只發生在空表中。
  2. 整個過程比較費時,繁瑣,在強調效率的情況下,堆塊合併就會被禁止,設置為佔用太。
  3. 空表中第一個塊的情況下不會向前發生合併,最後一個塊不會向後進行合併。

快表的申請與釋放

快表和空表的區別在於 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處,這是觀察快表的變化如圖所示:

運行程序到地址 0x40110D處觀察堆塊是否鏈如塊表:

如上圖所示h1 - h4已經鏈接進入塊表中並且都是處於佔用態。 地址 0x361e90指向下一個堆塊(因為h1 h2 同時為八位元組的空閑堆塊)

當程序運行到地址 0x401140時(也就是執行完申請內存的代碼時)

h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);

此時申請的堆塊應該從塊表中申請,此時查看堆表區的索引:

從以上兩圖中可以看到當繼續申請內存的時候,是從快表lookside[2]處卸下的堆塊。當釋放的時候,還是將空閑堆塊釋放到此處執行代碼:

HeapFree(hp,0,h2);

執行完後繼續查看上圖中地址的值:

如圖所示:當釋放完堆塊後還是鏈接進入啦快表 looksize[2]

原文首發看雪論壇:[分享]堆溢出研究二-『軟體逆向』-看雪安全論壇

作者:TKMoma

推薦閱讀:

TAG:堆棧溢出 | 軟體逆向工程 | 信息安全 |