VAD树

前面说的进程隐藏,模块隐藏

但是这个是骗不了例如PCHunter这种软件的

这是因为这种软件用了别的方法去遍历进程和模块

Vad树介绍

VAD(Virtual Address Descriptor)树是Windows操作系统用来管理进程的虚拟内存区域(memory regions)的一种数据结构。它以平衡二叉树(通常是自平衡的AVL树)的形式存在,存储了一个进程中所有的虚拟地址范围(内存区域)。这些虚拟地址区域包含了进程所使用的内存,比如模块加载的内存、堆、栈等。

为了隐藏进程,常见的技术就是断开活动进程链 ,虽然这种方法能够从用户态视角隐藏进程,但它并没有改变内存管理系统和其他内核结构中的信息,进程仍然在系统的其他地方可见。

每一个进程(EPROCESS)都有Vadroot结构,这保存着Vad树的根节点

1728127584087

用windbg可以自动解析Vad树

1
!vad [addr]

1728127609221

关于内存的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

然后在网上查阅资料,补一下结构介绍

  1. StringVpn 起始页 / EndingVpn结束页
    两者算法是不同的。起始页:startingVpn*0x1000 结束页:EndVpn*0x1000+0xfff
    也就是说要乘上一个分页,才是代表真正的区段地址

  2. 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;//表示已提交的内存页数。这个字段用于跟踪当前 VAD 使用了多少物理内存。
#else
ULONG_PTR CommitCharge:19;
#endif
ULONG_PTR NoChange:1;//指示该 VAD 是否在内存中没有变化。此标志通常用于优化内存管理。
ULONG_PTR VadType:3;//图像映射、一般映射或私有映射等。
ULONG_PTR MemCommit:1;//表示该 VAD 是否已经提交了内存。提交的内存是已分配并可以被访问的。
ULONG_PTR Protection:5;//描述该 VAD 的内存保护属性,例如可读、可写或可执行等。
ULONG_PTR Spare:2;//预留位,用于未来的扩展或对齐,不用于实际的内存管理。
ULONG_PTR PrivateMemory:1;//指示该 VAD 是否代表私有内存。如果为 1,表示这是私有内存(如通过 VirtualAlloc 分配),否则为 0。
} 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
//"header.h"
#pragma once
extern "C"
{
#include <ntifs.h>
#include <ntddk.h>

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING registeryPat);
}





//code.cpp
#include "header.h"

#pragma warning(disable: 4996)

VOID Unload(IN PDRIVER_OBJECT pDriverObject)
{
pDriverObject;
DbgPrint("Driver UnLoad!");
}

/*
遍历vad二叉树来遍历进程里的模块
*/



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;

/*ULONG ControlAreaLock;
ULONG ModifiedWriteCount;
ULONG StartingFrame;
ULONG64 WaitingForDeletion;
ULONG64 u2; //0x10字节
ULONG64 LockedPages;
_LIST_ENTRY ViewList;*/
};
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;//接收

//DbgBreakPoint();

//申请内存失败或者节点为null都退出
if (!Str || !Root)
return;

RtlZeroMemory(Str, 500);
//KdPrint(("-> %p\n", Root->Subsection->ControlArea->FilePointer.Value));
__try
{
if (MmIsAddressValid(Root->Subsection) &&
MmIsAddressValid(Root->Subsection->ControlArea) &&
MmIsAddressValid((PVOID)Root->Subsection->ControlArea->FilePointer.Value)
)
{
//清除低4位就是FILE_OBJECT
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(("基址:%p 大小:%p ",
// Root->Subsection->ControlArea->Segment->BasedAddress,
// Root->Subsection->ControlArea->Segment->SizeOfSegment));
KdPrint(("模块:%ws\n\n", Str->Name.Buffer));
TotalNum++;
}
else
{
KdPrint(("获取模块名称失败 %08X\n", Status));
}

}
}
}
__except (1)
{
//KdPrint(("地址无效!\n"));
}
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;
}