VAD(Virtual Address Descriptor)虛擬地址描述符


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結點中。

利用VAD和SECTION對象進行鎖頁(防止應用層修改頁面屬性)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM