x64内核内存空间结构
0x00 前言
本文主要是讨论Windows 7 x64下的内核虚拟地址空间的结构,可以利用WiinDBG调试的扩展命令"!CMKD.kvas"来显示x64下的内核虚拟地址空间的整体布局。了解内核的地址布局在某些情况下是很有的,比如说在研究New Blue Pill的源码和虚拟化的时候。
0x01 基本结构
X64的CPU的地址为64位,但实际上只支持48位的虚拟地址空间供软件使用。虚拟地址的高16位在用户模式下总是被设置为0000,而在内核模式下全置为FFFF。
因此用户模式的地址空间范围为0x00000000~00000000——0x0000FFFF~ffffffff,内核模式的地址空间范围为0xFFFF0000~00000000——0xFFFFffff~ffffffff,所以对操作系统可见的内核虚拟地址空间的大小为256TB。Windows操作系统将整个内核地址空间划分为若干个有特定用途的大小固定的虚拟地址空间。下表是关于Windows对于虚拟地址空间具体的划分:
|
起始地址 |
结束地址 |
内存大小 |
用途 |
|
FFFF0800`00000000 |
FFFFF67F`FFFFFFFF |
238TB |
未使用 |
|
FFFFF680`00000000 |
FFFFF6FF`FFFFFFFF |
512GB |
PTE内存空间 |
|
FFFFF700`00000000 |
FFFFF77F`FFFFFFFF |
512GB |
Hyper内存空间 |
|
FFFFF780`00000000 |
FFFFF780`00000FFF |
4KB |
系统共享空间 |
|
FFFFF780`00001000 |
FFFFF7FF`FFFFFFFF |
512GB-4K |
系统cache工作集 |
|
FFFFF800`00000000 |
FFFFF87F`FFFFFFFF |
512GB |
初始化映射区 |
|
FFFFF880`00000000 |
FFFFF89F`FFFFFFFF |
128GB |
系统PTE区域 |
|
FFFFF8a0`00000000 |
FFFFF8bF`FFFFFFFF |
128GB |
分页池区域 |
|
FFFFF900`00000000 |
FFFFF97F`FFFFFFFF |
512GB |
会话空间 |
|
FFFFF980`00000000 |
FFFFFa70`FFFFFFFF |
1TB |
内核动态虚拟空间 |
|
FFFFFa80`00000000 |
*nt!MmNonPagedPoolStart-1 |
6TB Max |
PFN 数据 |
|
*nt!MmNonPagedPoolStart |
*nt!MmNonPagedPoolEnd |
512GB Max |
不分页内存池 |
|
FFFFFFFF`FFc00000 |
FFFFFFFF`FFFFFFFF |
4MB |
HAL和加载器映射区 |
Windows操作系统用了一些特定的数据结构,比如说Push Locks,Ex Fast Referenced Pointers和Interlocked Slists,对这些数据结构的操作都需要CPU对同一个虚拟地址的数字执行两遍原子操作。因此虽然64位处理器的虚拟地址是64位,却必须要有128位长的CMPXCHG指令。但是在早期的64位处理器中是没有这样的指令的,在使用上述的数据结构的时候就会引发故障。64位CPU已经将虚拟地址有效位限制为48位,而Windows操作系统则进一步将虚拟地址有效位限制为44位,实际上可以用来存储上述数据结构的虚拟地址空间大小就是2^44,即64位虚拟地址空间的高8TB的空间,也就是0xFFFFF80000000000 - 0xFFFFFFFFFFFFFFFF。例如之前的”未使用空间”,“PTE内存空间”,“ Hyper内存空间”和“系统cache工作集”都超出了44位虚拟地址的限制,都无法存储这些特定的数据结构。这些限制也会影响到用户空间,将用户空间可用虚拟内存大小限制到了8TB,即0x00000000`00000000 - 0x000007FF`FFFFFFFF,内核空间可用虚拟虚拟内存大小也为8TB,即0xFFFFF000`00000000 - 0xFFFFFFFF`FFFFFFFF。需要说明的一点就是,由Windows操作系统使用的不在FFFF0800`00000000 - FFFFF7FF`FFFFFFFF范围内的虚拟内存,也并不是都会分配和保存上述的特定数据结构。
64位处理器物理页大小是4KB,CPU使用PTEs(Page Table Entry页表项)来完成从虚拟地址到物理地址的映射,因此每个PTE映射4K大小的物理页。64位处理器下的PTE占64位,也就是8个字节为了兼容更大的物理地址和PFNs(Page Frame Number,页帧号)。因此单个页表的物理页可以容纳512个PTE,所有的PTE可以映射2MB(512*4KB)的虚拟地址。同样的,因为PDEs(Page Directory Entries,页目录项)指向页表的物理页,所以单个的PDE可以映射2MB的虚拟地址空间。
0x02 内核虚拟空间组成
下面说明内核地址空间的具体组成部分,及其作用。
未使用的 (Unused System Space)
由nt!MmSystemRangeStart开始,这部分在Windows 7 X64下并未使用
PTE空间 (PTE Space)
这部分包含了x64下用户空间和内核空间的虚拟地址映射的4级页表。X64下不同页表页的映射范围如下:
PTE Pages FFFFF680`00000000
PDE Pages FFFFF6FB`40000000
PPE Pages FFFFF6FB`7DA00000
PXE Pages FFFFF6FB`7DBED000
Hyper空间 (HyperSpace)
映射进程的工作集。对每一个进程的EPROCESS.Vm.VmWorkingSetList 中包含的地址
0xFFFFF700`01080000就会映射到这片空间。这片空间包括MMWSL(Memory Manager Working Set List)结构和MMWSLE(Memory Manager Working Set List Entry)的数组结构,包括进程工作集的每个物理页。
需要注意的是虽然函数MiMapPageInHyperSpaceWorker()支持映射物理页到Hyper空间的虚拟地址,但实际上是将物理页映射到了PTE空间,而不是真正的Hyper空间。
共享系统页 (Shared System Page)
这4K大小的页是由用户空间和内核空间共享的,主要是用来在用户层和内核层之前快速的传递信息,共享数据的数据结构就是nt!_KUSER_SHARED_DATA。
系统cache工作集(System Cache Working Set)
包含系统cache的虚拟地址的工作集(Working Set)和工作集链表项(Working Set List Entries)。
内核变量nt!MmSystemCacheWs指向系统cache工作集的数据结构(即nt!_MMSUPPORT)。想要显示系统cache的工作集链表项可以使用WinDBG命令
"!wsle 1 @@(((nt!_MMSUPPORT *) @@(nt!MmSystemCacheWs))->VmWorkingSetList)"。而这些项会被用来修剪(trim)系统cache的虚拟内存的物理页。
初始化加载映射区 (Initial Loader Mappings)
Ntoskrnl,HAL和内核调试DLL(KDCOM,KD1394,KDUSB)都会被加载到这片区域。除此之外,这片空间包含idle线程的线程栈,DPC的栈,KPCR和idle线程的数据结构。
分页池区域 (Paged Pool Area)
分页池的结束地址保存在变量nt!MmPagedPoolEnd中。而分页池的大小保存在变量nt!MmSizeOfPagedPoolInBytes。当调用MiVaPagePool()时,MiObtainSystemVa()函数就会从这片区域分配内存,分页池的内存分配方式由变量nt!MiPagedPoolVaBitMap按位(bit)决定。
PFN数据库(PFN Database)
对于系统的每个物理页在PFN中都有对应的项(nt!MmHighestPossiblePhysicalPage+1)。可以在WinDBG中输入命令'? poi(nt!MmNonPagedPoolStart) - poi(nt!MmPfnDatabase)'来获得”PFN Database”的大小。也可以使用命令
'?(poi(nt!MmNonPagedPoolStart) - poi(nt!MmPfnDatabase))/ @@(sizeof(nt!_MMPFN))'来获得PFN中项的总数。而这片区域的起始地址保存在nt!MmPfnDatabase中。
不分页内存池(Non-Paged Pool)
不分页内存池的区域直接跟在PFN Database后面。不分页内存池的起始地址保存在nt!MmNonPagedPoolStart中。当调用MiVaNonPagedPool()时,MiObtainSystemVa()就会在这片区域分配内存。内存的分配方式由变量nt!MiNonPagePoolVaBitmap按位决定。
硬件抽象层和加载映射区(HAL and Loader Mappings)
内核全局变量nt!MiLowHalVa包含这片区域的起始地址,即0xFFFFFFFFFFC00000。结束地址和X64内核虚拟地址空间结束地址一致,为0xFFFFFFFFFFFFFFFF。这片区域仅用于系统启动时,也就是在MmInitSystem()函数中,这片区域中的内存在启动初始化完毕以后就不可以再被使用了。
在系统初始化函数MmInitSystem()的结尾处调用函数MiAddHalIoMappings()来扫描这片虚拟地址空间来判断是否有I/O映射到了这片空间,如果有,将会调用函数MiInsertIoSpaceMap()加入到由系统维护的I/O队列中。而对于每一个I/O映射区域,MiInsertIoSpaceMap()都会用池标签”Io space mapping trackers“创建一个tracker项,然后将其加入到头为nt!MmIoHeader的双向链表中,其中的每一项都表示的虚拟内存块都已经映射了物理地址,而tracker项中的一些字段也包含关于物理内存和虚拟地址映射的信息。

struct _IO_SPACE_MAPPING_TRACKER {
LIST_ENTRY Link;
PHYSICAL_ADDRESS Pfn;
ULONGLONG Pages;
PVOID Va;
. . .
}

会话空间 (Session Space)
关于会话(session)的数据结构,会话池和会话映像都会加载到这片区域。
会话映像包括驱动映像比如Win32k.sys(Windows Manager),CDD.dll(Canonical Display Driver),TSDDD.dll(Frame Buffer Display Driver),DXG.sys(DirectX Graphics Driver)等等。
对于任意一个进程,其EPROCESS->Session指向的MM_SESSION_SPACE就是其所属的会话结构,而会话池的范围由MM_SESSION_SPACE->PagesPoolStart 和MM_SESSION_SPACE->PagesPoolEnd指定。
系统PTE (Sys PTEs)
这片区域包括映射的View,MDL,adapter内存,驱动程序的映像和内核栈。当使用MiVaSystemPtes()时,就会调用函数MiObtainSystemVa()在这片区域分配内存。
内核动态虚拟空间(Dynamic Kernel VA Space)
这片区域由系统cache的view,特定的分页内存池和特定不分页内存池组成。nt!MiSystemAvailableVa保存动态内核虚拟空间可用的2MB的区域数量。
调用MiObtainSystemVa()的参数是MiVaSystemCache,MiVaSpecialPoolPaged或MiVaSpecialPoolNonpaged时,将会从这片区域分配内存。
0x03 内核虚拟内存的分配
内存管理器使用函数MiObtainSystemVa()来动态的从不同的内核虚拟地址空间分配不同的2MB的内存。当调用MiObtainSystemVa()函数时,调用者需要指定分配的PDE项的总数和系统虚拟内存的分配类型(nt!_MI_SYSTEM_VA_TYPE),而对于此函数有效的类型为MiVaPagedPool,MiVaNonPagedPool,MiVaSystemPtes,MiVaSystemCache,MiVaSpecialPoolPaged,MiVaSpecialPoolNonPaged 。
MiObtainSystemVa()可以满足不同的内核虚拟空间的分配请求。例如,MiVaPagedPool要求分配分页池区域(Paged Pool region),MiVaNonPagedPool要求分配不分页池区域(non-paged pool region),MiVaSystemPtes则分配系统PTE区域(System PTE region),而其他类型的分配请求则是直接分配系统动态虚拟内存(Dynamic System VA region)。内存的释放则是由函数MiReturnSystemVa()完成。
一个动态内存分配的例子就是MiExpandSystemCache()调用MiObtainSystemVa()来获取系统cache的view。MiExpandSystemCache()调用MiObtainSystemVa(MiVaSystemCache)来申请存放Cache Manager VACB(Virtual Address Control Block)数据结构的虚拟内存
0x04 系统PTE管理 (SysPTE Management)
由MiObtainSystemVa()从SysPTE区域分配的内存会由MiReservePtes()按照分配要求(nt!MiKernelStackPteInfo和nt!MiSystemPteInfo)进一步的划分为两类,其目的就是为了防止虚拟内存的碎片化。因为内核栈内存(尤其是system和服务进程的线程)生命期是很长的,而其他的类型分配,例如MDL的生命周期相对短很多。
两种结构类型nt!MiKernelStackPteInfo和nt!MiSystempteInfo都是属于nt!_MI_SYSTEM_PTE_TYPE,而这些结构体都是由函数MiInitializeSystemPtes()产生,他们的每一位包含的信息可以影响SysPTE区域的128GB的空间。而函数MiReservePtes()在被调用时需要这些结构体其中一个作为参数来申请SysPTE区域以外的内存,申请的内存由MiReleasePtes()进行释放。
当虚拟内存地址被nt!MiKernelStackPteInfo和nt!MiSystemPteInfo覆盖时,则已经耗尽了通过调用MiExpandPtes()(实际调用MiObtainSystemVa(MiVaSystemPtes))扩展的内存区域。
函数MmAllocateMappingAddress()和MmCreateKernelStack()都是申请nt!MiKernelStackPteInfo类型的内存,而函数MiVaildateLamgePfn()和MiCreateImageFileMap(),MiRelocateImagePfn(),MiRelocateImageAgain()申请nt!MiSystemPteInfo类型的内存。
jpg改rar
x64内核内存空间结构的更多相关文章
- Linux内核内存管理
<Linux内核设计与实现>读书笔记(十二)- 内存管理 内核的内存使用不像用户空间那样随意,内核的内存出现错误时也只有靠自己来解决(用户空间的内存错误可以抛给内核来解决). 所有内核 ...
- x64内核HOOK技术之拦截进程.拦截线程.拦截模块
x64内核HOOK技术之拦截进程.拦截线程.拦截模块 一丶为什么讲解HOOK技术. 在32系统下, 例如我们要HOOK SSDT表,那么直接讲CR0的内存保护属性去掉. 直接讲表的地址修改即可. 但是 ...
- Windows内核 内存管理基本概念
内存管理概念: 1)物理内存 PC上有三条总线:数据总线.地址总线和控制总线.32位CPU的寻址能力是4GB个字节,用户最多可以使用4GB的真实物理内存.PC中很多设备都提供了自己的设备内存,例如显卡 ...
- (笔记)Linux内核学习(九)之内核内存管理方式
一 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU)把虚拟地址转换为物理 地址,通常以页为单位进行处理.MMU以页大小为单位来管理系统中的也表. 32位系统:页大小4KB 64位系统:页 ...
- Linux内核学习笔记——内核内存管理方式
一 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU)把虚拟地址转换为物理 地址,通常以页为单位进行处理.MMU以页大小为单位来管理系统中的也表. 32位系统:页大小4KB 64位系统:页 ...
- clients(PV操作共享内核内存进行输入输出分屏) - server(进程间通信)模型实现
1.拓扑结构 2.PV操作共享内核内存进行输入输出分屏 (1) int semop(int semid,struct sembuf *sops,size_t nsops): 功能描述 操作一个或一组信 ...
- linux内核--内核内存管理
如题目所示,为什么要称作“内核内存管理”,因为内核所需要的内存和用户态所需要的内存,这两者在管理上是不一样的. 这篇文章描述内核的内存管理,用户态的内存管理在以后的文章中讲述. 首先简单的说明一下下面 ...
- linux内核--内存管理(二)
一.进程与内存 所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内 ...
- Linux内核内存管理架构
内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射.页面分配.页面回收.页面交换.冷热页面.紧急页面.页面碎片管理.页面缓存.页面统计等,而且对性能也有很高的要 ...
随机推荐
- PHP文件大小格式化函数合集
比如碰到一个很大的文件有49957289167B,大家一看这么一长串的数字后面单位是字节B,还是不知道这个文件的大小是一个什么概念,我们把它转换成GB为单位,就是46.53GB.用下面这些函数就可以完 ...
- AE开发实现GP工具IDW
IDW——空间插值 IDW(Inverse Distance Weighted)是一种常用而简便的空间插值方法,它以插值点与样本点间的距离为权重进行加权平均,离插值点越近的样本点赋予的权重越大. 设平 ...
- Fibonacci 数列算法分析
/************************************************* * Fibonacci 数列算法分析 ****************************** ...
- C++ GC
http://www.codeproject.com/Articles/912/A-garbage-collection-framework-for-C http://www.codeproject. ...
- JDK Collection 源码分析(1)—— Collection
JDK Collection JDK Collection作为一个最顶层的接口(root interface),JDK并不提供该接口的直接实现,而是通过更加具体的子接口(sub interface ...
- Redis总结(四)Redis 的持久化
前面已经总结了Redis 的安装和使用今天讲下Redis 的持久化. redis跟memcached类似,都是内存数据库,不过redis支持数据持久化,也就是说redis可以将内存中的数据同步到磁盘来 ...
- 三、Shell变量类型和运算符
一.Shell变量的应用 1.Shell变量的种类 ·用户自定义变量:由用户自己定义.修改和使用 ·预定义变量:Bash预定义的特殊变量,不能直接修改 ·位置变量:通过命令行给 ...
- Sql Server 分区演练 【转】
Sql Server 分区演练 [转] 代码加注释,希望对初学者有用. USE [master]GOif exists (select * from sys.databases where name ...
- Spring系列之AOP实现的两种方式
AOP常用的实现方式有两种,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ). 首先复习下AOP中一些比较重要的概念: Joinpoint(连接点):程序执行 ...
- [NHibernate]基本配置与测试
目录 写在前面 nhibernate文档 搭建项目 映射文件 持久化类 辅助类 数据库设计与连接配置 测试 总结 写在前面 一年前刚来这家公司,发现项目中使用的ORM是Nhibernate,这个之前确 ...