前言
本篇开始,进一步学习与物理页相关的内存管理知识。关于页的知识,在介绍段、页时已学习过一部分基础,可以参考以下链接:
进程空间的地址划分
进程空间地址的划分这个我们已经比较熟悉了,以x86为例,就是对4GB内存空间的划分,可以参考下面两张图:
![[该图取自My classmates博客]](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/divide_1.png)
![](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/divide_2.png)
说明:
- 线性地址有4GB,但未必都能访问。这个之前也学习过,由于一些地址没有挂物理页,因此无法访问。
- 有些地址可以访问,有些不能访问,有些具有特定权限才能访问,这些性质都需要被记录,那么Windows系统是如何分配,管理这些内存的呢?这就本篇要学习的主题,线性地址的管理。
内核空间的地址管理
内核空间,也就是我们常说的高2G,内核空间的地址是通过链表串起来的,遍历链表便可以找到各片地址的属性。之所以使用链表,主要是依据不同进程的高2G地址往往是相同的,因此高2G的地址变化较少,使用链表足矣。相比之下,用户空间的地址管理就较为复杂,这也是本篇主要讨论的内容。
用户空间的地址管理
搜索二叉树
与内核空间的地址使用链表串起来所不同的是,用户空间的地址通过一颗搜索二叉树来记录。它里面的每一个节点都记录了一块被占用的线性地址空间。
在Windows XP(32位)系统中任意打开一个进程,本篇以打开Dbgview为例。
![](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/tree_1.png)
找到Dbgview对应的EPROCESS结构体,定位到+0x11c处,有一个VadRoot成员,这个成员就是记录当前进程线性地址空间的搜索二叉树。其对应的地址,则是二叉树根节点的地址。
_MMVAD
_MMVAD是搜索二叉树节点的数据类型,根据MMVAD中的成员(ReactOS与Xp有所不同),可以对这个节点所对应的线性地址区域有个整体上的认识,以根节点为例:
![](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/MMVAD_1.png)
这里介绍部分本篇中涉及到的成员:
- StartingVpn:当前节点对应的内存的线性地址起始位置(以页为单位),本例中为0xab0000。
- EndingVpn:当前节点对应的内存的线程地址结束位置(以页为单位),本例中为0xab0000。说明当前节点对应的内存大小为1个物理页,从线性地址0xab0000开始到线性地址0xab0fff结束。
- Parent:父节点地址,本例中根节点没有父节点,所以为空。
- LeftChild:左子树地址。
- RightChild:右子树地址。
- u:用于标识内存属性。
- ControlArea:控制区域。
在知道左子树或右子树地址后,就可以通过
1 | kd> dt _MMVWAD 0x????????(左子树或右子树地址) |
一层一层找到所有的节点,不过这样就略显麻烦了,因此Windbg提供了一个指令
1 | kd> !vad 0x????????(进程根节点地址) |
通常运行该指令,就可以列出所指进程内线性地址的记录情况。
![](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/vad.png)
_CONTROL_AREA
根据列出的二叉树的内容,除了Start、End这种前面介绍的用于描述线性地址区间的属性,还有Level记录了当前节点位于二叉树的层数(depth),还有一些属性会逐个进行介绍,这一部分,介绍Private/Mapped这一列。
在介绍MMVAD时,它内部有一个ControlArea成员,这个成员也是一个结构体,以根节点为例:
![](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/private.png)
这里需要关注的是FilePointer字段,当这个值为空的时候,这块内存是private类型,也就是进程自己VirutalAlloc申请出来的内存。
![](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/mapped.png)
如果FilePointer这个字段不为空,则这块内存是mapped类型,也就是说它映射了其它类型(dll, exe, nls等)的文件到内存中。此时FilePointer会指向一个_FILE_OBJECT文件对象结构,这个文件对象结构可以看到被映射文件的相关描述性信息。
_MMVAD_FLAGS
_MMVAD中有一个成员u,其实指的是一个union共同体,这个共同体结构如下:
1 | union { |
尽管有两个成员,通常来说,只使用VadFlags这个成员。其结构如下:
![](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/vadFlags.png)
这里主要介绍几个比较重要的字段:
CommitCharge:最大可提供物理页的数目。
ImageMap:若值为1,则说明是映射(Mapped)了镜像文件(通常是.exe),若为0则不是。
Protection:表示当前_MMVAD节点描述的内存块的属性,取值如下:
Code1
2
3//1:READONLY 2:EXECUTE 3:EXECUTE _READ
//4:READWRITE 5:WRITECOPY 6:EXECUTE_READWRITE
//7:EXECUTE_WRITECOPY
总结
本篇,主要对用户空间的地址管理及其相关结构的进行简要介绍,部分结构的关系可以参考下图:
![](/2020/08/30/%E7%BA%BF%E6%80%A7%E5%9C%B0%E5%9D%80%E7%9A%84%E7%AE%A1%E7%90%86/relationship.png)
部分没有介绍到的,会在之后的篇章再作讨论。
参考资料
参考书籍:
- 《Windows内核原理与实现》p243 —— 潘爱民
参考教程:
- 海哥逆向中级预习班课程
参考链接:
- https://blog.csdn.net/weixin_42052102/article/details/83722047 (My classmates-线性地址的管理学习笔记)