Windows內核分析索引目錄:https://www.cnblogs.com/onetrainee/p/11675224.html
VAD樹的屬性以及遍歷
前面學習過的PFNDATABSAE是管理物理頁的,整個操作系統僅維護一個PFNDATABASE。
現在的VAD是管理虛擬內存的,每一個進程有自己單獨的一個VAD樹。
VAD樹:
- 比如你使用VirtualAllocate函數申請一個內存,則會在VAD樹上增加一個結點,其是_MMVAD結構體。
- 一個VAD結點可以有多個頁,這在StartingVpn和EndingVpn會介紹。
- 當以MEN_SERVIED保留屬性提交時,其只是在VAD樹上掛上一個節點,真正提交時這棵樹才是由意義的。
一、VAD結構體介紹
kd> dt _MMVAD
nt!_MMVAD
+0x000 StartingVpn : Uint4B
+0x004 EndingVpn : Uint4B
+0x008 Parent : Ptr32 _MMVAD
+0x00c LeftChild : Ptr32 _MMVAD
+0x010 RightChild : Ptr32 _MMVAD
+0x014 u : __unnamed
+0x018 ControlArea : Ptr32 _CONTROL_AREA
+0x01c FirstPrototypePte : Ptr32 _MMPTE
+0x020 LastContiguousPte : Ptr32 _MMPTE
+0x024 u2 : __unnamed
1. StringVpn 起始頁 / EndingVpn結束頁
1)兩者算法是不同的。起始頁:startingVpn*0x1000/結束頁:EndVpn*0x1000+0xfff。
2. Parent、LeftChild、RightChild - 其父節點、左子樹、右子樹。
1)我們遍歷這些樹時用到的就是這些結構體成員。
3. u - 其是_MMVAN_FLAGS屬性,非常重要的。
kd> dt _MMVAD_FLAGS
nt!_MMVAD_FLAGS
+0x000 CommitCharge : Pos 0, 19 Bits
+0x000 PhysicalMapping : Pos 19, 1 Bit
+0x000 ImageMap : Pos 20, 1 Bit
+0x000 UserPhysicalPages : Pos 21, 1 Bit
+0x000 NoChange : Pos 22, 1 Bit
+0x000 WriteWatch : Pos 23, 1 Bit
+0x000 Protection : Pos 24, 5 Bits
+0x000 LargePages : Pos 29, 1 Bit
+0x000 MemCommit : Pos 30, 1 Bit
+0x000 PrivateMemory : Pos 31, 1 Bit
1)CommitCharge 實際提交的頁數。
其19Bits,我們內存低字節7ffffff,正好十九位。
比如我們以MEN_RESERVED保留形式提交了4頁大小的內存,此時這里為2,將一頁改為EXECUTE屬性,這時這里就會變成2。
2)PyhsicalMapping:內核物理頁映射。
3)UserPhysicalPages:內核物理頁映射。
4)PrivateMemory:如果私有設置為1。
5)ImageMap:對dll/exe等文件進行保護,防止其被修改(使用映射寫拷貝之類的原理)
如果ImageMap為1,PrivateMemory為0,說明其為DLL。
6)NoChange:關於鎖頁技術。當置為1,像VirtualProtect等函數不會改變其頁的屬性。
7)LargePage:標志是否為大頁。
8)MemCommit:提交狀態,只要提交就會置為1(CommitCharge存儲提交了多少頁)
9)Protection:3bit,關於保護(比如頁的讀寫、可執行等)。
下面介紹了其對應相關文件的對應關系,看這個很好理解。
這篇博客詳細介紹了R3與R0中頁面保護的對應關系:R3環申請內存時頁面保護與_MMVAD_FLAGS位的對應關系
4. ContraArea 控制結構 :
其指向一個_CONTROL_AREA的數據結構,該結構就暫不表述了。
1)_CONTROL_AREA+ 0x24 FilePointer,文件指針,指向一個_FILE_OBJECT結構體。
2)_FILE_OBJECT結構體中,保存着文件對象很多關鍵的信息。
a> +0x30 FileName 文件名
若想知道該頁屬於哪個文件,可以查看這里。
將.sys文件偽裝成.dll文件,則必須修改這里。
b> +0x26-0x28 文件保護屬性
比如一個文件被獨占無法刪除,在內核中你可以將DeleteAccess位置1,之后強制刪除。
二、利用windbg遍歷VAD樹
1. 每個進程的VAD樹存儲在_EPROCESS+0x11c結構體中,其是ROOTVAD根結點。
2. 獲取每個進程的 _EPROCESS,使用指令!process 0 0,得到PROCESS的值就是指向_EPROCESS的指針。
3. 當獲取VadRoot之后,可以使用 !vad VadRoot來顯示該進程的VAD樹。
三、使用代碼實現VAD樹的遍歷
代碼核心就是先遍歷進程找出目標進程(這里默認 test.exe),之后對目標進程的VAD樹進行遍歷。
1 #include <ntddk.h> 2 3 4 //---------------------// 5 // MMVAD結構體簡單定義 // 6 //---------------------// 7 typedef struct _MMVAD { 8 ULONG StartingVpn; 9 ULONG EndingVpn; 10 struct _MMVAD * Parent; 11 struct _MMVAD * LeftChild; 12 struct _MMVAD * RightChild; 13 }MMVAD,*PMMVAD; 14 15 16 17 VOID Unload(IN PDRIVER_OBJECT pDriverObject) { 18 DbgPrint("Driver UnLoad!"); 19 } 20 21 //-----------// 22 // 遍歷VAD樹 // 23 //-----------// 24 VOID vad_enum(PMMVAD pVad) { 25 if (pVad) { 26 DbgPrint("Start: %x | End: %x | \r\n", pVad->StartingVpn, pVad->EndingVpn); 27 if (pVad->LeftChild) 28 vad_enum(pVad->LeftChild); 29 if (pVad->RightChild) 30 vad_enum(pVad->RightChild); 31 } 32 } 33 34 35 //-------------------------------------------------------------// 36 // 在內核中進程遍歷的原理就是先獲取系統進程EPROCESS結構 // 37 // 然后依照其鏈表來獲取其他的進程 // 38 // 依次遍歷出來 // 39 //-------------------------------------------------------------// 40 NTSTATUS process_enum() { 41 42 PEPROCESS pEprocess = NULL; // 得到系統進程地址 43 PEPROCESS pFirstEprocess = NULL; 44 ULONG ulProcessName = 0; // 字符串指針,指向進程名稱 45 ULONG ulProcessID = 0; // 進程ID 46 ANSI_STRING target_str; // 帶檢測進程的名稱 47 ANSI_STRING ansi_string; // 48 ULONG VadRoot; 49 50 //----------------------------// 51 // 得到當前系統進程的EPROCESS // 52 //----------------------------// 53 pEprocess = PsGetCurrentProcess(); 54 if (pEprocess == NULL) { 55 DbgPrint("獲取當前系統進程EPROCESS錯誤.."); 56 return STATUS_SUCCESS; 57 } 58 DbgPrint("pEprocess addr is %x0x8\r\n", pEprocess); 59 pFirstEprocess = pEprocess; 60 61 while (pEprocess) { 62 63 ulProcessName = (ULONG)pEprocess + 0x174; 64 ulProcessID = *(ULONG*)((ULONG)pEprocess + 0x84); 65 VadRoot = *(ULONG*)((ULONG)pEprocess + 0x11c); 66 67 //--------------------------------------// 68 // 將目標進程與當前進程的進程名進行對比 // 69 //--------------------------------------// 70 RtlInitAnsiString(&ansi_string, (PCSTR)ulProcessName); 71 RtlInitAnsiString(&target_str, "test.exe"); 72 if (RtlEqualString(&ansi_string, &target_str, TRUE)) { 73 DbgPrint("檢測到進程字符串,%x", ulProcessID); 74 vad_enum((PMMVAD)VadRoot); // 開始遍歷目標進程的VAD樹 75 return STATUS_SUCCESS; 76 } 77 pEprocess = (PEPROCESS)(*(ULONG*)((ULONG)pEprocess + 0x88) - 0x88); 78 79 if (pEprocess == pFirstEprocess || *(ULONG*)((ULONG)pEprocess + 0x84) <= 0) { 80 DbgPrint("遍歷結束!未檢測到進程ID!\r\n"); 81 break; 82 } 83 } 84 return STATUS_SUCCESS; 85 } 86 87 NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING registeryPat) { 88 DbgPrint("Driver Loaded!"); 89 pDriverObject->DriverUnload = Unload; 90 process_enum(); 91 return STATUS_SUCCESS; 92 }