Windows内存管理—内存映射、PAE、MmPfnDatabase

Windows内存管理

内存有两种,一种是由内存条构成的所谓的物理内存,这种内存读取速度快;
还有一种是虚拟内存,由硬盘构成,也就是把硬盘的一部分容量拿来当作内存用,但是速度没有内存条那么快。

两者的关系:
每个进程都有4GB的虚拟内存,但不是所有的虚拟内存都有对应的物理内存,它其实是在物理内存和磁盘之间进行倒换的。
虚拟内存在用的时候,会从磁盘倒入物理内存,不用的时候就还是存在磁盘上。因此,同一个物理内存在不同的时间可以被用于不同进程的不同虚拟内存。

基于页面映射的虚拟内存机制:
在硬件上:由CPU芯片内部或外部的存储管理单元MMU支持。
在软件上:由操作系统内核中的内存管理模块实现。

采用页式内存管理时,程序中所使用的内存地址,即CPU中的运算单元ALU所发出的都是虚拟地址。虚拟地址是不能直接用来访问物理内存的,需要转换成物理地址才能访问。

名词解释:
PDE:Page Directory Entry,页目录表项。
PTE:Page Table Entry,页表项。
PAE:Physical Address Extension,物理地址扩展。
PFT:Page Frame Table,页帧表。
PFN:Page Frame Number,页帧号。 PFN = 物理地址/4KB

虚拟地址转换为物理地址(x86)

虚拟地址转换为物理地址:由MMU(也就是存储管理单元)将虚拟地址映射到某个物理页面上,从而转换成物理地址。

每个进程都有一个页面映射表,首先是根据CR3寄存器找到页面映射表。

页面映射表分为两层,第一层是页目录表,它实际上是一个指针数组,有1024个表项,每一项PDE都指向一个页表,页表也有1024项,每一项PTE指向对应的物理页面。

x86下,虚拟地址有32位

31-22(10位):页目录表索引
21-12(10位):页表索引
11-0(12位):页内偏移

根据它的前20位能定位到相应的物理页面首地址,然后再加上页面偏移就得到物理地址了。
在这里插入图片描述

PAE 模式

PAE,Physical Address Extension,物理地址扩展。

它是通过设置CR4寄存器的第5位(PAE标志)来开启的,开启之后,CR3寄存器就指向了页目录指针表。

开启PAE:
bcdedit /set pae forceenable
关闭PAE:
bcdedit /set pae forcedisable

可以通过bcdedit命令来验证是否成功,开启成功显示:
在这里插入图片描述

开启PAE之后,页表和页目录中的表项都从32位扩为64位(8字节),而页表和页目录的总大小不变(4KB),所以页表和页目录都变成了512个表项。
这样就需要有4个页目录表,因此又多加了一个页目录指针表(32字节),里边有4项,每一项指向一个页目录表。

不会算的看这里:
原来是1个页目录表,1024个页表,一共有 1024 * 1024个PTE
现在每个页表有512个PTE,一共1024 * 1024个PTE,所以需要2048个页表
1个页目录表指向512个页表,所以现在需要4个页目录表。

x86下开启PAE模式,虚拟地址有32位

31-30(2位):页目录指针表的索引
29-21(9位):页目录表索引
20-12(9位):页表索引
11-0(12位):页内偏移
在这里插入图片描述

Windows内存空间布局

在这里插入图片描述
x86下,每个进程有4GB的虚拟内存,用户空间和内核空间各占2GB

用户空间的2GB:
前64KB(0x00000000-0x0000FFFF)是空指针赋值分区
后64KB(0x7FFF0000-0x7FFFFFFF)是禁入分区
中间(0x00010000-0x7FFEFFFF)才是真正的用户空间。

有一些特殊的地址:

  1. 0xFFDF0000SharedUserData 的地址,SharedUserData在内核空间,但是又划出来让用户空间的程序访问,目的是让用户空间的程序访问内核中的一些数据。
    SharedUserData是由所有进程所共享的,指向KUSER_SHARED_DATA结构,用户程序通过指针SharedUserData来读取这个结构中各个成分的内容。

  2. 进程的PEB总是安排在0x7FFDF000的地方,占4KB;TEB在PEB的上方(0x7FFDE000),进程中有几个线程就有几个TEB,每个TEB占一个4KB的页面。

  3. 进程EPROCESS中有个成员VadRoot,它指向该进程的用户空间。

#define MM_LOWEST_USER_ADDRESS	0x00010000
#define MM_HIGHEST_USER_ADDRESS	MmHighestUserAddress
#define MM_USER_PROBE_ADDRESS	MmUserProbeAddress
#define KI_USER_SHARED_DATA     0xFFDF0000
#define SharedUserData  		((KUSER_SHARED_DATA* const) KI_USER_SHARED_DATA)

3: kd> dd MmHighestUserAddress
849ac848  7ffeffff
3: kd> dd MmUserProbeAddress
849ac850  7fff0000
3: kd> dd MmSystemRangeStart
849ac84c  80000000

3: kd> dd SharedUserData
7ffe0000  00000000 0f99a027 34ed95a7 00000000
7ffe0010  00000000 8d296be5 01d60a39 01d60a39
3: kd> dd FFDF0000
ffdf0000  00000000 0f99a027 34ed95a7 00000000
ffdf0010  00000000 8d296be5 01d60a39 01d60a39

内核对物理内存的管理(MmPfnDatabase)

Windows通过几个全局变量来对物理内存进行管理:

MmPfnDatabase,页帧数据库(也叫FPN数据库),是MMPFN结构体类型的数组,它的索引是物理地址页帧号。

获得MmPfnDatabase的地址:84e00000
1: kd> dd MmPfnDatabase
83f7b700  84e00000 00000000 00000001 0005ffff

查看结构体发现,结构体大小为0x1C
1: kd> dt _MMPFN
nt!_MMPFN
   +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>
   
第一个数组项,也就是PFN为1的物理页面:
1: kd> dd 84e00000 + 1c
84e0001c  00000000 00000001 c07fe838 00460001
84e0002c  00000000 00000000 0000018a 
验证一下,可以发现各数据成员的值都是能对的上的:
1: kd> !pfn 1
    PFN 00000001 at address 84E0001C
    flink       00000000  blink / share count 00000001  pteaddress C07FE838
    reference count 0001   Cached     color 0   Priority 0
    restore pte 00000000  containing page 00018A  Active 
 
 
第二个数组项:
1: kd> dd 84e00000 + 1c*2
84e00038  00000000 00000001 c07fe920 00460001
84e00048  00000000 00000000 0000018a
同样再验证一下:
1: kd> !pfn 2
    PFN 00000002 at address 84E00038
    flink       00000000  blink / share count 00000001  pteaddress C07FE920
    reference count 0001   Cached     color 0   Priority 0
    restore pte 00000000  containing page 00018A  Active
    
......
以此类推,第n个数组项的地址是:84e00000 + 0x1c*n

MmNumberOfPhysicalPages,物理页面的个数,也是MmPfnDatabase数组的长度。

1: kd> dd MmNumberOfPhysicalPages
83f7b710 0005ff7e 7ffeffff 80000000 7fff0000

0x5ff7e * 4KB = 1535MB
在这里插入图片描述

看一下MmPfnDatabase的最后一个数组项
1: kd> dd 84e00000 + 1c*0005ff7e 
8587f1c8  00000000 00000001 c042cbf8 00560001
8587f1d8  00000000 00000000 00005a05
1: kd> !pfn 0005ff7e 
    PFN 0005FF7E at address 8587F1C8
    flink       00000000  blink / share count 00000001  pteaddress C042CBF8
    reference count 0001   Cached     color 0   Priority 0
    restore pte 00000000  containing page 005A05  Active     M       
    Modified

MmPageLocationList,链表头数组,物理页有六种状态,相同状态的物理页用一个链表维护。关于链表的详细内容后续会补充一下。

1: kd> dd MmPageLocationList
83e5b894  83f7c000 83f7c040 83f7c080 83f7c0c0
83e5b8a4  83f3b94c 83f3b960 00000000 00000000

typedef struct _MMPFNLIST
{
    PFN_NUMBER Total;
    MMLISTS ListName;
    PFN_NUMBER Flink;
    PFN_NUMBER Blink;
} MMPFNLIST, *PMMPFNLIST;
PMMPFNLIST MmPageLocationList[] =
{
	&MmZeroedPageListHead,		// 零化链表,可以使用并已经清零的内存
	&MmFreePageListHead,		// 空闲链表,被释放的内存,会定期检测是否有数据,如果有就清空并挂到MmZeroedPageListHead中
	&MmStandbyPageListHead,	    // 备用链表
	&MmModifiedPageListHead,    // 修改过的内存
	&MmModifiedNoWritePageListHead,	// 修改过但是没有写出的内存,即不会写到磁盘上
	&MmBadPageListHead,		    // 损坏的内存
	NULL,
	NULL
};

获得MmPfnDatabase的地址

可以通过KeCapturePersistentThreadState函数获得,
在windbg中查看x86下函数的汇编,我们可以发现其中有很多重要信息,
除了MmPfnDatabase,还可以获得PsLoadedModuleListPsActiveProcessHead等地址。
PsLoadedModuleList 是维护系统加载的所有内核模块的双向链表头;
PsActiveProcessHead 在之前的文章中有简单介绍过,是维护系统中所有进程EPROCESS的双向链表头。

0: kd> u KeCapturePersistentThreadState l 50
nt!KeCapturePersistentThreadState:
83edaad3 8bff            mov     edi,edi
83edaad5 55              push    ebp
......
83edab3d 894310          mov     dword ptr [ebx+10h],eax
83edab40 a100b7f783      mov     eax,dword ptr [nt!MmPfnDatabase (83f7b700)]
83edab45 894314          mov     dword ptr [ebx+14h],eax
83edab48 c7431850b8f583  mov     dword ptr [ebx+18h],offset nt!PsLoadedModuleList (83f5b850)
83edab4f c7431c183ff583  mov     dword ptr [ebx+1Ch],offset nt!PsActiveProcessHead (83f53f18)
83edab56 c743204c010000  mov     dword ptr [ebx+20h],14Ch
83edab5d a16cb9f783      mov     eax,dword ptr [nt!KeNumberProcessors (83f7b96c)]
83edab62 894324          mov     dword ptr [ebx+24h],eax
......

具体获得方法:
首先获得KeCapturePersistentThreadState的地址FuncPtr,然后遍历其内存

for (PBYTE i = FuncPtr; i < (PBYTE)FuncPtr + 0xFF; i++)
{
	// 89 43 10             mov [ebx+10h],eax
	// a1 xxxxxxxx          mov eax, ds:MmPfnDataBase
	if (MmIsAddressValid(PAGE_ALIGN(i)) &&
		*(i + 0) == 0x89 &&
		*(i + 1) == 0x43 &&
		*(i + 2) == 0x10 &&
		*(i + 3) == 0xa1)
	{
		RtlCopyMemory(&MmPfnDatabaseAddr, i + 4, sizeof(ULONG32));
		break;
	}
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值