VAD树结构体的属性以及遍历
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 }