Windows内存管理
工作集:
指进程已映射的物理内存部分(即这些内存块全在物理内存中,并且CPU可以直接访问),还有一部分不在工作集中的虚拟内存则可能在转换列表中(CPU不能通过虚地址访问,需要Windows映射之后才能访问),还有一部分则在磁盘上的页面文件里。
内存管理器的主要任务:
1)将一个进程的虚拟地址空间翻译或映射成物理内存,从而使运行在该进程上下文中的线程读写虚拟地址空间时,能够引用正确的物理地址空间。
2)当物理内存过载时(如,当运行的线程或内核代码尝试请求比当前可用内存更多的时候),将其中部分内容换出至磁盘,以及在需要时将这些内容换会物理内存。
线性地址的管理
内核的地址空间主要通过一个链表来保存已分配和未分配的地址,因为所有进程共享所以用一个链表就可以了。
用户层的地址空间是一个进程一个相对比较复杂。
申请内存的两种方式:
1)通过VirtualAlloc/VirtualAllocEx申请的:Private Memory : 线性地址的物理页是进程独享的,自己私有的
2)通过CreateFileMapping映射的:Mapped Memory : 这段线性地址的物理页是在多个进程中共享的
进程的虚拟空间分为:
1)空闲
2)保留:保留内存意味着设置一段连续的虚拟地址供日后使用,并没有挂物理页
3)提交:也叫做私有页面,用到了保留的内存时,提交分配私有的物理内存
4)可共享:申请挂物理页,用到了保留的内存时,共享分配可共享的物理内存
提交和可共享的页面(虚拟页面),是最终会转换为物理内存中有效页面(物理页面)的页面。
保留内存意味着设置一段连续的虚拟地址供日后使用(类似于数组),但与此同时这一过程只占用微乎其微的系统资源,并在应用程序运行过程中根据需要提交以保留空间的不同部分。或者如果已经预先知道需要的大小,进程可以通过一次操作完成保留和提交操作。随后,这两种情况所产生的提交的页面即可被进程中的任何线程访问(已经分配了物理内存)。如果试图访问空闲或保留的内存,会导致访问冲突异常,因为所访问的页面尚未映射至任何能够解析该引用的存储。
进程空间的地址划分
分区 x86--32位Windows
空指针赋值区 0x00000000 -- 0x0000FFFF
用户模式区 0x00010000 -- 0x7FFEFFFF
64KB禁入区 0x7FFF0000 -- 0x7FFFFFFF
内核 0x80000000 -- 0xFFFFFFFF
注意:
1)线性地址有4G,但未必都能访问(所谓的不能访问起始都是页的限制)
2)所以需要记录,哪些地址分配了
用户的地址空间(低2G进程空间)
用户的地址空间保存在进程对象中:
_EPROCESS.VadRoot
是一个二叉树的入口点,这个二叉树中的每一个节点都记录了一个被占用的线性地址,VadRoot中保存的结构为(在更高版本可能有所不同,这个结构体是32位的):
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 //通过malloc分配的内存
+0x024 MappedSubsection : Ptr32 _MSUBSECTION //通过文件映射产生的内存
+0x028 FirstPrototypePte : Ptr32 _MMPTE
+0x02c LastContiguousPte : Ptr32 _MMPTE
+0x030 ViewLinks : _LIST_ENTRY
+0x038 VadsProcess : Ptr32 _EPROCESS
之所以使用二叉树来管理地址是因为二叉树的查找性能更好。
**+0x000 u1 :
kd> dx -id 0,0,855d0ae8 -r1 (*((ntkrpamp!_MMVAD *)0x86b18928)).u1
(*((ntkrpamp!_MMVAD *)0x86b18928)).u1 [Type: <unnamed-tag>]
[+0x000 ( 1: 0)] Balance : 0 [Type: long]
[+0x000] Parent : 0x85865d80 [Type: _MMVAD *]
保存当前节点的父节点
**+0x014 u:
kd> dx -id 0,0,855d0ae8 -r1 (*((ntkrpamp!_MMVAD *)0x86b18928)).u
(*((ntkrpamp!_MMVAD *)0x86b18928)).u [Type: <unnamed-tag>]
[+0x000] LongFlags : 0x7200004 [Type: unsigned long]
[+0x000] VadFlags [Type: _MMVAD_FLAGS]
_MMVAD_FLAGS:保存内存的属性,如可读、可写、可执行等:
kd> dt _MMVAD_FLAGS
nt!_MMVAD_FLAGS
+0x000 CommitCharge : Pos 0, 19 Bits
+0x000 NoChange : Pos 19, 1 Bit
+0x000 VadType : Pos 20, 3 Bits
+0x000 MemCommit : Pos 23, 1 Bit
+0x000 Protection : Pos 24, 5 Bits //标识可读、可写
+0x000 Spare : Pos 29, 2 Bits
+0x000 PrivateMemory : Pos 31, 1 Bit //用来标识内存是Mapped的还是Private的,PrivateMemory = 1为Private的,0为Mapped的
Protection:
1)Protection = 1:READONLY
2)Protection = 2:EXECUTE
3)Protection = 3:EXECUTE_READ
4)Protection = 4:READWITER
5)Protection = 5:WRITECOPY
6)Protection = 6:EXECUTE_READWITER
7)Protection = 7:EXECUTE_WRITECOPY
_MMVAD.LeftChild:
当前节点的左子树。
_MMVAD.RightChild:
当前节点的右子树。
_MMVAD.StartingVpn:
所描述的线性地址起始位置,以4KB为单位,不够后面补零
_MMVAD.EndingVpn:
所描述的线性地址结束位置,以4KB为单位,不够后面补零
+0x024 Subsection : Ptr32 _SUBSECTION:
分段内存:
kd> dt _SUBSECTION
nt!_SUBSECTION
+0x000 ControlArea : Ptr32 _CONTROL_AREA //控制区
+0x004 SubsectionBase : Ptr32 _MMPTE
+0x008 NextSubsection : Ptr32 _SUBSECTION
+0x00c PtesInSubsection : Uint4B
+0x010 UnusedPtes : Uint4B
+0x010 GlobalPerSessionHead : Ptr32 _MM_AVL_TABLE
+0x014 u : <unnamed-tag>
+0x018 StartingSector : Uint4B
+0x01c NumberOfFullSectors : Uint4B
+0x024 MappedSubsection : Ptr32 _MSUBSECTION:
分段内存:
kd> dt _MSUBSECTION
nt!_MSUBSECTION
+0x000 ControlArea : Ptr32 _CONTROL_AREA //控制区
+0x004 SubsectionBase : Ptr32 _MMPTE
+0x008 NextSubsection : Ptr32 _SUBSECTION
+0x008 NextMappedSubsection : Ptr32 _MSUBSECTION
+0x00c PtesInSubsection : Uint4B
+0x010 UnusedPtes : Uint4B
+0x010 GlobalPerSessionHead : Ptr32 _MM_AVL_TABLE
+0x014 u : <unnamed-tag>
+0x018 StartingSector : Uint4B
+0x01c NumberOfFullSectors : Uint4B
+0x020 u1 : <unnamed-tag>
+0x024 LeftChild : Ptr32 _MMSUBSECTION_NODE
+0x028 RightChild : Ptr32 _MMSUBSECTION_NODE
+0x02c DereferenceList : _LIST_ENTRY
+0x034 NumberOfMappedViews : Uint4B
在win7-32系统中的VadRoot
在win7-32的VadRoot和XP的不同,VadRoot是一个 _MM_AVL_TABLE 结构体:
kd> dt _MM_AVL_TABLE
nt!_MM_AVL_TABLE
+0x000 BalancedRoot : _MMADDRESS_NODE //保存当前节点信息
+0x014 DepthOfTree : Pos 0, 5 Bits
+0x014 Unused : Pos 5, 3 Bits
+0x014 NumberGenericTableElements : Pos 8, 24 Bits
+0x018 NodeHint : Ptr32 Void //_MMVAD结构体的地址
+0x01c NodeFreeHint : Ptr32 Void
_MMADDRESS_NODE结构体:
kd> dt _MMADDRESS_NODE
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 _CONTROL_AREA
nt!_CONTROL_AREA
+0x000 Segment : Ptr32 _SEGMENT
+0x004 DereferenceList : _LIST_ENTRY
+0x00c NumberOfSectionReferences : Uint4B
+0x010 NumberOfPfnReferences : Uint4B
+0x014 NumberOfMappedViews : Uint4B
+0x018 NumberOfUserReferences : Uint4B
+0x01c u : <unnamed-tag>
+0x020 FlushInProgressCount : Uint4B
+0x024 FilePointer : _EX_FAST_REF
+0x028 ControlAreaLock : Int4B
+0x02c ModifiedWriteCount : Uint4B
+0x02c StartingFrame : Uint4B
+0x030 WaitingForDeletion : Ptr32 _MI_SECTION_CREATION_GATE
+0x034 u2 : <unnamed-tag>
+0x040 LockedPages : Int8B
+0x048 ViewList : _LIST_ENTRY
+0x024 FilePointer : _EX_FAST_REF:
FilePointer的值为NULL时,此的这块内存指向真正的物理页,也就说这段内存是我们通过malloc申请的内存。
当FilePointer的值不为空的时候,他指向了一个 _FILE_OBJECT 的文件对象:
kd> dt _FILE_OBJECT
ntdll!_FILE_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Vpb : Ptr32 _VPB
+0x00c FsContext : Ptr32 Void
+0x010 FsContext2 : Ptr32 Void
+0x014 SectionObjectPointer : Ptr32 _SECTION_OBJECT_POINTERS
+0x018 PrivateCacheMap : Ptr32 Void
+0x01c FinalStatus : Int4B
+0x020 RelatedFileObject : Ptr32 _FILE_OBJECT
+0x024 LockOperation : UChar
+0x025 DeletePending : UChar
+0x026 ReadAccess : UChar
+0x027 WriteAccess : UChar
+0x028 DeleteAccess : UChar
+0x029 SharedRead : UChar
+0x02a SharedWrite : UChar
+0x02b SharedDelete : UChar
+0x02c Flags : Uint4B
+0x030 FileName : _UNICODE_STRING //文件名
+0x038 CurrentByteOffset : _LARGE_INTEGER
+0x040 Waiters : Uint4B
+0x044 Busy : Uint4B
+0x048 LastLock : Ptr32 Void
+0x04c Lock : _KEVENT
+0x05c Event : _KEVENT
+0x06c CompletionContext : Ptr32 _IO_COMPLETION_CONTEXT
+0x070 IrpListLock : Uint4B
+0x074 IrpList : _LIST_ENTRY
+0x07c FileObjectExtension : Ptr32 Void
即这段内存是文件映射过来的内存。
注意:如果你在一个进程的PEB中将模块链表断掉实现的模块隐藏可以通过这个查到。
查看进程内存(win7-32)
先查看记事本进程信息找到 VadRoot(在EPROCESS中):
PROCESS 8608f810 SessionId: 1 Cid: 0fc0 Peb: 7ffda000 ParentCid: 0704
DirBase: 3f2c33e0 ObjectTable: 943f3938 HandleCount: 74.
Image: notepad.exe
通过WINDUB命令:!vad 查看 VadRoot:
其中:
1)Mapped 文件映射的内存
2)Private 自己申请的内存
3)Start 和 End的地址后面要加3个零,因为是以页为单位,4096个字节(16进制后面多3个零即可)
kd> !vad 0x8608fa88
VAD Level Start End Commit
040d0058: Unable to get nt!_FILE_OBJECT.FileName.Buffer
8608fa88 0 0 0 14599 Mapped NO_ACCESS (null)
86b12238 6 10 1f 0 Mapped READWRITE Pagefile section, shared commit 0x10
8597c520 5 20 25 0 Mapped READONLY Pagefile section, shared commit 0x6
8597cd20 6 30 33 0 Mapped READONLY Pagefile section, shared commit 0x4
85964540 4 40 41 0 Mapped READONLY Pagefile section, shared commit 0x2
869d3818 6 50 50 1 Private READWRITE
85992120 5 60 c6 0 Mapped READONLY \Windows\System32\locale.nls
86a47d80 6 d0 d0 0 Mapped READWRITE Pagefile section, shared commit 0x1
85bfc5f8 7 e0 e0 1 Private READWRITE
86a425c0 3 f0 12f 19 Private READWRITE
86322dc8 6 130 130 1 Private READWRITE
8697f070 5 140 14f 1 Private READWRITE
8593d628 7 150 151 0 Mapped READONLY Pagefile section, shared commit 0x2
8593d248 6 160 160 0 Mapped READWRITE Pagefile section, shared commit 0x1
86941e88 4 190 19f 3 Private READWRITE
859c1e18 6 1b0 1df 4 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\notepad.exe
8696f508 5 1e0 2a7 0 Mapped READONLY Pagefile section, shared commit 0x5
8678e4c0 2 300 3ff 44 Private READWRITE
86a2a2a0 6 400 500 0 Mapped READONLY Pagefile section, shared commit 0x101
86ac51f8 5 510 110f 0 Mapped READONLY Pagefile section, shared commit 0x12
85c220d0 6 1110 150a 0 Mapped READONLY Pagefile section, shared commit 0x3fb
85bf7d20 4 1510 15ee 0 Mapped READONLY Pagefile section, shared commit 0xdf
86a78118 7 1620 165f 43 Private READWRITE
85e1f998 6 1680 16bf 16 Private READWRITE
867b67c8 7 16c0 1fef 0 Mapped READONLY \Windows\Fonts\StaticCache.dat
8595d0d0 5 1ff0 22be 0 Mapped READONLY \Windows\Globalization\Sorting\SortDefault.nls
86316430 7 22c0 233f 1 Private READWRITE
8682f070 6 2340 237f 19 Private READWRITE
8678e238 3 740c0 74110 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\winspool.drv
85f23628 6 74730 74742 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\dwmapi.dll
85865d80 5 748f0 7492f 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\uxtheme.dll
86b18928 6 74aa0 74c3d 4 Mapped Exe EXECUTE_WRITECOPY \Windows\winsxs\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.17514_none_41e6975e2bd6f2b2\comctl32.dll
86b06bb0 4 75010 75018 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\version.dll
868fed20 5 75a60 75a6b 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\cryptbase.dll
85940248 1 75bc0 75c09 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll
85971940 5 75e10 75eac 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\usp10.dll
8598d428 4 75eb0 75ec8 4 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\sechost.dll
859bf8e8 3 75f00 75fab 8 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\msvcrt.dll
8582fa80 5 76010 760d8 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\user32.dll
85999ac0 4 76170 761c6 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\shlwapi.dll
85973c28 6 761d0 7624a 5 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\comdlg32.dll
86ac0590 5 76250 762de 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\oleaut32.dll
868ca108 6 762e0 7643b 5 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ole32.dll
85947628 2 76440 76513 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel32.dll
85841648 6 76520 765eb 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\msctf.dll
8596f180 5 765f0 7668f 5 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\advapi32.dll
8596a760 4 76690 766dd 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\gdi32.dll
8635bcb8 6 766e0 77329 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\shell32.dll
869245d0 5 777d0 77870 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\rpcrt4.dll
861369d8 3 779c0 77afb 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll
8595cd20 6 77b00 77b09 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\lpk.dll
85941de8 7 77bd0 77bee 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\imm32.dll
85966bf0 5 77c00 77c00 0 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\apisetschema.dll
85772e98 6 7f6f0 7f7ef 0 Mapped READONLY Pagefile section, shared commit 0x5
8597f3f0 4 7ffa0 7ffd2 0 Mapped READONLY Pagefile section, shared commit 0x33
86afbc90 6 7ffda 7ffda 1 Private READWRITE
86a5b008 5 7ffde 7ffde 1 Private READWRITE
85e1e768 6 7ffdf 7ffdf 1 Private READWRITE
Total VADs: 58, average level: 6, maximum depth: 7
Total private commit: 0x39fb pages (59372 KB)
Total shared commit: 0x64a pages (6440 KB)
通过节点查看:
kd> dt _MM_AVL_TABLE 0x8608fa88 (vabRoot的地址)
nt!_MM_AVL_TABLE
+0x000 BalancedRoot : _MMADDRESS_NODE
+0x014 DepthOfTree : 0y00111 (0x7)
+0x014 Unused : 0y000
+0x014 NumberGenericTableElements : 0y000000000000000000111001 (0x39)
+0x018 NodeHint :
Void +0x01c NodeFreeHint : (null)
其中 BalancedRoot 是一个 _MMADDRESS_NODE:
kd> dt _MMADDRESS_NODE 0x8608fa88
nt!_MMADDRESS_NODE
+0x000 u1 : <unnamed-tag>
+0x004 LeftChild : (null)
+0x008 RightChild : 0x85940248 _MMADDRESS_NODE //右节点
+0x00c StartingVpn : 0
+0x010 EndingVpn : 0
查看右节点,左节点为空:
kd> dt _MMADDRESS_NODE 0x85940248
nt!_MMADDRESS_NODE
+0x000 u1 : <unnamed-tag>
+0x004 LeftChild : 0x8678e4c0 _MMADDRESS_NODE
+0x008 RightChild : 0x85947628 _MMADDRESS_NODE
+0x00c StartingVpn : 0x75bc0
+0x010 EndingVpn : 0x75c09
kd> dx -id 0,0,855d0ae8 -r1 (*((ntkrpamp!_MMADDRESS_NODE *)0x85940248)).u1
(*((ntkrpamp!_MMADDRESS_NODE *)0x85940248)).u1 [Type: <unnamed-tag>]
[+0x000 ( 1: 0)] Balance : 0 [Type: long]
[+0x000] Parent : 0x8608fa88 [Type: _MMADDRESS_NODE *] //可以看出u1保存的是父节点
通过这方式可以查看到进程用户层所有的内存使用情况。
继续看 _MM_AVL_TABLE.NodeHint 保存一个 _MMVAD 结构体:
kd> dt _MMVAD 0x86b18928
nt!_MMVAD
+0x000 u1 : <unnamed-tag>
+0x004 LeftChild : (null)
+0x008 RightChild : (null)
+0x00c StartingVpn : 0x74aa0
+0x010 EndingVpn : 0x74c3d
+0x014 u : <unnamed-tag>
+0x018 PushLock : _EX_PUSH_LOCK
+0x01c u5 : <unnamed-tag>
+0x020 u2 : <unnamed-tag>
+0x024 Subsection : 0x868ddc78 _SUBSECTION
+0x024 MappedSubsection : 0x868ddc78 _MSUBSECTION
+0x028 FirstPrototypePte : 0x89dec038 _MMPTE
+0x02c LastContiguousPte : 0xfffffffc _MMPTE
+0x030 ViewLinks : _LIST_ENTRY [ 0x8598c840 - 0x868ddc70 ]
+0x038 VadsProcess : 0x8608f811 _EPROCESS
可以看出StartingVpn、EndingVpn 和 _MMVAD的地址0x86b18928 与上面vad指令查到的相匹配。
其中 Subsection 和 MappedSubsection 分别保存malloc分配的内存和文件映射分配的内存:
Subsection:
kd> dt _SUBSECTION 0x868ddc78
nt!_SUBSECTION
+0x000 ControlArea : 0x868ddc28 _CONTROL_AREA
+0x004 SubsectionBase : 0x89dec038 _MMPTE
+0x008 NextSubsection : 0x868ddc98 _SUBSECTION
+0x00c PtesInSubsection : 1
+0x010 UnusedPtes : 0
+0x010 GlobalPerSessionHead : (null)
+0x014 u : <unnamed-tag>
+0x018 StartingSector : 0
+0x01c NumberOfFullSectors : 2
MappedSubsection:
kd> dt _MSUBSECTION 0x868ddc78
nt!_MSUBSECTION
+0x000 ControlArea : 0x868ddc28 _CONTROL_AREA
+0x004 SubsectionBase : 0x89dec038 _MMPTE
+0x008 NextSubsection : 0x868ddc98 _SUBSECTION
+0x008 NextMappedSubsection : 0x868ddc98 _MSUBSECTION
+0x00c PtesInSubsection : 1
+0x010 UnusedPtes : 0
+0x010 GlobalPerSessionHead : (null)
+0x014 u : <unnamed-tag>
+0x018 StartingSector : 0
+0x01c NumberOfFullSectors : 2
+0x020 u1 : <unnamed-tag>
+0x024 LeftChild : 0x89dec040 _MMSUBSECTION_NODE
+0x028 RightChild : 0x868ddcb8 _MMSUBSECTION_NODE
+0x02c DereferenceList : _LIST_ENTRY [ 0x14b - 0x0 ]
+0x034 NumberOfMappedViews : 6
这两个结构体的第一个成员是一个控制区结构体:
kd> dt _CONTROL_AREA 0x868ddc28
nt!_CONTROL_AREA
+0x000 Segment : 0x89dec008 _SEGMENT
+0x004 DereferenceList : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x00c NumberOfSectionReferences : 0
+0x010 NumberOfPfnReferences : 0x161
+0x014 NumberOfMappedViews : 7
+0x018 NumberOfUserReferences : 7
+0x01c u : <unnamed-tag>
+0x020 FlushInProgressCount : 0
+0x024 FilePointer : _EX_FAST_REF
+0x028 ControlAreaLock : 0n0
+0x02c ModifiedWriteCount : 0
+0x02c StartingFrame : 0
+0x030 WaitingForDeletion : (null)
+0x034 u2 : <unnamed-tag>
+0x040 LockedPages : 0n1
+0x048 ViewList : _LIST_ENTRY [ 0x86b18958 - 0x86933e00 ]
若 FilePointer 不为空可以通过 _FILE_OBJECT 查看映射过来的文件信息。
Private Memory
通过VirtualAlloc/VirtualAllocEx申请的:Private Memory : 线性地址的物理页是线程独享的
VirtualAlloc
VirtualAlloc函数:
LPVOID VirtualAlloc
{
LPVOID lpAddress, //要分配的内存区域的地址
DWORD dwSize, //分配的大小
DWORD flAllocationType, //分配的类型
DWORD flProtect //该内存的初始保护属性
};
其中 lpAddress 要分配的地址一定不是二叉树中所存在的。
flAllocationType 分配类型:
MEM_RESERV : 会分配一个节点并指定虚拟地址,但不会分配物理页。
MEM_COMMIT : 会分配一个节点并指定虚拟地址,并指定物理页,即可以有物理页,但不是立即或者一直有。
VirtualAlloc函数只能在自己进程中分配空间,而VirtualAllocEx函数可以在其他进程中分配空间。
注:通过VirtualAlloc和VirtualAllocEx函数分配的内存都是 Private 属性的。
VirtualAlloc函数标志位
在VirtualAlloc函数分配内存的时候,即使flAllocationType指定了MEM_COMMIT : 会分配一个节点并指定虚拟地址,并指定物理页,即可以有物理页,但不是立即或者一直有。只有当线性地址真的被使用的时候才会分配物理内存。
例:
#include "stdafx.h"
#include <iostream>
#include <string>
#include <Windows.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
LPVOID pAddr = VirtualAlloc(NULL, 4096 * 8, MEM_COMMIT, PAGE_READWRITE);
printf("%p \n", pAddr);
string str;
cin >> str;
*(PDWORD)pAddr = 0x123456;
printf("%p \n", pAddr);
return 0;
}
在我们没用使用我们分配出的物理内存时,用windbg查看pAddr对应的物理内存:
kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 8585ecf8 SessionId: 1 Cid: 020c Peb: 7ffd6000 ParentCid: 0574
DirBase: 3f2bd3e0 ObjectTable: 89096c08 HandleCount: 8.
Image: Mallcoe.exe
DirBase就是当前进程的页目录表基址:
kd> !vtop 3f2bd3e0 000D0000
X86VtoP: Virt 00000000000d0000, pagedir 000000003f2bd3e0
X86VtoP: PAE PDPE 000000003f2bd3e0 - 00000000268fc801
X86VtoP: PAE PDE 00000000268fc000 - 0000000026c89867
X86VtoP: PAE PTE 0000000026c89680 - 0000000000000000
X86VtoP: PAE zero PTE
Virtual address d0000 translation fails, error 0xD0000147.
PAE说明是2-9-9-12分页,其中3f2bd260为页目录表基址,000D0000为我们分配内存映射出来的线性地址,由上面的数据可以看出我们的PTE所对应的地址为0,所以操作系统并没有给我们分配物理内存,即并没有给我们挂物理页。
当我们给pAdd赋值后再次查看:
kd> !vtop 3f2bd3e0 000D0000
X86VtoP: Virt 00000000000d0000, pagedir 000000003f2bd3e0
X86VtoP: PAE PDPE 000000003f2bd3e0 - 00000000268fc801
X86VtoP: PAE PDE 00000000268fc000 - 0000000026c89867
X86VtoP: PAE PTE 0000000026c89680 - 8000000027601867 这
X86VtoP: PAE Mapped phys 0000000027601000
Virtual address d0000 translates to physical address 27601000.
可以看出我们此时的PTE已经对应了一个物理地址。
从Vad中可以看出,操作系统确实给我们保留了8个页面的大小,但只有在使用的时候才会提交分配物理内存。
kd> !vad 0x8585cc30
VAD Level Start End Commit
00530048: Unable to get nt!_FILE_OBJECT.FileName.Buffer
8585cc30 0 0 0 5637 Mapped NO_ACCESS (null)
86b1d5e0 4 10 1f 0 Mapped READWRITE Pagefile section, shared commit 0x10
857761b8 3 20 2f 0 Mapped READWRITE Pagefile section, shared commit 0x10
867ea970 4 30 33 0 Mapped READONLY Pagefile section, shared commit 0x4
8604b570 2 40 40 0 Mapped READONLY Pagefile section, shared commit 0x1
8582d0d0 4 50 50 1 Private READWRITE
85ffd100 3 60 c6 0 Mapped READONLY \Windows\System32\locale.nls
86810890 5 d0 df 16 Private READWRITE
86ad09c0 4 e0 e7 8 Private READWRITE 这里
8632cde8 5 f0 1ef 3 Private READWRITE
86801720 1 220 31f 11 Private READWRITE
868681c8 5 4a0 59f 16 Private READWRITE
86a8c1e0 4 d30 d35 2 Mapped Exe EXECUTE_WRITECOPY \Users\WGH\Desktop\Mallcoe.exe
85839a10 5 6f840 6f8e2 8 Mapped Exe EXECUTE_WRITECOPY \Windows\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6161_none_50934f2ebcb7eb57\msvcr90.dll
867ea190 3 6ff40 6ffcd 4 Mapped Exe EXECUTE_WRITECOPY \Windows\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6161_none_50934f2ebcb7eb57\msvcp90.dll
867e6210 4 75bb0 75bf9 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll
867ebe10 5 773d0 774a3 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel32.dll
8611fe58 2 777b0 778eb 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll
8630de78 4 779f0 779f0 0 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\apisetschema.dll
86b10458 5 7f6f0 7f7ef 0 Mapped READONLY Pagefile section, shared commit 0x5
86b13968 3 7ffa0 7ffd2 0 Mapped READONLY Pagefile section, shared commit 0x33
86afc5a8 4 7ffd4 7ffd4 1 Private READWRITE
8690fe38 5 7ffdf 7ffdf 1 Private READWRITE
Total VADs: 23, average level: 4, maximum depth: 5
Total private commit: 0x165a pages (22888 KB)
Total shared commit: 0x5d pages (372 KB)
如果在使用 VirtualAlloc 函数的时候指定 flAllocationType 为 MEM_RESERV 此时Commit(保留)的位置会为0。
所以 MEM_RESERV 和 MEM_COMMIT区别在于,前者只有线性地址,而后者则会保留申请的内存空间(保留内存意味着设置一段连续的虚拟地址供日后使用),但都不会直接分配物理内存,只有在使用才会分配物理内存。
堆、栈和全局变量
new与malloc:
1)new 和 malloc并不是真正的申请内存,真正的申请内存只有上面的两种方式。
2)new 和 malloc实际上是一个东西,new的底层实现就是malloc。
3)malloc底层实际上调用了HeapAlloc,HeapAlloc也并没有真正分配内存。
堆:堆就是在程序执行前操作系统提前给我们分配了一块很大的内存,之后挂到vad二叉树中,在后续的代码中无论是通过 new 还是 malloc 分配内存实际上都是通过 HeapAlloc 函数从操作系统为我们分配好的这段内存中获取一段内存。所以使用new 还是 malloc分配内存时就不会出现页的概念了。
int y = (int*)malloc(sizeof(int) * 128); //从堆中分配
栈:程序启动操作系统给我们分配的,与堆相同,只是使用方式不同。(栈地址使用从大地址向小地址使用)
int i = 123456; //栈,并没有进零环,当然不存在分配内存
全局变量:
全局变量在程序编译后就已经生成了,在exe文件中有自己的地址空间,所以全局变量在使用的时候是通过文件映射方式分配的内存所以文件属性为map。
实验代码:
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
int _x = 123;
int _tmain(int argc, _TCHAR* argv[])
{
string str;
cin >> str;
//申请内存后
int y = 456;
int* z = (int*)malloc(sizeof(int) * 128);
printf("_x = %x \n", &_x);
printf("y = %x \n", &y);
printf("z = %x \n", z);
cin >> str;
return 0;
}
实验结果:
程序启动未分配内存时:
kd> !vad 0x858492a8
VAD Level Start End Commit
858492a8 0 0 0 5125 Mapped NO_ACCESS Pagefile section, shared commit 0
858368f8 4 10 1f 0 Mapped READWRITE Pagefile section, shared commit 0x10
86b2f2a0 5 20 2f 0 Mapped READWRITE Pagefile section, shared commit 0x10
86a1e540 3 30 33 0 Mapped READONLY Pagefile section, shared commit 0x4
85849388 4 40 40 0 Mapped READONLY Pagefile section, shared commit 0x1
86114970 2 50 50 1 Private READWRITE
858bace0 4 90 18f 16 Private READWRITE
86b23410 3 1b0 2af 11 Private READWRITE
858442c0 4 2b0 316 0 Mapped READONLY \Windows\System32\locale.nls
858b2238 5 360 36f 16 Private READWRITE
85844520 1 ca0 ca5 2 Mapped Exe EXECUTE_WRITECOPY \Users\WGH\Desktop\Mallcoe.exe
858c5fc0 4 6d910 6d9b2 8 Mapped Exe EXECUTE_WRITECOPY \Windows\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6161_none_50934f2ebcb7eb57\msvcr90.dll
8698e5c0 5 6d9e0 6da6d 4 Mapped Exe EXECUTE_WRITECOPY \Windows\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6161_none_50934f2ebcb7eb57\msvcp90.dll
858341d0 3 75ae0 75b29 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll
868c0090 4 77600 776d3 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel32.dll
85829c10 2 776e0 7781b 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll
858b7868 4 77920 77920 0 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\apisetschema.dll
86114998 5 7f6f0 7f7ef 0 Mapped READONLY Pagefile section, shared commit 0x5
8676dbb0 3 7ffa0 7ffd2 0 Mapped READONLY Pagefile section, shared commit 0x33
85880508 4 7ffd9 7ffd9 1 Private READWRITE
86b048a8 5 7ffdf 7ffdf 1 Private READWRITE
Total VADs: 21, average level: 4, maximum depth: 5
Total private commit: 0x144f pages (20796 KB)
Total shared commit: 0x5d pages (372 KB)
分配内存后:
kd> !vad 0x858492a8
VAD Level Start End Commit
858492a8 0 0 0 5381 Mapped NO_ACCESS Pagefile section, shared commit 0
858368f8 4 10 1f 0 Mapped READWRITE Pagefile section, shared commit 0x10
86b2f2a0 5 20 2f 0 Mapped READWRITE Pagefile section, shared commit 0x10
86a1e540 3 30 33 0 Mapped READONLY Pagefile section, shared commit 0x4
85849388 4 40 40 0 Mapped READONLY Pagefile section, shared commit 0x1
86114970 2 50 50 1 Private READWRITE
858bace0 4 90 18f 16 Private READWRITE
86b23410 3 1b0 2af 11 Private READWRITE
858442c0 5 2b0 316 0 Mapped READONLY \Windows\System32\locale.nls
858b2238 4 360 36f 16 Private READWRITE
855f9a70 5 370 46f 3 Private READWRITE
85844520 1 ca0 ca5 2 Mapped Exe EXECUTE_WRITECOPY \Users\WGH\Desktop\Mallcoe.exe
858c5fc0 4 6d910 6d9b2 8 Mapped Exe EXECUTE_WRITECOPY \Windows\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6161_none_50934f2ebcb7eb57\msvcr90.dll
8698e5c0 5 6d9e0 6da6d 4 Mapped Exe EXECUTE_WRITECOPY \Windows\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6161_none_50934f2ebcb7eb57\msvcp90.dll
858341d0 3 75ae0 75b29 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll
868c0090 4 77600 776d3 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel32.dll
85829c10 2 776e0 7781b 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll
858b7868 4 77920 77920 0 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\apisetschema.dll
86114998 5 7f6f0 7f7ef 0 Mapped READONLY Pagefile section, shared commit 0x5
8676dbb0 3 7ffa0 7ffd2 0 Mapped READONLY Pagefile section, shared commit 0x33
85880508 4 7ffd9 7ffd9 1 Private READWRITE
86b048a8 5 7ffdf 7ffdf 1 Private READWRITE
Total VADs: 22, average level: 4, maximum depth: 5
Total private commit: 0x1552 pages (21832 KB)
Total shared commit: 0x5d pages (372 KB)
可以看出分配内存后,vad中的节点并没有增加,通过 VirtualAlloc/VirtualAllocEx和CreateFileMapping 分配的内存则会增加一个节点,请自行实验。所以全局变量、局部变量、malloc并没有分配额外的内存。
程序输出分析:
_x = ca301c //全局变量
y = 2afab4 //局部变量
z = 36f970 //malloc分配的内存
全局变量(mapped):
85844520 1 ca0 ca5 2 Mapped Exe EXECUTE_WRITECOPY \Users\WGH\Desktop\Mallcoe.exe
可以看出 ca301c 这个地址在 ca0 到 ca5之间,通过Mapped分配的即文件映射方式,并且是从我们当前的exe中映射的。所以全局变量是在程序编译的过程中就已经写入到了文件中,后通过文件映射到内存中的。
局部变量(栈):
86b23410 3 1b0 2af 11 Private READWRITE
可以看出 2afab4 这个地址在 1b0 到 2af之间,通过Private分配的,并且这段内存在我们创建局部变量前就已经存在了,即我们是直接使用的这段内存。因为栈的特性内存是从大到小使用的,所以从2af开始分配。
malloc分配的内存(堆):
858b2238 4 360 36f 16 Private READWRITE
可以看出 36f970 这个地址在 360 到 36f之间,通过Private分配的,并且这段内存在我们创建局部变量前就已经存在了,即我们是直接使用的这段内存。与栈相同,只是堆按顺序分配。
结论:
堆与栈实际上都是在程序执行前操作系统提前给我们分配了一块很大的内存,只是这每段内存的使用方式不同,而已。而全局变量则是通过文件映射来的与其不同。
Mapped Memory
Mapped方式分配的内存可以分为两类:
1)共享的是一个文件
858341d0 3 75ae0 75b29 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll
2)共享的是一个物理页
85849388 4 40 40 0 Mapped READONLY Pagefile section, shared commit 0x1
CreateFileMapping函数:
CreateFileMapping函数的第一个参数接受一个句柄,如果句柄为空则创建一个物理页,大小为参数设定的大小。这种方式创建的就是一个共享的物理页。如果传入的句柄不为空,而是一个文件句柄时,函数会准备一个物理页与这个文件关联起来。
共享文件
通过这种方式申请的共享内存,申请的是共享文件的形式。适合处理大的文件。
申请共享内存写读值:
#include "stdafx.h"
#include <windwos.h>
int _tmain(int argc, _TCHAR* argv[])
{
//创建一个文件
HANDLE hFile = CreateFileW(L"C:\\CeShi.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
//创建一个文件共享内存
HANDLE hMapFile = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, BUFSIZ, NULL);
//将物理地址与线性地址进行映射,即将物理页映射到2叉树中
LPTSTR lpBuff = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);
//写值
*(PDWORD)lpBuff = 0x12345678;
return 0;
}
CreateFile当文件已经存在时,则打开一个文件。
共享物理页
通过这种方式申请的共享内存,申请的是共享的物理页的形式。
申请共享内存写值:
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
//申请共享内存,
HANDLE hMapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READONLY, 0, BUFSIZ, L"共享内存");
//将物理地址与线性地址进行映射,即将物理页映射到2叉树中
LPTSTR lpBuff = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);
*(PDWORD) lpBuff = 0x12345678;
printf("A Process Write Address: %p -- %x", lpBuff, *(PDWORD) lpBuff);
return 0;
}
读取共享内存的值:
#include "stdafx.h"
#include <iostream>
#include <string>
#include <Windows.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
//打开一个共享内存
HANDLE hMapFile = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"共享内存");
//将物理地址与线性地址进行映射,即将物理页映射到2叉树中
LPTSTR lpBuff = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);
printf("A Process Read Address: %p -- %x", lpBuff, *(PDWORD)lpBuff);
return 0;
}
物理内存的管理
<1> 最大物理内存
10-10-12分页 最多识别物理内存为4GB
2-9-9-12分页 最多识别物理内存为64GB
32位进程在32位windwos上虚拟内存大小位2GB,在64位windows上虚拟内存大小为4GB
64位进程在64位windwos上虚拟内存大小位7152GB(如果启用了大地址空间会增大)
<2> 操作系统限制
32位进程在32位windwos上虚拟内存大小位2GB,在64位windows上虚拟内存大小为4GB
64位进程在64位windwos上虚拟内存大小位7152GB(如果启用了大地址空间会增大--启用 PAE 和 “large address space aware”)
XP中操作系统限制使用4GB以上的虚拟内存的函数:_ExVerifySuitE@4(ntoskrnl.dll)
<3> 实际物理内存
MmNumberOfPhysicalPages * 4 = 物理内存
MmNumberOfPhysicalPages是一个全局变量保存物理页的个数:
kd> dd MmNumberOfPhysicalPages
83f7b710 0003ff7e 7ffeffff 80000000 7fff0000
83f7b720 80741000 0003ffff c03fff78 c0601ff8
此时有 0003ff7e(262014-十进制) 个物理页,就有:
262014 * 4 / 1024 = 1024MB = 1GB
大小的物理内存, 并且最后一个物理页的基址为3ff7e000(物理地址)。
物理内存的管理
<1> 全局数组
数组指针:_MMPFN* MmPfnDatabase
数组长度:MmNumberOfPhysicalPages
<2> 数组成员
kd> dt _MMPFN
nt!_MMPFN -- (可以去WRK中查看)
+0x000 u1 : <unnamed-tag>
+0x004 u2 : <unnamed-tag>
+0x008 PteAddress : Ptr32 _MMPTE
+0x008 VolatilePteAddress : Ptr32 Void
+0x008 Lock : Int4B
+0x008 PteLong : Uint4B
+0x00c u3 : <unnamed-tag>
+0x010 OriginalPte : _MMPTE
+0x010 AweReferenceCount : Int4B
+0x018 u4 : <unnamed-tag>
WRK中的_MMPFN:
typedef struct _MMPFN {
union {
PFN_NUMBER Flink;
WSLE_NUMBER WsIndex;
PKEVENT Event;
NTSTATUS ReadStatus;
SINGLE_LIST_ENTRY NextStackPfn;
} u1;
PMMPTE PteAddress;
union {
PFN_NUMBER Blink;
ULONG_PTR ShareCount;
} u2;
union {
struct {
USHORT ReferenceCount;
MMPFNENTRY e1;
};
struct {
USHORT ReferenceCount;
USHORT ShortFlags;
} e2;
} u3;
#if defined (_WIN64)
ULONG UsedPageTableEntries;
#endif
union {
MMPTE OriginalPte;
LONG AweReferenceCount;
};
union {
ULONG_PTR EntireFrame;
struct {
#if defined (_WIN64)
ULONG_PTR PteFrame: 57;
#else
ULONG_PTR PteFrame: 25;
#endif
ULONG_PTR InPageError : 1;
ULONG_PTR VerifierAllocation : 1;
ULONG_PTR AweAllocation : 1;
ULONG_PTR Priority : MI_PFN_PRIORITY_BITS;
ULONG_PTR MustBeCached : 1;
};
} u4;
} MMPFN, *PMMPFN;
MmPfnDatabase指针中的每个成员都对应一个_MMPFN结构体用来描述一个物理页的属性,即有多少个物理页就有多少个_MMPFN结构体,MmNumberOfPhysicalPages变量用来记录有多少个物理页,即有MmPfnDatabase有多少个_MMPFN结构体。
查找每个_MMPFN结构体对应的物理地址:
在_MMPFN结构体中并没用保存当前结构体对应的物理页基址的变量,而是用以下方式保存物理地址的:
MmPfnDatabase 数组中的第一个成员描述第一个物理页(下标为0): 0000 (4KB) - 物理地址
MmPfnDatabase 数组中的第二个成员描述第二个物理页(下标为1): 1000 (4KB) - 物理地址
...以此类推
当我们知道一个_MMPFN结构体的地址后查找对应的物理页的地址:
(_MMPFN结构体地址 - MmPfnDatabase数组基址)/ _MMPFN结构体大小 = 第几个物理页(当前值(16进制)后面补3个零就是当前物理页的基址)
(_MMPFN结构体地址 - MmPfnDatabase数组基址)/ _MMPFN结构体大小 - 1 = 物理页在MmPfnDatabase数组中的下标
(物理地址清零后3位/4096) - 1后,就是这个物理地址对应在MmPfnDatabase结构体的下标。(物理地址清零后3位/4096)就是第几个物理页,MmPfnDatabase结构体的下标转为16进制后,在后面补三个零就是物理页的基址(物理地址)。
描述物理页当前的状态:
在_MMPFN结构体中,u3这个变量保存用于描述物理页当前状态的数据:
u3: union { struct { USHORT ReferenceCount; MMPFNENTRY e1; }; struct { USHORT ReferenceCount; USHORT ShortFlags; } e2; } u3;
e1:
typedef struct _MMPFNENTRY {
USHORT Modified : 1;
USHORT ReadInProgress : 1;
USHORT WriteInProgress : 1;
USHORT PrototypePte: 1;
USHORT PageColor : 4;
USHORT PageLocation : 3; //保存页的状态
USHORT RemovalRequested : 1;
USHORT CacheAttribute : 2;
USHORT Rom : 1;
USHORT ParityError : 1;
} MMPFNENTRY;
windbg查看:
kd> dx -id 0,0,855cfae8 -r1 (*((ntkrpamp!_MMPFN *)0x84e00018)).u3
(*((ntkrpamp!_MMPFN *)0x84e00018)).u3 [Type: <unnamed-tag>]
[+0x000] ReferenceCount : 0xe838 [Type: unsigned short]
[+0x002] e1 [Type: _MMPFNENTRY]
[+0x000] e2 [Type: <unnamed-tag>]
kd> dx -id 0,0,855cfae8 -r1 (*((ntkrpamp!_MMPFNENTRY *)0x84e00026))
(*((ntkrpamp!_MMPFNENTRY *)0x84e00026)) [Type: _MMPFNENTRY]
[+0x000 ( 2: 0)] PageLocation : 0x7 [Type: unsigned char] //保存页的状态
[+0x000 ( 3: 3)] WriteInProgress : 0x1 [Type: unsigned char]
[+0x000 ( 4: 4)] Modified : 0x1 [Type: unsigned char]
[+0x000 ( 5: 5)] ReadInProgress : 0x1 [Type: unsigned char]
[+0x000 ( 7: 6)] CacheAttribute : 0x1 [Type: unsigned char]
[+0x001 ( 2: 0)] Priority : 0x0 [Type: unsigned char]
[+0x001 ( 3: 3)] Rom : 0x0 [Type: unsigned char]
[+0x001 ( 4: 4)] InPageError : 0x0 [Type: unsigned char]
[+0x001 ( 5: 5)] KernelStack : 0x0 [Type: unsigned char]
[+0x001 ( 6: 6)] RemovalRequested : 0x1 [Type: unsigned char]
[+0x001 ( 7: 7)] ParityError : 0x1 [Type: unsigned char]
注:一个物理页可以分为空闲和使用两大状态。
空闲状态:
其中 (PageLocation & 1) = TRUE时(空闲状态)物理页会有一下六种状态:
0:MmZeroedPageListHead //零化状态链表
1:MmFreePageListHead //空闲页链表
2:MmStandbyPageListHead //备用链表
3:MmModifiedPageListHead
4:MmModifiedNoWritePageListHead
5:MmBadPageListHead //坏链
windows操作系统通过6个链表将所有相同属性的物理页串在一起:
这些链表中保存的是一个MMPFNLIST的结构体:
kd> dt _MMPFNLIST
nt!_MMPFNLIST
+0x000 Total : Uint4B
+0x004 ListName : _MMLISTS
+0x008 Flink : Uint4B
+0x00c Blink : Uint4B
+0x010 Lock : Uint4B
<1> MmBadPageListHead (链表名)
坏链。
MMPFNLIST MmBadPageListHead = {
0, // Total
BadPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
<2> MmZeroedPageListHead (链表名)
零化链表:没有人使用的物理页,并且已经清零的物理页。操作系统在空闲的时候进行零化的,不是程序自己清零的那种。
MMPFNLIST MmZeroedPageListHead = {
0, // Total
ZeroedPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
<3> MmFreePageListHead (链表名)
空闲链表:物理页是周转使用的,刚被释放的物理页是没有清零的,系统空闲的时候有专门的线程从这个队列摘取物理页,加以清零后再挂入MmZeroedPageListHead链表中。
MMPFNLIST MmFreePageListHead = {
0, // Total
FreePageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
<4> MmStandbyPageListHead (链表名)
备用链表:当系统内存不够的时候,操作系统会把物理内存中的数据交换到硬盘上,此时页面并不是直接挂到空闲链表上去,而是挂到备用链表上,虽然我释放了,但里面的内容还是有意义的。如刚释放了这个页后,又要申请这个页数据,则可以直接从备用链表中获取,而不用从硬盘上获取。
MMPFNLIST MmStandbyPageListHead = {
0, // Total
StandbyPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
<5> MmModifiedPageListHead (链表名)
MMPFNLIST MmModifiedPageListHead = {
0, // Total
ModifiedPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
<6> MmModifiedNoWritePageListHead (链表名)
MMPFNLIST MmModifiedNoWritePageListHead = {
0, // Total
ModifiedNoWritePageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
查看其中一个链表中值对应在 MMPFN 的地址:
上一个MMPFN地址 = MmPfnDatabase基址 + _MMPFNLIST.Flink(上一个的索引) * _MMPFN大小
下一个MMPFN地址 = MmPfnDatabase基址 + _MMPFNLIST.Flink(下一个的索引) * _MMPFN大小
使用中的物理页:
进程的EPROCESS.VM是一个结构体:
kd> dt _MMSUPPORT
ntdll!_MMSUPPORT
+0x000 WorkingSetMutex : _EX_PUSH_LOCK
+0x004 ExitGate : Ptr32 _KGATE
+0x008 AccessLog : Ptr32 Void
+0x00c WorkingSetExpansionLinks : _LIST_ENTRY
+0x014 AgeDistribution : [7] Uint4B
+0x030 MinimumWorkingSetSize : Uint4B
+0x034 WorkingSetSize : Uint4B
+0x038 WorkingSetPrivateSize : Uint4B
+0x03c MaximumWorkingSetSize : Uint4B
+0x040 ChargedWslePages : Uint4B
+0x044 ActualWslePages : Uint4B
+0x048 WorkingSetSizeOverhead : Uint4B
+0x04c PeakWorkingSetSize : Uint4B
+0x050 HardFaultCount : Uint4B
+0x054 VmWorkingSetList : Ptr32 _MMWSL
+0x058 NextPageColor : Uint2B
+0x05a LastTrimStamp : Uint2B
+0x05c PageFaultCount : Uint4B
+0x060 RepurposeCount : Uint4B
+0x064 Spare : [1] Uint4B
+0x068 Flags : _MMSUPPORT_FLAGS
+0x030 MinimumWorkingSetSize : Uint4B:
当前进程能使用的最小的物理页大小设置。
+0x03c MaximumWorkingSetSize : Uint4B:
当前进程能使用的最大的物理页大小设置。
+0x054 VmWorkingSetList : Ptr32 _MMWSL:
这个List中保存了当前所有正在使用的物理页。
缺页异常
在PTE的属性中,P位表示当前页面是否有效:
当CPU访问一个地址,其中PTE的P位为0,此时就会产生缺页异常。
只有当前线性地址真正使用的时候才会挂上物理页,否则处于保留状态。当物理页不够用时,会将数据保存到页面文件中。
写拷贝
PTE只读
→ 修改页面内存触发异常 → 异常处理函数
VAD写拷贝 ↓
发现VAD为写拷贝
↓
创建一个新的物理页
先将PTE只读置为只读,然后将VAD设置为写拷贝,之后修改页面,因为PTE为只读所以触发页异常(所有的页面异常都使用一个异常处理函数,包括缺页异常),然后如异常处理函数中,发现VAD设置写拷贝,后创建一个新的物理页,将原先数据拷贝过去后进行修改,最后将要修改进程的线性地址指向新的这个修改后的物理页。
如果,将PTE置为可读可写,那就不会触发异常,也不会触发写拷贝,就可以实现用户层HOOK一个进程的函数来控制所有进程,因为修改的物理页所有进程都在使用。
镜像文件
LoadLibrary:
HMODULE hModule = LoadLibrary("C:\\Notepad.exe");
1)LoadLibrary 就是通过内存映射的方式实现的
2)为了避免影响别人,属性为写拷贝。
在不调用LoadLibrary时,我们将PE文件进行拉伸对齐,后拷贝到内存中,则并不会在 VadRoot 中留下记录。
写拷贝:
我们所调用的如dll这种文件,操作系统实际上只映射了一份,所有进程共同使用,即如果进程执行这个DLL的线性地址可能不同但这些线性地址解析的物理页都是同一个,要是在程序中修改了DLL的映射时,那就会触发写拷贝,操作系统会为你这个进程单独拷贝一份到新的物理页中,此时你的线性地址指向的就是这个新的物理页,供你修改使用。这也就是你修改了自己进程中由文件映射出来的系统DLL时,不影响其他进程的原因。即:
858341d0 3 75ae0 75b29 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll //镜像文件
868c0090 4 77600 776d3 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel32.dll //镜像文件
85829c10 2 776e0 7781b 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll //镜像文件
这种形式的 Mapped 就是我们通过 LoadLibrary 函数将dll或exe文件映射到了内存中,操作系统也是采用这种方式将要用到的模块映射到内存中,不过操作系统用设进去的模块类型只会为 EXECUTE_WRITECOPY(写拷贝)。