Windows安全 之 內存映射文件(Section對像)MmCreateSection與MmMapViewOfSection 流程分析
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 SubsectionBase; PSUBSECTION NextSubsection; ULONG PtesInSubsection; union { ULONG UnusedPtes; PMM_AVL_TABLE GlobalPerSessionHead; }; ULONG u; ULONG StartingSector; ULONG NumberOfFullSectors; } SUBSECTION, *PSUBSECTION;文件內核對像
1. 其中一個域指向SECTION_OBJECT_POINTERS結構,包含兩個成員DataSectionObject和ImageSectionObject
2. 他們同時指向CONTROL_AREA對像
以上的數據結構是為映射到內存而初始化,及創建
對於不同類型的Section對象,Windows使用不同方法進行映射供上層程式使用
MmCreateSection的三類共享文件的初始化:
如頁面文件類別(MiCreatePagingFile):
1.判斷如果MmCreateSection的fileobject與filehandle為NULL
2.使用MiCreatePagingFile創建SEGMENT對像及PTE隊列(分配在分頁內存)
3.MiCreatePagingFile內部CONTROL_AREA及一個SUBSECTION對象 (分配在非分頁內存,即物理內存)
4.MiCreatePagingFile內部初始化所有剛分配好的對象(注: 原型PTE隊列的分配類別如為SEC_LARGE_PAGES 則直接分配大頁面物理內存)
5.並返回後由MmCreateSection填充到Section
由於以上這類別完全不涉及內核文件對像 整體操作比較簡單
如映像文件類別(MiCreateImageFileMap): 如果AllocationAttributes 為SEC_IMAGE
1. 如果文件對像本身沒有CONTROL_AREA對像和SEGMENT對像
2. MmCreateSection會首先調用MiInsertImageSectionObject添加新創建的CONTROL_AREA對像與SEGMENT對像
3. 進入MiInsertImageSectionObject後他會把新創建的CONTROL_DATA->Section指針指向FileObject->ImageSectionObject(這個只是暫時占位用途)
4. 調用MiCreateImageFileMap(假如第一次被映射 以上步驟才會執行)
5. 同樣地把SEGMENT->ControlArea 指向 FileObject->ImageSectionObject
MiCreateImageFileMap內部:
1. 首先讀取PE頭,計算多少個頁面需要保留
2. 創建真正CONTROL_AREA對像(非分頁內存池) 以及 SubSection對像
3. 創建SEGMENT對像(分頁內存池) 以及 MMPTE陣列(大小為頁面總和,如10頁就有10個MMPTE元素)
4. CONTROL_AREA對像附帶多個SUBSECTION對像 (等如PE文件中有多少個段 就有多少個SUBSECTION對像)
5. SEGMENT對像則附帶PTE陣列,用於描述整個映像的大小(大小為頁面總和,如10頁就有10個MMPTE元素)
6. 利用PE文件填充CONTROL_AREA對像及SEGMENT對像
P.S PTE陣列中存放著映像中所有頁面的信息 分配如下
SizeOfSegment = sizeof(SEGMENT) + (sizeof(MMPTE) * ((ULONG)NumberOfPtes - 1)) + sizeof(SECTION_IMAGE_INFORMATION); //注意這裡是分頁內存 即虛擬內存啊 NewSegment = ExAllocatePoolWithTag (PagedPool | POOL_MM_ALLOCATION, SizeOfSegment, MMSECT); *Segment = NewSegment; RtlZeroMemory (NewSegment, sizeof(SEGMENT));
//MMPTE結構如下, 記錄頁面在文件中偏移 typedef struct _MMPTE { ULONG u; } MMPTE, *PMMPTE;即使以上對於內存的分配及初始化已經完成, 但進程仍然不能訪問內存, 因為即使分配了,進程沒有方法透過Section對像訪問內存
因此需要透過Section對像,進行映射,與進程中某虛擬空間進行映射
----------------------------------------------------------------------
NtMapViewOfSection->MmMapViewOfSection
它按Section類型調用下一層->
1. MiMapViewOfDataSection/
2. MiMapViewOfPhysicalSection/
3. MiMapViewOfImageSection
可以看得出也是三種映射方式 跟segment類別一致
三類映射函數的實現:
MiMapViewOfDataSection:
1. 函數首先確定內存區對像在目標進程中的地址範圍
2. 調用MiFindEmptyAddressRangeDown / MiFindEmptyAddressRange 在進程空間中尋找一段能包含大小的地址範圍
3. 申請VAD對像,初始化內存中所有開銷, 初始化成員指向傳入的Section->ControlArea 最後把VAD插入到進程的VAR AVL樹
4. 此時進程的VAD樹便多了一個內存描述
MiMapViewOfImageSection:
1. 函數首先確定基地址(由參數CapturedBase/ Section->Segment->BasedAddress )
2. 若基地址已被占用, 則調用MiFindEmptyAddressRangeDown / MiFindEmptyAddressRange 找到空閒地址, 大小為整個映像文件的大小
3. 申請VAD對像,初始化內存中所有開銷, 初始化成員指向傳入的Section->ControlArea 最後把VAD插入到進程的VAR AVL樹
4. 此時進程的VAD樹便多了一個內存描述
//我們首先回顧一下這個片段: //我們了解一下MiCreateImageFileMap的函數, 可以得出以下代碼.. //直到看到這段代碼, 就明白到PTE陣列 與 磁盤之間的關係, //陣列中每一個PTE都對應文件中的每一頁, 好了可以往下再看看MmMapViewOfSection的代碼 // //獲取第一個節 //MiCreateImageFileMap的代碼 Subsection = (PSUBSECTION)(ControlArea + 1); //獲取第一個節 NewSubsection = (PSUBSECTION)(LargeControlArea + 1); //遍歷所有節 for (i = 0; i < SubsectionsAllocated; i += 1) { //把節複製 RtlCopyMemory (NewSubsection, Subsection, sizeof(SUBSECTION)); //設置ControlArea NewSubsection->ControlArea = (PCONTROL_AREA) LargeControlArea; //下一個節 NewSubsection->NextSubsection = (NewSubsection + 1); //獲取指向文件中的第一個頁面 PointerPte = NewSegment->PrototypePte; // TempPte.u.Long = MiGetSubsectionAddressForPte (NewSubsection); TempPte.u.Soft.Prototype = 1; //遍歷所有Ptes for (j = 0; j < NewSegment->TotalNumberOfPtes; j += 1) { ASSERT (PointerPte->u.Hard.Valid == 0); if ((PointerPte->u.Soft.Prototype == 1) && (MiGetSubsectionAddress (PointerPte) == Subsection)) { OriginalProtection = MI_GET_PROTECTION_FROM_SOFT_PTE (PointerPte); TempPte.u.Soft.Protection = OriginalProtection; //讓CONTROL上的所有原型PET都變成指向文件的原型PTE MI_WRITE_INVALID_PTE (PointerPte, TempPte); } PointerPte += 1; } Subsection += 1; NewSubsection += 1; } ------------------------------------------------------------------------------------------------ //MmMapViewOfSection的代碼節錄: if (Process->VmTopDown == 1) { if (ZeroBits != 0) { HighestUserAddress = (PVOID)((ULONG_PTR)MM_USER_ADDRESS_RANGE_LIMIT >> ZeroBits); if (HighestUserAddress > MM_HIGHEST_VAD_ADDRESS) { HighestUserAddress = MM_HIGHEST_VAD_ADDRESS; } } else { HighestUserAddress = MM_HIGHEST_VAD_ADDRESS; } Status = MiFindEmptyAddressRangeDown (&Process->VadRoot, // VAD根節點 , 遍歷所有已分配內存 NeededViewSize, // 需要空間 HighestUserAddress,// 用戶空間 X64K, &StartingAddress // 返回空間地址 ); } else { Status = MiFindEmptyAddressRange (NeededViewSize, //大致同上 X64K, (ULONG)ZeroBits, &StartingAddress); } LargeStartingAddress = StartingAddress; LargeEndingAddress = EndingAddress; AllocateVad: //....省略 Vad = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD), MMVADKEY); //....省略 RtlZeroMemory (Vad, sizeof(MMVAD)); //設置VAD成員為空閒塊空間 Vad->StartingVpn = MI_VA_TO_VPN (LargeStartingAddress); Vad->EndingVpn = MI_VA_TO_VPN (LargeEndingAddress); SectionOffset->LowPart = SectionOffset->LowPart & ~(X64K - 1); //大整數低32位 SectionOffset為映射虛擬地址 , 虛擬地址右移12位 得到PFN 調試中用!search指令可觀察變化 PteOffset = (ULONG)(SectionOffset->QuadPart >> PAGE_SHIFT); //這裡就把PTE 這裡的PTE是指向文件中每一個節的偏移, 然後設置到VAD Vad->FirstPrototypePte = &Subsection->SubsectionBase[PteOffset]; Vad->LastContiguousPte = MM_ALLOCATION_FILLS_VAD; //來到這裡應該明白到在MiCreateImageFileMap中經過一大輪工作後 出來的陣列是有什麼用了吧?? //把PTE陣列的元素添加至VAD 最大原因是因為缺頁異常發生時, 內核的內存管理器會首先遍歷VAD,如果發現可以換頁 則換入分頁頁面到物理內存 解決問題 //這樣一來已經完全解決了所有問題, 進程只需要知道哪個範圍內的領空屬於這個文件, 那進程就可以隨時訪問他, 讓他產生缺頁異常, 由系統自動修補 //這樣的話,內存就不需要加載文件的所有頁面, 而且所有進程指向的都是文件頁面, 因而達致共享效果
P.S. 缺頁異常指: 訪問的虛擬地址所映射的空間不在物理內存, 因此發生中斷
以下提供一張全景分析圖:
Comments
Post a Comment