內存管理
線性地址的管理
進程空間的地址劃分
分區 | x86 32位Windows |
---|---|
空指針賦值區 | 0x00000000 - 0x0000FFFF |
用戶模式區 | 0x00010000 - 0x7FFEFFFF |
64KB禁入區 | 0x7FFF0000 - 0x7FFFFFFF |
內核 | 0x80000000 - 0xFFFFFFFF |
線性地址有4GB,但是并不是所有的地方都能訪問(這里的不能訪問只是默認情況下,一但給這些區域掛上物理頁還是可以訪問的),所以需要記錄哪些地方分配了
在內核空間是通過一個鏈表把所有未分配的空間鏈在一起
但是在用戶空間,這樣管理的效率太低,而是通過收索二叉樹來管理
在_EPROCESS結構體當中有一個成員VadRoot,這個成員就是這個二叉樹的入口點
由于我是用64位windbg分析32位的系統,版本等原因導致VadRoot的地址未被正常解析出來,所以我們直接加上偏移來解析這個地址
dt _RTL_AVL_TREE (ac110040 + 310) // 進程地址ac110040 + 偏移310 VadRoot處
得到地址:0xbb925678
VadRoot通常每一個節點都是_MMVAD結構,但是現在的windows對VadRoot進行了優化,并不直接指向_MMVAD,而是通過AVL 樹/紅黑樹的結構來進行優化訪問和存儲可以使用以下命令直接遍歷VadRoot
!vad 地址 // 遍歷vad
字段 | 示例值 | 含義 |
---|---|---|
VAD 節點地址 | bb923260 | 該 VAD 節點在內核中的內存地址(_MMVAD 結構地址) |
Level | 8 | 該節點在 VAD 樹中的深度(層級) |
Start | 580 | 內存區域的起始頁號(需轉換為虛擬地址:Start << PAGE_SHIFT ,32位系統 PAGE_SHIFT=12 ,即 0x580000 ) |
End | 5a7 | 內存區域的結束頁號(0x5A7000 ) |
Commit | 5 | 已提交的物理頁數量(單位:頁,每頁通常 4KB) |
Type | Mapped | 內存類型: ? Private (私有內存,如堆/棧)? Mapped (映射文件或共享內存) |
Subtype | Exe | 子類型(僅適用于 Mapped 類型):? Exe (可執行文件映射)? Image (鏡像文件)? 其他(如 Pagefile ) |
Protection | EXECUTE_WRITECOPY | 內存保護標志: ? READONLY /READWRITE ? EXECUTE /EXECUTE_WRITECOPY ? PAGE_GUARD (保護頁) |
File/Desc | \Users\...\x32dbg.exe | 如果是文件映射,顯示文件路徑;如果是共享內存,顯示描述信息(如 Pagefile section ) |
Private Memory
申請內存的兩種方式:
- 通過VirtualAlloc/VirtualAllocEx申請的:Private Memory(當前的進程獨享內存)
- 通過CreateFileMapping映射的:Mapped Memory
我們來通過代碼來看一下VirtualAlloc在沒有分配和分配后的線性地址
#include<iostream>
#include<windows.h>LPVOID lpAddr;int main() {printf("當前內存還未申請!");getchar();lpAddr = VirtualAlloc(NULL, 0x1000 * 2, MEM_COMMIT, PAGE_READWRITE);printf("申請的內存地址:0x%x", lpAddr);system("pasue");return 0;
}
此時內存還未申請,我們用windbg查看一下當前進程的線程地址
回到程序讓程序申請內存后我們再來看下
可以看到在我們沒有分配內存時,
0xbc0
位置是沒有分配的,可以看上面對應的屬性和我們申請時填寫的一致
堆與棧
那這個VirtualAlloc和我們在寫c/c++程序時,用到的molloc/new關鍵字有什么區別呢,c/c++使用的申請是在堆
當中申請的它們的低層實現是HeapAlloc,它是由操作系統提前通過VirtualAlloc申請好的一塊內存空間,當使用molloc/new時,就會把申請好的地址給掛過去
代碼測試
#include<iostream>
#include<Windows.h>int num = 0x789;int main() {printf("申請內存之前!");getchar();// 在棧上分配內存int stack = 0x123;// 在堆上分配內存int* heap = new int(0x456);printf("棧空間的地址:0x%x\n",&stack);printf("堆空間的地址:0x%x\n",heap);printf("全局變量的地址:0x%x\n", &num);system("pause");return 0;
}
可以發現在我們程序中無論是全局變量,還是堆空間,棧空間中的內存在程序運行時就已經存在了
可以發現全局變量是在我們的程序中的一個位置寫死的
Mapped Memory
上面講到Private Memory是推私有的,而Mapped Memory是共享的,可以是文件共享或者是物理頁共享
在上圖中,Mapped后面有對應文件路徑的就是文件共享,反之就是物理頁共享
代碼測試
#include<iostream>
#include<windows.h>int main(){// 第一個參數如果提供一個文件的句柄,那么創建出來的就是文件映射,否則就是內存映射。HANDLE g_hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,BUFSIZ,L"共享內存");// 將物理頁與線性地址進行關聯LPTSTR g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile,FILE_MAP_ALL_ACCESS,0,0,BUFSIZ);*(PDWORD)g_lpBuff = 0x12345678;printf("A進程寫入地址內容:%p - %x",g_lpBuff,*(PDWORD)g_lpBuff);system("pause");return 0;
}
我們再到windbg中遍歷一下
可以看到
B30
的位置已經分配好了物理頁,然后我們就可以在其他進程獲取到這個創建好的內存空間
代碼測試
#include <iostream>
#include <windows.h>int main() {HANDLE g_hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"共享內存");// 將物理頁與線性地址進行映射LPTSTR g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);printf("B進程讀取%x", *(PDWORD)g_lpBuff);system("pause");return 0;
}
可以看到我們成功的讀取到了內容
共享文件
#include<iostream>
#include<windows.h>int main(){HANDLE g_hFile = CreateFile(L"newMemory.exe",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS, FILE_ATTRIBUTE_READONLY, NULL);HANDLE g_hMapFile = CreateFileMapping(g_hFile,NULL,PAGE_READWRITE,0,BUFSIZ,NULL);LPTSTR g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);printf("地址:0x%x",g_lpBuff);system("pause");return 0;
}
windbg中查看
可以看到已經成功的映射到了我們的文件上
寫拷貝
可以看到這里的有一部分它的類型是EXECUTE_WRITECOPY,Mapped的后面還有一個Exe,這又是什么呢?
代碼測試
#include<iostream>
#include<windows.h>int main(){LoadLibrary(L"C:\\Users\\win10x32\\Desktop\\gxnc.exe");system("pause");return 0;
}
可以看到當我們以LoadLibrary載入一個PE文件時,它的屬性會被設置為EXECUTE_WRITECOPOY,所以我們看到的kernel32.dll,KernelBase.dll,其實都是操作系統用LoadLibrary一個個加載的,本質上沒有任何區別,設置為EXECUTE_WRITECOPOY是因為當前系統環境有很多進程都在使用,也都可以對該文件進行修改,那這樣以來,一但某一個進程修改了系統dll,那其他使用這個dll的進程就會出問題