XP SP3堆研究
本文原創作者:icq5f7a075d,本文屬i春秋原創獎勵計劃,未經許可禁止轉載! https://bbs.ichunqiu.com/thread-33885-1-1.html
本文如果出現錯誤,歡迎指出,感激不盡!
本文中的所有程序請在虛擬機中運行。 前言 堆溢出漏洞是一種常見的軟件漏洞,但是堆的結構比較復雜,不同版本操作系統的堆結構存在較大差異,網絡上關于堆的介紹也存在名詞不統一的情況,這都極大的增加了新手學習堆溢出漏洞的難度。本文將詳細剖析XP SP3系統的堆結構,讓新手對堆的結構有初步了解。當然,本文只是堆的初步介紹,頁堆、CRT堆等深層次的內容,本文并沒有涉及。不同版本的操作系統堆結構存在較大差異,本文分析的XP SP3系統上的堆結構,請在XP SP3系統上進行本文的實驗。 1.?堆與棧的區別 既然談到了堆,就不得不一起說說棧。堆和棧是兩種數據結構,程序在執行時需要兩種不同數據結構的內存(堆和棧)來協同配合。??臻g由操作系統自動分配釋放,存放函數的參數值、局部變量的值、函數返回地址等。進程在創建時,操作系統就會為其創建棧。棧在使用的時候不需要額外的申請操作,系統會根據函數中的變量聲明自動在函數棧幀中給其預留空間。??臻g由系統維護,它的分配和回收都由操作系統來完成,最終達到棧平衡。
堆在程序運行時分配,由程序指定分配的大小。堆在使用時需要程序員用專用函數進行申請,如C語言中的 malloc等函數、C++中的 new 函數。堆內存申請有可能成功,也有可能失敗,這與申請內存的大小、機器性能和當前運行環境有關。使用完畢后需要把堆指針傳給堆釋放函數回收這片內存,否則會造成內存泄露。每個進程通常都有很多個堆,程序可以通過自己的需要創建新的堆。每個進程都會有一個默認的進程堆,指向這個堆的指針被存放在進程環境塊PEB中,而這個進程的所有堆,都以鏈表的形式被掛在PEB上。
下圖是某個進程的內存空間,如圖所示,0x12D000的內存空間為棧空間,內存中只有一個??臻g;0x140000的內存空間為進程堆的空間,內存中只有一個進程堆空間,這里大小為0x8000;0x240000、0x250000、0x380000、0x3A0000的內存空間為普通堆的空間,這些是程序根據需要自己創建的新的堆,不同堆的大小可能不同: 2.?堆的管理機制 普通程序員在使用堆時,只需要申請、使用、釋放就可以了。但是在內存中卻需要進行一系列復雜的操作,如何在“雜亂”的內存中“辨別” 出哪些內存是正在被使用的,哪些內存是空閑的,并最終“尋找”到一片“恰當”的空閑內存區域,分配給程序使用?這些復雜的過程都是由堆管理器完成的。 在理解堆管理器如何完成上述復雜的操作前,我們必須先了解如下幾個名詞:堆、堆表、堆段、段表、堆塊、塊首。 2.1.?堆的基本結構 (1)堆 在上圖中,可以看到內存中有五段內存塊被標記為堆:0x140000、0x240000、0x250000、0x380000、0x3A0000,它們大小并不相同。每一個這樣的內存塊區域就被稱為一個堆,《0day》書中將其稱為一個堆區。一個堆,最小為0x1000。 (2)堆塊和塊首 一個堆的內存按照不同大小組織成塊,以堆塊為單位進行標識,而不是傳統的字節標識。程序申請和使用堆,實際上使用的都是這些堆塊。一個堆塊包括兩個部分:塊首和塊身。塊首是HEAP_ENTRY結構,它是一個堆塊頭部的8個字節,用來標識這個堆塊自身的信息(塊的大小、使用狀態等);塊身是進跟在塊首后面的部分,也是最終分配給用戶使用的數據區。整個堆都被分割成了一個個的堆塊,接下來我們提到的段表和堆表也是堆塊。每個堆塊都是8字節的倍數。 (3)堆段和段表 一個堆會被分成幾個段,這些段又被分成一個個堆塊。堆管理器在創建堆時會創建一個段,這個段是0號段,這個段目前就是整個堆區的大小;如果這個段是可增長的,也就是堆標志中含有HEAP_GROWABLE(2)標志,那么堆管理器會再分配一個段。堆表中Segments[64]數組記錄著每一個堆段。每個堆都至少有一個段,即0號段,最多可以有64個段。段表則是記錄段信息的結構體,是一個HEAP_SEGMENT結構,除了0號段,其他段的段表一般位于段的起始位置,0號段的起始位置記錄著堆表,堆表接下來才是段表。 (4)堆表 堆表,是一個HEAP結構,位于整個堆的起始位置,用于索引堆中所有堆塊和堆段的重要信息,包括堆段的索引、堆塊的位置、堆塊的大小、堆塊的使用狀態等。在 Windows 中,堆表只索引所有空閑的堆塊,正在使用的的堆塊被使用它的程序索引。 下圖,顯示堆的數據布局 如圖所示,堆中的內存區被分割成一列不同大小的堆塊,每個堆塊的起始處一定是一個8byte的HEAP_ENTRY 結構,后面便是提供應用程序使用的區域,也稱為用戶區。堆表所占的空間是一個堆塊,同時也是0號堆段的起始位置。 對于0號段,這個結構位于HEAP結構之后,對于其他段,這個結構就在段的起始處,堆表位于0號段 段中的所有已經提交的空間都屬于一個堆塊,即使是HEAP結構和HEAP_SEGMENT 結構所占的空間也是分別屬于一個單獨的堆塊,這兩個結構的起始處都是一個HEAP_ENTRY結構。 HEAP_ENTRY前兩個字節以分配粒度表示堆塊的大小,分配粒度通常是8,這意味著每個堆塊的最大值是0x10000*8=0x80000=512KB。因為每個堆塊知識有8字節的管理信息,因此應用程序可以使用的最大堆塊便是0x80000-8=0x7FFF8。如果程序想申請大于512KB的堆塊時怎么辦? 當一個應用程序要分配大于512KB的堆塊時,如果堆標志中含有HEAP_GROWABLE(2),那么堆管理器便會直接調用ZwAllocateVirtualMemory()來滿足這次分配,并把分得的地址記錄在HEAP結構的VirualAllocdBlocks所指向的鏈表中,這意味著,堆管理器批發過來的大內存塊,有兩種形式,一種形式是段,另一種形式是直接的虛擬內存分配,將后一種形式稱為大虛擬內存塊。因為堆管理器是以鏈表方式來管理大虛擬內存塊的,因此數量是沒有限制的。每個大虛擬內存塊的起始處是一個HEAP_VIRTuAL_ENTRY結構(32字節)。 2.2.?堆管理器 堆表索引堆中所有空閑的堆塊,那么堆表是如何索引的?堆塊又是如何組織的?這就不得不說堆管理器(又叫堆分配器)。堆管理器主要分為前端堆管理器和后端堆管理器,有些文章里也叫前端堆管理器和核心堆管理器。前端堆管理器是一個高性能的子系統,而核心堆層管理器則是一個強大的通用堆的實現,這兩個組件在結構上是分開的。 2.2.1.?前端管理器 在處理內存分配和釋放的時候,前端堆管理器是優先做處理的。前端堆管理器有三種模式:none、LAL(Look-aside Lists,預讀列表\旁氏列表\快表)和LFH(Low-Fragmentation Heap,低碎片堆 )。none模式實際上就是不使用前端堆管理器意思,當一個堆不可擴展時,前端堆管理器為none模式。當堆為可擴展堆時,前端堆管理器開啟,XP SP3下默認使用LAL,Windows Vista系統默認使用LFH。進程堆默認開啟前端分配器。 LAL可以處理小于1024字節的分配請求。它用一系列的單鏈表的數據結構去存放大小在0到1024之間的空塊。LFH可以處理大小在0-16k的請求。 (1)LAL Look-aside Lists,在《軟件調試》一書中被稱為旁視列表,在《0day》中被稱為快表。 LAL是一張表,包含128個項(是一個有128個元素的數組),每一項對應一個單項鏈表,每個單向鏈表中都包含一組固定大小的空閑堆塊,這個固定大小的值等于數組下標*8。因為塊首就要占有8字節,所以索引為0的項和索引為1的項永遠不會被使用。如果應用程序請求24字節的空間,前端分配器將查找大小為32字節的空閑堆塊。 HEAP結構中 FrontEndHeap字段記錄這前端管理器信息,當啟動LAL時,這個值一般指向堆基址偏移+0x688的地方,這里就是LAL。 在XP系統中分析LAL,HEAP結構中 FrontEndHeap的指針一般指向堆基址偏移+0x688的地方,在這里可以找到這個LAL。LAL是一個數組,每個元素對應一個單項鏈表索引。每個元素的數據結構大小為0x30,包括了性能相關的變量,包括當前長度,最大長度,以及更重要的一個指向與索引對應的單鏈表堆塊的指針。如果當前沒有空閑塊,則該指針為NULL。同樣,在單鏈表末尾是指向的是NULL。 LAL也被稱為“快表”,是因為這類單向鏈表中從來不會發生堆塊合并(其中的空閑塊標記會被標為BUSY,阻止后端堆管理器對它進行分配或合并的操作)。 LAL總是被初始化為空,而且每條鏈表最多只有 4 個結點,故很快就會被填滿。 當分配內存的時候, 列表頭部的結點被彈出。 當一個塊被釋放, 把它從單鏈表的頭部壓入, 并且把指針更新。 如果用戶的請求是小于(1024-8)字節,那么它便可以用 LAL 前端來分配。下圖顯示了 LAL的結構,如圖前端有一個大小為 1016 的列表,它對應著的序列號是 127,用戶可以申請 1008 字節。
(2)LFH Low-Fragmentation Heap,低碎片堆。堆的內存空間被反復分配和釋放,堆上可用空間可能被分割得支離破碎,當試圖從這個堆上分配空間時,即使可用空間加起來的總額大于請求的空間,但是因為沒有一塊連續的空間可用滿足要求,那么分配請求仍會失敗。堆碎片化和磁盤碎片化的形成機理是一樣的,但多個磁盤碎片相加仍可以滿足磁盤分配請求,但是堆碎片是無法通過累加來滿足內存分配要求,因為堆函數返回的必須是地址聯系的一段空間。于是便引入了LFH來降低碎片。 LFH將堆上的可用空間劃分成128個桶位(Bucket),編號為1~128,每個桶位的空間大小依次遞增,1號桶為8個字節,128號桶為16384字節(16KB)。當需要從LFH上分配空間時,堆管理器會根據堆函數參數中所請求的字節將滿足要求的最小可用桶分配出去。例如,如果程序請求分配7個字節,而且1號桶空閑,那么便將1號桶分配給它(分配8字節),如果1號桶已經分配出去了(busy),那么便嘗試分配2號桶。 LFH不同編號區域的桶使用不同的粒度,桶的容量越大,粒度也越大: LFH在下面三種情況下不會開啟,1,固定大小的堆,2,Heap_no_serialize,3,debug 狀態。XP SP3默認不使用LAF,只有在應用程序調用了HeapSetInformation以后LFH才被打開。從Windows Vista開始,LFH默認啟用。 [C++]?純文本查看?復制代碼?
| 123456 | ULONG HeapFragValue=2;#HEAP_LFHBOOL bSuccess=HeapSetInformation(GetProcessHeap(),HeapCompatibilityInformation,&HeapFragValue,sizeof(HeapFragValue)); |
| 分配 | 釋放 | |
| 小塊 | 首先使用LAL分配;若LAL分配失敗,進行普通FreeLists分配;若普通FreeLists分配失敗,使用堆緩存(heap cache)分配;若堆緩存分配失敗,嘗試零號空表分配(FreeLists[0]);若零號空表分配失敗,進行內存緊縮后再嘗試分配;若仍無法分配,返回 NULL | 優先鏈入LAL;如果LAL滿,則將其鏈入相應的FreeLists |
| 大塊 | 首先使用堆緩存進行分配;若堆緩存分配失敗,使用FreeLists[0]中的大塊進行分配 | 優先將其放入堆緩存;若堆緩存滿,將鏈入 freelists[0] |
| 巨塊 | 一般說來,巨塊申請非常罕見,要用到虛分配方法(實際上并不是從堆區分配的); | 直接釋放,沒有堆表操作 |
3.3.?HEAP_ENTRY結構 塊首結構8字節: 調用HeapAlloc函數將返回HEAP_ENTRY之后的地址。此地址減去8Byte便可以得到_HEAP_ENTRY結構。 Flags字段的值: 4.?堆的調試 調試堆的時候不能直接使用調試器加載實驗程序,而要利用附加程序的方式進行調試,這是因為調試態堆管理策略和常態堆管理策略有很大差異,集中體現在: (1)調試堆不使用前端管理器; (2)所有堆塊都被加上了多余的 16 字節尾部用來防止溢出(防止程序溢出而不是堆溢出攻擊),這包括 8 個字節的 0xAB 和 8 個字節的 0x00; (3)塊首的標志位不同。 為了避免程序檢測出調試器而使用調試堆管理策略,我們可以在創建堆之后加入一個人工斷點:_asm int 3,然后讓程序單獨執行。當程序把堆初始化完后,斷點會中斷程序,這時再用調試器 attach 進程,就能看到真實的堆了。 我們需要將調試器設置為及時調試器,程序遇到int3中斷時會自動附加調試器。選用Windbg調試器,Windbg調試器的符號表可以很清晰的展現出堆的結構。 Windgb設置為JIT調試器的方式很簡單,只需要在命令行中執行“windbg.exe -I”命令: 設置成功后,注冊表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AeDebug下存在Debugger項,數值為windbg.exe信息 4.1.?FreeList調試 調試代碼: [C++]?純文本查看?復制代碼?
| 010203040506070809101112131415161718192021 | #include <windows.h>int main(){HLOCAL h1,h2,h3,h4,h5,h6;HANDLE hp;hp = HeapCreate(0,0x500,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 coalesesHeapFree(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;} |
我們使用HeapCreate (0,0x500,0x10000)創建了一個大小為0x500,但是系統卻為我們分配了0x1000的內存空間,這是因為堆段的大小最小為0x1000, 圖中+0x584的地方指向前端管理器,但是這里這個指針為 NULL,說明沒有啟用前端管理器。這是因為只有堆是可擴展的時候前端管理器才會啟用,要想啟用前端管理器,在創建堆的時候就不能使用 HeapCreate (0,0x500,0x10000)來創建堆了,而要使用 HeapCreate(0,0,0)創建一個可擴展的堆。 當一個堆剛剛被初始化時,它的堆塊狀況是非常簡單的。 (1)HEAP結構+0178的位置為FreeLists索引區,總共128對指針用來索引128條空閑雙鏈表。目前除了Freelist[0]之外,所有索引指向自身,也就是說這些空閑鏈表都為空。 (2)Freelist[0]位于堆偏移 0x0688 處(啟用前端堆管理器后這個位置將是旁氏列表/低碎片堆),這里算上堆基址就是 0x003a0688。Freelist[0]前向指針和后向指針都指向0x003a0688,說明Freelist[0]指向的是目前堆中的唯一一個塊“尾塊”。 在觀察0x3a0688這個堆塊之前,首先我們來看一下一個正在使用的堆塊的結構,圖中的Block head塊首結構實際上就是我們前文提到的HEAP_ENTRY: 空閑態堆塊和占用態堆塊的塊首結構基本一致,只是將塊首后數據區的前 8 個字節用于存放空表指針了。這 8 個字節在變回占用態時將重新分回塊身用于存放數據。 現在我們來看看0x3a0688 這個尾塊的狀態: 實際上這個堆塊開始于 0x3a0680,一般引用堆塊的指針都會躍過8字節的塊首,直接指向數據區。 Windbg顯示的數據是小端模式,尾塊目前的大小為 0x0130,計算單位是 8 個字節,也就是 0x980 字節。尾塊的前向指針和后向指針都指向0x3a0178,這與我們前面的分析一致。 4.1.3.?堆的分配 現在我們繼續調試,在Windbg中使用p命令單步執行,執行6此堆申請的操作。 第一次申請3字節的內存,系統將0x3a0680~0x3a0690的空間分配出去,FreeList[0]指向變為0x3a0698(尾塊3a0690)。雖然申請了3個字節,但實際分配了0x16字節的內存空間,解析尾塊的塊首結構,前一個堆塊的確是了0x16個字節。0x3a0680的標志位也被修改為了0x01,busy狀態。 接下來繼續執行5次內存申請的操作,分別申請5、6、8、19、24字節的內存,共041字節,追蹤內存分配的情況: 由此總結出下表:
| 堆句柄 | 請求字節 | 實際分配字節 | 基址 |
| H1 | 3 | 16 | 0x30680 |
| H2 | 5 | 16 | 0x30690 |
| H3 | 6 | 16 | 0x306a0 |
| H4 | 8 | 16 | 0x306b0 |
| H5 | 19 | 32 | 0x306c0 |
| H6 | 24 | 32 | 0x306e0 |
雖然申請的內存總數是0x41字節,但實際分配的情況存在“找零錢”現象,一共分配了0x80字節,使得尾塊的大小由 0x130 被削減為 0x120。堆塊的大小實際包括了塊首在內,即如果請求 32 字節,實際會分配的堆塊為 40 字節:8 字節塊首+32 字節塊身。堆塊的單位是 8 字節,不足 8 字節的部分按 8 字節分配。但是堆的指針卻直接指向數據區,而不是塊首,中間差了8字節,表中的基址一列都經過了減8處理,后文如無說明,默認做減8處理。 初始狀態下,快表和空表都為空,不存在精確分配。請求將使用“次優塊”進行分配。 這個“次優塊”就是位于偏移 0x0688 處的尾塊。 由于次優分配的發生,分配函數會陸續從尾塊中切走一些小塊,并修改尾塊塊首中的 size 信息,最后把 freelist[0]指向新的尾塊位置。 4.1.4.?堆塊的釋放 前文的操作,我們創建了6個堆塊,他們在內存中是連續存放的,接下來我們釋放堆,直觀了解堆塊釋放的過程。 首先執行HeapFree(hp,0,h1),釋放第一個堆塊,執行之后的效果如下圖所示,h1(堆塊0x3a0680)被釋放后,HEAP_ENTRY的標志位由忙碌態修改為0x00,前向指針和后向指針均指向0x3a0188,0x3a0188實際上就是FreeList[2],查看FreeList[2],其前向指針和后向指針的確都指向0x3a0688。 我們繼續單步,釋放h3和h5,這三次釋放的堆塊在內存中不連續,所以不會發生堆塊合并的現象,此時查看內存,h1、h3、h5均是空閑塊,h1、h3都被鏈入了FreeList[2],其后向鏈表關系:FreeList[2]→h1→h3→FreeList[2],h5被鏈入了FreeList[4]: 可以看到空表在鏈入新的堆塊時,將其鏈入鏈表尾部。 4.1.5.?堆塊的合并 繼續單步,釋放h4。h3、h4、h5這3個空閑塊彼此相鄰,這時會發送堆塊合并操作。 堆塊合并是主動的,我們可以看到h3、h4 的大小都是 16 字節,h5 是32 字節,合并后的新塊為64字節(8個堆單位),將被鏈入 freelist[8]。但是h4在釋放時,數據沒有反生變化,而是h3塊首的堆塊大小標志修改為8,h6塊首前一個堆塊大小標志修改為8,同時h3前/后向指向 freelist[8]。也就是說,在發生堆合并時,h4并不會被鏈入空表,事實上h4并沒有執行釋放的操作,h4直接被并入了空閑堆塊h3中,可以說h4在一無所知的情況下直接被抹殺了。 空表索引區的 freelist[2],原來標識的空表中有兩個空閑塊 h1和 h3,而現在只剩下h1,因為h3在合并時被摘下了; freelist[4]原來有一個空閑塊h5,現在被改為指向自身,因為h5在合并時被摘下了。 4.2.?LAL調試 前文我們介紹了Freelist中堆塊的申請與釋放過程,現在我們再來看看LAL中堆塊的申請與釋放過程。使用下列代碼進行分析: [C++]?純文本查看?復制代碼?
| 01020304050607080910111213141516171819 | #include <stdio.h>#include <windows.h>void main(){HLOCAL h1,h2,h3,h4,h5;HANDLE hp;hp = HeapCreate(0,0,0);__asm int 3h1 = 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);h5 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);HeapFree(hp,0,h5);} |
| 堆句柄 | 請求字節 | 實際分配字節 | 基址 |
| H1 | 8 | 16 | 0x31e88 |
| H2 | 8 | 16 | 0x31e98 |
| H3 | 16 | 24 | 0x31ea8 |
| H4 | 24 | 32 | 0x31ec0 |
| 0102030405060708091011121314151617181920212223 | #include <stdio.h>#include <windows.h>void main(){HLOCAL? h1,h2,h3,h4,h5,h6,h7;HANDLE hp;hp = HeapCreate(0,0,0);__asm int 3h1 = 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);h6 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);HeapFree(hp,0,h1);HeapFree(hp,0,h3);HeapFree(hp,0,h5);HeapFree(hp,0,h2);HeapFree(hp,0,h4);HeapFree(hp,0,h6);h7 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);HeapFree(hp,0,h7);} |
本章最后說一點,本章沒有介紹LFH,因為在XP SP3上使用LFH的情況很少,從Windows Vista開始,LFH才會默認啟用。在之后的Win 7堆研究文章里,我們再詳細研究LFH。 5.?堆的安全研究
5.1.?堆Cookie _HEAP_ENTRY結構中的SmallTagIndex 字段記錄堆的Cookie信息,大小只有1字節。當一個塊通過RtlHeapFree()被釋放的時候,這個值被檢查,但當這個塊被分配的時候并不會被檢查。堆溢出發生時,cookie數據會被破壞。但是,XP SP3中只是引入了cookie了,并沒有對cookie的值進行檢測,在XP之后的系統中這個機制才開始使用。因此在XP SP3中我們仍然可以進行堆溢出利用。 5.2.?堆溢出利用 XP上的堆漏洞利用主要堆溢出利用,但是隨著之后版本的操作系統開始應用堆Cookie機制,堆溢出利用越來越來少,越來越多的堆利用使用堆風水和堆構造的技術。本章只是簡單分析兩個堆溢出利用,和大家分享一下堆溢出的知識,在之后的文章中,筆者會再分析其他類型的漏漏洞利用。 來看兩個攻擊實例: 5.2.1.?覆蓋CommitRoutine 調試代碼: [C++]?純文本查看?復制代碼?
| 010203040506070809101112131415161718192021 | #include <stdio.h>#include <windows.h>int main(){HLOCAL h1,h2,h3,h4;HANDLE hp;//alloc a heaphp = HeapCreate(0,0x1000,0);h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);HeapFree(hp,0,h1);HeapFree(hp,0,h2);memcpy(h1,"AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD\x78\x05\x3a\x00",36);//0x3a0578,0x3a0000是新建堆的基址,此時覆蓋h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);//當h2從lookaside表中移掉//這時候,再分配時,將會從0x3a0578的地址開始分配h4 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);//然后,我們覆蓋CommitRoutine的值memcpy((char *)h4+4,"AAAA",4);//之后如果我們繼續申請大內存,會觸發CommitRoutine這個函數指針,而由于這個指針我們可控,所以可以導致執行任意代碼。HeapDestroy(hp);return 0;} |
| 0102030405060708091011121314151617181920212223242526272829303132333435363738 | #include <stdio.h>#include <windows.h>class test {//定義一個類結構public:test(){memcpy(m_test,"1111111111222222",16);};virtual void testfunc(){//等下我們要覆蓋的虛函數printf("aaaa\n");}char m_test[16];};int main(){HLOCAL hp;HLOCAL h1,h2,h3;hp = HeapCreate(0,0x1000,0);//新創建一個堆塊__asm int 3;//申請一樣大小的三塊,申請24.h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,24);//將第一塊內充滿shellcode,memcpy(h1,"AAAABBBBCCCCDDDDEEEEFFFFGGGG",24);test *testt = new test();test *tp;memcpy(h3,testt,24);//將創建的類的結構拷貝到第三個堆塊中去//釋放后將它們都會自動添加到lookaside鏈表中去。H3->h2->h1HeapFree(hp,0,h1);HeapFree(hp,0,h2);HeapFree(hp,0,h3);//添加完后,其虛函數的地址被修改為h1的地址//下面調用其虛函數。tp = (test *)h3;tp->testfunc();//此時執行的是0000AAAABBBB這些填充的shellcodedelete testt;HeapDestroy(hp);return 0;} |
6.?小結 本文分析了XP SP3的堆,對堆管理器的使用、堆漏洞的利用都進行了介紹。XP系統已經有了十幾年的歷史,本文敘述的許多知識不免過時,但也正是因為XP有著這么多年的歷史,各路大神對其的研究也已經很詳細,我們這些新人就能沿著前人走過的路,迅速掌握堆的知識,掌握堆分析研究的技巧。當然,本文只是拋磚引玉,更復雜堆的就在不遠處等著我們呢。
參考資料: 《BHUSA09-Practical Windows Heap Exploitation》 《軟件調試》 《0day安全》
總結
- 上一篇: intellij运行awt项目时,菜单栏
- 下一篇: 软考中级网络工程师学习笔记(知识点汇总)