Windows XP x86分页机制分析

前言

32位的CPU支持两种分页方式,分别是10 10 12分页和2 9 9 12分页,其中2 9 9 12分页模式称为PAE分页。10 10 12分页模式下,PDE和PTE的每一项只占用4字节,此模式下CPU不支持数据执行保护。而PAE分页中PAPTE、PDE、PTE中的每一项都占用8字节,这8字节能够拿出更多的位作为页保护属性和物理地址访问。所以PAE分页模式相对10 10 12分页模式增加了数据执行保护和更多的物理地址访问能力。也正因此PAE分页模式更被广泛的使用。

PAE分页模式

PAE分页模式将32位的线性地址按2 9 9 12位方式进行拆分,作为各级页目录项的索引,PAE分页模式使用3级页表,分别是PAPTE、PDE、PTE。

总共有2^2=4项PAPTE,每一项占8字节,总共占32字节

每项PAPTE有2^9=512项PDE,每一项占8字节,每一个PDE页表总共占4KB字节

PTE有2^9=512项每一项占8字节,总共占4KB字节

当目标进程获得被执行权限时,cpu的cr3寄存器保存了该进程的PAPTE表的基地址的物理地址。

实验验证

现在通过一例将线性地址转换为物理地址的实验,来完成对PAE分页的认识。首先任务是在目标进程中找到一个线性地址(注意:线性地址=虚拟地址+段内偏移,由于段内偏移基本位为0,所以通常情况下虚拟地址=线性地址)然后对其进行分析得到对应的物理地址,通过两处地址中的内容比对来验证PAE分页机制。

这里被调试的XP系统是一台采用2 9 9 12分页的虚拟机,而物理机则是安装有windbg调试器的Windows10操作系统。

对DebugView.exe进行内存搜索,发现显示字符串“8322200”的线性地址为9b400f。

使用CheatEngine.exe工具将该地址内容修改为helloWord.。

接下来在windbg中使用!process 0 0查看所有进程的信息,这里主要查看DebugView的进程信息:

得到DebugView.exe的进程信息后,使用.process /i 82061da0切换到该进程的空间中。

使用db 9b400f命令以byte单位查看DebugView.exe进程的9b400f虚拟内存的数据:

现在开始通过cr3寄存器查看9b400f地址的物理地址,也就是说使用物理地址方式查看该部分的数据。

首先,通过之前的!process 0 0命令我们已经查看到DebugView.exe的分页使用的基地址的物理地址是2b40300,另外对9b400f按二进制拆分成2 9 9 12方式:

00                                 0

000000 100                  4

11011 0100                  1b4

0000 0000 1111            f

由于开始的偏移是0,那么开始使用命令!dq 2b40300+0*8获得PDPTE 值:

显示的是1d825801,将后三位替换成0后是1d825000,偏移是4,则使用命令!dq 1d825000+4*8获得了PDE的值:

(注意:1d825801中低12位是页属性位,前面的30位是页针编号。页针编号是物理内存从低到高每4KB一个单位指定的编号,每一级页表都是如此)

显示的值是1d6b2867,将后三位替换成0后是1d6b2000,偏移是1b4,则使用命令!dq 1d6b2000+1b4*8获得了PTE的值:

显示的值是1daa3825,将后三位替换成0后是1daa3000,而最后的页内偏移是f,所以使用命令!db 1daa3000+f*1获得保存的数据:

所以在当前运行下,DebugView.exe进程内线性地址9b400f对应的物理地址是1daa300f。

对于以上步骤,可以通过在windbg使用命令!vtop 进程的cr3值 线性地址 查看其将线性地址转译成物理地址的过程,这里也即执行命令!vtop 02b40300 9b400查看线性地址到物理地址的转译过程:

上图显示线性地址转换位物理地址的转译过程。

通过以上过程了解了线性地址转译成物理地址的过程。但是,在保护模式下,程序无法直接访问物理内存,只能通过虚拟地址进行访问数据。那么如何才能通过虚拟地址访问内存方式获得各级页表的数据呢?

在Windbg中使用命令!pte 虚拟地址 可以获得该虚拟地址的各级页目录项所在的虚拟地址:

上图表示虚拟地址009b400f的PDE项即PDT在虚拟地址C0600020中保存,PTE项即PTT在虚拟地址C0004DA0中保存。

从对0地址的查看可以知道,实际上微软故意的将32位操作系统的PAE分页模式的PDE基址放在c0600000处,而将PTE放在C0000000开始的位置。

微软将PTE按顺序放在起始线性地址为C0000000的内存区域,那么所有的pte占用的内存空间为4*512*512*8=0x800000所以[c0000000,c0800000)保存了所有的PTE,很容易发现这里面包含c0600000(前面说到的其中一个PDE),那么说明每一个PDE也是其中一个PTE。

看看PDT需要多少个字节呢?需要512*4*8=0x4000,所以[C0600000,c0604000)保存了四个PDT表,但是他们仍然是PTE。

虽然[c0000000,c0800000)是属于内核区域的,但是它并不是所有进程中数据都一样的,因为它保存了这个进程的页表。

使用虚拟地址访问页表

通过以上分析,了解了进程的页表就在进程的空间当中。这样一来,就可以通过访问进程空间的[c0000000,c0800000)区域来修改页表内容。

为了能够修改,需要直到一个由虚拟地址A快速计算A的PDE和PTE所在虚拟地址呢?

VA[PDE]=C060 0000+A[31:30]*0x1000+A[29:21]*8

VA[PTE]=C000 0000+ A[31:30]*0x200000+ A[29:21]*0x1000+A[20:12]*8

例如:

若A=0x12345678= 00010010 00110100 01010110 01111000

那么A[31:30]=0,A[29:21]=0x91,A[20:12]=0x145

PDE= 0xC0600000+0*0x1000+0x91*8= 0xC0600488

PTE= 0xC0000000+0*0x200000+0x91*0x1000+0x145*0x8= C0091A28

若A=0x87504832=10000111 01010000 01001000 00110010

那么A[31:30]=2,A[29:21]=0x3A,A[20:12]=0x104

PDE= 0xC0600000+2*0x1000+0x3a*8= 0xC06021D0

PTE= 0xC0000000+2*0x200000+0x3a*0x1000+0x104*0x8= 0xc043a820

若A=0xf0483215= 11110000 01001000 00110010 00010101

那么A[31:30]=3,A[29:21]=0x182,A[20:12]=0x83

PDE= 0xC0600000+3*0x1000+0x182*8= 0xC0603C10

PTE= 0xC0000000+3*0x200000+0x182*0x1000+0x83*0x8= 0xC0782418

注意:计算过程可以在python中快速计算

猜你喜欢

转载自www.cnblogs.com/wf751620780/p/10447453.html
今日推荐