VAD树
前面说的进程隐藏,模块隐藏
但是这个是骗不了例如PCHunter这种软件的
这是因为这种软件用了别的方法去遍历进程和模块
Vad树介绍
VAD(Virtual Address Descriptor)树是Windows操作系统用来管理进程的虚拟内存区域(memory regions)的一种数据结构。它以平衡二叉树(通常是自平衡的AVL树)的形式存在,存储了一个进程中所有的虚拟地址范围(内存区域)。这些虚拟地址区域包含了进程所使用的内存,比如模块加载的内存、堆、栈等。
为了隐藏进程,常见的技术就是断开活动进程链 ,虽然这种方法能够从用户态视角隐藏进程,但它并没有改变内存管理系统和其他内核结构中的信息,进程仍然在系统的其他地方可见。
每一个进程(EPROCESS)都有Vadroot结构,这保存着Vad树的根节点
用windbg可以自动解析Vad树
关于内存的Private,Mapped属性
我们可以看到,解析出来的属性里面,内存类别有Mapped,还有Private,只是什么东西?
在windows内存管理里面,只有两种属性,分别是 Private 、 Mapped ,即私有内存和映射内存
这两类内存的区别主要有2点不同:
申请内存的方式不同
私有内存:通过 VirtualAlloc/VirtualAllocEx 申请的
映射内存:通过 CreateFileMapping 映射的
使用方式不同
私有内存:独享物理页
映射内存:可能要与其它进程共享物理页
我们提到只有 VirtualAlloc 和 CreateFileMapping 这两个函数申请的内存,称为私有内存和映射内存
但是令人疑惑的是,难道C语言常用的malloc和C++的new就申请的不是私有内存吗?为什么说只有VirtualAlloc申请的才是私有内存?
其实它们也算是申请的私有内存,but,最终来源还是VirtualAlloc
具体来说
在C语言中使用 malloc 和在C++中使用 new 分配的堆空间并不是真正的内存, new 运算符实际上调用了一系列的操作,包括调用 operator new 函数来分配内存,然后调用构造函数来初始化对象,在内部,operator new 函数通常使用 malloc 函数来分配内存,而 malloc 的底层实现是 HeapAlloc ,但是这个 HeapAlloc 并没有进0环,而是通过在操作系统一开始用 VirtualAlloc 已经分配好的一大块空间里面取一块
通过IDA跟踪 malloc 和 new 的调用过程,如下所示
1 2
| malloc -> _nh_malloc_dbg -> _heap_alloc_dbg -> _heap_alloc_base -> HeapAlloc new -> _nh_malloc -> _nh_malloc_dbg -> _heap_alloc_dbg -> _heap_alloc_base -> HeapAlloc
|
这里要了解一下堆的概念,什么是堆呢?堆其实就是操作系统通过调用 VirtualAlloc 函数预先分配好 的一大块内存。 HeapAlloc 的作用就是在这一大块已经预先分配好的内存里面,分一些小份出来用。作 个比喻,可以认为 VirtualAlloc 就是批发市场,一次必须批量从操作系统那里购买内存,必须是 4KB 的整数倍才可以;而 HeapAlloc 就是零售商,从 VirtualAlloc 已经批来的货里面(堆)买一部分走
关于虚拟映射的地址的小问题:
为什么在一个程序的内存中改了例如user32.dll,不会影响到全局 ?
因为修改一个进程的内存中加载的模块(例如 user32.dll
)通常不会影响到全局的其他进程。这是因为Windows的内存管理系统为每个进程提供了独立的虚拟地址空间。 即便多个进程加载了相同的DLL文件(例如 user32.dll
),它们在虚拟地址空间中的表现是独立的。
DLL文件通常是以共享的方式加载到多个进程中,但这种共享是只读的。当一个进程尝试修改DLL的内容时,操作系统会触发写时复制机制,将该进程的修改从共享内存复制到它自己的虚拟内存空间。
VAD结构体介绍
在Windbg里面,微软似乎可以隐瞒了关于_MMVAD的结构
1 2 3 4 5 6 7 8 9 10 11 12
| 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
|
然后在网上查阅资料,补一下结构介绍
StringVpn 起始页 / EndingVpn结束页
两者算法是不同的。起始页:startingVpn*0x1000 结束页:EndVpn*0x1000+0xfff
也就是说要乘上一个分页,才是代表真正的区段地址
u - 其是_MMVAN_FLAGS属性,非常重要的
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| typedef struct _MMVAD_FLAGS { #ifdef _WIN64 ULONG_PTR CommitCharge:51; #else ULONG_PTR CommitCharge:19; #endif ULONG_PTR NoChange:1; ULONG_PTR VadType:3; ULONG_PTR MemCommit:1; ULONG_PTR Protection:5; ULONG_PTR Spare:2; ULONG_PTR PrivateMemory:1; } MMVAD_FLAGS, *PMMVAD_FLAGS;
|
通过Vad树遍历到进程的全部模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
| #pragma once extern "C" { #include <ntifs.h> #include <ntddk.h>
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING registeryPat); }
#include "header.h"
#pragma warning(disable: 4996)
VOID Unload(IN PDRIVER_OBJECT pDriverObject) { pDriverObject; DbgPrint("Driver UnLoad!"); }
typedef struct _MMADDRESS_NODE { ULONG64 u1; struct _MMADDRESS_NODE* LeftChild; struct _MMADDRESS_NODE* RightChild; ULONG64 StartingVpn; ULONG64 EndingVpn; }MMADDRESS_NODE, * PMMADDRESS_NODE; typedef struct _EX_FAST_REF { union { PVOID Object; ULONG_PTR RefCnt : 3; ULONG_PTR Value; }; } EX_FAST_REF, * PEX_FAST_REF; struct _SEGMENT { struct _CONTROL_AREA* ControlArea; ULONG TotalNumberOfPtes; ULONG SegmentFlags; ULONG64 NumberOfCommittedPages; ULONG64 SizeOfSegment; union { struct _MMEXTEND_INFO* ExtendInfo; void* BasedAddress; }; ULONG64 SegmentLock; ULONG64 u1; ULONG64 u2; struct _MMPTE* PrototypePte; ULONGLONG ThePtes[0x1]; };
struct _CONTROL_AREA { struct _SEGMENT* Segment; struct _LIST_ENTRY DereferenceList; unsigned __int64 NumberOfSectionReferences; unsigned __int64 NumberOfPfnReferences; unsigned __int64 NumberOfMappedViews; unsigned __int64 NumberOfUserReferences; ULONG u; ULONG FlushInProgressCount; struct _EX_FAST_REF FilePointer;
}; struct _SUBSECTION { struct _CONTROL_AREA* ControlArea; struct _MMPTE* SubsectionBase; struct _SUBSECTION* NextSubsection; ULONG PtesInSubsection; ULONG UnusedPtes; struct _MM_AVL_TABLE* GlobalPerSessionHead; ULONG u; ULONG StartingSector; ULONG NumberOfFullSectors; }; typedef struct _MMVAD { ULONG64 u1; struct _MMVAD* LeftChild; struct _MMVAD* RightChild; ULONG64 StartingVpn; ULONG64 EndingVpn; ULONG64 u; ULONG64 PushLock; ULONG64 u5; ULONG64 u2; struct _SUBSECTION* Subsection; struct _MSUBSECTION* MappedSubsection; struct _MMPTE* FirstPrototypePte; struct _MMPTE* LastContiguousPte; struct _LIST_ENTRY ViewLinks; struct _EPROCESS* VadsProcess; }MMVAD; typedef struct tag_MM_AVL_TABLE { struct _MMADDRESS_NODE BalancedRoot; ULONG64 DepthOfTree; ULONG64 Unused; ULONG64 NumberGenericTableElements; void* NodeHint; void* NodeFreeHint; }MM_AVL_TABLE, * PMMAVL_TABLE;
long TotalNum = 0;
VOID EnumVad(MMVAD* Root) { POBJECT_NAME_INFORMATION Str = (POBJECT_NAME_INFORMATION)ExAllocatePool(PagedPool, 500); ULONG RetLen = 0;
if (!Str || !Root) return;
RtlZeroMemory(Str, 500); __try { if (MmIsAddressValid(Root->Subsection) && MmIsAddressValid(Root->Subsection->ControlArea) && MmIsAddressValid((PVOID)Root->Subsection->ControlArea->FilePointer.Value) ) { PFILE_OBJECT pFileObj = (PFILE_OBJECT)((Root->Subsection->ControlArea->FilePointer.Value >> 4) << 4); if (MmIsAddressValid(pFileObj)) { NTSTATUS Status = ObQueryNameString(pFileObj, Str, 500, &RetLen); if (NT_SUCCESS(Status)) { KdPrint(("模块:%ws\n\n", Str->Name.Buffer)); TotalNum++; } else { KdPrint(("获取模块名称失败 %08X\n", Status)); }
} } } __except (1) { } ExFreePool(Str); __try { if (MmIsAddressValid(Root->LeftChild)) EnumVad(Root->LeftChild);
if (MmIsAddressValid(Root->RightChild)) EnumVad(Root->RightChild); } __except (1) { KdPrint(("异常!!")); return; }
} BOOLEAN EnumProcessModule(ULONG Pid) { PEPROCESS Peprocess = 0; if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)Pid, &Peprocess))) { KeAttachProcess(Peprocess);
PMMAVL_TABLE Table = (PMMAVL_TABLE)((UCHAR*)Peprocess + 0x448); KdPrint(("%p", Table->BalancedRoot.RightChild));
if (Table->BalancedRoot.LeftChild) EnumVad((MMVAD*)Table->BalancedRoot.LeftChild);
if (Table->BalancedRoot.RightChild) EnumVad((MMVAD*)Table->BalancedRoot.RightChild);
KeDetachProcess();
KdPrint(("总模块数量为%ld", TotalNum)); } else { KdPrint(("PsLookupProcessByProcessId failed")); return FALSE; }
return TRUE; }
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING registeryPat) { registeryPat; DbgPrint("Driver Loaded!"); pDriverObject->DriverUnload = Unload; EnumProcessModule(2524); return STATUS_SUCCESS; }
|