《0day安全》學習筆記,主要討論WIndows2000~WIndowsSP1平臺的堆管理策略。node
0X01 堆與棧的區別程序員
棧空間是在程序設計時已經規定好怎麼使用,使用多少內存空間。典型的棧變量包括函數內部的普通變量、數組等。棧變量在使用的時候不須要額外的申請操做,系統棧會根據函數中的變量聲明自動在函數棧中給其預留空間。棧空間由系統維護,它的分配和回收都由系統來完成,最終達到棧平衡。全部這些對程序員都是透明的。數組
堆具有如下特性:安全
1.堆是程序運行時動態分配的內存。所謂動態是指所需內存的大小在程序設計時不能預先決定的,須要在程序運行時參考用戶的反饋。數據結構
2.堆在使用時須要程序員使用專用的函數進行申請,如C語言中的malloc等函數、C++中的new函數等都是最多見的分配堆內存的函數。堆內存申請有可能成功,也有可能失敗,這與申請內存的大小、機器性能和當前運行環境有關。ide
3.通常用一個堆指針來使用申請的內存,讀、寫、釋放都是經過這個指針來完成。函數
4.使用完畢後要經過堆釋放函數進行回收這片內存,不然會形成內存泄漏。如free,delete等。性能
0X02堆的數據結構與管理策略學習
對於管理系統來講,響應程序的內存使用申請就意味着要在「雜亂」的堆區中辨別哪些內存是正在被使用的,哪些內存是空閒的,並最終「尋找」到一片「恰當」的空閒內存區域,以指針形式返回給程序。spa
堆塊:堆區額內存按不一樣大小組織成塊,以堆塊爲單位進行標識,而不是傳統的按字節標識。一個堆塊包括兩個部分:塊首和塊身。塊首是一個堆塊頭部的幾個字節,用來標識這個塊首自身的信息,例如,大小、空閒或佔用。塊身是緊跟在塊首後面的部分,也是最終分配給用戶使用的數據區。
注意:塊管理系統返回的指針通常是塊身的起始位置,連續申請內存就是發現返回的內存之間存在「空隙」,那就是塊首。
堆表:堆表通常位於堆區的起始位置,用於檢索堆區中全部堆塊的總要信息,包括堆塊的位置、堆塊的大小、空閒或佔用等。 堆表的數據結構決定了整個堆區的組織方式。堆表每每不知一種數據結構:如平衡二叉樹等。
堆的內存組織如圖所示:
在Windows中佔用態的堆被使用它的程序管理,堆表只是管理空閒態的堆塊。
其中最重要的堆表有兩種:空閒雙向鏈表Freelist(空表)以及快速單項鍊表Lookaside(快表)
空表:
空閒堆塊塊首包含一對指針,這對指針把空閒堆塊組織成雙向鏈表。按照堆塊大小的不一樣,空表總共被分紅128條。
堆區一開始的堆表區中有一個128項的指針數組,被稱做空表索引。該數組每一項包含兩個指針,用於標識一條空表。
好比空表的第二項free[1]標識了項中全部大小爲8字節的空閒堆塊,以後每隔索引指示的空閒堆塊遞增8字節,free[2]標識16字節,free[3]標識24字節空閒堆塊,free[127]標識1016字節空閒堆塊。
空閒堆塊大小 = 索引項 * 8字節
把空閒堆塊鏈入不一樣的空表,能夠方便管理。空表第一項free[0]鏈入全部大於等於1024字節的堆塊(小於512K)。這些堆塊按照各自的大小在零號空表中升序排列。
快表
快表是Windows用來加速分配而採用的一種堆表。這類單向鏈表中不會發生堆塊合併(其中空閒堆塊塊首置爲佔用態)。
快表也有128條,組織結構與空表相似,只是堆塊按單鏈表組織,並且每條快表最多隻有4個節點。
0X03 堆的操做
堆得操做分爲:堆的分配、堆得釋放、堆得合併。分配與釋放是由程序執行的,堆的合併是由堆管理系統自動完成的。
1.堆的分配
堆分配分三類:快表分配,普通表分配,零號空表(free[0])分配。
從快表分配:尋找大小到大小匹配的空閒堆塊、將其狀態修改成佔用態、把它從堆表中卸下,最後返回一個指向堆塊塊身的指針給程序使用。
普通表分配:首先尋找最優的空閒塊分配,若失敗,則尋找次優的空閒塊分配。
零號空表分配:按照大小升序鏈着大小不一樣的空閒塊,找最優結果。
注意:當空表中找不到最優的堆塊時,會發生次優分配,即從大塊按照請求的大小精確割出一塊進行分配,而後給剩下部分從新標註塊首,鏈入空表。
2.堆塊釋放
將堆塊狀態改成空閒,鏈入相應的堆表。全部的釋放塊都鏈入堆表的末尾。
3.堆塊合併
通過反覆的申請與釋放,堆區產生不少內存碎片。堆管理系統發現兩個空閒堆塊彼此相鄰,就會進行堆塊合併。 包括將兩個塊從空閒鏈表中卸下、合併堆塊、調整合並後大塊的塊首信息、將新塊從新鏈入空閒鏈表。
幾個注意點:
1.快表空閒塊被置爲佔用態,因此不會發生堆塊合併操做。
2.快表只有精確分配時纔會分配。
3.分配與失敗有限使用快表,失敗用空表。
0X04 DWORD SHOOT 堆溢出利用原理
堆溢出的利用的精髓就是精心構造的數據溢出下一個堆塊的塊首,改寫塊首的前向指針和後向指針,而後在分配、釋放、合併等操做發生時得到一次向內存任意地址讀寫任意數據的機會。
原理:
Int remove(ListNode * node) { node->blink->flink = node -> flink; Node->flink->blink= node ->blink; Return0; }
那麼四個字節的利用咱們能夠作什麼呢?
1.內存變量:修改能影響程序執行的重要標誌變量,例如更改身份驗證函數的返回值。
2.代碼邏輯:修改代碼段重要函數關鍵邏輯,如程序分支處的判斷邏輯。
3.函數返回地址:堆溢出也能夠利用DWORD SHOOT更改函數返回地址。
4.攻擊異常處理:程序產生異常,Windows轉入異常處理機制,包括SEH等。
5.函數指針:如C++的虛函數調用。改寫這些指針後,函數調用每每就能夠劫持進程。
6.PEB中線程同步函數入口地址:每一個進程PEB存放着一對同步指針,指向RtlEnterCriticalSection()和RtlLeaveCriticalSection(),而且被ExitProcess()函數調用。