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对象进行锁页(防止应用层修改页面属性)

posted @ 2022-03-25 17:13  怎么可以吃突突  阅读(1925)  评论(0编辑  收藏  举报