SECTION对象

SECTION OBJECT 内存区对象

在VAD树节点中除了进程通过VirtualAlloc申请的私有的(privated)内存块之外就是用于多个进程间共享内存的SECTION对象(mapped内存块)。

nt!_SECTION_OBJECT
   +0x000 StartingVa       : Ptr32 Void
   +0x004 EndingVa         : Ptr32 Void
   +0x008 Parent           : Ptr32 Void
   +0x00c LeftChild        : Ptr32 Void
   +0x010 RightChild       : Ptr32 Void
   +0x014 Segment          : Ptr32 _SEGMENT_OBJECT

内存区对象(section)分为以页交换文件为物理存储介质的和以磁盘文件为物理存储介质的。以页交换文件为物理存储介质的实际是以pagefile.sys为基础,以磁盘文件为物理存储介质的可能是exe/dll的映射或者是数据文件。

内存区对象(section)对应的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;

windows系统将内存区对象(SECTION_OBJECT)与VAD结点(_MMVAD)关联起来。

无效PTE与原型PTE

内存区对象(section)用来共享内存,例如A进程与B进程通过一个SECTION内存区对象共享内存,一开始A/B进程共享内存的PTE都无效并且都指向原型PTE,接着A进程开始首次访问共享内存,经过地址解析后发现对应共享内存的PTE为无效的所以页异常处理程序就会为A无效的PTE分配一个物理页,从此A的此PTE就指向此物理页而对应的原型PTE也指向此物理页。当B进程访问共享内存时,经过地址解析后发现对应共享内存的PTE为无效的,但是发现其指向原型PTE指向了物理页,所以页异常处理程序就会将原型PTE填充此PTE,从此此PTE也指向对应的物理页。

原型PTE阵列存储在_MMVAD._SUBSECTION结构的ThePtes数组中(多个进程的_MMVAD如果是同一个section,其共用同一个ThePtes数组)


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数组)
};

SECTION对应的物理页都有一个与之对应的原型PTE,当一个进程的PTE无效时(V位=0)其就会指向对应的原型PTE。当无效PTE被再次访问时(页异常处理程序)就会查看此原型PTE是否指向实际的物理内存,如果没有指向实际的物理内存就分配一个新页让无效的PTE和原型PTE都指向此物理页,如果原型PTE已经指向了实际的物理内存就用原型PTE填充自己。所以原型PTE在section实现跨进程共享内存中起着重要作用。

利用SECTION防止页面被修改

使用以页交换文件为物理存储器的内存区对象(section),利用参数SEC_NO_CHANGE使VirtualProtect无法改变对应内存页的属性。

HANDLE SectionHandle;
PVOID BaseAddress = NULL;
SIZE_T ViewSize = 0;
LARGE_INTEGER MaximumSize = { 0 };
MaximumSize.QuadPart = 0x10000;
ZwCreateSection(&SectionHandle, SECTION_ALL_ACCESS, NULL, &MaximumSize, PAGE_EXECUTE_READ, SEC_COMMIT | SEC_NO_CHANGE, NULL);
ZwMapViewOfSection(SectionHandle, GetCurrentProcess(), &BaseAddress, 0, 0x10000, NULL, &ViewSize, (SECTION_INHERIT)1, 0, PAGE_EXECUTE_READ);

原理是VirtualProtect在修改Section内存块属性时会判断是否含有SEC_NO_CHANGE属性位,如果有就返回STATUS_INVALID_PAGE_PROTECTION。

https://jev0n.com/2021/09/23/Self-Remapping.html

特殊的SECTION对象

有一种特殊的SECTION对象\Device\PhysicalMemory,他是在系统启动初期创建的内存区对象。利用这个内存区对象可以直接将物理内存映射到虚拟地址空间中,实现直接读写物理内存。
在windows较早的版本中可以支持在应用层直接利用此对象读写物理内存,后来windows系统不在支持应用层只支持内核层使用此对象。
参考链接:https://cloud.tencent.com/developer/article/1769576
《windows内核原理与实现》

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