概述

在上一篇的总结中,我们有一个小小的愿望:把整个 4GB 线性地址的 U/S 位改成 1。我们要去 WinDbg 中手动一个一个的修改吗?显然不可能,给你一年时间你也未必改的完。

既然如此,我们必然要能通过程序来修改 PDE 和 PTE 了。要知道,无论如何,程序中使用的都只能是线性地址。这可为难了,我们并不知道页目录基址。可别想着把 CR3 中保存的那个页目录基址拿来用,CR3 中保存的那可是物理地址,你拿过来,程序也没法用。所以,我们必须要想到,会不会存在这么个线性地址,它直接就可以访问 PDT。

整个 CPU 中,除了 CR3 保存的是页目录的物理地址,以及页目录和页表中保存的是物理页索引号(也可以当成物理地址),就没有其它地方保存物理地址了。

给某个线性地址挂上 PDT 或 PTT 的物理页

除非,有一个线性地址,它指向了页目录。这很容易做到,在《读写空指针》那一篇你已经学会了。无非就是把那一篇的图10中的 PTT 的第一个 PTE 修改为 PDT 的物理地址而已。就好比图1这样。


24-PDT/PTT基址_linux


图1 让线性地址

​0x00000000​​ 映射到页目录的物理地址


如此一来,访问 PDT 不就轻而易举,只要 ​​0x00000000, 0x00000004, 0x00000008, ... , 0x00000ffc​​ 这样便可以访问到所有的 PDE 了。

那么,如何再去访问 PTT 呢?要知道,PTT 可多达 1024 个。比如再把 PTT 中的第一个 PTE 修改一下,指向这个 PTT 自己,这样又可以访问整个 PTT 了。就像图2这样。


24-PDT/PTT基址_页表_02


图2 让线性地址

​0x00000000​​ 映射到PTT的物理地址。


如此一来,你又能按照同样的方法去挨个访问这个PTT中所有的PTE了。

虽然这个方法比你手工改1024个PDE和1024×1024个PTE要强的多,可是你不觉得这仍然很烦吗?

PTT 本身也在物理页

首先,我们要承认一个事实,PTT 本身也占用一个物理页。既然如此,我们有理由可以构造出一个特殊的 PTT,这个PTT中的每个表项,它指向了所有的 PTT。如果你觉得有点乱,看下面的图3.


24-PDT/PTT基址_保护模式_03


图3 自己构造一个 PTT,让所有PTE指向1024个PTT,包括它自己.


你会说,这个特殊的PTT不就是页目录吗?慢着慢着,我还没说它是页目录,这玩意是我自己手工在 WinDbg 中构造出来的。不要笑,我搞了一个通宵。

修改PDE指向PTT768

好了,还差一步。看图4.


24-PDT/PTT基址_linux_04


图4 页目录页表全貌


到这里大功告成了,如此一来,访问所有页表不是轻而易举了。

继续改进

我知道,有朋友觉得这么做很傻。搞了一个通宵,就是为了让一个PTT指向所有其它PTT。为何自己不好好利用一下页目录呢?换句话说,如果 PTT768就是页目录……


24-PDT/PTT基址_保护模式_05


图5 PTT768,其实就是页目录


好吧,我承认,我骗了你,我没搞一个通宵。我确实利用了页目录。这个页目录,既充当了页目录,又充当了页表,不是吗?

768是什么?

768 并不是我随便写的,而是有预谋的。

768 的16 进制,就是 0x300,刚刚那个特殊PTT768,也就是第 0x300 个页表,很自然的,PTT768中的第 0x300 个 PTE,里头填写的是 PTT 768 的物理地址。

然而,这个PTT768又是页目录。

PTT768充当着三重身份——页目录、页表、普通物理页。

PDT 基址

如此,PTT768的线性地址的三段式就是​​300-300-0​​​,页目录的0x300号PDE指向的页表(还是PTT768)的第 0x300 号PTE指向的物理页(还是PTT768)。这个三段式换算成线性地址就是 ​​0xc0300000​​。

PTT 基址

一共有1024个PTT,第 0 个PTT的那就是页目录的0x300号PDE指向的页表(还是PTT768)的第 0 号PTE指向的物理页(PTT0),它的线性地址三段式​​300-000-000​​。

总结

本篇有点绕。无论如何,你都要记住这个上帝PTT768,它有三重身份。下节,我们会重点分析​​0xc0300000​​。

其实,无论是 Windows 还是 Linux,都存在着这么个上帝 PTT,只要你有一双发现它的眼睛。