Posts

Showing posts with the label 操作系統內存

Windows內核安全之 線程調度

Image
線程一個比較重要的概念是, CPU中永遠調度的是線程, 而不是進程, 而進程的切換完全是源於是否同一個CR3寄存器, 詳見SwapContext的實現 以下先列出KTHREAD對象 內核中對線程優先級(priority) 分別使用0~31表示 實時(Real-time)類別: 16~31 動態(Dynamic)類別: 1~15 系統(System)類別: 0 線程在內核的執行體層(Executable Layer)中 使用以下6種優先級 實時(Realtime) 高(High) 普通之上(Above normal) 普通(normal) 普通之下(Below normal) 低(Low) 執行體層透過一個數組PspPriorityTable 轉換成真正的表示 以下是他們的對應關係 實時(Realtime) 24 高(High) 13 普通之上(Above normal) 10 普通(normal) 8 普通之下(Below normal) 6 低(Low) 4 進程的對象EPROCESS結構中 BasePriority 指定一個進程中所有線程的預設優先級 可以通過KeSetPriorityAndQuantumProcess 或 KeSetBasePriorityThread 函數設置優先級 KTHREAD中的Priority域記錄一個線程當前實際優先級, 他一般是在BasePriority基礎上提升到某個增量 Priority不會超過0~15 內核通過調用KiComputeNewPriority計算並調整非實時的線程優先級 我們可以使用NtSetInformationProcess為進程的基礎優先級進行微調, 所以一條線程在執行體中的值並非數組中指定的值 優先級的提升: ------------- 由於每個線程優先級都有對應的鏈表維護著相同優先級的線程 方便調度 所以優先級的提升, 一定是在加入鏈表之前 內核中不小函數的參數使用KPRIORITY Increment進行提升 如: KeInsertQueueApc KePulseEv...

Windows安全 之 內存映射文件(Section對像)MmCreateSection與MmMapViewOfSection 流程分析

Image
MmCreateSection 內存區(Section)內核對像有兩種,但都是基於分頁內存的: 一種是基於頁面文件的 一種是基於其他文件,以文件空間作為基礎的 如EXE在硬盤上的空間 MmCreateSection主要目的: 1. 填充CONTROL_AREA對像 2. 填充SEGMENT對像 3. 調用ObCreateObject創建Section內核對像 4. 返回Section內核對像 SEGMENT是真正描述映射區域的對像 , 內核函數MmCreateSection中有三種創建方式: 1. MiCreatePagingFileMap //以PageFile(頁面文件)為基礎的文件共享 2. MiCreateImageFileMap //以可執行映像作文件共享 3. MiCreateDataFileMap //以普通數據文件作為基 p.s. 後兩者雖表面內核文件對像, 從函數內部邏輯可發現, 如果映射同一個文件時, Section對像會共用同一個Segment對像 由於Section對像的創建完全是建基於SEGMENT對像, 因此探討SEGMENT對像才是正確方向 了解共享實現 SEGMENT(段對像,分配在分頁內存): 1. 分配的共享空間頁面總數量大小的MMPTE陣列緊隨著SEGMENT對像(用於建立映射視圖時) 2. 指向CONTROL_AREA指針(CONTROL_AREA, 分配在非分頁內存,即物理內存, 它亦會指向SEGMENT對像, 建立互指的關係) 3. 記錄對應節中的所有頁面的數量 4. 全部MMPTE最終指向節的起始地址 建數內部會再次創建真正的: CONTROL_AREA(控制區對象,分配在分頁內存) 1. 指向文件對像指針 2. 尾隨著多個SUBSECTION對像 (指PE中各個節的起始地址,使用Segment的MMPTE進行初始化->再讓VAD中會使用SubSection) 3. SUBSECTION形成鏈表,其中一個指針會指回CONTROL_DATA SubSection結構如下: typedef struct _SUBSECTION { PCONTROL_AREA ControlArea; PMMPTE SubsectionB...

Windows安全之 應用層(Ring3)的)內存管理(四) 堆(Heap)

Image
4. Windows 最小單位的內存管理 - 堆 進程的默認堆是1MB, 透過修改鏈接器的/HEAP 屬性可以自定義大小 相比之前兩章, 堆是用來管理鏈表和樹的最好內存結構, 堆的優點就是可以不顧分配粒度, 和頁面邊界的事情, 但缺點就是分配及釋放比較慢一點, 而且也沒法再對物理存備器調撥進行直接控制。 4.1 創建額外的堆  HANDLE WINAPI HeapCreate( _In_ DWORD  flOptions, _In_ SIZE_T dwInitialSize, _In_ SIZE_T dwMaximumSize  ); 使用創建 HeapCreate 額外的堆 使用額外的堆 可使線程開銷減低, 而且把儲存對象分類, 不然如果把鏈表及二叉樹的對象放在同一個堆中, 會發現鏈表的節點有可能會令二叉樹的節點被破壞,  如下圖 創建額外的堆的原因 假設NODE1 有代碼會覆蓋它的下8個字節的地方, 這樣就會使NODE1及BRAJNCH2,3 被破壞 所以我們是需要創建額外的堆來保護我們的組件, 不應把所有類別都在放同一個堆中 使用堆, 能夠快速釋放, 例如我們將一個二叉樹的所有節點 進行釋放, 需要遍歷所有節點進行釋放, 但現在 我們使用堆存放著所有節點, 我們只需要把這個堆釋放, 例如Windows的資源管理器使用二叉樹來遍歷系統所有進程, 那如果刷新一次, 他就需要重新遍歷一次系統 看進程是否已經存在, 但使用堆就可以直接清除, 最後重新遍歷, 不用再比對什麼 4.2 從堆中分配內存塊 使用以下函數 LPVOID WINAPI HeapAlloc( _In_ HANDLE hHeap, //指定哪一塊堆中建立內存塊 _In_ DWORD  dwFlags, _In_ SIZE_T dwBytes ); 使用創建HeapAlloc在堆中分配一塊內存,  如果成功返回內存塊地址, 若較大的內存分配應直接使用VirtualAlloc , 而不使用堆函數 LPVOID WINAPI HeapReAlloc( _In_ HANDLE hHeap, ...

Windows安全之 應用層(Ring3)的內存管理(三) 之 線程棧 Thread Stack

Image
在前兩章提及過, 系統有時侯會在進程地址空間中,分配及調撥空間,(PEB,TEB), 在這裡線程棧也是一個例子 3.1 線程棧  我們每創建一個線程, Windows也會為線程創建棧空間, 預設是1MB大小 透過修改Mircosoft C++ Compiler 的選項 /F 或 /STACK 選項, CreateThread 及_beginThreadex 都有一個參數是,可以用來指定一開始要調撥多少空間作為線程棧的用途 線程棧的存在有什麼意義呢? 主要就是供線程的所有函數使用, 隨著線程調用的函數樹愈深, 一直到棧底 線程棧初始時, 只會調撥分配到的首兩頁到內存(物理內存/ 虛擬內存 ) , 所有頁面也有PAGE_READWRITE屬性,  一開始調撥棧頂的0x080FF000這頁面 , 而他的下一頁面0x080FE00就會有PAGE_GUARD屬性, 此項技術使系統需要用到棧時才增大棧空間(調撥內存), 隨著函數一直調用, 參數不停的壓棧, 調用樹加深, 令指針接近棧底, 沒有及時返回, 就會棧位溢出(stack overflow) 假設: 調用樹非常深, 棧指針指到0x8003004, 而此時線程調用另一個函數 , 由於繼續壓入參數,  物理內存必需要調撥更多的物理存備器(虛擬或物理內存), 一如以往正常會調撥 0x08002000頁面 及 設置0x08001000保護屬性(PAGE_GUARD),   但是系統為0x08001000的頁面調撥時(棧底前的一頁), 系統對他進行特別處理, 變成以下樣子 , 此時能看見到與一開始調撥的慣例很不同, 他的下一頁並沒有PAGE_GUARD屬性, 這是表明系統已經分配所有棧的空間給程序, 系統是"永遠不會調撥棧底頁面" , 具體原因如下: 當系統給0x08000000頁面調撥物理存儲器的時侯,它會執行一些額外的操作 -- 拋出棧位溢出異常(EXECEPTION_STACK_OVERFLOW異常) , 該異常代號 0x0C00000FD,通過使用結構化異常處理(SEH), 如果在異常後繼續使用棧, 那他會盡量用0x08001000頁面空間, 最終"試圖"訪問0x08000000 地址的頁面(該頁面並沒有調撥物理存儲器) , 系統會拋出訪問...

Windows安全之 應用層(Ring3)的內存管理(二) 內存映射文件

Image
2.1 內存映射文件 映射到內存的可執行文件與DLL 載入.exe到地址空間 1. 使用CreateProcess 所指定可執行文件的位置, 如果無法找到.exe 則返回FALSE 2. 如果找到, 系統會為進程創建一個進程內核對象 3. 系統為進程創建4GB私有地址空間 4. 在地址空間裡, 系統預訂(Reserve)一塊足夠大的空間來容納.EXE文件,待預訂的具體位置在x86是0x00400000, 在x64 是動態加載, 5. 系統會為地址空間區域進行標示, 表明該區域 的後備存儲器是在.EXE文件自身, 而非頁交換文件 載入.dll 到地址空間, 使用LoadLibrary函數 載入DLL 1. 系統會預訂一塊足夠大的地址空間區域來容納.dll, 待預訂的地址在.dll中指明, 預設為0x00100000 , 如已加載 , 加載到別的地方時, .DLL內部會進行重定位, 一開始都假設區域的加載地址是0x00100000 , 那以這個地址作為基址, 進行偏移就錯了, 所以需要重新獲取dll 加載地址進行重定位(Relocation), 在我PE文件章中已提及過 2. 系統會對地址空間進行標示, 標示是後備存儲器是來自.DLL文件(硬盤上) ,如果因無法分配dll文件大小使無法加載到原本指定位置(0x00100000), 而系統需要進行重定位, 系統會額外標示有一部份是映射到頁交換文件 2.2 內存不會共享靜態數據 如果一個應用程序已在運行, 我們再打開一個新進程時, 系統會創建4GB內存地址空間及新的進程及線程對象,假設第二個進程現在開始, 這時系統只不過把應用程序的代碼與數據的虛擬內存頁面,映射到第二個實例空間中 如下圖 P.S 這裡的圖為什麼說是虛擬內存, 沒有物理內存, 因為所有虛擬地址空間跟物理內存本來就存在映射關係,所以在內存中他們地址也是一樣的,不用特別複製, 虛擬地址映射到虛擬內存是因為當頁面沒有用時被交換到硬盤(虛擬內存) 是會隨機變動的, 頁面就更新到新進程到。 2.3 頁面寫時複製 但這裡存第另一個問題, 當一個進程修改了頁面內容, 就改變了本來進程初始化的樣貌, 甚至令其他同時運行的程序崩潰. 為解決這個問題, 在寫入頁面時 系統會首先攔截這項嘗試, 把要修改的頁面, 複製到...

Windows安全之 應用層(Ring3)的內存管理(一) 進程的虛擬地址空間 與 虛擬內存(頁交換文件), 及物理內存的關係

Image
Windows應用層,有三種內存管理 1. 進程的虛擬地址空間 2. 內存映射文件(Memory-Mapping File) 3. 堆(Heap) 本章節先剖析 WIN32進程的虛擬地址空間 一個進程的虛擬地址空間為 2^系統位數, 如32位就2^32 = 4GB , 代表每個進程擁有4GB的虛擬地址空間 但這4GB是"虛擬的" 不是實質存在, 在我的另一篇文章中寫了WINDOWS是怎麼把虛擬地址跟物理掛勾起來 實現每人也有4gb的虛擬地址空間 而這裡是說我們如何使用?? 進程的虛擬地址空間 在Windows中4GB空間被劃分成以下區域 就32位Windows而言 1.1 0x00000000 - 0x0000FFFF 是空指針分區 以下代碼 int *ptr = (int*)malloc(sizeof(int)); *ptr = 5; 這個代表沒有檢測到分配內存空間不成功時侯的情況 , windows系統,針對這種情況 , 劃分這區域, 由於ptr分配失敗的時侯, ptr = NULL , 亦即0x00000000 程序就會拋出異常 1.2 0x00010000 - 0x7FFEFFFF 是用戶模式分區 1.2.1 分配虛擬地址空間 , 粒度與頁面大小限制 用戶模式分區,在32位系統中有2GB空間 當程序被系統加載時, 大部份的虛擬內存都是還沒跟物理內存掛勾起來的,或說閒置(free)的,我們必須調用VirtualAlloc函數分配區域,分配的動作稱為預訂(Reserving) 當程序預訂地址時,會確保區域的起始地址正好是分配粒度的整倍數, 大部份CPU 分配粒度也是64kb 也就是說, 分配一個區域時系統會分配的地址是64KB的整數倍 而區域的大小為頁面大小4KB 有時侯系統會以應用程序的名義來預訂區域, 例如系統會分配一塊空間用來存放系統環境塊(Process Environment Block(PEB)), PEB是一個完全由系統創建的內存塊, 當系統創建進程時, 同時都會替他在他的虛擬內存中,預訂一塊區域,存放PEB, 管理所有進程信息 系統同時會創建線程環境塊(Thread Environemnt Block(TEB))來協助管理所有線程, 系統會在創...