VAD樹
應用層進程會通過調用VirtualAlloc分配多個內存塊,每個內存塊包含1個或多個內存頁。windows操作系統為了有效的管理這些內存塊構建了一個AVL二叉樹,這個AVL樹就是VAD樹。應用層的每一個內存塊(包含VirtualAlloc申請的私有的和Mapping共享的)都對應一個VAD樹結點(結構體類型為_MMVAD)。
//win7 32位
typedef struct _MMVAD
{
ULONG u1; //包含父結點
struct _MMVAD* LeftChild; //左子樹
struct _MMVAD* RightChild; //右子樹
ULONG StartingVpn; //內存塊起始地址的頁幀
ULONG EndingVpn; //內存塊結束地址的頁幀(對於4kb(0x1000)的頁而言,內存塊地址就是StartingVpn*0x1000~EndingVpn*0x1000+0xfff)
ULONG u; //_MMVAD_FLAGS類型的指針,包含了內存塊的一些屬性位
EX_PUSH_LOCK PushLock;
ULONG u5; //_MMVAD_FLAGS3類型的指針,包含了內存塊的一些屬性位
ULONG u2; //_MMVAD_FLAGS2類型的指針,包含了內存塊的一些屬性位
struct _SUBSECTION* Subsection; //包含了_CONTROL_AREA結構和_SEGMENT結構,含有更多的詳細信息
struct _MSUBSECTION* MappedSubsection;
struct _MMPTE* FirstPrototypePte; //原型PTE,對於Mapped共享的內存塊有意義(private私有內存塊無意義)。第一個原型PTE
struct _MMPTE* LastContiguousPte; //原型PTE,對於Mapped共享的內存塊有意義(private私有內存塊無意義)。最后一個原型PTE
struct _LIST_ENTRY ViewLinks;
struct _EPROCESS* VadsProcess;
}MMVAD;
當我們調用VirtualAlloc時如果傳入的參數flAllocationType的值為reserved保留,這時windows操作系統也會構造並增加一個VAD結點來描述這塊內存塊。但是因為只是預留了一塊虛擬地址,操作系統並未為其映射實際的物理內存,所以此時此VAD結點是沒有實際意義的。但是這樣做的好處是通過較小的開銷(VAD結點構造)來預留一塊虛擬地址待需要使用再分配PTE頁表等結構為其映射實際的物理內存。(線程棧就是這樣做的,當線程棧首先預留一塊較大的虛擬內存,隨着線程棧的增長不斷提交新的虛擬內存塊,映射到實際的物理內存中)
與VAD有關的結構
nt!_MMADDRESS_NODE
+0x000 u1 : <unnamed-tag>
+0x004 LeftChild : Ptr32 _MMADDRESS_NODE
+0x008 RightChild : Ptr32 _MMADDRESS_NODE
+0x00c StartingVpn : Uint4B
+0x010 EndingVpn : Uint4B
kd> dt _MMVAD_SHORT
nt!_MMVAD_SHORT
+0x000 u1 : <unnamed-tag>
+0x004 LeftChild : Ptr32 _MMVAD
+0x008 RightChild : Ptr32 _MMVAD
+0x00c StartingVpn : Uint4B
+0x010 EndingVpn : Uint4B
+0x014 u : <unnamed-tag>
+0x018 PushLock : _EX_PUSH_LOCK
+0x01c u5 : <unnamed-tag>
kd> DT _MMVAD
nt!_MMVAD
+0x000 u1 : <unnamed-tag>
+0x004 LeftChild : Ptr32 _MMVAD
+0x008 RightChild : Ptr32 _MMVAD
+0x00c StartingVpn : Uint4B
+0x010 EndingVpn : Uint4B
+0x014 u : <unnamed-tag>
+0x018 PushLock : _EX_PUSH_LOCK
+0x01c u5 : <unnamed-tag>
+0x020 u2 : <unnamed-tag>
+0x024 Subsection : Ptr32 _SUBSECTION
+0x024 MappedSubsection : Ptr32 _MSUBSECTION
+0x028 FirstPrototypePte : Ptr32 _MMPTE
+0x02c LastContiguousPte : Ptr32 _MMPTE
+0x030 ViewLinks : _LIST_ENTRY
+0x038 VadsProcess : Ptr32 _EPROCESS
_MMADDRESS_NODE, _MMVAD_SHORT與 _MMVAD三個結構很相似,_MMVAD_SHORT是_MMADDRESS_NODE的擴展,_MMVAD又是_MMVAD_SHORT和_MMADDRESS_NODE的擴展。因為對於Private私有內存來說並不涉及到section對象,所以其對應的VAD樹結點應該是_MMVAD_SHORT類型,至於其余擴展字段對於Private私有內存塊是沒有意義的。_MMVAD擴展的結構是用來描述Section對象(Mapped內存)的。
nt!_MMVAD_FLAGS
+0x000 CommitCharge : Pos 0, 19 Bits //實際提交的頁數
+0x000 NoChange : Pos 19, 1 Bit //是否允許應用層修改對應內存頁的保護屬性(置1的話,應用層調用VirtualProtect無法改變內存頁的保護屬性)
+0x000 VadType : Pos 20, 3 Bits //置1時表示此內存塊為Exe的Mapped
+0x000 MemCommit : Pos 23, 1 Bit //提交狀態(置1為已提交)
+0x000 Protection : Pos 24, 5 Bits //對應內存塊的保護屬性
+0x000 Spare : Pos 29, 2 Bits
+0x000 PrivateMemory : Pos 31, 1 Bit //私有內存塊,調用VirtualAlloc申請的(置1為私有內存塊Private,置0為映射Mapped(DLL/Exe))
kd> dt _MMVAD_FLAGS2
nt!_MMVAD_FLAGS2
+0x000 FileOffset : Pos 0, 24 Bits
+0x000 SecNoChange : Pos 24, 1 Bit
+0x000 OneSecured : Pos 25, 1 Bit
+0x000 MultipleSecured : Pos 26, 1 Bit
+0x000 Spare : Pos 27, 1 Bit
+0x000 LongVad : Pos 28, 1 Bit
+0x000 ExtendableFile : Pos 29, 1 Bit
+0x000 Inherit : Pos 30, 1 Bit
+0x000 CopyOnWrite : Pos 31, 1 Bit //寫拷貝屬性(置1為寫拷貝)
_MMVAD的u成員對應的是_MMVAD_FLAGS結構體,u2成員對應的是_MMVAD_FLAGS2結構體,包含了對應內存塊的屬性信息。
typedef struct _SUBSECTION
{
struct _CONTROL_AREA* ControlArea;
struct _MMPTE* SubsectionBase;
struct _SUBSECTION* NextSubsection;
ULONG PtesInSubsection;
ULONG UnusedPtes;
struct _MM_AVL_TABLE* GlobalPerSessionHead; //此鏈表將所有Mapped類型並且需要映射到每一個進程中虛擬地址都一樣的(SEC_BASED類型)內存塊組織起來
ULONG u;
ULONG StartingSector;
ULONG NumberOfFullSectors;
}SUBSECTION,* PSUBSECTION;
_MMVAD._SUBSECTION結構中包含一個重要的結構_CONTROL_AREA。
struct _SEGMENT
{
struct _CONTROL_AREA* ControlArea;
ULONG TotalNumberOfPtes; //內存塊包含的頁數
ULONG SegmentFlags;
ULONG NumberOfCommittedPages;
ULONGLONG SizeOfSegment; //對應內存塊的大小
union
{
struct _MMEXTEND_INFO* ExtendInfo;
void* BasedAddress; //對應內存塊的基地址
};
EX_PUSH_LOCK SegmentLock;
ULONG u1;
ULONG u2;
struct _MMPTE* PrototypePte; //原型PTE
//_MMPTE ThePtes[0x1]; //內存塊所有頁面對應的PTE(一個大型PTE數組)
};
#pragma pack(push,1)
typedef struct _EX_FAST_REF
{
union
{
PVOID Object;
ULONG_PTR RefCnt : 3;
ULONG_PTR Value;
};
} EX_FAST_REF, * PEX_FAST_REF;
#pragma pack(pop)
typedef struct _CONTROL_AREA
{
struct _SEGMENT* Segment;
struct _LIST_ENTRY DereferenceList;
ULONG NumberOfSectionReferences;
ULONG NumberOfPfnReferences;
ULONG NumberOfMappedViews;
ULONG NumberOfUserReferences;
ULONG u;
ULONG FlushInProgressCount;
struct _EX_FAST_REF FilePointer; //指向_FILE_OBJECT
}CONTROL_AREA,* PCONTROL_AREA;
_CONTROL_AREA結構的FilePointer指向的是內存塊對應的FILE_OBJECT對象(后3位需要置0),如果_FILE_OBJECT有效就可以得到對應文件的FileName等信息。
_CONTROL_AREA結構的Segment成員為_SEGMENT 結構,_SEGMENT.BasedAddress為對應內存塊的基地址,_SEGMENT.SizeOfSegment為對應內存塊的大小。
VAD樹遍歷
通過驅動程序獲取進程EPROCESS並遍歷ActiveProcessLinks鏈表找到需要處理的進程(通過斷此鏈可以實現進程隱藏),獲取到對應進程的EPROCESS后獲得其VADRoot根結點並進行遍歷VAD樹,代碼如下:
#include <ntifs.h>
#pragma pack(push,1)
typedef struct _EX_FAST_REF
{
union
{
PVOID Object;
ULONG_PTR RefCnt : 3;
ULONG_PTR Value;
};
} EX_FAST_REF, * PEX_FAST_REF;
#pragma pack(pop)
struct _SEGMENT
{
struct _CONTROL_AREA* ControlArea;
ULONG TotalNumberOfPtes;
ULONG SegmentFlags;
ULONG NumberOfCommittedPages;
ULONGLONG SizeOfSegment;
union
{
struct _MMEXTEND_INFO* ExtendInfo;
void* BasedAddress;
};
EX_PUSH_LOCK SegmentLock;
ULONG u1;
ULONG u2;
struct _MMPTE* PrototypePte;
//_MMPTE ThePtes[0x1];
};
struct _CONTROL_AREA
{
struct _SEGMENT* Segment;
struct _LIST_ENTRY DereferenceList;
ULONG NumberOfSectionReferences;
ULONG NumberOfPfnReferences;
ULONG NumberOfMappedViews;
ULONG 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
{
ULONG u1;
struct _MMVAD* LeftChild;
struct _MMVAD* RightChild;
ULONG StartingVpn;
ULONG EndingVpn;
ULONG u;
EX_PUSH_LOCK PushLock;
ULONG u5;
ULONG u2;
struct _SUBSECTION* Subsection;
struct _MSUBSECTION* MappedSubsection;
struct _MMPTE* FirstPrototypePte;
struct _MMPTE* LastContiguousPte;
struct _LIST_ENTRY ViewLinks;
struct _EPROCESS* VadsProcess;
}MMVAD;
typedef struct _MMADDRESS_NODE {
ULONG u1;
struct _MMADDRESS_NODE* LeftChild;
struct _MMADDRESS_NODE* RightChild;
ULONG StartingVpn;
ULONG EndingVpn;
}MMADDRESS_NODE, * PMMADDRESS_NODE;
typedef struct _MM_AVL_TABLE {
MMADDRESS_NODE BalancedRoot;
ULONG sTruct;
PVOID NodeHint;
PVOID NodeFreeHint;
}MM_AVL_TABLE, * PMM_AVL_TABLE;
VOID Unload(IN PDRIVER_OBJECT pDriverObject) {
UNREFERENCED_PARAMETER(pDriverObject);
DbgPrint("Driver UnLoad!");
}
//中序遍歷VAD的AVL樹
VOID _EnumVAD(PMMADDRESS_NODE pVad) {
if (pVad == NULL) return;
_EnumVAD(pVad->LeftChild);
//處理數據
ULONG uRetLen = 0;
MMVAD* Root = (MMVAD*)pVad;
POBJECT_NAME_INFORMATION stFileBuffer = (POBJECT_NAME_INFORMATION)ExAllocatePool(PagedPool, 0x100);
if (MmIsAddressValid(Root->Subsection) && MmIsAddressValid(Root->Subsection->ControlArea) && stFileBuffer!=NULL)
{
if (MmIsAddressValid((PVOID)Root->Subsection->ControlArea->FilePointer.Value))
{
PFILE_OBJECT pFileObject = (PFILE_OBJECT)((Root->Subsection->ControlArea->FilePointer.Value >> 3) << 3);
if (MmIsAddressValid(pFileObject))
{
NTSTATUS Status = ObQueryNameString(pFileObject, stFileBuffer, 0x100, &uRetLen);
if (NT_SUCCESS(Status))
{
KdPrint(("Base:%08X Size:%dKb ", Root->Subsection->ControlArea->Segment->BasedAddress,\
(Root->Subsection->ControlArea->Segment->SizeOfSegment) / 0x1000));
KdPrint(("Name:%ws\n", stFileBuffer->Name.Buffer));
}
}
}
}
if (stFileBuffer)
{
ExFreePool(stFileBuffer);
}
_EnumVAD(pVad->RightChild);
return;
}
NTSTATUS _EnumProcess() {
ULONG VadRoot;
PEPROCESS pEprocess = NULL;
PEPROCESS pFirstEprocess = NULL;
ULONG ProcessName = 0; //指向進程的名稱
ANSI_STRING szaTestingProcessName; //待檢測進程的名稱
ANSI_STRING szaProcessName; //進程的名稱
//獲得EPROCESS進程內核對象
pEprocess = PsGetCurrentProcess();
if (pEprocess == NULL) {
return STATUS_SUCCESS;
}
pFirstEprocess = pEprocess;
while (pEprocess) {
//獲得進程的名稱
ProcessName = (ULONG)pEprocess + 0x16c;
//獲得VAD樹根結點的地址
VadRoot = ((ULONG)pEprocess + 0x278);
RtlInitAnsiString(&szaProcessName, (PCSTR)ProcessName);
RtlInitAnsiString(&szaTestingProcessName, "test.exe");
if (RtlEqualString(&szaProcessName, &szaTestingProcessName, TRUE)) {
//遍歷VAD的AVL樹
_EnumVAD(((PMMADDRESS_NODE)VadRoot)->RightChild);
return STATUS_SUCCESS;
}
//繼續遍歷ActiveProcessLinks鏈表
pEprocess = (PEPROCESS)(*(ULONG*)((ULONG)pEprocess + 0x0b8) - 0x0b8);
if (pEprocess == pFirstEprocess) {
break;
}
}
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) {
UNREFERENCED_PARAMETER(pRegistryPath);
pDriverObject->DriverUnload = Unload;
_EnumProcess();
return STATUS_SUCCESS;
}
注意_MM_AVL_TABLE.BalancedRoot結構為MMADDRESS_NODE ,其實際是個內嵌結構體不能擴展到_MMVAD結構,其LeftChild未使用,RightChild才為真正的根結點。
最后DebugView的打印結果為:
VAD隱藏
_MMVAD.StartingVpn = _MMVAD.EndingVpn
直接在VAD樹遍歷的處理代碼中加入_MMVAD.StartingVpn = _MMVAD.EndingVpn即可。
隱藏前OD查看內存為:
隱藏后OD查看內存為
VAD結點融合(宿主_MMVAD.EndingVpn = 待隱藏_MMVAD.StartingVpn)
通過VAD結點融合,將需要隱藏的內存塊的VAD結點合並到其他VAD結點中。