If we look at the lowest level, the PT entry associated with the address 0xFFFF8000'00000000 is the PHYSICAL address pointed by the PAGE DIRECTORY entry number 0 used to map the memory range 0~2MB. Which means that, when the CPU reads the address 0xFFFF8000'00000000, it will read all the PAGE TABLE entries used to map this memory range. Let's see another example by using the address 0xFFFF807F'FFFFF000. This address points to the last 4KB of the 512GB assigned for PAGING MANAGMENT. The path to be followed by the CPU is this: - PML4e(0x100) --> PDPTe(0x1FF) --> PDe(0x1FF) --> PTe(0x1FF). Which it is the same to: - PML4e(0x100) --> PDPTe(PML4e(0x1FF)) --> PDe(PDPTe(0x1FF)) --> PTe(PDe(0x1FF)). As a result, the CPU will have access to the PAGE TABLE used to map the highest 4MB of the virtual address memory range addressable by 64-bit Intel processors, located at 0xFFFFFFFF'FFC00000~0xFFFFFFFF'FFFFFFFF. Thus, the CPU, and obviously the operating system, are able to access to any paging level, what is absolutely necessary for manage them. Depending on which address it's chosen within the memory range 0xFFFF8000'00000000~0xFFFF807F'FFFFFFFF, the operating system is able to access to any paging table used to map virtual memory in the valid 48 bits of the virtual memory range allowed by Intel's x64 CPUs. Is short, this mechanism makes a DOBLE USE of paging tables, because on one hand these are usually used to map virtual memory, and at the same time are re-used by shifting one or more paging levels, when the CPU accesses them through a self-ref entry. This technique is really eficient because it only uses 8 bytes (one entry) to be able to manage ALL paging tables declared by the operation system.
Self-ref entry - Weakness
While this technique is highly efficient, nothing is perfect. There is an implicit "problem" in the use of self-ref entries related to the absolute lack of randomization. We could give a formal definition like this: "Given a self-ref entry, it's possible to calculate the virtual address of any paging table mapped by the operating system". Let's see an example using the self-ref entry number 0x100. This entry maps the memory range: 512GB * 0x100 ~ 512GB * 0x101 Using real numbers, the virtual memory range is: - 0xFFFF0000'00000000 + 0x8000000000 ( 512GB ) * 0x100 ~ 0xFFFF0000'00000000 + 0x8000000000 ( 512GB ) * 0x101 What is the same to 0xFFFF8000'00000000~0xFFFF8080'00000000, which it's used for Paging Managment. Now, we want to get the PAGE TABLE that maps the virtual memory range 0~2MB: - PML4e(0x100) --> PDPTe(PML4e(0)) --> PDe(PDPTe(0)) --> PTe(PDe(0)). What it's the same to: - 0xFFFF0000'00000000 + 512GB * 0x100 (self -ref entry) + (1GB * 2MB * 4KB ) * 0x0 = 0xFFFF8000'00000000 So, it means that the PAGE TABLE sought is located at the virtual address 0xFFFF8000'00000000. --------------------- Now let's try the self-ref entry number 0x101 . This entry maps the memory range: 512GB ~ 512GB * * 0x101 0x102 Using real numbers, the virtual memory range is: - 0xFFFF0000'00000000 + 0x8000000000 ( 512GB ) * 0x101 ~ 0xFFFF0000'00000000 + 0x8000000000 ( 512GB ) * 0x102 What is the same to 0xFFFF8080'00000000 ~ 0xFFFF8100'00000000, which it's used for Paging Managment. Now, we want to get again the PAGE TABLE that maps the virtual memory range 0~2MB: - PML4e(0x101) --> PDPTe(PML4e(0)) --> PDe(PDPTe(0)) --> PTe(PDe(0)). What it's the same to: - 0xFFFF0000'00000000 + 512GB * 0x101 (self -ref entry) + (1GB * 2MB * 4KB ) * 0x0 = 0xFFFF8080'00000000 So, it means that this PAGE TABLE is located at the virtual address 0xFFFF8080'00000000. --------------------- If we compare the PAGE TABLE addresses obtained previously, we get a difference of 0x80'00000000, which is the same to 512GB. It means that, the difference between the PML4 entry 0x100 and the PML4 entry 0x101 is 512GB. So, if an operating system uses a different self-ref entry, it's possible to calculate exactly where the PAGE TABLE that maps the memory range 0~2MB is. This is true for any of the 512 entries availables in the PML4, which these can be abused by an attacker that knows the number of the self-ref entry used by the operating system. So, the only way to implement KASLR (Kernel randomization) for paging tables is by randomizing the position of the PML4 self-ref entry. Thus, an attacker is not able to calculate exactly where the paging tables are, although it could in theory, because the PML4 has only 512 entries which, in general, only the 256 highest entries are intended for KERNEL SPACE. Yes well 1/256 possibilities is not a big number to protect a system, but this is still better than 1/1 ;-) In short, there is no way of paging tables randomization when the position of the self-ref entry is fixed.
Windows Paging implementation
As I said at the beginning, Microsoft decided to use the self-ref technique for Windows Paging Management, both for 32 and 64 bits. Talking about Windows 64 bits, the first 256 PML4 entries are used for USER SPACE and the next 256 entries for KERNEL SPACE. The PML4 self-ref entry chosen by Microsoft is the entry 0x1ED (493), located in kernel space, obviously. To obtain the PML4 Windows address, we have to do the same calculation explained above: - ( 512GB * 1GB * 2MB * 4KB ) * 0x1ED (self-ref entry) = 0xF6FB'7DBED000 Adding the canonical address 0xFFFF0000'00000000, we obtain the virtual address 0xFFFFF6FB'7DBED000. It can be easily proved by using the previous python script:
entry: PML4:0x1ed PDPT:0x1ed PD:0x1ed PT:0x1ed
offset: PML4:0xf68 PDPT:0xf68 PD:0xf68 PT:0xf68
Windows Paging implementation - Weakness
The fact that Windows uses a fixed PML4 self-referential entry for all running processes, it makes that an attacker can calculate exactly where the paging tables/entries are, independently of the physical address used by everyone. In this way, an attacker is able to modify valid entries or add new ones. This is valid for LOCAL and REMOTE attacks against the Windows kernel, independently if it's Windows 2000 or Windows 10. In few words, this throws overboard the whole Windows kernel randomization (KASLR). A good example of "modify valid entries" can be seen in our "Windows SMEP bypass: U=S" Ekoparty presentation. To be continued ...