-
计算机的运行内存一般是8G、16G、32G不等,该内存称之为运行内存,与我们平常认识的磁盘这种硬物理内存不同,该片内存是计算机系统提供给各个进程进行运行的 “容器” ,各个进程都是在该片进程中运行的。
-
可内存的大小一共就这么多(8G、16G),根本无法运行大量的进程,那怎么才能让多个进程在内存中互不干扰的独立运行呢,此时就引申出了虚拟内存这一概念,通过物理内存的映射,用物理内存的每一小段在虚拟内存映射出一大片的空间供多个进程运行,这就是为什么内存的大小非常少却能运行多个进程且进程间不相互影响的原因。
-
虚拟内存这一概念一出来,就会有人问,那相同的虚拟地址对应的是不是相同的物理内存呢,答案肯定是不。即使是虚拟内存的地址相同,也是在物理内存的不同的 “小段” 映射出来的,所以即使两个进程某个变量的虚拟地址相同,但是真正存在在物理内存的地址都不一样。
-
那我们是怎么通过某个进程的虚拟地址去找到其真正对应的物理地址呢,在Win32下有两种相应的机制,一种是启用PAE的寻址方式,一种是不启用PEA的寻址方式。
一、启用PAE
-
每一个虚拟地址空间实际上都对应着一份实际的物理地址,那么从虚拟地址寻找物理地址的方法才是本篇文章种最重要的。
-
比如说,我们写一个VS的程序,让字符串 v1 = “HelloWorld” ,此时的v1存储的就是该字符串开头的虚拟地址。此时我们可以将该地址打印出来查看(注意,此时打印出来后使用gettchar阻断程序,如果程序结束该栈区也会自动清理内存,那么我们就找不到所谓的HelloWorld了)。
-
这里我们举一个例子,在虚拟地址空间中,HelloWorld的地址为
(1)如何查看是否启用PAE
-
因为该机制需要在Win732位的虚拟机下进行测试,查看是否启用PAE,这个时候需要打开命令提示符,输入以下指令:
bcdedit /enum
-
这个时候我们通过命令提示行显示的内容去查看以下部分:
-
上图红色的标记部分的 nx 则代表着整个系统的PAE是否处于开启状态,若是OptIn,则是处于开启状态,若是Optout,则是处于非开启状态。
-
其实区分二者是否处于开启状态还有一个方法就是通过驱动的第一加载模块的名称来获得,若是开启的PAE模式,驱动的第一加载模块实际是ntkrnlpa.exe。若是不开启PAE的状态下,驱动的第一加载模块应当是 ntoskrnl.exe,所以我们也可以通过驱动第一模块加载的名称来判断是否是支持PAE机制的或者PAE是否开启。
(2)如何开启PAE
-
一般来说,计算机都是启用PAE的状态,所以如果想要不启用PAE,我们需要使用以下命令来关闭或者开启PAE机制:
//关闭PAE bcdedit /set PAE forcedisable bcdedit /set {current} nx AlwaysOff //开启PAE bcdedit /set PAE forceenable bcdedit /set {current} nx AlwaysOn
(3)PAE的机制以及寻址方式
-
首先,我们写一个小demo项目,该项目里面我们就定义一个字符串变量并将该变量的地址打印出来,字符串为 “Hello World”,地址打印出来后我们可以看到地址:
001DFDA8
-
该地址转换为二进制为:0000 0000 0001 1101 1111 1101 1010 1000
-
之后,我们就需要了解 32 位下的 PAE 的机制,如下图所示:
-
由上图我们可以知道,在PAE 开启的情况下虚拟地址分为四部分(这里按照上述地址作为示范):
- 页目录指针索引(2位):00
- 页目录索引(9位) :00 0000 000
- 页表索引(9位):1 1101 1111
- 页内偏移(12位):1101 1010 1000
-
第一步:先找到页表目录指针表的基地址。首先,我们要知道整个程序的起始地址,该位置的信息实际上是被存在
EPROCESS
这个结构体当中(成员 DirBase),这个结构体可以通过! process 0 0指令扫描出来的,我们找到对应的进程名称就可以知道对应进程的地址起始位置(实际上如果该进程正在被CPU运行,就由CR3寄存器保存这个值),我们先扫描进程: -
可以看到,这里的 DirBase 的值是 be6ec500,其实该地址就是该进程的 页表目录指针表 的基地址。如下:
-
第二步:先找到页目录的地址。通过该基地址加上虚拟地址中的前两位页目录指针索引可以找到索引对应的页表目录地址,方式如下:
页目录指针表地址+页目录指针索引*8
因为索引是0,所以和上图不会有太大变化,如下:
-
第三步:根据页目录地址和页目录索引找到页表地址。此时我们可以看到其对应的页表目录地址为 5fed0801 ,但是页目录指针表中的地址后面的12位为标志位,不计入,故此时的页表目录基地址为5fed0000,这个时候我们再通过虚拟地址中的 页目录索引(9位) 去寻找相应的页目录,计算方式如下:
页目录地址+页目录索引*8
但是因为其索引仍然是0,故最后的结果则是5fed0000,如下:
-
第四步:根据页表地址和页表索引找到页对应的物理地址 ,我们由上图可看到页目录索引对应着的页目录,而页目录当中存的则是页表,还是和之前一样,后面的12位不计入,其计算方法如下:
页表地址+页表索引*8
这里的计算结果就不一样了,通过计算器,我们可以得出该地址为:6AE8 6EF8 ,我们查看内存如下:
-
此时,我们就获得到了页表的数据(目前只记录了一个成员),其第一个成员就是我们要搜寻的物理页,还是原先的规矩后面的12位不计入,其物理页基地址为 4a2cf000 。
-
第五步:对应页的物理地址+页内偏移=物理地址 ,这里的业内偏移我们就不需要去乘相关系数,因为是在页内搜寻,该偏移就是绝对偏移。计算后得:4A2C FDA8 。最后查看内存:
-
如此,我们就成功的找到了我们的 Hello World 所在的物理内存的真正地址!!!
二、不启用PAE
-
一般来说,计算机都是启用PAE的状态,所以如果想要不启用PAE,我们需要使用以下命令来关闭或者开启PAE机制:
//关闭PAE bcdedit /set PAE forcedisable bcdedit /set {current} nx AlwaysOff //开启PAE bcdedit /set PAE forceenable bcdedit /set {current} nx AlwaysOn
-
非PAE下32位虚拟地址的格式:
| 页目录(10位) | 页表(10位) | 偏移(12位) |
-
我们可以看到,这里对于PAE开启状态下的页目录指针索引消失了,但是从而流出了更多位用于别的索引的使用,整个非PAE的寻址过程如下:
-
和开启PAE方式的情况下,这里也是少了一层目录的寻找。
(1)非PAE的寻址过程
-
首先,我们用跟上述情况相同的小demo程序进行测试,我们先关掉PAE,重启并保存一个快照:
-
重启后我们先查看是否是关闭PAE的状态:
-
这里可以拍摄一个快照,等以后需要用到非PAE的环境时可以迅速定位到状态。
-
运行我们的demo,查看结果:
001DFB60
翻译后的2进制:0000 0000 0001 1101 1111 1010 0110 0000
页目录:0000 0000 00
页表:01 1101 1111
页内偏移:1010 0110 0000
-
第一步:遍历整个进程,获得Dirbase ,这里我们直接展示图片:
-
接下来我们可以看到Dirbase所代表的页目录:
-
第二步:根据页目录地址和页目录索引找到页表地址。 这里需要用Dirbase的内容加上高10位的偏移才能得出,但由于高10位是0,所以这里就算加上偏移也相当于没加:
-
这里得到的 0xa0d50867 是某个页表的地址,但是后12位为标志位,故该页表的页表地址为 0xa0d50000 。
-
第三步:根据页表地址和页表索引找到对应的物理页地址。 这里使用我们的 0xa0d50000 和中10位的偏移去找,公式为:
页表地址 + 10位偏移 * 4
根据该公式的寻找结果为:
-
这里的 a1483847 也要去掉后12位才是真正的页基址。为 a1483000
-
第四步:根据页基址和页内偏移寻找真正的物理地址。 这里的公式为:
页基址 + 后12位偏移
根据该公式寻找结果为:
-
至此,我们成功的找到了!!!!!!