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



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 頁面寫時複製

但這裡存第另一個問題, 當一個進程修改了頁面內容, 就改變了本來進程初始化的樣貌, 甚至令其他同時運行的程序崩潰.

為解決這個問題, 在寫入頁面時 系統會首先攔截這項嘗試, 把要修改的頁面, 複製到新頁面再進行修改, 那結果是其他進程或新打開進程不會被影響

這項機制使內存減少分配 十個進程也共用一個 ,只有修改頁面內容時 才會寫時複製

如下圖:





在這種情況下, 所有進程, 包括自己進程都是不會互相影響的, 都是獨立的, 但有時侯,我們也是雖然在進程間共享數據, 那就出現本文的重點, 內存映射文件

2.4 內存映射文件

1. 創建/打開文件內核對象 (目標為 .EXE) , 透過CreateFile , (第一個參表明後備存儲器位置, 就是文件路徑) 失敗返回-1

創建後會預訂一塊地址空間區域, 然後再向物理存儲器調撥 , 而是文件在硬盤上的位置調撥 而不是頁交換文件, 

HANDLE WINAPI CreateFile(
  _In_     LPCTSTR               lpFileName,       //路徑
  _In_     DWORD                 dwDesiredAccess,
  _In_     DWORD                 dwShareMode,
  _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  _In_     DWORD                 dwCreationDisposition,
  _In_     DWORD                 dwFlagsAndAttributes,
  _In_opt_ HANDLE                hTemplateFile
);

2. CreateFile 後, 使用CreateFileMapping 創建文件映射對象, 創建後會預訂一塊地址空間區域, 然後再向物理存儲器調撥 , 而是文件在硬盤上的位置調撥 而不是頁交換文件, , 失敗返回NULL



HANDLE WINAPI CreateFileMapping(
  _In_     HANDLE                  hFile,                      //CreateFile返回值, 文件對象句柄
  _In_opt  LPSECURITY_ATTRIBUTES   lpAttributes, 
  _In_     DWORD                   flProtect, 
  _In_     DWORD                   dwMaximumSizeHigh,          //For 64GB 大小文件 最高32位 如文件大小為4GB 則永遠為0
  _In_     DWORD                   dwMaximumSizeLow,           //For 64GB 大小文件 最低32位
  _In_opt_ LPCTSTR                 lpName                      //為對象命名 供不同進程使用
);
3. 最後調用MapViewOfFile 把創建好的文件映射對象 映射到進程地址空間內, 映射後返回LPVOID指針 可以遍歷整個文件

LPVOID WINAPI MapViewOfFile(
  _In_ HANDLE hFileMappingObject,       //文件映射對象句柄
  _In_ DWORD  dwDesiredAccess,     //訪問權限 
  _In_ DWORD  dwFileOffsetHigh,    //高32位, 表示從文件中那個位置開始
  _In_ DWORD  dwFileOffsetLow,     //低32位, 表示從文件中那個位置開始 合起來成位64位的地址 一定要這樣 , 因為最大文件Windows支持16EB
  _In_ SIZE_T dwNumberOfBytesToMap      //把由偏移量開始到dwNumberOfBytesToMap的大小 映射到進程內 , 給0會嘗試遍歷整個文件
);

//另外, 我們可以透過調用MapViewOfFileEx 指定映射到進程地址空間的基地址 
LPVOID WINAPI MapViewOfFileEx(
  _In_     HANDLE hFileMappingObject,
  _In_     DWORD  dwDesiredAccess,
  _In_     DWORD  dwFileOffsetHigh,
  _In_     DWORD  dwFileOffsetLow,
  _In_     SIZE_T dwNumberOfBytesToMap,
  _In_opt_ LPVOID lpBaseAddress        //指定基址
);

Comments

Popular posts from this blog

Android Kernel Development - Kernel compilation and Hello World

How does Nested-Virtualization works?

Understanding ACPI and Device Tree