考虑这样一个问题,我们现在可以通过在Windbg里找到线性地址所在的物理页,通过修改物理页的属性,就可以实现一些原本受限的功能。例如将常量区对应的物理页R/W属性修改为1,便可以修改位于常量区的元素。
但是,以上操作都是基于Windbg在双击调试的环境中实现的,那么一旦脱离了调试器,该如何通过代码来实现对物理页属性的修改呢?这就需要借助于页目录表基址和页表基址了。
页目录表基址
结论:C0300000就是页目录表基址,接下来我们来验证这个结论。
C0300000拆分
C0300000: 1100 0000 0011 0000 0000 0000 0000 0000
每部分位数 | 二进制 | 十六进制 |
---|---|---|
10 | 11 0000 0000 | 300 |
10 | 11 0000 0000 | 300 |
12 | 0000 0000 0000 | 0 |
Cr3
这里以记事本(notepad.exe)为例,来验证一下,C0300000就是页目录表基址,首先查看记事本对应的Cr3指向的物理地址。
我们知道,Cr3指向的是PDT的首地址,这里可以看到4个PDE有值。
查看C0300000物理页
接下来的步骤就是比较熟悉的,根据拆分后的线性地址,寻找物理页的过程了。但是这一次,要慢点看。
这一步很容易理解,Cr3.base + 300*4,通过Cr3和线性地址的前10位,我们找到了PDE的值:7ea49063
什么?又重复了一遍?实际上不是,由于PDE的值为7ea49063,后12位是属性位,因此,7ea49000是我们要找的PTT的首地址,然后通过PTT.base + 300*4,就得到了PTE的值:7ea49063。
有了PTE的值,加上最后12位的偏移(此处为0),就可以找到物理页。
得到结果后,是不是很惊讶?C0300000这个线性地址对应的物理页上的物理地址,竟然和Cr3指向的物理地址完全一样!也就是说,以后不需要Cr3,只需在当前程序内,通过C0300000这个线性地址就可以得到当前程序PDT的首地址了
如何利用
是啊,C0300000这个地址有啥用呢?当然有用,而且非常有用。回到文章开头的问题,我们该如何在不使用Windbg的情况下,修改物理页的属性呢?
我们知道,想要修改物理页的属性,需要先修改物理页对应的PDE和PTE,那要如何找到PDE和PTE呢,由于在编写代码时,用到的都是线性地址,而C0300000这个线性地址刚好就可以找到PDT的首地址,这样我们拆分想要修改的物理页属性的线性地址,将前10位加上C0300000即可找到对应的PDE。
既然PDE找到了,那不就有了PTT的首地址,这样PTE不也就可以找到了吗?并不是这样,尽管找到了PDE,但是由于PDE里面存着的是物理地址,如果直接访问PDE里面存的那个地址,在代码中会转变为一个线性地址,因此并不能通过PDE获取PTT的首地址,也就不能获取到PTE了,想要找到PTE,还得需要用到另外一个基地址,就是页表基址。
页表基址
还是直接上结论,页表基址:C0000000
接下来我们来验证。
C0000000拆分
C0000000: 1100 0000 0000 0000 0000 0000 0000 0000
每部分位数 | 二进制 | 十六进制 |
---|---|---|
10 | 11 0000 0000 | 300 |
10 | 00 0000 0000 | 0 |
12 | 0000 0000 0000 | 0 |
Cr3
这里还是以记事本(notepad.exe)为例:
我们查看Cr3指向的物理地址,当前共有4个PDE的有值的,而PDE的值,就是PTT的首地址,以第一个PDE(36c24067)为例,其中PTT的首地址为36c24000。
查看C0000000物理页
步骤和之前一样,就直接看结果好了。
发现,C0000000这个线性地址所对应的物理页,刚好是36c24000,也就是第一个PDE对应的PTT的首地址。由此可以进一步推断,C0001000则是第二个PDE对应的PTT的首地址,以此类推。
再看10-10-12分页
现在再来看10-10-12分页时,看法就会有所不一样了。
- 实际上页表(PTT)被映射到了从0xC0000000到0xC03FFFFF的4M地址空间
- 在这1024个表中有一张特殊的表:页目录表(PDT)
- 页目录表(PDT)被映射到了0xC030000开始处的4KB大小的地址空间
总结
有了0xC0300000和0xC0000000能做什么?
掌握了这两个地址,就掌握了一个进程所有的物理内存读写权限。
公式总结:
- 什么是PDI和PTI? 将32位线性地址拆分位10(PDI)-10(PTI)-12
- 访问页目录表(PDT)的公式:0xC0300000 + PDI x 4
- 访问页表(PTT)的公式:0xC0000000 + PDI x 1000 + PTI x 4(不用*号因为会被转义)
其它关于页的细节
- 高2G有一些大页,即4MB页
- 两个进程低2G几乎不同,高2G几乎相同
- 一个进程低2G的内存空间,前64K与后64K是没有使用的(线性地址0 - 00010000 与 7FFF0000 - 7FFFFFFFF)
谁填充了这些表呢
进程本身可以通过0xC0300000和0xC0000000访问修改任意物理页,那么是谁为我们填充0xC0300000和0xC0000000的PDE与PTE呢?
进程的创建过程:当创建B进程时,先在A进程中将B进程所有信息全部构建好,然后切换Cr3即可。也就是说,最开始的这张表是由A进程填充的。