写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。
看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。
🔒 华丽的分割线 🔒
概述
本篇博文只是介绍一下Windows
内存管理的方式,并不介绍操作系统是如何把真实的物理内存抽象为一个一个物理页的。如果对改实现感兴趣,请自行查阅其他文档。
在介绍物理内存的管理之前,我们先简单回顾以前我们物理内存相关的知识。对于101012
分页模式下,操作系统最多能识别的大小为4GB,而29912
分页模式下,最多识别的大小为64GB。但是XP
操作系统为什么无论是哪种分页模式下最多识别4GB呢?是因为操作系统内部实现的限制,如下是我逆向部分的代码:
if ( ExVerifySuite(DataCenter) == 1 )
{
MaxNumberOfPhysicalPages = 0x1000000; // 64GB,16M个物理页
}
else if ( MmProductType == 6881367
|| (isEnterprise = ExVerifySuite(Enterprise) == 1, MaxNumberOfPhysicalPages = 0x800000, !isEnterprise) )
{
MaxNumberOfPhysicalPages = 0x100000; // 4GB,1M个物理页
}
if ( MmNumberOfPhysicalPages + v5 > MaxNumberOfPhysicalPages )
{
v5 = MaxNumberOfPhysicalPages - MmNumberOfPhysicalPages;
v6 = MaxNumberOfPhysicalPages - MmNumberOfPhysicalPages + v4;
v40 = MaxNumberOfPhysicalPages - MmNumberOfPhysicalPages;
v37 = v6;
}
而上面的伪代码片段来自于MmAddPhysicalMemoryEx
函数,如下是win2k
在初始化系统函数中的限制代码:
#if defined(_X86PAE_)
if (ExVerifySuite(DataCenter) == TRUE) {
MmHighestPossiblePhysicalPage = 0x1000000 - 1;
}
else if ((MmProductType != 0x00690057) &&
(ExVerifySuite(Enterprise) == TRUE)) {
MmHighestPossiblePhysicalPage = 0x200000 - 1;
}
else {
MmHighestPossiblePhysicalPage = 0x100000 - 1;
}
#else
MmHighestPossiblePhysicalPage = 0x100000 - 1;
#endif
也就是说,这个和分页没有任何关系,纯粹是操作系统捣的鬼。
初识物理内存管理
我们的物理内存被划分为一个个物理页,一个物理页的大小为4KB。所以知道物理页的个数,我们就可以知道物理内存的大小,而这些数据可以通过任务管理器进行查看,如下图所示:
对于物理内存,操作系统采用帧管理数据库的模式。一个物理页就是一帧,被称之为PFN
这个东西。在操作系统中,有一个全局变量,被称之为帧数据库,如下所示:
extern PMMPFN MmPfnDatabase;
上面的数据库本质就是一个数组,那么我们如何知道它的大小呢?是通过如下变量知道的:
//
// Total number of physical pages available on the system.
//
PFN_COUNT MmNumberOfPhysicalPages;
我们到Windbg
中dd
一下:
kd> dd MmPfnDatabase
80559968 80c86000 0000ff00 00000000 0000003f
kd> dd MmNumberOfPhysicalPages
805599c8 0009ff7c 00000040 00000000 7fff0000
0x9ff7c
这个就是我虚拟机的物理页的数目,乘上4之后,就是十进制的2620912
,和任务管理器显示的一模一样。下面我们来学习物理页帧的结构:
kd> dt _MMPFN
nt!_MMPFN
+0x000 u1 : __unnamed
+0x004 PteAddress : Ptr32 _MMPTE
+0x008 u2 : __unnamed
+0x00c u3 : __unnamed
+0x010 OriginalPte : _MMPTE
+0x018 u4 : __unnamed
有很多共用体。如下是从WRK
搬运过来的:
typedef struct _MMPFN {
union {
PFN_NUMBER Flink;
WSLE_NUMBER WsIndex;
PKEVENT Event;
NTSTATUS ReadStatus;
//
// Note: NextStackPfn is actually used as SLIST_ENTRY, however
// because of its alignment characteristics, using that type would
// unnecessarily add padding to this structure.
//
SINGLE_LIST_ENTRY NextStackPfn;
} u1;
PMMPTE PteAddress;
union {
PFN_NUMBER Blink;
//
// ShareCount transitions are protected by the PFN lock.
//
ULONG_PTR ShareCount;
} u2;
union {
//
// ReferenceCount transitions are generally done with InterlockedXxxPfn
// sequences, and only the 0->1 and 1->0 transitions are protected
// by the PFN lock. Note that a *VERY* intricate synchronization
// scheme is being used to maximize scalability.
//
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;
看到有我们熟悉的成员Flink
和Blink
,但它不是链表地址而是帧索引,因为物理页的管理是基于帧管理的。
物理页种类管理
物理页有多种状态,比如正在使用,未被使用,被交换到磁盘未被使用等等。如下是它的种类枚举:
kd> dt _MMLISTS
nt!_MMLISTS
ZeroedPageList = 0n0
FreePageList = 0n1
StandbyPageList = 0n2
ModifiedPageList = 0n3
ModifiedNoWritePageList = 0n4
BadPageList = 0n5
ActiveAndValid = 0n6
TransitionPage = 0n7
如下是WRK
中的代码:
#define NUMBER_OF_PAGE_LISTS 8
typedef enum _MMLISTS {
ZeroedPageList,
FreePageList,
StandbyPageList, //this list and before make up available pages.
ModifiedPageList,
ModifiedNoWritePageList,
BadPageList,
ActiveAndValid,
TransitionPage
} MMLISTS;
而这个又是通过帧管理的链表,它的结构如下:
kd> dt _MMPFNLIST
nt!_MMPFNLIST
+0x000 Total : Uint4B
+0x004 ListName : _MMLISTS
+0x008 Flink : Uint4B
+0x00c Blink : Uint4B
如下是列表的结构体:
typedef ULONG PFN_NUMBER, *PPFN_NUMBER;
typedef struct _MMPFNLIST {
PFN_NUMBER Total;
MMLISTS ListName;
PFN_NUMBER Flink;
PFN_NUMBER Blink;
} MMPFNLIST;
如下是操作系统管理的物理页的链表:
MMPFNLIST MmZeroedPageListHead = {
0, // Total
ZeroedPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
MMPFNLIST MmFreePageListHead = {
0, // Total
FreePageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
MMPFNLIST MmStandbyPageListHead = {
0, // Total
StandbyPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
MMPFNLIST MmModifiedPageListHead = {
0, // Total
ModifiedPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
MMPFNLIST MmModifiedNoWritePageListHead = {
0, // Total
ModifiedNoWritePageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
MMPFNLIST MmBadPageListHead = {
0, // Total
BadPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
//
// Note the ROM page listhead is deliberately not in the set
// of MmPageLocationList ranges.
//
MMPFNLIST MmRomPageListHead = {
0, // Total
StandbyPageList, // ListName
MM_EMPTY_LIST, //Flink
MM_EMPTY_LIST // Blink
};
PMMPFNLIST MmPageLocationList[NUMBER_OF_PAGE_LISTS] = {
&MmZeroedPageListHead,
&MmFreePageListHead,
&MmStandbyPageListHead,
&MmModifiedPageListHead,
&MmModifiedNoWritePageListHead,
&MmBadPageListHead,
NULL,
NULL };
我就介绍比较重要的链表含义:
- MmBadPageListHead :坏链,物理页被损坏挂到上面。
- MmZeroedPageListHead :零化链表,是系统在空闲的时候进行零化的,不是程序自己清零的那种。
- MmFreePageListHead :空闲链表,物理页是周转使用的,刚被释放的物理页是没有清0,系统空闲的时候有专门的线程从这个队列摘取物理页,加以清0后再挂入
MmZeroedPageListHead
。 - MmStandbyPageListHead :备用链表,当系统内存不够的时候,操作系统会把物理内存中的数据交换到硬盘上,此时页面不是直接挂到空闲链表上去,而是挂到备用链表上,虽然我释放了,但里边的内容还是有意义的。
实验测试
在做实验之前,我们来捋顺知识点,如下图所示:
那我们就拿MmZeroedPageListHead
这张链表来做示范吧,如下是Windbg
的命令:
kd> dd MmPfnDatabase l1
80559968 80c86000
kd> dd MmZeroedPageListHead l4
8054b0e0 00089a0c 00000000 0001eea4 0001f25b
kd> dd 80c86000 + 1c*0001f25b l4
ReadVirtual: 80fee1f4 not properly sign extended
80fee1f4 ffffffff c000bf81 0001efe0 00003000
kd> dd 80c86000 + 1c*0001efe0 l4
ReadVirtual: 80fe9c80 not properly sign extended
80fe9c80 0001f25b c07085a1 0001f31f 00003000
如果理解了上面的那张图,这个实验也不难理解,就不赘述了。